Repository navigation
Expand file tree
/
Copy pathcodedeck
More file actions
executable file
·280 lines (255 loc) · 10.6 KB
/
Copy pathcodedeck
File metadata and controls
executable file
·280 lines (255 loc) · 10.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
#!/usr/bin/env bash
# codedeck — one entrypoint for managing this fork: the bridge container,
# the optional Tor daemon, Android APK builds, and workspace checks.
#
# Everything here just wraps commands already established elsewhere in the
# repo (docker compose, apps/android/docker/build-apk.sh, cargo, pnpm) — this
# script adds no new mechanism, only a short memorable surface over them.
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
# Git Bash (MSYS) on Windows rewrites leading-/ arguments as if they were
# Windows paths before docker.exe sees them — breaks `docker exec -w /path`.
# Scoped (not exported globally): doing that also breaks docker cp's HOST-side
# path resolution. No-op on real Linux/macOS. Same fix as build-apk.sh.
dexec() { MSYS_NO_PATHCONV=1 docker exec "$@"; }
usage() {
cat <<'EOF'
codedeck — manage this CodeDeck fork
Usage: ./codedeck <command> [args]
Bridge (the deployed Docker service):
bridge up Build (if needed) and start the bridge
bridge down Stop the bridge (and codedeck-tor, if running)
bridge restart Rebuild the image and restart the bridge
bridge build Rebuild the bridge image only
bridge logs Follow the bridge's logs (Ctrl-C to stop)
bridge status Show what's running
bridge pair Print the most recent pairing QR/URL from the logs
bridge shell Open a shell inside the running bridge container
Tor (optional codedeck-tor service, Compose `tor` profile):
tor up Start codedeck-tor (also (re)starts the bridge,
since Compose profiles apply per `up` invocation)
tor down Stop codedeck-tor
Android APK (apps/android/docker/build-apk.sh — see that file for details):
apk [--target <triple>] Debug APK of the native app into dist/ (default:
arm64 device + x86_64 emulator)
Workspace checks (throwaway Node and Rust containers — nothing installed
locally is required):
check typecheck + test: the agent host and every crate
typecheck typecheck only (agent host + cargo check)
test test only
Bindings (run in a throwaway Rust container):
gen-android-bindings Regenerate apps/android/app/src/main/java/uniffi/**
from crates/client-ffi via uniffi-bindgen
(source of truth — see that crate's own doc
comment). CI fails if this drifts.
help Show this
EOF
}
require_running_bridge() {
if ! docker ps --filter "name=codedeck-bridge" --filter "status=running" --format '{{.Names}}' | grep -q codedeck-bridge; then
echo "codedeck-bridge is not running — try: ./codedeck bridge up" >&2
exit 1
fi
}
cmd_bridge() {
case "${1:-}" in
up)
docker compose up -d --build codedeck-bridge
;;
down)
docker compose down
;;
restart)
docker compose build codedeck-bridge
docker compose up -d codedeck-bridge
;;
build)
docker compose build codedeck-bridge
;;
logs)
docker compose logs -f codedeck-bridge
;;
status)
docker compose ps
;;
pair)
require_running_bridge
# The bridge process itself owns bridge.lock (CDX-033) — a second
# `pair` invocation inside the same container would just fail to
# acquire it, so this reads the QR the running `run` command already
# printed (it opens a pairing window on its own whenever nothing's
# paired) rather than trying to trigger a new one.
if ! docker compose logs codedeck-bridge 2>&1 | grep -q "Pairing URL"; then
echo "No pairing QR found in the logs yet. If the window already" >&2
echo "expired, restart the bridge for a fresh one: ./codedeck bridge restart" >&2
exit 1
fi
# The QR is printed just above its URL line.
docker compose logs --no-log-prefix codedeck-bridge 2>&1 | grep -B 32 -A 1 "Pairing URL" | tail -34
;;
shell)
require_running_bridge
dexec -it codedeck-bridge bash
;;
*)
echo "usage: $0 bridge {up|down|restart|build|logs|status|pair|shell}" >&2
exit 1
;;
esac
}
cmd_tor() {
case "${1:-}" in
up)
docker compose --profile tor up -d --build
echo
echo "codedeck-tor is up. Point the bridge at it with, in .env:"
echo " CODEDECK_TOR_PROXY_URL=socks5h://codedeck-tor:9050"
echo "then: ./codedeck bridge restart"
;;
down)
docker compose stop codedeck-tor
docker compose rm -f codedeck-tor
;;
*)
echo "usage: $0 tor {up|down}" >&2
exit 1
;;
esac
}
cmd_apk() {
# args pass through, e.g. `./codedeck apk --target aarch64-linux-android`
exec ./apps/android/docker/build-apk.sh "$@"
}
# shellcheck source=scripts/lib/pack-repo.sh
. scripts/lib/pack-repo.sh
# Start a throwaway container that is removed when the script exits, whether
# it succeeds, fails or is interrupted. Extra args go to `docker create`
# (volumes, limits); MSYS path conversion is off so `-v vol:/path` survives.
start_throwaway() {
local name="$1" image="$2"
shift 2
docker rm -f "$name" >/dev/null 2>&1 || true
MSYS_NO_PATHCONV=1 docker create --name "$name" "$@" "$image" sleep infinity >/dev/null
docker start "$name" >/dev/null
# Double-quoted so $name is baked in NOW, as a literal — the trap fires
# after the calling function has returned (its locals are gone by then), so
# a single-quoted trap referencing $name would fail under `set -u`.
trap "docker rm -f '$name' >/dev/null 2>&1 || true" EXIT
}
# Build a small tool image from an inline Dockerfile (no build context).
# Docker's layer cache makes every run after the first a no-op, so per-run
# `apt-get install` / `npm install -g` cost is paid once per machine.
ensure_image() {
local tag="$1"
shift
echo "==> Preparing $tag image (cached after the first run)"
docker build -q -t "$tag" "$@" - >/dev/null
}
# --- Workspace checks, in throwaway containers — nothing needs to be
# installed on the host.
#
# TypeScript (the agent host): a Node image with git and the pnpm pinned by
# package.json's packageManager; downloaded packages persist in the
# codedeck-pnpm-store volume, so `pnpm install` only fetches what the lockfile
# changed. Only the pnpm workspace is copied.
#
# Rust (every crate): the stock Rust image with clippy; the cargo registry
# and target dir live in named volumes, so a rerun only recompiles what
# changed. Only the cargo workspace is copied. ---
check_typescript() {
local name="codedeck-check-$$" pnpm_version
pnpm_version="$(sed -n 's/.*"packageManager": *"pnpm@\([^"]*\)".*/\1/p' package.json)"
ensure_image codedeck-check --build-arg "PNPM_VERSION=$pnpm_version" <<'DOCKERFILE'
FROM node:22-slim
ARG PNPM_VERSION
RUN apt-get update \
&& apt-get install -y --no-install-recommends git ca-certificates \
&& rm -rf /var/lib/apt/lists/* \
&& npm install -g "pnpm@${PNPM_VERSION}"
ENV npm_config_store_dir=/pnpm-store
DOCKERFILE
start_throwaway "$name" codedeck-check -v codedeck-pnpm-store:/pnpm-store
pack_repo_into "$name" /app package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.base.json packages
echo "==> Installing dependencies"
dexec -w /app "$name" pnpm install --frozen-lockfile
case "$1" in
typecheck) dexec -w /app "$name" pnpm -r --no-bail run typecheck ;;
test) dexec -w /app "$name" pnpm -r --no-bail run test ;;
check)
dexec -w /app "$name" pnpm -r --no-bail run typecheck
dexec -w /app "$name" pnpm -r --no-bail run test
;;
esac
}
check_rust() {
local name="codedeck-check-rust-$$"
ensure_image codedeck-check-rust <<'DOCKERFILE'
FROM rust:1-bookworm
RUN rustup component add clippy
DOCKERFILE
start_throwaway "$name" codedeck-check-rust \
-v codedeck-cargo-registry:/usr/local/cargo/registry \
-v codedeck-check-target:/repo/target
# The pack list must carry every repository-root file the workspace reads
# at compile time: bridge-runtime's tests include_str! config.example.json.
pack_repo_into "$name" /repo crates Cargo.toml Cargo.lock config.example.json
case "$1" in
typecheck) dexec -w /repo "$name" cargo clippy --locked --workspace --all-targets -- -D warnings ;;
test) dexec -w /repo "$name" cargo test --locked --workspace ;;
check)
dexec -w /repo "$name" cargo clippy --locked --workspace --all-targets -- -D warnings
dexec -w /repo "$name" cargo test --locked --workspace
;;
esac
}
# Each in a subshell: start_throwaway's EXIT trap removes that container.
run_checks() {
(check_typescript "$1")
(check_rust "$1")
}
# --- Regenerate apps/android/app/src/main/java/uniffi/** from Rust (see
# crates/client-ffi/src/lib.rs's own doc comment — the Kotlin binding
# surface is a build artifact of `uniffi-bindgen`'s library-mode generation,
# never hand-edited). Builds the cdylib
# for the HOST target (the generated Kotlin source is architecture-
# independent — it only reads the crate's embedded UniFFI metadata, not the
# target triple), runs the bindgen binary, copies the output back. Does NOT
# cross-compile the actual .so for an Android ABI — that's a separate,
# NDK-dependent build step (apps/android's own Gradle Rust task, once it
# exists), out of scope for a pure Kotlin-source regeneration command. Only
# the cargo workspace is copied; the cargo registry and target dir are
# cached volumes. ---
cmd_gen_android_bindings() {
local name="codedeck-gen-android-bindings-$$"
start_throwaway "$name" rust:1-bookworm \
-v codedeck-cargo-registry:/usr/local/cargo/registry \
-v codedeck-uniffi-target:/repo/target
pack_repo_into "$name" /repo crates Cargo.toml Cargo.lock
echo "==> Building crates/client-ffi (host target)"
dexec -w /repo "$name" cargo build --locked -p client-ffi --lib
echo "==> Running uniffi-bindgen (Kotlin, library mode)"
dexec -w /repo "$name" cargo run --locked -p client-ffi --bin uniffi-bindgen -- \
generate --library target/debug/libclient_ffi.so --language kotlin --no-format \
--out-dir /tmp/uniffi-kotlin-out
local dest="apps/android/app/src/main/java/uniffi"
rm -rf "$dest"
mkdir -p "$dest"
docker cp "$name":/tmp/uniffi-kotlin-out/uniffi/. "$dest"
echo "==> Wrote $dest/**"
}
case "${1:-help}" in
bridge) shift; cmd_bridge "$@" ;;
tor) shift; cmd_tor "$@" ;;
apk) shift; cmd_apk "$@" ;;
check) run_checks check ;;
typecheck) run_checks typecheck ;;
test) run_checks test ;;
gen-android-bindings) cmd_gen_android_bindings ;;
help|-h|--help) usage ;;
*)
echo "unknown command: $1" >&2
echo >&2
usage >&2
exit 1
;;
esac