Skip to content

refactor(tooling): move repository orchestration from shell to typed Bun scripts #1552

Description

@FelixTJDietrich

Outcome

Repository-owned developer and orchestration logic runs as typed, testable Bun/TypeScript. Shell remains only where it is the native boundary and contains no substantial product or policy logic.

Evidence

After PR #1545, six repository scripts contain about 1,300 lines of shell with process supervision, destructive cleanup, environment parsing, HTTP/SQL setup, secrets, and deployment smoke checks, but no enforced ShellCheck/shfmt or direct tests:

  • scripts/check-ports.sh
  • scripts/db-utils.sh
  • scripts/e2e-setup.sh
  • scripts/jean-public-test.sh
  • scripts/jean-setup.sh
  • scripts/qualify-bun-lockfile.sh

The repository already has a successful pattern of linted, type-checked, unit-tested TypeScript tooling. #1469 makes Bun available as the dependency-free TypeScript runner.

Decided invariants

  • Migrate the six scripts above; update package commands, jean.json, workflows, documentation, and instructions atomically with each command.
  • Redesign rather than transliterate: use structured filesystem, process, HTTP, JSON, hashing, and crypto APIs instead of wrapping bash -c, curl, jq, sed, or openssl in TypeScript.
  • Spawn commands with argument arrays. Shell interpolation of environment/user-derived values is prohibited.
  • Scripts that run before dependency installation use Bun built-ins only.
  • Preserve exit codes, signals, idempotency, secret handling, and cleanup. Destructive database/process orchestration gets planted failure/interruption tests; pure parsing/rendering/decision logic gets unit tests.
  • The normal format/lint/typecheck/test gate owns every migrated file.
  • Keep a small documented shell allowlist for justified boundaries. In particular, do not add Bun/Node to the final nginx image solely to replace webapp/docker/entrypoint.sh.
  • Minimal Husky launchers, simple Actions run steps, Dockerfile/Compose commands, and container bootstrap may remain shell when they immediately delegate and contain no substantive logic.

The implementation may use stacked PRs and choose migration order. Prefer low-risk scripts first, but preserve a working command surface at every layer.

Acceptance criteria

  • No substantive .sh remains under scripts/; every former command has one documented Bun entry point.
  • Behavioral equivalence is tested for normal, invalid-input, external-command failure, signal/interruption, and cleanup paths appropriate to each script.
  • Tests prove secrets do not enter process arguments, logs, generated public files, or thrown diagnostics.
  • CI, Jean, database, E2E, and public-test flows exercise the migrated commands.
  • Retained shell files are enumerated with their boundary rationale and contain no duplicated validation/policy logic.
  • perf(dx): define and enforce a fast, deterministic local verification contract #1549 measures the resulting local command surface rather than carrying this refactor's scope.

Non-goals

A cosmetic zero count of shell syntax, converting every GitHub Actions run: block, claiming Unix/Docker-specific tools are cross-platform, or mechanically rewriting the nginx runtime-config entrypoint.

Blocked by #1469. P1.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ciGitHub Actions, workflows, build pipeline changesmaintenanceChores, cleanup, non-functional improvementsrefactorCode restructuring without changing behavior

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions