feat(openemr-cmd): wrap pre-commit tooling, add prek-in-container dispatch#718
feat(openemr-cmd): wrap pre-commit tooling, add prek-in-container dispatch#718bradymiller wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends openemr-cmd to act as a host-side bridge into the Flex container for pre-commit tooling, enabling contributors to run the repository’s pre-commit hooks (and related composer checks) without installing the underlying runtimes locally. It also adjusts the Flex container startup so required dev-only packages persist across container recreation scenarios.
Changes:
- Add
openemr-cmd prekdispatcher (in-containerpre-commit) plus host-onlyprek-install/prek-uninstallhook shims; add short wrapper commands for several composer-based checks. - Extend the Flex
devtoolsentrypoint to expose new composer commands and include key ones inclean-sweep. - Move installation of dev-only Alpine packages (chromium, codespell, pre-commit, actionlint) earlier in
openemr.shunder theDEVELOPER_TOOLS=yesgate; updateopenemr-cmd-hfilter count and bumpopenemr-cmdversion.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| utilities/openemr-cmd/openemr-cmd-h | Updates php-management help filter line count to reflect additional commands. |
| utilities/openemr-cmd/openemr-cmd | Adds worktree-cwd-aware container resolution, pre-commit dispatch (prek), host hook install/uninstall commands, new PHP/composer wrappers, and bumps version. |
| docker/openemr/flex/utilities/devtools | Adds devtools commands for composer-based pre-commit checks and wires key ones into clean-sweep. |
| docker/openemr/flex/openemr.sh | Installs dev-only Alpine packages early (idempotent) to avoid missing tooling after container recreation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| cfg=/tmp/openemr-cmd-pre-commit-config.yaml | ||
| if [ -f .pre-commit-config.yaml ]; then | ||
| sed "s/actionlint-docker/actionlint-system/g" .pre-commit-config.yaml > "${cfg}" | ||
| exec pre-commit "$@" --config "${cfg}" |
| cfg=/tmp/openemr-cmd-pre-commit-config.yaml | ||
| if [ -f .pre-commit-config.yaml ]; then | ||
| sed "s/actionlint-docker/actionlint-system/g" .pre-commit-config.yaml > "${cfg}" | ||
| exec pre-commit "$@" --config "${cfg}" |
| docker ps \ | ||
| --filter "label=com.docker.compose.project=openemr-${slug}" \ | ||
| --filter "label=com.docker.compose.service=openemr" \ | ||
| --format "{{.ID}}" | head -n 1 |
| #!/bin/sh | ||
| # openemr-cmd-managed commit-msg hook -- do not edit by hand | ||
| # ${PREK_HOOK_MARKER} | ||
| exec openemr-cmd prek run --hook-stage commit-msg --commit-msg-filename "\$1" | ||
| HOOKEOF | ||
| ;; | ||
| *) | ||
| cat > "${target}" <<HOOKEOF | ||
| #!/bin/sh | ||
| # openemr-cmd-managed pre-commit hook -- do not edit by hand | ||
| # ${PREK_HOOK_MARKER} | ||
| exec openemr-cmd prek run --hook-stage pre-commit | ||
| HOOKEOF |
| return 1 | ||
| fi | ||
| local backup | ||
| backup="${target}.bak.$(date +%s)" |
…patch
Adds in-container access to every pre-commit hook from .pre-commit-config.yaml
so developers can `git commit` from the host without installing PHP, Node,
Python, codespell, pre-commit, or actionlint locally. The openemr flex
container becomes the execution environment; openemr-cmd is the bridge.
devtools subcommands and openemr-cmd wrappers for each pre-commit composer
script: codespell (cps), conventional-commits-check (ccc), require-checker
(crc), composer-validate (cv), composer-normalize (cn), composer-normalize-fix
(cnf), composer-checks (cck), code-quality (cq). First five are also wired
into clean-sweep so the existing aggregate stays comprehensive.
prek dispatcher (`openemr-cmd prek <args>`) is a passthrough to in-container
pre-commit. Substitutes actionlint-docker -> actionlint-system at invocation
time so the actionlint hook works without DinD; the repo's
.pre-commit-config.yaml is untouched and host-side prek/pre-commit usage is
unaffected. Resolves the target container via cwd
(wt_resolve_container_from_cwd) so a commit fired from worktree foo
dispatches to foo's container, not whichever worktree container happens to
be running.
prek-install (pi) and prek-uninstall (pu) manage host-side
.git/hooks/{pre-commit,commit-msg} shims. Linked worktrees share gitcommondir,
so a single install on the primary covers every current and future worktree.
Both refuse to clobber non-managed hooks without --force and reject
invocation via '-d <container>' or 'worktree exec' with a clear error.
Container packages (actionlint, pre-commit, py3-codespell, plus the existing
chromium/chromium-chromedriver) now installed at top of openemr.sh under the
DEVELOPER_TOOLS=yes gate. Previously these lived inside the NEED_COMPOSER_BUILD
branch, so they did not survive `docker compose down --keep-volumes && up`
(vendor preserved -> block skipped -> packages missing). Moved out and
guarded with `apk info -e` so they re-install on container recreation but
are no-ops on subsequent starts.
Bumps openemr-cmd VERSION to 1.0.31. Updates openemr-cmd-h's php-management
filter line count from a stale 9 to 23 so all entries display.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…atch * SC2312 (line 185): process substitution masked jq's return value; add explicit '|| true' so the helper's empty-state handling is unambiguous to readers and to shellcheck. * SC2155 (line 885): split 'local backup="..."' so the assignment's exit code isn't masked by the 'local' builtin. * SC2310 (four sites in cmd_prek_install / cmd_prek_uninstall): drop 'func || exit 1' and 'if ! func' patterns (both of which disable set -e inside the called function) in favor of bare assignments that propagate the function's exit code through the script's set -euo pipefail. * --config placement (Copilot): pre-commit parses '--config' at the top-level parser. Trailing placement (after the subcommand) gave "unrecognized arguments". Move to precede "$@". * /tmp config race (Copilot): parallel openemr-cmd prek invocations in the same container could clobber each other's substituted config mid-run. Use mktemp for a unique path per invocation and trap-clean up on EXIT/INT/TERM. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docker ps in wt_resolve_container_from_cwd swallows daemon-unreachable errors (2>/dev/null) and forces a zero exit (|| true) so the caller can see an empty result rather than having 'set -o pipefail' kill the script before the dispatcher's own "no running openemr container" handling can run. * Hook shims bake in the absolute path to the openemr-cmd binary that installed them ($0 resolved via realpath, with command -v fallback). Git frequently invokes hooks with a reduced PATH (especially from GUI clients), so 'exec openemr-cmd ...' could fail when openemr-cmd lives in a user-local bindir like ~/bin or ~/.local/bin. * Backup filename for clobbered hooks uses mktemp instead of date +%s, so rapid 'prek-install --force' re-runs within the same second don't overwrite each other's backups. mktemp is portable across macOS BSD and Linux GNU coreutils (date +%s%N is GNU-only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
480537a to
0904563
Compare
| composer phpstan-baseline | ||
| fi | ||
|
|
||
| if [ "$1" = "codespell" ] || [ "$1" = "clean-sweep" ]; then |
There was a problem hiding this comment.
Suggest case/esac for this section, instead of if. There also really ought to be a way to avoid duplicating that cd. A shell function perhaps?
|
|
||
| while IFS=$'\t' read -r b d; do | ||
| [[ -z "${b}" ]] && continue | ||
| local d_real |
| [[ -z "${b}" ]] && continue | ||
| local d_real | ||
| d_real=$(realpath -m "${d}" 2>/dev/null) || continue | ||
| if [[ "${d_real}" == "${toplevel_real}" ]]; then |
There was a problem hiding this comment.
Recommend single equals. Just a style thing here, but double equals has subtle breakage when dealing with some shells. (Only time you need == in bash is in arithmetic context.)
Summary
Adds in-container access to every pre-commit hook from OpenEMR's
.pre-commit-config.yamlso developers cangit commitfrom the host without installing PHP, Node, Python, codespell, pre-commit, or actionlint locally. The openemr flex container becomes the execution environment; openemr-cmd is the bridge.cps(codespell),ccc(conventional-commits-check),crc(require-checker),cv(composer-validate),cn(composer-normalize),cnf(composer-normalize-fix),cck(composer-checks),cq(code-quality). First five wired intoclean-sweepso the existing aggregate stays comprehensive.openemr-cmd prek <args>: passthrough to in-containerpre-commit. Substitutesactionlint-docker→actionlint-systemat invocation time (DinD isn't available from inside the container); the repo's.pre-commit-config.yamlis untouched and host-side prek/pre-commit usage continues to work unchanged. cwd-aware container resolution (wt_resolve_container_from_cwd) so a commit fired from worktreefooalways dispatches tofoo's container.openemr-cmd pi/pu(prek-install/prek-uninstall): manage host-side.git/hooks/{pre-commit,commit-msg}shims. One install on the primary repo covers every linked worktree (current and future) via gitcommondir. Refuses to clobber non-managed hooks without--force. Runtime safeguard rejects invocation via-d <container>orworktree execwith a clear error.chromium,chromium-chromedriver,py3-codespell, plus the newpre-commitandactionlint, now installed at top ofopenemr.shunderDEVELOPER_TOOLS=yesgate (idempotent viaapk info -eguards). Previously insideNEED_COMPOSER_BUILD=true, which meant they didn't survivedocker compose down --keep-volumes && up(vendor preserved → block skipped → packages missing on the recreated container).openemr-cmdVERSION to 1.0.31. Fixes a staleopenemr-cmd-hfilter line count (was 9, now 23).End-to-end workflow this unlocks
For a contributor with only Docker installed on their host:
Manual invocation paths remain:
openemr-cmd prek run --all-files— full-codebase checkopenemr-cmd cq— full composer code-quality suite (direct composer path, bypasses prek)openemr-cmd worktree exec <branch> prek run --all-files— target a specific worktree from anywhereTest plan
openemr-cmdsmoke checksopenemr-cmd --versionreports 1.0.31openemr-cmd --helpshows thepre-commit-management:section withprek,pi/prek-install,pu/prek-uninstallentries, plus the eight new short-form wrappers underphp-managementopenemr-cmd-h phpdisplays all 23 php-management entries (was truncated before due to stale filter count)Per-tool wrappers (run inside any DEVELOPER_TOOLS=yes container;
openemr-cmdfrom host)openemr-cmd cps→ runs codespell, no crash on warningsopenemr-cmd cv→ reports composer.json validationopenemr-cmd cn→ dry-run normalization, reports diff or "already normalized"openemr-cmd crc→ composer-require-checker outputopenemr-cmd cq→ runs the full composer code-quality suiteprek dispatcher
openemr-cmd prek --versionreports the in-container pre-commit versionopenemr-cmd prek run --all-filesexecutes the full hook suite; actionlint runs without DinDopenemr-cmd prek run phpstanruns the single hook/tmp/openemr-cmd-pre-commit-config.yamlshowsactionlint-system(not-docker)foo,openemr-cmd prek rundispatches tofoo's container (verify viadocker pswhile it runs)Hook installation
openemr-cmd pifrom the primary repo writespre-commitandcommit-msgshims to<primary>/.git/hooks/__openemr_cmd_prek_hook__markergit commitfrom a worktree fires the shim and dispatches into that worktree's containergit commitfrom the primary fires the shim and dispatches into the primary's containeropenemr-cmd pirefuses to overwrite a non-managed pre-existing hook;pi --forcebacks it up to*.bak.<timestamp>and installsopenemr-cmd puremoves only marker-bearing hooks; skips non-managed ones with a noticeRuntime safeguards
openemr-cmd -d <any-container> piexits 1 with "host-only command" error (does not enter container)openemr-cmd worktree exec <branch> piproduces the same error transitivelypu/prek-uninstallContainer package persistence (the NEED_COMPOSER_BUILD fix)
openemr-cmd worktree down --keep-volumes <branch> && openemr-cmd worktree up <branch>, all ofchromium-browser,codespell,pre-commit, andactionlintresolve to installed binaries inside the new containerapk info -eshort-circuit)Compatibility
prek installfollowed bygit commitstill works for users who haven't runopenemr-cmd pi(no change to host workflow)🤖 Generated with Claude Code