From b5d048590e21cca3e13c4327c5a48205aec42fde Mon Sep 17 00:00:00 2001 From: lread Date: Wed, 25 May 2022 14:12:13 -0400 Subject: [PATCH] Flesh out developer guide and add some bb tasks New bb tasks: - dev - start a Clojure nREPL server - bb-dev - start a Babashka nREPL server - lint - lint sources with clj-kondo - drivers - list or kill WebDriver processes, replaces old `make kill` - cljdoc-preview - preview what our docs will look like on cljdoc - outdated - list outdated deps Enough build.clj support added to support cljdoc-preview's need to install to local maven repo. Full release workflow coming soon. Also: - described library versioning scheme in README --- .clj-kondo/config.edn | 5 +- .clj-kondo/etaoin/{ => impl}/util.clj | 7 +- Makefile | 7 - README.adoc | 12 +- bb.edn | 32 +++- build.clj | 63 +++++++ deps.edn | 23 ++- doc/01-user-guide.adoc | 2 + doc/02-developer-guide.adoc | 201 +++++++++++++++++++- script/cljdoc_preview.clj | 253 ++++++++++++++++++++++++++ script/drivers.clj | 87 +++++++++ script/lint.clj | 71 ++++++++ 12 files changed, 739 insertions(+), 24 deletions(-) rename .clj-kondo/etaoin/{ => impl}/util.clj (74%) create mode 100644 build.clj create mode 100644 script/cljdoc_preview.clj create mode 100644 script/drivers.clj create mode 100644 script/lint.clj diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index e0993099..2a2c2c84 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -3,9 +3,8 @@ :hooks ;; for now we'll use the simple macroexpand, can move to hooks for finer grained errors later {:macroexpand - {etaoin.util/defmethods etaoin.util/defmethods - etaoin.util/with-tmp-dir etaoin.util/with-tmp-dir - etaoin.util/with-tmp-file etaoin.util/with-tmp-file + {etaoin.impl.util/defmethods etaoin.impl.util/defmethods + etaoin.impl.util/with-tmp-file etaoin.impl.util/with-tmp-file etaoin.api/with-key-down etaoin.api/with-key-down etaoin.api/with-pointer-btn-down etaoin.api/with-pointer-btn-down diff --git a/.clj-kondo/etaoin/util.clj b/.clj-kondo/etaoin/impl/util.clj similarity index 74% rename from .clj-kondo/etaoin/util.clj rename to .clj-kondo/etaoin/impl/util.clj index bc21e5a3..3995b75b 100644 --- a/.clj-kondo/etaoin/util.clj +++ b/.clj-kondo/etaoin/impl/util.clj @@ -1,4 +1,4 @@ -(ns etaoin.util) +(ns etaoin.impl.util) (defmacro defmethods "Declares multimethods in batch. For each dispatch value from @@ -11,8 +11,3 @@ (defmacro with-tmp-file [prefix suffix bind & body] `(let [~bind "somepath"] ~@body)) - -;; essence only for linting -(defmacro with-tmp-dir [prefix bind & body] - `(let [~bind "somepath"] - ~@body)) diff --git a/Makefile b/Makefile index 471a45ce..b6684691 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,3 @@ -.PHONY: kill -kill: - pkill chromedriver || true - pkill geckodriver || true - pkill safaridriver || true - pkill phantomjs || true - IMAGE := etaoin .PHONY: docker-build diff --git a/README.adoc b/README.adoc index fa60a00c..8dc9e78c 100644 --- a/README.adoc +++ b/README.adoc @@ -14,7 +14,7 @@ https://clojars.org/{project-mvn-coords}[image:https://img.shields.io/clojars/v/ https://babashka.org[image:https://raw.githubusercontent.com/babashka/babashka/master/logo/badge.svg[bb compatible]] https://clojurians.slack.com/archives/C7KDM0EKW[image:https://img.shields.io/badge/slack-join_chat-brightgreen.svg[Join chat]] -A pure Clojure implementation of the link:{url-webdriver}[Webdriver] protocol named after link:{url-wiki}[Etaoin Shrdlu] -- a typing machine that became alive after a mysterious note was produced on it. +A pure Clojure implementation of the link:{url-webdriver}[Webdriver] protocol named after link:{url-wiki}[Etaoin Shrdlu] -- a typing machine that came to life after a mysterious note was produced on it. Use the Etaoin library to automate a browser, test your frontend behaviour, simulate human actions or whatever you want. @@ -62,6 +62,16 @@ Ivan's blog-post about pitfalls that can occur when testing UI. You are welcome to submit your company to this list. +== Versioning + +Eatoin uses: `major`.`minor`.`patch`-`test-qualifier` + +* `major` increments when a non alpha release API has been broken - something, as a rule, we'd like to avoid. +* `minor` increments to convey significant new features have been added. +* `patch` indicates bug fixes or minor changes - it is the total number of releases to date. +* `test-qualifier` is absent for stable releases. +Can be `alpha`, `beta`, `rc1`, etc. + == People === Contributors diff --git a/bb.edn b/bb.edn index ae5dbf6b..7cb969f4 100644 --- a/bb.edn +++ b/bb.edn @@ -5,12 +5,36 @@ dev.nubank/docopt {:mvn/version "0.6.1-fix7"}} :tasks {;; setup - :requires ([clojure.string :as string] + :requires ([babashka.classpath :as cp] + [babashka.fs :as fs] + [clojure.string :as string] + [helper.shell :as shell] [lread.status-line :as status]) :enter (let [{:keys [name]} (current-task)] (status/line :head "TASK %s %s" name (string/join " " *command-line-args*))) :leave (let [{:keys [name]} (current-task)] (status/line :detail "\nTASK %s done." name)) ;; commands - download-deps {:task download-deps/-main :doc "download all deps (useful for CI prep)"} - tools-versions {:task tools-versions/-main :doc "report on tools versions"} - test {:task test/-main :doc "run all or a subset of tests, use --help for args"}}} + + dev {:doc "start a Clojure nrepl server/prompt" + :task (shell/command "clj" "-M:test:repl/cider")} + bb-dev {:doc "start a Babashka nrepl server" + :task (let [cp (-> (shell/clojure "-Spath -M:test:bb-spec:bb-test") + with-out-str) + bbcp (cp/get-classpath)] + (shell/command "bb" + "--classpath" (str cp fs/path-separator bbcp) + "--nrepl-server"))} + test {:doc "run all or a subset of tests, use --help for args" + :task test/-main} + drivers {:doc "[list|kill] any running WebDrivers" + :task drivers/-main} + lint {:doc "[--rebuild] lint source code" + :task lint/-main} + cljdoc-preview {:doc "preview what docs will look like on cljdoc, use --help for args" + :task cljdoc-preview/-main} + tools-versions {:doc "report on tools versions" + :task tools-versions/-main} + download-deps {:doc "download all deps (useful for CI prep)" + :task download-deps/-main} + outdated {:task (shell/clojure {:continue true} "-M:outdated") + :doc "report on outdated dependencies"}}} diff --git a/build.clj b/build.clj new file mode 100644 index 00000000..9fa3d950 --- /dev/null +++ b/build.clj @@ -0,0 +1,63 @@ +(ns build + (:require [clojure.tools.build.api :as b] + [clojure.string :as string])) + +(defn- num-releases + "We'll assume num tags = num releases" + [] + (-> (b/git-process {:git-args "tags"}) + (string/split-lines) + count)) + +(def lib 'etaoin/etaoin) +(def version (format "1.0.%d" (inc (num-releases)))) +(def class-dir "target/classes") +(def basis (b/create-basis {:project "deps.edn"})) +(def jar-file (format "target/%s-%s.jar" (name lib) version)) +(def built-jar-version-file "target/built-jar-version.txt") + +(defn clean [_] + (b/delete {:path "target"})) + +(defn jar + "Build library jar file. + Also writes built version to target/built-jar-version.txt for easy peasy pickup by any interested downstream operation. + + We use the optional :version-suffix to optionally distinguish local installs from productin releases. + For example, when installing for a cljdoc preview suffix is: cljdoc-preview." + [{:keys [version-suffix] :as opts}] + (let [version (if version-suffix + (format "%s-%s" version version-suffix) + version)] + + (b/write-pom {:class-dir class-dir + :lib lib + :version version + :scm {:tag 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}) + (spit built-jar-version-file version) + (assoc opts :built-jar-version version))) + +(defn install [opts] + (clean opts) + (let [{:keys [built-jar-version]} (jar opts)] + (b/install {:class-dir class-dir + :lib lib + :version built-jar-version + :basis basis + :jar-file jar-file}))) + +(defn publish [opts] + (clean 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) diff --git a/deps.edn b/deps.edn index baec02b2..227ee503 100644 --- a/deps.edn +++ b/deps.edn @@ -21,4 +21,25 @@ :sha "644a7fc216e43d5da87b07471b0f87d874107d1a"}}} ;; for babashka testing, allows us to use cognitect test-runner :bb-test {:extra-deps {org.clojure/tools.namespace {:git/url "https://github.com/babashka/tools.namespace" - :git/sha "a13b037215e21a2e71aa34b27e1dd52c801a2a7b"}}}}} + :git/sha "a13b037215e21a2e71aa34b27e1dd52c801a2a7b"}}} + ;; for consistent linting we use a specific version of clj-kondo through the jvm + :clj-kondo {:extra-deps {clj-kondo/clj-kondo {:mvn/version "2022.04.25"}} + :main-opts ["-m" "clj-kondo.main"]} + + :build {:deps {io.github.clojure/tools.build {:git/tag "v0.8.2" :git/sha "ba1a2bf"} + slipset/deps-deploy {:mvn/version "0.2.0"}} + :ns-default build} + + :outdated {:extra-deps {com.github.liquidz/antq {:mvn/version "1.6.774"} + org.slf4j/slf4j-simple {:mvn/version "1.7.36"} ;; to rid ourselves of logger warnings + } + :main-opts ["-m" "antq.core"]} + + :repl/cider + {:extra-deps {nrepl/nrepl {:mvn/version "0.9.0"} + cider/cider-nrepl {:mvn/version "0.28.4"} + refactor-nrepl/refactor-nrepl {:mvn/version "3.5.2"}} + :jvm-opts ["-XX:-OmitStackTraceInFastThrow"] + :main-opts ["-m" "nrepl.cmdline" + "--middleware" "[refactor-nrepl.middleware/wrap-refactor,cider.nrepl/cider-middleware]" + "-i"]}}} diff --git a/doc/01-user-guide.adoc b/doc/01-user-guide.adoc index e6e9fd82..1e352e27 100644 --- a/doc/01-user-guide.adoc +++ b/doc/01-user-guide.adoc @@ -22,6 +22,7 @@ If Etaoin is not your cup of tea, you might also consider: * https://github.com/tatut/clj-chrome-devtools[clj-chrome-devtools] +[[supported-os-browser]] === Supported OSes & Browsers Etaoin's test suite covers the following OSes and browsers run with both regular Clojure and Babashka: @@ -107,6 +108,7 @@ See https://github.com/babashka/spec.alpha[babashka/spec.alpha] for current docs :url-webkit: https://webkit.org/blog/6900/webdriver-support-in-safari-10/ :url-edge-dl: https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/ +[[install-webdrivers]] === Installing the Browser WebDrivers Etaoin controls web browsers via their WebDrivers. diff --git a/doc/02-developer-guide.adoc b/doc/02-developer-guide.adoc index a8925991..0da2d03e 100644 --- a/doc/02-developer-guide.adoc +++ b/doc/02-developer-guide.adoc @@ -2,5 +2,202 @@ :toclevels: 5 :toc: -== Introduction -Coming soon +== Contributing + +We very much appreciate contributions from the community. + +=== Issue First Please + +If you have an idea or a fix, please do raise a GitHub issue before investing in any coding effort. That way we can discuss first. +Writing code is the easy part, maintaining it forever is the hard part. + +That said, if you notice a simple typo, a PR without an issue is fine. + +=== Submitting a Pull Request + +The entire <> can take several minutes to run. +Depending on your change, you might choose to sanity test with a subset of tests or browsers. + +When you submit a PR, GitHub Actions will kick in and test across all supported browsers, OSes, Babashka and Clojure. +There's no shame in it finding a problem you didn't anticipate. +Given the nature of WebDrivers and browsers, it is not entirely unusual for a job or two to fail. +You can request GitHub Actions to rerun the failed jobs. +If they fail a second time you might have an issue to solve. + +== Environmental Overview + +=== Supported Environments + +Etaoin is tested on macOS, Ubuntu and Windows via GitHub Actions on each commit to the master branch. +All tests are run under Clojure and Babashka. +We test against against Chrome, Firefox, Edge and Safari xref:01-user-guide.adoc#supported-os-browser[depending on the OS]. + +=== Developer Prerequisites + +* Java Development Kit 1.8 or above +* Current version of Clojure cli for `clojure` command +** Note: the Etaoin library itself supports Clojure v1.9 and above +* Current vesion of Babashka +* Browsers and WebDrivers, see xref:01-user-guide.adoc#install-webdrivers[installation tips instructions in user guide] +** We currently test against is installed by GitHub Actions on their virtual environments. +They seem to keep browsers and drivers up to date. If we find we need to, we'll invest in tweaking these defaults, but we don't see a need as of this writing. +* ImageMagick - used by tests to verify that screenshots produce valid PNG files + +It is also useful to have access to the variety of OSes that Etaoin supports to diagnose and fix any OS-specific issues that may arise. + +=== Babashka Compatibility + +Etaoin is babashka compatible. + +Babashka supports everything that Etaoin needs, but when making changes, be aware that your code must also work under Babashka. For example, to make Etaoin Babashka compatible we made the following changes: + +1. Turf unused reference to `java.lang.IllegalThreadStateException` +2. Replace use of `org.clojure/data.codec` with JDK's `Base64` +3. Replace use of `ImageIO` in tests with a callout to ImageMagick instead. +4. Replace some JDK file related class references with `babashka/fs` abstractions +5. Use `http-client-lite` in place of `http-client` when running under Babashka +6. Run existing tests with cognitect test runner by including `babashka/tools.namespace` + +Nothing earth shattering there, but gives you and idea. + +== Docs + +All documentation is written in AsciiDoc. +@lread likes to follow https://asciidoctor.org/docs/asciidoc-recommended-practices/#one-sentence-per-line[AsciiDoc best practice of one sentence per line] but won't be entirely pedantic about that. + +We host our docs on cljdoc and have support for <> + +== Babashka Tasks + +We use Babashka tasks, to see all available tasks run: + +[source,shell] +---- +bb tasks +---- + +=== Launching a REPL + +For a Clojure REPL +[source,shell] +---- +bb dev +---- + +For a babashka REPL +[source,shell] +---- +bb bb-dev +---- + +=== Checking Tools Versions + +Used by GitHub Actions, but also an interesting way to check your prerequisites: + +[source,shell] +---- +bb tools-versions +---- + +[[running-tests]] +=== Runing tests + +The `test` task provides a coarse grained facility to invoke tests. +It was written to satisfy the use case of running tests in parallel on GitHub Actions. + + +[source,shell] +---- +bb test --help +---- + +We'll likely add finer grained test selection to satisfy developer needs. +For now, temporarily tweak `./script/test.clj` if you need to. + +=== WebDriver Processes + +Sometimes WebDriver process might hang around longer than you'd like. + +To list them: +[source,shell] +---- +bb drivers +---- + +To terminate them: +[source,shell] +---- +bb drivers kill +---- + +=== Linting + +We use clj-kondo to lint Etaoin source code. + +To lint Etaoin sources: +[source,shell] +---- +bb lint +---- + +We like to keep our code free of lint warnings, but don't currently fail CI if there are lint issues. + +TIP: https://github.com/borkdude/clj-kondo/blob/master/doc/editor-integration.md[Integrate clj-kondo into your editor] to catch mistakes as you type them. + +=== Outdated dependencies + +To run check Etaoin dependencies: + +[source,shell] +---- +bb outdated +---- + +[[cljdoc-preview]] +=== Cljdoc Preview + +Before a release, it can be comforting to preview what docs will look like on https://cljdoc.org/[cljdoc]. + +[NOTE] +==== +This task should be considered experimental, I have only tested running on macOS, but am fairly confident it will work on Linux. +Not sure about Windows at this time. +==== + +[TIP] +==== +You have to push your changes to GitHub to preview them. This allows for a full preview that includes any links (source, images, etc) to GitHub. +This works fine from branches and forks. +==== + +Run `bb cljdoc-preview --help` for help. + +* `bb cljdoc-preview start` downloads (if necessary) and starts the cljdoc docker image +* `bb cljdoc-preview ingest` installs etaoin to your local maven repo and imports it into locally running cljdoc +* `bb cljdoc-preview view` opens a view to your imported docs in your default web browser +* `bb cljdoc-preview stop` stops the docker image + +== Other Notes + +=== Logging + +When running tests under the JVM, info level logging is configured via `env/test/resources/logback.xml`. This is automatically selected via the `:test` alias. You can prefix the `:debug` alias for debug level logging. See `script/test.clj` and tweak if necessary. + +For Babashka, logging levels are controlled via the built-in timbre library. +See `script/bb_test_runner.clj` and tweak if necessary. + +Sometimes tools like WireShark can also be helpful. +@lread personally used a combination of RawCap and WireShark on Windows to successfully diagnose an issue. + +=== Clj-kondo Export Config [COMING SOON] + +Users of Etaoin and clj-kondo benefit from our clj-kondo export configuration. + +This configuration is included in the Etaoin release jar and available when folks reference Etaoin from their `deps.edn` form a `git` dependency. + +[NOTE] +==== +Etaoin contains a fair number of macros. +Clj-kondo can need special configuration (including hooks) to understand the effects of these macros. +So, when adding any new macros, think also about our Etaoin users and our clj-kondo export configuration. +==== diff --git a/script/cljdoc_preview.clj b/script/cljdoc_preview.clj new file mode 100644 index 00000000..f7843dbe --- /dev/null +++ b/script/cljdoc_preview.clj @@ -0,0 +1,253 @@ +#!/usr/bin/env bb + +(ns cljdoc-preview + (:require [babashka.curl :as curl] + [babashka.fs :as fs] + [clojure.java.browse :as browse] + [clojure.string :as string] + [helper.main :as main] + [helper.shell :as shell] + [lread.status-line :as status])) + +;; +;; helpers +;; + +(defn- on-path? [prog-name] + (when-let [p (fs/which prog-name)] + (fs/executable? p))) + +;; +;; constants +;; + +(def project "etaoin/etaoin") +(def cljdoc-root-temp-dir "/tmp/cljdoc-preview") +(def cljdoc-db-dir (str cljdoc-root-temp-dir "/db")) +(def cljdoc-container {:name "cljdoc-server" + :image "cljdoc/cljdoc" + :port 8000}) + +;; +;; Prerequisites +;; + +(defn check-prerequisites [] + (let [missing-cmds (doall (remove on-path? ["git" "docker"]))] + (when (seq missing-cmds) + (status/die 1 (string/join "\n" ["Required commands not found:" + (string/join "\n" missing-cmds)]))))) + +;; +;; project build info +;; + +(defn local-install [] + (status/line :head "installing thin jar") + (shell/clojure "-T:build install :version-suffix cljdoc-preview")) + +(defn built-version [] + (slurp "target/built-jar-version.txt")) + +;; +;; git +;; + +(defn git-sha [] + (-> (shell/command {:out :string} + "git rev-parse HEAD") + :out + string/trim)) + +(defn https-uri + ;; stolen from cljdoc's http-uri + "Given a URI pointing to a git remote, normalize that URI to an HTTP one." + [scm-url] + (cond + (.startsWith scm-url "http") + scm-url + + (or (.startsWith scm-url "git@") + (.startsWith scm-url "ssh://")) + (-> scm-url + (string/replace #":" "/") + (string/replace #"\.git$" "") + ;; three slashes because of prior :/ replace + (string/replace #"^(ssh///)*git@" "https://")))) + +(defn git-origin-url-as-https [] + (-> (shell/command {:out :string} + "git config --get remote.origin.url") + :out + string/trim + https-uri)) + +(defn uncommitted-code? [] + (-> (shell/command {:out :string} + "git status --porcelain") + :out + string/trim + seq)) + +(defn unpushed-commits? [] + (let [{:keys [:exit :out]} (shell/command {:continue true :out :string} + "git cherry -v")] + (if (zero? exit) + (-> out string/trim seq) + (status/die 1 "Failed to check for unpushed commits to branch, is your branch pushed?")))) + +;; +;; docker +;; + +(defn status-server [ container ] + (let [container-id (-> (shell/command {:out :string} + "docker ps -q -f" (str "name=" (:name container))) + :out + string/trim)] + (if (string/blank? container-id) "down" "up"))) + +(defn docker-pull-latest [ container ] + (shell/command "docker pull" (:image container))) + +(defn stop-server [ container ] + (when (= "down" (status-server container)) + (status/die 1 + "%s does not appear to be running" + (:name container))) + (shell/command "docker" "stop" (:name container) "--time" "0")) + +(defn wait-for-server + "Wait for container's http server to become available, assumes server has valid root page" + [container] + (status/line :head "Waiting for %s to become available" (:name container)) + (when (= "down" (status-server container)) + (status/die 1 + "%s does not seem to be running.\nDid you run the start command yet?" + (:name container))) + (status/line :detail "%s container is running" (:name container)) + (let [url (str "http://localhost:" (:port container))] + (loop [] + (if-not (try + (curl/get url) + url + (catch Exception _e + (Thread/sleep 4000))) + (do (println "waiting on" url " - hit Ctrl-C to give up") + (recur)) + (println "reached" url))))) + +(defn status-server-print [container] + (status/line :detail (str (:name container) ": " (status-server container)))) + +;; +;; cljdoc server in docker +;; + +(defn cljdoc-ingest [container project version] + (status/line :head "Ingesting project %s %s\ninto local cljdoc database" project version) + (shell/command "docker" + "run" "--rm" + "-v" (str cljdoc-db-dir ":/app/data") + "-v" (str (fs/home) "/.m2:/root/.m2") + "-v" (str (fs/cwd) ":" (fs/cwd) ":ro") + "--entrypoint" "clojure" + (:image container) + "-M:cli" + "ingest" + ;; project and version are used to locate the maven artifact (presumably locally) + "--project" project "--version" version + ;; use git origin to support folks working from forks/PRs + "--git" (git-origin-url-as-https) + ;; specify revision to allow for previewing when working from branch + "--rev" (git-sha))) + +(defn start-cljdoc-server [container] + (when (= "up" (status-server container)) + (status/die 1 + "%s is already running" + (:name container))) + (status/line :head "Checking for updates") + (docker-pull-latest container) + (status/line :head "Starting %s on port %d" (:name container) (:port container)) + (shell/command "docker" + "run" "--rm" + "--name" (:name container) + "-d" + "-p" (str (:port container) ":8000") + "-v" (str cljdoc-db-dir ":/app/data") + "-v" (str (fs/home) "/.m2:/root/.m2") + "-v" (str (fs/cwd) ":" (fs/cwd) ":ro") + (:image container))) + +(defn view-in-browser [url] + (status/line :head "opening %s in browser" url) + (when (not= 200 (:status (curl/get url {:throw false}))) + (status/die 1 "Could not reach:\n%s\nDid you run the ingest command yet?" url)) + (browse/browse-url url)) + + +;; +;; main +;; + +(defn git-warnings [] + (let [warnings (remove nil? + [(when (uncommitted-code?) + "There are changes that have not been committed, they will not be previewed") + (when (unpushed-commits?) + "There are commits that have not been pushed, they will not be previewed")])] + (when (seq warnings) + (status/line :warn (string/join "\n" warnings))))) + +(defn cleanup-resources [] + (when (fs/exists? cljdoc-db-dir) + (fs/delete-tree cljdoc-db-dir))) + +(def args-usage "Valid args: (start|ingest|view|stop|status|--help) + +Commands: + start Start docker containers supporting cljdoc preview + ingest Locally publishes your project for cljdoc preview + view Opens cljdoc preview in your default browser + stop Stops docker containers supporting cljdoc preview + status Status of docker containers supporting cljdoc preview + +Options: + --help Show this help + +Must be run from project root directory.") + +(defn -main [& args] + (check-prerequisites) + (when-let [opts (main/doc-arg-opt args-usage args)] + (cond + (get opts "start") + (do + (start-cljdoc-server cljdoc-container) + nil) + + (get opts "ingest") + (do + (git-warnings) + (local-install) + (cljdoc-ingest cljdoc-container project (built-version)) + nil) + + (get opts "view") + (do + (wait-for-server cljdoc-container) + (view-in-browser (str "http://localhost:" (:port cljdoc-container) "/d/" project "/" (built-version))) + nil) + + (get opts "status") + (status-server-print cljdoc-container) + + (get opts "stop") + (do + (stop-server cljdoc-container) + (cleanup-resources) + nil)))) + +(main/when-invoked-as-script + (apply -main *command-line-args*)) diff --git a/script/drivers.clj b/script/drivers.clj new file mode 100644 index 00000000..073e7368 --- /dev/null +++ b/script/drivers.clj @@ -0,0 +1,87 @@ +(ns drivers + (:require [babashka.fs :as fs] + [doric.core :as doric] + [helper.os :as os] + [helper.main :as main] + [lread.status-line :as status]) + ;; noice! bb is JDK 11 so we have ProcessHandle + (:import (java.lang ProcessHandle))) + +(def web-drivers + [{:name "Chrome" :bin "chromedriver"} + {:name "Firefox":bin "geckodriver"} + {:name "Microsoft Edge" :bin "msedgedriver"} + {:name "Safari" :bin "safaridriver"} + {:name "PhantomJS" :bin "phantomjs"}]) + +(defn all-processes [] + (for [p (-> (ProcessHandle/allProcesses) .iterator iterator-seq) + :when (some-> p .info .command .isPresent) + :let [info (.info p) + command (-> info .command .get) + arguments (when (-> info .arguments .isPresent) + (->> info .arguments .get (into [])))]] + {:handle p + :command command + :arguments arguments})) + +(defn driver-processes [] + (->> (all-processes) + (keep (fn [p] + (let [pfname (-> p :command fs/file-name) + pfname (if (= :win (os/get-os)) + (fs/strip-ext pfname) + pfname)] + (when-let [driver (first (filter #(= pfname (:bin %)) web-drivers))] + (assoc p :name (:name driver)))))))) + +(defn kill [{:keys [handle]}] + (.destroy handle)) + +(defn is-alive? [{:keys [handle]}] + (.isAlive handle)) + +(defn wait-for-death [{:keys [handle]}] + (let [deadline (+ (System/currentTimeMillis) 2000)] + (loop [] + (when (and (.isAlive handle) + (< (System/currentTimeMillis) deadline)) + (Thread/sleep 100) + (recur))))) + +(defn report[processes] + (->> processes + (doric/table (keep identity [{:name :name :title "WebDriver"} + :command + ;; arguments are don't seem to populate on Windows + (when (not= :win (os/get-os)) :arguments)])) + println)) + +(def args-usage "Valid args: + [list|kill] + +Commands: + list List running WebDriver processes (default) + kill Kill running WebDriver processes") + +(defn -main [& args] + (when-let [opts (main/doc-arg-opt args-usage args)] + (let [drivers (driver-processes) + kill? (get opts "kill")] + (if (not (seq drivers)) + (status/line :detail "No WebDrivers seem to be running.") + (if (not kill?) + (do (status/line :head "List of running WebDrivers") + (report drivers)) + (do (status/line :head "Attempting to kill running WebDrivers") + (report drivers) + (run! kill drivers) + (run! wait-for-death drivers) + (let [still-running (filter is-alive? drivers)] + (if (seq still-running) + (do (status/line :warn "Did not manage to kill the following WebDrivers") + (report still-running)) + (status/line :detail "Success!"))))))))) + +(main/when-invoked-as-script + (apply -main *command-line-args*)) diff --git a/script/lint.clj b/script/lint.clj new file mode 100644 index 00000000..c049aa7c --- /dev/null +++ b/script/lint.clj @@ -0,0 +1,71 @@ +(ns lint + (:require [babashka.classpath :as bbcp] + [babashka.fs :as fs] + [clojure.string :as string] + [helper.main :as main] + [helper.shell :as shell] + [lread.status-line :as status])) + +(def clj-kondo-cache ".clj-kondo/.cache") + +(defn- cache-exists? [] + (fs/exists? clj-kondo-cache)) + +(defn- delete-cache [] + (when (cache-exists?) + (fs/delete-tree clj-kondo-cache))) + +(defn- build-cache [] + (when (cache-exists?) + (delete-cache)) + (let [clj-cp (-> (shell/clojure {:out :string} + "-Spath -M:test" ) + with-out-str + string/trim) + bb-cp (bbcp/get-classpath)] + + (status/line :detail "- copying configs") + (shell/command "clojure -M:clj-kondo --skip-lint --copy-configs --lint" clj-cp bb-cp) + (status/line :detail "- creating cache") + (shell/command "clojure -M:clj-kondo --dependencies --lint" clj-cp bb-cp))) + +(defn- check-cache [{:keys [rebuild-cache]}] + (status/line :head "clj-kondo: cache check") + (if-let [rebuild-reason (cond + rebuild-cache + "Rebuild requested" + + (not (cache-exists?)) + "Cache not found" + + :else + (let [updated-dep-files (fs/modified-since clj-kondo-cache ["deps.edn" "bb.edn"])] + (when (seq updated-dep-files) + (format "Found deps files newer than lint cache: %s" (mapv str updated-dep-files)))))] + (do (status/line :detail rebuild-reason) + (build-cache)) + (status/line :detail "Using existing cache"))) + +(defn- lint [opts] + (check-cache opts) + (status/line :head "clj-kondo: linting") + (let [{:keys [exit]} + (shell/command {:continue true} + "clojure -M:clj-kondo --lint src test script env deps.edn bb.edn")] + (cond + (= 2 exit) (status/die exit "clj-kondo found one or more lint errors") + (= 3 exit) (status/die exit "clj-kondo found one or more lint warnings") + (> exit 0) (status/die exit "clj-kondo returned unexpected exit code")))) + +(def args-usage "Valid args: [options] + +Options: + --rebuild Force rebuild of clj-kondo lint cache and check for config imports. + --help Show this help.") + +(defn -main [& args] + (when-let [opts (main/doc-arg-opt args-usage args)] + (lint {:rebuild-cache (get opts "--rebuild")}))) + +(main/when-invoked-as-script + (apply -main *command-line-args*))