forked from ferriskey/ferriskey
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathjustfile
More file actions
241 lines (220 loc) · 9.34 KB
/
justfile
File metadata and controls
241 lines (220 loc) · 9.34 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
set shell := ["bash", "-eu", "-o", "pipefail", "-c"]
# FerrisKey developer task runner
#
# Usage:
# just # same as: just help
# just help # list available recipes
# just <recipe> ... # run a recipe
#
# Common local workflows:
# 1) Start DB + run migrations (first time):
# just dev-setup
# 2) Run API with auto-reload (Rust):
# just dev
# 3) Run frontend dev server (Node/pnpm):
# just web
# just dev-web (local)
#
# Notes:
# - Some recipes may prompt to install missing tools (Docker, Node, pnpm, etc).
# - Environment files are created on-demand:
# api/.env from api/env.example
# front/.env from front/.env.example
# docker compose wrapper (always uses repo docker-compose.yaml)
compose := "docker compose -f docker-compose.yaml"
default: help
# List all recipes defined in this file.
help:
@just --list
# Internal helpers.
# Naming convention: recipes starting with '_' are not meant to be called directly.
_ensure-docker:
@if command -v docker >/dev/null 2>&1; then \
exit 0; \
fi; \
echo "Docker not found."; \
read -r -p "Install Docker now using get.docker.com? [y/N] " ans; \
case "${ans:-}" in \
y|Y|yes|YES) ;; \
*) echo "Skipping Docker install. Install Docker, then re-run." >&2; exit 1;; \
esac; \
if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then \
echo "Need 'curl' or 'wget' to install Docker." >&2; \
exit 1; \
fi; \
install_cmd=''; \
if command -v curl >/dev/null 2>&1; then install_cmd='curl -fsSL https://get.docker.com | sh'; \
else install_cmd='wget -qO- https://get.docker.com | sh'; fi; \
if command -v sudo >/dev/null 2>&1; then sudo sh -lc "$install_cmd"; else sh -lc "$install_cmd"; fi; \
echo "Docker install finished. If this is Linux, you may need to log out/in for group permissions."
_ensure-docker-running: _ensure-docker
@if ! docker info >/dev/null 2>&1; then \
echo "Docker daemon is not running (or you don't have permission)." >&2; \
echo "Start Docker Desktop (macOS/Windows) or start the daemon (Linux), then re-run." >&2; \
exit 1; \
fi
@if ! docker compose version >/dev/null 2>&1; then \
echo "'docker compose' is not available." >&2; \
echo "Install Docker Compose v2, then re-run." >&2; \
exit 1; \
fi
_ensure-node:
@if command -v node >/dev/null 2>&1; then \
exit 0; \
fi; \
echo "Node.js not found."; \
read -r -p "Install latest Node LTS via nvm? [y/N] " ans; \
case "${ans:-}" in \
y|Y|yes|YES) ;; \
*) echo "Skipping Node install." >&2; exit 1;; \
esac; \
if ! command -v curl >/dev/null 2>&1; then \
echo "Need 'curl' to install nvm." >&2; \
exit 1; \
fi; \
export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"; \
if [ ! -s "$NVM_DIR/nvm.sh" ]; then \
NVM_VERSION="$(curl -fsSL https://api.github.com/repos/nvm-sh/nvm/releases/latest | sed -n 's/.*\"tag_name\"[[:space:]]*:[[:space:]]*\"\\([^\\\"]*\\)\".*/\\1/p' | head -n1 || true)"; \
if [ -z "${NVM_VERSION:-}" ]; then NVM_VERSION="v0.40.04"; fi; \
curl -fsSL "https://raw.githubusercontent.com/nvm-sh/nvm/${NVM_VERSION}/install.sh" | bash; \
fi; \
. "$NVM_DIR/nvm.sh"; \
nvm install --lts; \
nvm use --lts
_ensure-pnpm: _ensure-node
@if command -v pnpm >/dev/null 2>&1; then \
exit 0; \
fi; \
echo "pnpm not found."; \
read -r -p "Install latest pnpm via corepack? [y/N] " ans; \
case "${ans:-}" in \
y|Y|yes|YES) ;; \
*) echo "Skipping pnpm install." >&2; exit 1;; \
esac; \
corepack enable; \
corepack prepare pnpm@latest --activate
_ensure-cargo-watch:
@if ! command -v cargo >/dev/null 2>&1; then \
echo "Rust toolchain not found (missing 'cargo'). Install rustup, then re-run." >&2; \
exit 1; \
fi; \
if command -v cargo-watch >/dev/null 2>&1; then \
exit 0; \
fi; \
echo "cargo-watch not found."; \
read -r -p "Install cargo-watch (latest) now? [y/N] " ans; \
case "${ans:-}" in \
y|Y|yes|YES) ;; \
*) echo "Skipping cargo-watch install." >&2; exit 1;; \
esac; \
cargo install cargo-watch
_ensure-sqlx-cli:
@if command -v sqlx >/dev/null 2>&1; then \
exit 0; \
fi; \
if ! command -v cargo >/dev/null 2>&1; then \
echo "Missing 'sqlx' (sqlx-cli) and Rust toolchain (missing 'cargo'). Install rustup, then re-run." >&2; \
exit 1; \
fi; \
echo "sqlx-cli not found."; \
read -r -p "Install sqlx-cli (postgres) now? [y/N] " ans; \
case "${ans:-}" in \
y|Y|yes|YES) ;; \
*) echo "Skipping sqlx-cli install." >&2; exit 1;; \
esac; \
cargo install sqlx-cli --no-default-features --features postgres
_wait-db: _ensure-docker-running
@# Wait for the Postgres container to accept connections.
@if [ ! -f api/.env ]; then cp api/env.example api/.env; fi
@set -a; . api/.env; set +a; \
echo "Waiting for Postgres to accept connections..."; \
for i in {1..60}; do \
if {{compose}} exec -T db pg_isready -U "${DATABASE_USER:-ferriskey}" -d "${DATABASE_NAME:-ferriskey}" >/dev/null 2>&1; then \
exit 0; \
fi; \
sleep 1; \
done; \
echo "Postgres did not become ready in time." >&2; \
exit 1
dev-setup: _ensure-docker-running
@# Bootstrap local development prerequisites:
@# - Create api/.env if missing
@# - Start the Postgres container
@# - Optionally run SQL migrations
@if [ ! -f api/.env ]; then cp api/env.example api/.env; fi
@POSTGRES_IMAGE=postgres:18.2 {{compose}} up -d db
@just _wait-db
@read -r -p "Run DB migrations now? [Y/n] " ans; \
case "${ans:-Y}" in \
n|N|no|NO) echo "Skipping migrations. You can run: just migrate";; \
*) just migrate;; \
esac
migrate: _ensure-sqlx-cli
@# Apply SQL migrations from core/migrations.
@# Uses DATABASE_URL if set; otherwise constructs it from api/.env values.
@# NOTE: When constructing DATABASE_URL, DATABASE_USER and DATABASE_PASSWORD are percent-encoded first.
@if [ -n "${DATABASE_URL:-}" ]; then \
sqlx migrate run --source core/migrations; \
exit 0; \
fi; \
if [ ! -f api/.env ]; then cp api/env.example api/.env; fi; \
set -a; . api/.env; set +a; \
: "${DATABASE_HOST:=localhost}"; \
: "${DATABASE_PORT:=5432}"; \
: "${DATABASE_NAME:=ferriskey}"; \
: "${DATABASE_USER:=ferriskey}"; \
: "${DATABASE_PASSWORD:=ferriskey}"; \
if command -v python3 >/dev/null 2>&1; then \
DATABASE_USER_ENC="$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "${DATABASE_USER}")"; \
DATABASE_PASSWORD_ENC="$(python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.argv[1], safe=""))' "${DATABASE_PASSWORD}")"; \
elif command -v python >/dev/null 2>&1; then \
DATABASE_USER_ENC="$(python -c 'import sys\ntry:\n from urllib.parse import quote\nexcept ImportError:\n from urllib import quote\nprint(quote(sys.argv[1], safe=""))' "${DATABASE_USER}")"; \
DATABASE_PASSWORD_ENC="$(python -c 'import sys\ntry:\n from urllib.parse import quote\nexcept ImportError:\n from urllib import quote\nprint(quote(sys.argv[1], safe=""))' "${DATABASE_PASSWORD}")"; \
else \
echo "Missing python3/python to percent-encode DATABASE_USER/DATABASE_PASSWORD when building DATABASE_URL." >&2; \
echo "Workaround: set DATABASE_URL directly (recommended), or ensure DATABASE_USER/DATABASE_PASSWORD are already percent-encoded." >&2; \
exit 1; \
fi; \
export DATABASE_URL="postgres://${DATABASE_USER_ENC}:${DATABASE_PASSWORD_ENC}@${DATABASE_HOST}:${DATABASE_PORT}/${DATABASE_NAME}"; \
sqlx migrate run --source core/migrations
dev: _ensure-cargo-watch
@# Run the Rust API locally with auto-reload on file changes.
@# Ensure api/.env exists (app loads it via dotenv)
@if [ ! -f api/.env ]; then cp api/env.example api/.env; fi
@cd api && cargo watch -x "run --bin ferriskey-api"
dev-test: _ensure-docker-running
@# Bring up the full stack using docker compose "build" profile (build + run containers).
@# "Full" build profile run (build + run containers)
@{{compose}} --profile build up -d --build
db-down: _ensure-docker-running
@# Stop and remove the compose stack + its volumes.
@# This is destructive for local data (drops your local DB state).
@{{compose}} down -v db || true
dev-test-down: _ensure-docker-running
@# Tear down docker compose build profile containers and volumes.
@{{compose}} --profile build down -v
dev-test-rm: _ensure-docker-running
@# Tear down docker compose build profile containers and volumes and remove images.
@{{compose}} --profile build down -v --rmi local
# Run frontend dev server locally (Node/pnpm)
dev-web: _ensure-pnpm
@cd front && pnpm install && pnpm run dev
web: _ensure-pnpm
@# Run the frontend server inside container.
@{{compose}} down -v webapp-build || true
@{{compose}} up webapp-build
gen-api:
@# Generate OpenAPI spec by running the API binary with a special command.
@cd api && cargo run --bin ferriskey-api -- gen-api --output ../openapi.json
@echo "OpenAPI spec written to openapi.json" >&2
gen-client: _ensure-docker-running gen-api
@# Use OpenAPI Generator CLI Docker image to generate Rust client code from openapi.json.
@docker run --rm \
--user "$(id -u):$(id -g)" \
-v "${PWD}:/local" \
openapitools/openapi-generator-cli generate \
-i /local/openapi.json \
-g rust \
-o /local/client \
--additional-properties packageName=ferriskey-client,packageVersion=0.1.0,library=reqwest,supportAsync=true
@echo "Rust client generated in client/" >&2