Skip to content

Commit cdf9fe0

Browse files
feat: dual-runtime JSON (cheshire on bb / charred on JVM) + Clojars publish
bridge runs two ways: as a babashka CLI (`bb bridge`) and as an embedded JVM library (`bridge.api`, used by Vis). babashka bundles cheshire but cannot load charred; a JVM native-image host wants charred (reflection-free) and no Jackson. Add bridge.json, a tiny shim that selects the backend via reader conditionals: #?(:bb cheshire (built into babashka) :clj charred (declared JVM dep)) Route evidence.clj / cli.clj / summary_test.clj through it. Net effect: a JVM consumer gets charred only (no cheshire/Jackson on its classpath), while `bb bridge` keeps using babashka's built-in cheshire. Full suite green in both contexts (83 tests, 0 failures on the JVM; `bb bridge` loads cleanly). Publish as com.blockether/bridge to Clojars (mirrors Blockether/svar): build.clj (tools.build + deps-deploy), resources/bridge/VERSION as the source of truth, plus CI (JVM tests + babashka) and tag-driven deploy workflows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0111BBUk1JkRVkpQ5VJ1rdT4
1 parent 360cca1 commit cdf9fe0

52 files changed

Lines changed: 6226 additions & 13 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
branches: ["main"]
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
test:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v5
17+
18+
- uses: actions/setup-java@v5
19+
with:
20+
distribution: temurin
21+
java-version: '21'
22+
23+
# bridge runs as a babashka CLI; cli-test spawns `bb bridge ...` and the
24+
# JSON backend resolves to cheshire (built into babashka) under :bb.
25+
- name: Set up Babashka
26+
shell: bash
27+
run: |
28+
set -euo pipefail
29+
curl -sLO https://raw.githubusercontent.com/babashka/babashka/master/install
30+
chmod +x install
31+
sudo ./install --dir /usr/local/bin
32+
33+
- uses: DeLaGuardo/setup-clojure@13.6.1
34+
with:
35+
cli: latest
36+
37+
- uses: actions/cache@v5
38+
with:
39+
path: |
40+
~/.m2/repository
41+
~/.gitlibs
42+
~/.clojure/.cpcache
43+
key: deps-${{ runner.os }}-${{ hashFiles('deps.edn') }}
44+
restore-keys: |
45+
deps-${{ runner.os }}-
46+
47+
# JVM test suite — the JSON backend resolves to charred under :clj.
48+
- name: Run tests
49+
run: clojure -M:test
50+
51+
- name: Build jar
52+
run: clojure -T:build jar

.github/workflows/deploy.yml

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
name: Release & Deploy to Clojars
2+
3+
# Publishes com.blockether/bridge to Clojars on a v* tag. Mirrors
4+
# Blockether/svar. Required org secret: CLOJARS_DEPLOY_TOKEN (deploy token for
5+
# the blockether-deployer account, scoped to the com.blockether group).
6+
on:
7+
push:
8+
tags: ['v*']
9+
10+
permissions:
11+
contents: write
12+
13+
jobs:
14+
release:
15+
runs-on: ubuntu-latest
16+
name: Release & Deploy to Clojars
17+
steps:
18+
- uses: actions/checkout@v5
19+
with:
20+
ref: main
21+
fetch-depth: 1
22+
token: ${{ secrets.GITHUB_TOKEN }}
23+
24+
- name: Fetch tags and changelog history
25+
run: |
26+
git fetch --tags --no-recurse-submodules origin
27+
git fetch --deepen=200 origin main
28+
29+
- uses: actions/setup-java@v5
30+
with:
31+
distribution: temurin
32+
java-version: '21'
33+
34+
- uses: DeLaGuardo/setup-clojure@13.6.1
35+
with:
36+
cli: latest
37+
38+
- uses: actions/cache@v5
39+
with:
40+
path: |
41+
~/.m2/repository
42+
~/.gitlibs
43+
~/.clojure/.cpcache
44+
key: deps-${{ runner.os }}-${{ hashFiles('deps.edn') }}
45+
restore-keys: |
46+
deps-${{ runner.os }}-
47+
48+
- name: Generate changelog
49+
run: |
50+
PREV_TAG=$(git tag --sort=-v:refname | head -n 2 | tail -n 1)
51+
CURRENT_TAG=${{ github.ref_name }}
52+
if [ -z "$PREV_TAG" ] || [ "$PREV_TAG" = "$CURRENT_TAG" ]; then
53+
CHANGELOG=$(git log --pretty=format:"- %s (%h)" $CURRENT_TAG)
54+
else
55+
CHANGELOG=$(git log --pretty=format:"- %s (%h)" ${PREV_TAG}..${CURRENT_TAG})
56+
fi
57+
{
58+
echo "## What's Changed"
59+
echo ""
60+
echo "$CHANGELOG"
61+
echo ""
62+
echo "**Full Changelog**: https://github.com/Blockether/bridge/compare/${PREV_TAG}...${CURRENT_TAG}"
63+
} > /tmp/release_body.md
64+
65+
- name: Create GitHub Release
66+
uses: softprops/action-gh-release@v3
67+
with:
68+
body_path: /tmp/release_body.md
69+
70+
- name: Check if version exists on Clojars
71+
id: check-clojars
72+
run: |
73+
VERSION="${GITHUB_REF_NAME#v}"
74+
STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
75+
"https://repo.clojars.org/com/blockether/bridge/$VERSION/bridge-$VERSION.jar")
76+
if [ "$STATUS" = "200" ]; then
77+
echo "exists=true" >> "$GITHUB_OUTPUT"
78+
echo "::notice::Version $VERSION already exists on Clojars — skipping deploy"
79+
else
80+
echo "exists=false" >> "$GITHUB_OUTPUT"
81+
fi
82+
83+
- name: Build & Deploy to Clojars
84+
if: steps.check-clojars.outputs.exists != 'true'
85+
env:
86+
VERSION: ${{ github.ref_name }}
87+
CLOJARS_USERNAME: blockether-deployer
88+
CLOJARS_PASSWORD: ${{ secrets.CLOJARS_DEPLOY_TOKEN }}
89+
run: clojure -T:build deploy

build.clj

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
(ns build
2+
"Publish com.blockether/bridge to Clojars. Mirrors Blockether/svar +
3+
Blockether/rift-clojure: tools.build + deps-deploy, version from the VERSION
4+
env (CI sets it from the release tag) else the canonical resources/bridge/VERSION
5+
file + -SNAPSHOT for local builds. bridge is pure Clojure(Script) — the jar
6+
ships sources (no AOT), loaded at require by consumers."
7+
(:require [clojure.string :as str]
8+
[clojure.tools.build.api :as b]
9+
[deps-deploy.deps-deploy :as dd]))
10+
11+
(def lib 'com.blockether/bridge)
12+
13+
(def version
14+
(let [v (System/getenv "VERSION")]
15+
(cond
16+
(and v (str/starts-with? v "v")) (subs v 1)
17+
(and v (seq v)) v
18+
:else (str (str/trim (slurp "resources/bridge/VERSION")) "-SNAPSHOT"))))
19+
20+
(def class-dir "target/classes")
21+
(def jar-file (format "target/%s.jar" (name lib)))
22+
(def basis (delay (b/create-basis {:project "deps.edn"})))
23+
24+
(defn clean [_]
25+
(b/delete {:path "target"}))
26+
27+
(defn jar [_]
28+
(clean nil)
29+
(b/write-pom {:class-dir class-dir
30+
:lib lib
31+
:version version
32+
:basis @basis
33+
:src-dirs ["src"]
34+
:pom-data [[:description
35+
"bridge — evidence-driven change & spec-traceability toolkit (CLI + library)."]
36+
[:url "https://github.com/Blockether/bridge"]
37+
[:licenses
38+
[:license
39+
[:name "Apache License, Version 2.0"]
40+
[:url "https://www.apache.org/licenses/LICENSE-2.0"]]]]})
41+
(b/copy-dir {:src-dirs ["src" "resources"] :target-dir class-dir})
42+
(b/jar {:class-dir class-dir :jar-file jar-file})
43+
(println "Built:" jar-file "version:" version))
44+
45+
(defn deploy [_]
46+
(jar nil)
47+
(dd/deploy {:installer :remote
48+
:artifact jar-file
49+
:pom-file (b/pom-path {:lib lib :class-dir class-dir})}))
50+
51+
(defn install
52+
"Install the built jar into the local Maven repository (~/.m2) so downstream
53+
projects pinning {:mvn/version ...} can pick it up without a remote deploy."
54+
[_]
55+
(jar nil)
56+
(dd/deploy {:installer :local
57+
:artifact jar-file
58+
:pom-file (b/pom-path {:lib lib :class-dir class-dir})}))

deps.edn

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
{:paths ["src" "resources"]
2+
;; JSON backend is chosen per-runtime in bridge.json via #?(:bb/:clj):
3+
;; - babashka (`bb bridge`): cheshire, which ships built into babashka
4+
;; (so it is NOT declared here);
5+
;; - JVM (embedded via bridge.api): charred — reflection-free and
6+
;; GraalVM native-image friendly, so a JVM host gets NO Jackson.
27
:deps {org.clojure/clojure {:mvn/version "1.12.0"}
38
clj-commons/clj-yaml {:mvn/version "1.0.29"}
4-
cheshire/cheshire {:mvn/version "5.13.0"}}
9+
com.cnuernber/charred {:mvn/version "1.038"}}
510
:aliases {:test {:extra-paths ["test"]
611
:main-opts ["-m" "bridge.test-runner"]}
712
:bridge {:main-opts ["-m" "bridge.cli"]}
8-
:build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.10"}}
9-
:ns-default build}
10-
:deploy {:deps {slipset/deps-deploy {:mvn/version "0.2.2"}}
11-
:exec-fn deps-deploy.deps-deploy/deploy
12-
:exec-args {:installer :remote
13-
:artifact "target/sparse-layout.jar"}}}}
13+
;; Publish com.blockether/bridge to Clojars: clojure -T:build deploy
14+
:build {:deps {io.github.clojure/tools.build {:mvn/version "0.10.13"}
15+
slipset/deps-deploy {:mvn/version "0.2.2"}}
16+
:ns-default build}}}

features/demo.feature.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
feature:
2+
name: demo
3+
product: demo
4+
components:
5+
CORE:
6+
requirements:
7+
1: Maps requirement to implementation

profile.edn

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{:subsystems [],
2+
:project-name "demo",
3+
:formal-paths ["specs"],
4+
:artifact-paths {:root "artifacts"},
5+
:test-paths ["test"],
6+
:docs-paths ["docs"],
7+
:phases [],
8+
:requirement-sources
9+
[{:kind :acai-feature-yaml,
10+
:globs ["features/**/*.feature.yaml"],
11+
:id-scheme :acid}],
12+
:kind "project-profile",
13+
:code-paths ["src"],
14+
:root-path ".",
15+
:canonical-commands []}

resources/bridge/VERSION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
0.1.0

src/bridge/cli.clj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
[bridge.summary :as summary]
1818
[bridge.templates :as templates]
1919
[bridge.workflow :as workflow]
20-
[cheshire.core :as json]
20+
[bridge.json :as json]
2121
[clojure.edn :as edn]
2222
[clojure.java.io :as io]
2323
[clojure.pprint :as pprint]
@@ -648,7 +648,7 @@
648648
(case format
649649
"edn" (println-data status)
650650
"summary" (println-data (summary/status-summary status))
651-
"summary-json" (println (json/generate-string (summary/status-summary status) {:pretty true}))
651+
"summary-json" (println (json/write-str (summary/status-summary status) :pretty true))
652652
(print (next-guide/render-plain status {:color? color?})))
653653
(flush)
654654
(when (= "check" command)

src/bridge/evidence.clj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
(:require [bridge.io :as bio]
33
[bridge.profile :as profile]
44
[bridge.schema :as schema]
5-
[cheshire.core :as json]
5+
[bridge.json :as json]
66
[clojure.edn :as edn]
77
[clojure.string :as str]))
88

@@ -51,7 +51,7 @@
5151

5252
(defn- maybe-json [s]
5353
(try
54-
(json/parse-string s keyword)
54+
(json/read-str s)
5555
(catch Exception _ nil)))
5656

5757
(defn- maybe-edn [s]

src/bridge/json.cljc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
(ns bridge.json
2+
"JSON read/write that works in BOTH execution contexts bridge runs in:
3+
4+
- Babashka (`bb bridge ...`): uses cheshire, which is built into babashka.
5+
- JVM (e.g. embedded as a library via `bridge.api`): uses charred, which is
6+
reflection-free and GraalVM native-image friendly — so a JVM host does NOT
7+
drag Jackson onto its classpath.
8+
9+
The `#?(:bb ... :clj ...)` reader conditionals select the backend at read
10+
time; charred is the only declared JVM dependency (cheshire ships with bb)."
11+
(:require #?(:bb [cheshire.core :as impl]
12+
:clj [charred.api :as impl])))
13+
14+
(defn read-str
15+
"Parse a JSON string into Clojure data with keyword keys."
16+
[s]
17+
#?(:bb (impl/parse-string s keyword)
18+
:clj (impl/read-json s :key-fn keyword)))
19+
20+
(defn write-str
21+
"Serialize Clojure data to a JSON string. With `:pretty true`, indent output."
22+
[x & {:keys [pretty]}]
23+
#?(:bb (impl/generate-string x {:pretty (boolean pretty)})
24+
:clj (if pretty
25+
(impl/write-json-str x :indent-str " ")
26+
(impl/write-json-str x))))

0 commit comments

Comments
 (0)