Liking cljdoc? Tell your friends :D

Configuration reference

hive-cljs reads its config from either or both of two files, found by walking up from the directory you invoked it in. Nothing is required beyond one build id.

The two sources

FileShapeUse when
.hive-project.edn:hive.cljs {…} submap, or flat :hive.cljs/* keysthe project is already a hive project — one descriptor, no extra file
hive-cljs.ednflat :hive.cljs/* keysstandalone project, or you want scenarios in their own file

Both present ⇒ they merge one level deep, and hive-cljs.edn wins key by key. So a descriptor can hold connectivity while the dedicated file holds scenarios:

;; .hive-project.edn
{:project-id "my-app"
 :hive.cljs {:shadow {:port 9630 :nrepl-port 7889}
             :builds {:app {:http-port 8280}}}}

;; hive-cljs.edn — overrides :port, keeps :nrepl-port, adds :e2e
{:hive.cljs/shadow {:port 9633}
 :hive.cljs/e2e    {:scenarios [{:id :smoke :steps [[:goto "/"]]}]}}

resolves to shadow {:port 9633 :nrepl-port 7889}, builds [:app], scenarios [:smoke].

Inside .hive-project.edn both spellings work — nested short keys (:hive.cljs {:builds …}) and flat namespaced keys (:hive.cljs/builds …). Flat wins on collision. Descriptor keys that are not hive-cljs's (:project-id, :carto, …) are ignored.

Where config is found

Two separate mechanisms, deliberately: one always on, one you ask for.

Discovery is unconditional. Resolution walks up from directory to the nearest ancestor that authors hive-cljs config, exactly as git walks up to the nearest .gitignore. That directory becomes :manifest/root. So cljs doctor works from a subdirectory:

my-app/.hive-project.edn          ← authors config
my-app/src/frontend/widgets/      ← invoke from here; root resolves to my-app/

Because :manifest/root moves, :artifacts-dir and the inferred :base-url derive from the project, not from where you happened to stand.

Inheritance is opt-in. An ancestor that also authors config contributes nothing unless the nearest level asks for it:

;; workspace/.hive-project.edn — shared conventions
{:project-id "workspace"
 :hive.cljs  {:e2e {:browser :chromium :headless true :timeout-ms 15000}}}

;; workspace/my-app/hive-cljs.edn — opts in, overrides one key
{:hive.cljs/inherit true
 :hive.cljs/e2e     {:timeout-ms 30000}
 :hive.cljs/builds  {:app {:http-port 8280}}}

resolves to chromium, headless, :timeout-ms 30000. Drop :hive.cljs/inherit and the workspace defaults are simply not consulted — a parent descriptor can never silently change a child's :base-url or :port behind your back.

:manifest/sources lists every file that contributed, highest precedence first: hive-cljs.edn before .hive-project.edn within a level, nearest level before its ancestors. cljs doctor surfaces it, so the question "where did this value come from, and what would I have to change to override it?" has one answer.

No level authors config ⇒ :manifest/not-found, carrying :searched (every path examined on the way up) and :candidates — directories below you that do author config, found to a bounded depth. Invoking from a workspace root with four apps underneath lists all four rather than guessing one.

#hive/env — values from the environment

Any config value may be read from the environment, for harnesses that assign ports at run time:

{:hive.cljs/shadow {:port       #hive/env SHADOW_PORT
                    :nrepl-port #hive/env [SHADOW_NREPL_PORT 7889]}}

Bare form: the variable's value, or nil when unset. Vector form: the second element is the fallback for unset-or-blank. A value that reads as an integer is coerced to a long, so :port type-checks without a wrapper.

Sections

:hive.cljs/shadow — connectivity

{:host       "localhost"   ; default
 :port       9630          ; default — the shadow HTTP/relay port
 :nrepl-port 7889}         ; NO default; omit and the runtime channel is :down

:port must match what shadow actually bound, which is not always 9630 — it takes the next free port with only a warning. :nrepl-port is shadow-cljs.edn's :nrepl {:port …}; without it, :eval, :expect-sub, :expect-db and :dispatch report :cljs-eval/no-nrepl-port.

:hive.cljs/builds — build targets

{:app {:shadow/id :app        ; defaults to the map key
       :http-port 8280        ; the dev-http port; used to infer :base-url
       :entry     "/"}}       ; optional

A scenario that names no :build inherits the project's build when there is exactly one. With two or more the choice is ambiguous, :plan/build is left unset, and any runtime step returns a typed error telling you to set :build.

:hive.cljs/e2e — browser and scenarios

{:base-url      "http://localhost:8280"   ; inferred from a build's :http-port
 :browser       :chromium                 ; :chromium | :firefox | :webkit
 :headless      true
 :timeout-ms    15000
 :artifacts-dir "<root>/.hive-cljs/artifacts"
 :scenarios     [{:id    :login           ; required
                  :build :app             ; optional — inherited if unambiguous
                  :tags  [:smoke]         ; optional — selects for `e2e run` / watch
                  :doc   "…"              ; optional
                  :steps [[:goto "/"] …]}]}

Relative :goto URLs resolve against :base-url; absolute ones pass through. Screenshots land in :artifacts-dir and are listed in the run report's :run/artifacts.

Step vocabulary: steps.md.

:hive.cljs/watch — build → e2e coupling

{:on-build-success [[:run-e2e {:tags [:smoke]}]]
 :on-build-failure []                      ; default: report the failure
 :debounce-ms      500
 :builds           #{:app}}                ; optional — omit to watch all builds

Actions are [kind opts] tuples; a bare keyword or 1-element vector is accepted and normalized. Selectors for :run-e2e: {:scenarios [:a :b]}, {:tags [:smoke]}, or neither, which runs every scenario.

The watcher always produces a decision, including :ignore with a reason ("within debounce window", "build not watched by policy", "no :on-build-success actions configured"), so cljs watch status explains why nothing ran.

Defaults, in one place

KeyDefault
shadow :host / :port"localhost" / 9630
shadow :nrepl-portnone — runtime channel disabled
e2e :base-urlhttp://localhost:<first build's :http-port>, else http://localhost:8080
e2e :browser / :headless / :timeout-ms:chromium / true / 15000
e2e :artifacts-dir<root>/.hive-cljs/artifacts
watch :debounce-ms500
watch :on-build-success / :on-build-failure[] / []
watch :buildsall builds

Validation

The normalized manifest is checked against a closed malli schema. A bad value is returned as :manifest/invalid with a humanized explanation rather than throwing:

{:error :manifest/invalid
 :explain {:manifest/shadow {:port ["should be an integer"]}}}

Unparseable EDN gives :manifest/unreadable with the cause.

Trusting what you resolved — cljs staleness

A session caches the resolved manifest. cljs staleness reports whether that cached view still describes reality:

code {command: "cljs staleness", directory: "/path/to/my-app"}
{:staleness/manifest        :stale
 :staleness/sources         [{:source/path "…/hive-cljs.edn" :source/exists? true
                              :source/modified 1753… :source/size 412} …]
 :staleness/server          :mismatch
 :staleness/declared-builds [:app]
 :staleness/reported-builds [:other-app]}
KeyReads
:staleness/manifest:stale when a contributing file changed on disk since the session opened
:staleness/server:ok when the connected server serves a build we declare, :mismatch when the sets are disjoint, :unknown when either side is empty

:mismatch is the port-drift trap from setup.md step 3 made visible: you are talking to another project's shadow server, and every verdict it gives you is confident and wrong. cljs doctor carries the same verdict under :server, plus a :warnings entry spelling it out.

Both axes need two witnesses. Nothing recorded on either side reports :unknown, never a reassuring :ok — an unverified claim is not a passing one.

Reading the report is not required to get fresh config: any command reopens the session automatically when its sources changed. staleness reports against the view as it was before the call, so an edit is still visible in the report that refreshes it.

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close