Tests, licence, and the tooling to keep the repo consistent
A test suite with no dependencies beyond bash and the Python standard library, run by one command: scripts/test.sh (or: make test) scripts/test.sh --windows [host] adds the PowerShell suite over SSH - tests/static.sh: script syntax, JSON validity, VERSION against the widget manifest and CHANGELOG, providers.json against the kinds, probes and checks the code implements (and ids that would shadow a command), qmllint and shellcheck when installed. - tests/python: unit tests for cm-json.py - providers and the column contract bash depends on, the original three scaffolds byte for byte, every catalogue parser and the cache, session scan/dismiss/repair, rename, defaults, health, the cost guard. - tests/cli: the CLI in a sandbox with its own HOME, CM_ROOT, CLAUDE_CONFIG_DIR and a file-only vault, against fake Ollama, LM Studio, keyed-gateway and proxy servers - preflight, presets, models, doctor, a real switch, sessions, and the key helper under a rename race. - tests/windows: the same idea on a Windows host, with USERPROFILE pointed at a temp folder so the real install is never touched. Also: MIT licence, CHANGELOG.md reconstructed from history, .editorconfig and .gitattributes pinning LF everywhere (what the tree already is), and scripts/bump-version.sh, since the version lives in two files and drifted once before. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Executable
+145
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env bash
|
||||
# Checks that need nothing but the tree: script syntax, JSON validity, the
|
||||
# version recorded in both places, and providers.json against the kinds of
|
||||
# behaviour the code knows. qmllint and shellcheck run when installed and are
|
||||
# skipped - out loud - when not.
|
||||
set -uo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
fails=0
|
||||
err="$(mktemp)"
|
||||
tmp="$(mktemp -d)"
|
||||
trap 'rm -rf "$err" "$tmp"' EXIT
|
||||
|
||||
check() {
|
||||
local what="$1"; shift
|
||||
if "$@" >"$err" 2>&1; then
|
||||
printf ' ok %s\n' "$what"
|
||||
else
|
||||
printf ' FAIL %s\n' "$what"
|
||||
sed 's/^/ | /' "$err" | head -n 20
|
||||
fails=$((fails + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
bash_syntax() {
|
||||
local f
|
||||
for f in linux/claude-mode linux/*.sh linux/lib/*.sh omarchy/install.sh scripts/*.sh \
|
||||
tests/*.sh tests/cli/*.sh tests/windows/*.sh; do
|
||||
[ -e "$f" ] || continue
|
||||
bash -n "$f" || { echo "$f"; return 1; }
|
||||
done
|
||||
}
|
||||
check 'shell scripts parse (bash -n)' bash_syntax
|
||||
|
||||
check 'python compiles' env PYTHONPYCACHEPREFIX="$tmp" python3 -m py_compile \
|
||||
linux/cm-json.py tests/fake_server.py tests/python/test_cm_json.py
|
||||
|
||||
check 'JSON files parse' python3 -c '
|
||||
import json, sys, glob
|
||||
for f in ["providers.json", "omarchy/smoido.claude-mode/manifest.json"] + glob.glob("presets/*.json"):
|
||||
try:
|
||||
json.load(open(f, encoding="utf-8"))
|
||||
except ValueError as e:
|
||||
sys.exit("%s: %s" % (f, e))'
|
||||
|
||||
check 'VERSION matches the widget manifest' python3 -c '
|
||||
import json
|
||||
v = open("VERSION").read().strip()
|
||||
m = json.load(open("omarchy/smoido.claude-mode/manifest.json"))["version"]
|
||||
assert v == m, "VERSION is %s but manifest.json says %s - use scripts/bump-version.sh" % (v, m)'
|
||||
|
||||
check 'CHANGELOG has an entry for VERSION' python3 -c '
|
||||
v = open("VERSION").read().strip()
|
||||
assert ("## %s " % v) in open("CHANGELOG.md", encoding="utf-8").read(), "no CHANGELOG.md entry for " + v'
|
||||
|
||||
# The values each field may take are the ones the code has a branch for. A new
|
||||
# kind of behaviour needs code in cm-json.py, the bash CLI and claude-mode.ps1
|
||||
# before it can appear here - this is where that is enforced.
|
||||
check 'providers.json is consistent with the code and the presets' python3 - <<'PY'
|
||||
import json, os, sys
|
||||
KINDS = {"openrouter", "lmstudio", "ollama", "openai", "static"}
|
||||
PROBES = {"always", "lenient", "local"}
|
||||
KEYS = {"required", "optional"}
|
||||
MODELS = {"per-tier", "one-for-all"}
|
||||
CHECKS = {"openrouter-key", "guardrail", "message-check", "catalogue-models",
|
||||
"ollama-context", "lmstudio-templates"}
|
||||
COLORS = {"cyan", "green", "yellow", "magenta", "white", "gray", "dkcyan"}
|
||||
COMMANDS = {"menu", "status", "anthropic", "presets", "preset", "set-key", "models",
|
||||
"doctor", "health", "preflight", "setup", "sessions", "repair-session",
|
||||
"repair", "help"}
|
||||
errors = []
|
||||
def bad(pid, msg): errors.append("%s: %s" % (pid, msg))
|
||||
|
||||
doc = json.load(open("providers.json", encoding="utf-8"))
|
||||
seen = {}
|
||||
for p in doc["providers"]:
|
||||
pid = p.get("id", "?")
|
||||
for name in [pid] + list(p.get("aliases") or []):
|
||||
if name in COMMANDS:
|
||||
bad(pid, "'%s' would shadow the '%s' command" % (name, name))
|
||||
if name in seen:
|
||||
bad(pid, "'%s' is already used by %s" % (name, seen[name]))
|
||||
seen[name] = pid
|
||||
for field in ("title", "label", "blurb", "defaultPreset", "preset", "catalogue", "setup", "logo"):
|
||||
if not p.get(field):
|
||||
bad(pid, "missing " + field)
|
||||
cat, srv, setup = p.get("catalogue") or {}, p.get("server") or {}, p.get("setup") or {}
|
||||
if cat.get("kind") not in KINDS:
|
||||
bad(pid, "catalogue.kind %r is not one of %s" % (cat.get("kind"), sorted(KINDS)))
|
||||
if cat.get("kind") == "static" and not cat.get("static"):
|
||||
bad(pid, "a static catalogue needs a static list")
|
||||
if srv.get("probe", "local") not in PROBES:
|
||||
bad(pid, "server.probe %r is not one of %s" % (srv.get("probe"), sorted(PROBES)))
|
||||
if srv.get("probe") in ("always", "lenient") and not srv.get("paths"):
|
||||
bad(pid, "a probed server needs server.paths")
|
||||
if setup.get("key", "required") not in KEYS:
|
||||
bad(pid, "setup.key %r is not one of %s" % (setup.get("key"), sorted(KEYS)))
|
||||
if setup.get("models", "per-tier") not in MODELS:
|
||||
bad(pid, "setup.models %r is not one of %s" % (setup.get("models"), sorted(MODELS)))
|
||||
for c in p.get("doctor") or []:
|
||||
if c not in CHECKS:
|
||||
bad(pid, "unknown doctor check %r" % c)
|
||||
if p.get("color", "gray") not in COLORS:
|
||||
bad(pid, "color %r is not one of %s" % (p.get("color"), sorted(COLORS)))
|
||||
auth = (p.get("preset") or {}).get("auth") or {}
|
||||
if auth.get("mode") not in ("vault", "literal"):
|
||||
bad(pid, "preset.auth.mode must be vault or literal")
|
||||
preset = os.path.join("presets", "%s.json" % p.get("defaultPreset"))
|
||||
if not os.path.exists(preset):
|
||||
bad(pid, "default preset %s does not exist" % preset)
|
||||
elif json.load(open(preset, encoding="utf-8")).get("provider") != pid:
|
||||
bad(pid, "default preset %s belongs to another provider" % preset)
|
||||
for f in sorted(os.listdir("presets")):
|
||||
pp = json.load(open(os.path.join("presets", f), encoding="utf-8"))
|
||||
if pp.get("provider") not in {p["id"] for p in doc["providers"]}:
|
||||
errors.append("presets/%s: provider %r is not in providers.json" % (f, pp.get("provider")))
|
||||
if errors:
|
||||
sys.exit("\n".join(errors))
|
||||
PY
|
||||
|
||||
QMLLINT="$(command -v qmllint || true)"
|
||||
[ -z "$QMLLINT" ] && [ -x /usr/lib/qt6/bin/qmllint ] && QMLLINT=/usr/lib/qt6/bin/qmllint
|
||||
if [ -n "$QMLLINT" ]; then
|
||||
# The shell's own modules (qs.*, Quickshell) do not resolve outside it, so
|
||||
# only real syntax errors count; unresolved-type warnings are expected.
|
||||
qml_syntax() {
|
||||
local f out
|
||||
for f in omarchy/smoido.claude-mode/*.qml; do
|
||||
out="$("$QMLLINT" "$f" 2>&1 | grep -iE 'error|expected token|syntax|unexpected|duplicate' || true)"
|
||||
[ -z "$out" ] || { echo "$f"; echo "$out"; return 1; }
|
||||
done
|
||||
}
|
||||
check 'QML has no syntax errors (qmllint)' qml_syntax
|
||||
else
|
||||
printf ' skip QML syntax (qmllint not installed)\n'
|
||||
fi
|
||||
|
||||
if command -v shellcheck >/dev/null 2>&1; then
|
||||
check 'shellcheck (errors only)' shellcheck -S error -s bash \
|
||||
linux/claude-mode linux/*.sh omarchy/install.sh scripts/*.sh tests/*.sh tests/cli/*.sh
|
||||
else
|
||||
printf ' skip shellcheck (not installed)\n'
|
||||
fi
|
||||
|
||||
[ "$fails" -eq 0 ]
|
||||
Reference in New Issue
Block a user