-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvox
More file actions
259 lines (235 loc) · 9.4 KB
/
Copy pathvox
File metadata and controls
259 lines (235 loc) · 9.4 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
#!/usr/bin/env bash
# ============================================================================
# vox — VoxForge Docker launcher
#
# One entrypoint for every Docker workflow. Run with no args for an
# interactive menu, or pass a command directly:
#
# ./vox # interactive menu
# ./vox build # compile the solution
# ./vox test # run unit tests (-> ./test-results)
# ./vox publish # self-contained binaries -> ./dist
# ./vox run # build + launch the GUI (X11)
# ./vox shell # drop into an SDK sandbox with the repo mounted
# ./vox clean # remove images / containers / build outputs
# ./vox doctor # check the local environment
#
# Env overrides: RID=linux-x64 ./vox publish
# DOTNET_VERSION=10.0 ALPINE_VERSION=3.22 ./vox build
# ============================================================================
set -Eeuo pipefail
cd "$(dirname "$(readlink -f "$0")")"
export DOTNET_VERSION="${DOTNET_VERSION:-10.0}"
export ALPINE_VERSION="${ALPINE_VERSION:-3.22}"
export RID="${RID:-linux-musl-x64}"
PUBLISH_TRIMMED="${PUBLISH_TRIMMED:-false}"
if [[ -t 1 ]]; then
BOLD=$'\e[1m'; DIM=$'\e[2m'; RED=$'\e[31m'; GRN=$'\e[32m'
YLW=$'\e[33m'; BLU=$'\e[34m'; CYN=$'\e[36m'; RST=$'\e[0m'
else
BOLD=''; DIM=''; RED=''; GRN=''; YLW=''; BLU=''; CYN=''; RST=''
fi
say() { printf '%s\n' "${CYN}▶ ${RST}$*"; }
ok() { printf '%s\n' "${GRN}✓ ${RST}$*"; }
warn() { printf '%s\n' "${YLW}! ${RST}$*" >&2; }
die() { printf '%s\n' "${RED}✗ ${RST}$*" >&2; exit 1; }
run() { printf '%s\n' "${DIM}\$ $*${RST}"; "$@"; }
trap 'die "failed at line $LINENO (exit $?)"' ERR
need_docker() {
command -v docker >/dev/null 2>&1 || die "docker is not installed or not on PATH"
docker info >/dev/null 2>&1 || die "docker daemon not reachable (is it running? are you in the 'docker' group?)"
}
DC=(docker compose)
compose() { run "${DC[@]}" "$@"; }
cmd_build() {
need_docker
say "Building solution (.NET ${DOTNET_VERSION}, RID ${RID})"
compose --profile build build build
ok "build image ready ${DIM}(voxforge:build)${RST}"
}
cmd_fmt() {
if command -v dotnet >/dev/null 2>&1; then
say "Auto-fixing formatting (dotnet format)"
run dotnet format
ok "formatting applied"
else
need_docker
say "Auto-fixing formatting via Docker"
compose --profile dev run --rm shell sh -c "dotnet format"
ok "formatting applied"
fi
}
cmd_lint() {
if command -v dotnet >/dev/null 2>&1; then
say "Checking formatting (dotnet format --verify-no-changes)"
run dotnet format --verify-no-changes
ok "formatting clean"
else
need_docker
say "Checking formatting via Docker"
compose --profile lint build lint
ok "formatting clean"
fi
}
cmd_test() {
need_docker
mkdir -p test-results
say "Running tests"
compose --profile test build test
compose --profile test run --rm test
ok "results in ${BOLD}./test-results${RST}"
}
cmd_publish() {
case "${1:-}" in
windows|win) RID=win-x64 ;;
linux|lnx) RID=linux-musl-x64 ;;
mac|macos|osx) RID=osx-x64 ;;
arm|mac-arm) RID=osx-arm64 ;;
"") : ;;
*) die "unknown platform '$1' — use: windows, linux, mac, arm" ;;
esac
need_docker
say "Publishing self-contained app (RID ${RID}, trimmed=${PUBLISH_TRIMMED})"
rm -rf dist && mkdir -p dist
run docker build \
--target artifacts \
--build-arg "DOTNET_VERSION=${DOTNET_VERSION}" \
--build-arg "ALPINE_VERSION=${ALPINE_VERSION}" \
--build-arg "RID=${RID}" \
--build-arg "PUBLISH_TRIMMED=${PUBLISH_TRIMMED}" \
--output dist \
.
ok "binaries in ${BOLD}./dist${RST}"
ls -lh dist 2>/dev/null || true
}
cmd_run() {
need_docker
say "Building runtime image"
compose --profile run build app
if [[ -n "${DISPLAY:-}" ]] && command -v xhost >/dev/null 2>&1; then
run xhost +local:docker >/dev/null 2>&1 || warn "xhost failed; GUI may not appear"
trap 'xhost -local:docker >/dev/null 2>&1 || true' EXIT
else
warn "no DISPLAY/xhost detected — a GUI window may not open (headless host?)"
fi
say "Launching VoxForge GUI"
compose --profile run run --rm --service-ports app "$@"
}
cmd_dev() {
if command -v dotnet >/dev/null 2>&1; then
say "Dev session (dotnet watch) — saves to .axaml trigger a rebuild+restart; Ctrl+C to stop"
# HotAvalonia (true in-place XAML reload) is off until a net10-compatible build
# ships, so compiled-binding .axaml edits are 'rude edits' that watch won't hot-apply.
# --no-hot-reload makes watch just rebuild+restart on every save, which is reliable;
# DOTNET_WATCH_RESTART_ON_RUDE_EDIT keeps it from prompting if that path is hit.
DOTNET_WATCH_RESTART_ON_RUDE_EDIT=1 \
run dotnet watch --no-hot-reload --project src/VoxForge.App run "$@"
else
warn "dotnet not found on PATH — falling back to Docker (slow on WSL2)"
warn "Install SDK natively: curl -sSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel 10.0"
if [[ -n "${DISPLAY:-}" ]] && command -v xhost >/dev/null 2>&1; then
run xhost +local:docker >/dev/null 2>&1 || warn "xhost failed; GUI may not appear"
trap 'xhost -local:docker >/dev/null 2>&1 || true' EXIT
fi
compose --profile watch run --rm watch
fi
}
cmd_shell() {
need_docker
say "SDK sandbox (.NET ${DOTNET_VERSION}) — repo mounted at /src, type 'exit' to leave"
compose --profile dev run --rm shell
}
cmd_clean() {
need_docker
warn "Removing VoxForge images, containers and build outputs"
compose down --remove-orphans 2>/dev/null || true
docker image rm -f voxforge:build voxforge:test voxforge:runtime 2>/dev/null || true
rm -rf dist test-results
ok "cleaned"
}
cmd_doctor() {
printf '%s\n' "${BOLD}VoxForge environment${RST}"
printf ' %-16s %s\n' "docker" "$(command -v docker >/dev/null 2>&1 && docker --version || echo "${RED}missing${RST}")"
printf ' %-16s %s\n' "compose" "$(docker compose version 2>/dev/null | head -1 || echo "${RED}missing${RST}")"
printf ' %-16s %s\n' "daemon" "$(docker info >/dev/null 2>&1 && echo "${GRN}running${RST}" || echo "${RED}unreachable${RST}")"
printf ' %-16s %s\n' "DISPLAY" "${DISPLAY:-${YLW}unset (no GUI)${RST}}"
printf ' %-16s %s\n' "xhost" "$(command -v xhost >/dev/null 2>&1 && echo present || echo "${YLW}absent${RST}")"
printf '\n %-16s %s\n' "DOTNET_VERSION" "$DOTNET_VERSION"
printf ' %-16s %s\n' "ALPINE_VERSION" "$ALPINE_VERSION"
printf ' %-16s %s\n' "RID" "$RID"
if [[ ! -f VoxForge.sln ]]; then
printf '\n'; warn "VoxForge.sln not found — source tree not scaffolded yet; builds will fail until src/ & tests/ exist"
fi
}
usage() {
cat <<EOF
${BOLD}vox${RST} — VoxForge Docker launcher
${BOLD}Usage:${RST} ./vox [command]
${GRN}build${RST} compile the solution (Release)
${GRN}fmt${RST} auto-fix code formatting (dotnet format)
${GRN}lint${RST} check code formatting (dotnet format)
${GRN}test${RST} run unit tests -> ./test-results
${GRN}publish${RST} self-contained binaries -> ./dist [windows|linux|mac|arm]
${GRN}dev${RST} hot-reload dev session (dotnet watch + HotAvalonia)
${GRN}run${RST} build + launch the GUI (X11 forwarded)
${GRN}shell${RST} SDK sandbox with the repo mounted
${GRN}clean${RST} remove images, containers & build outputs
${GRN}doctor${RST} check the local environment
${GRN}help${RST} this message
Run with no command for an interactive menu.
Overrides: ${DIM}RID=linux-x64 PUBLISH_TRIMMED=true DOTNET_VERSION=10.0 ./vox ...${RST}
EOF
}
menu() {
printf '%s\n' "${BOLD}${BLU}╔══════════════════════════════════╗${RST}"
printf '%s\n' "${BOLD}${BLU}║ VoxForge · vox ║${RST}"
printf '%s\n' "${BOLD}${BLU}╚══════════════════════════════════╝${RST}"
PS3=$'\n'"${CYN}Pick a task #${RST} "
local choice
select choice in \
"build — compile the solution" \
"lint — check code formatting" \
"test — run unit tests" \
"publish — self-contained binaries" \
"dev — hot-reload dev session" \
"run — launch the GUI" \
"shell — SDK sandbox" \
"clean — remove images & outputs" \
"doctor — check environment" \
"quit"; do
case "${choice%% *}" in
build) cmd_build; break ;;
fmt) cmd_fmt; break ;;
lint) cmd_lint; break ;;
test) cmd_test; break ;;
publish) cmd_publish; break ;;
dev) cmd_dev; break ;;
run) cmd_run; break ;;
shell) cmd_shell; break ;;
clean) cmd_clean; break ;;
doctor) cmd_doctor; break ;;
quit) exit 0 ;;
*) warn "invalid choice" ;;
esac
done
}
main() {
local cmd="${1:-}"
[[ $# -gt 0 ]] && shift || true
case "$cmd" in
build) cmd_build "$@" ;;
fmt|format) cmd_fmt "$@" ;;
lint) cmd_lint "$@" ;;
test) cmd_test "$@" ;;
publish) cmd_publish "$@" ;;
dev|watch) cmd_dev "$@" ;;
run) cmd_run "$@" ;;
shell|sh) cmd_shell "$@" ;;
clean) cmd_clean "$@" ;;
doctor|check) cmd_doctor "$@" ;;
help|-h|--help) usage ;;
"") menu ;;
*) warn "unknown command: $cmd"; echo; usage; exit 2 ;;
esac
}
main "$@"