Skip to content

env.sh: project selector for the transition, re-derived on cwd change #90

Description

@koenkooi

Part of #72. Depends on #76 (M2, the selector and mackas projects) and #77 (M3, pinned configs). Natural home alongside #79 (M5), which already owns the generated env files.

Why

During the transition there is a window where some projects are pinned and the old flat work/ layout is still in use. In that window . ~/oe/env.sh is the one command that says "I am about to build", and it is exactly when the tool knows least: cwd may be ~, or a legacy checkout, or a workspace whose pin was created months ago and forgotten. Today it resolves silently or not at all, and the first sign of a wrong answer is a cold build against volumes the user did not expect.

This adds an interactive selector when derivation cannot resolve a project, and a clear error when it cannot even offer a choice.

What ships

When env.sh is sourced:

  1. MACKAS_PROJECT_SELECT already set → do nothing. An explicit value always wins, following the same only-if-unset discipline _mackas_derive_project() already uses.
  2. No pinned configs at all → silent, byte-identical to today. See "Not an error" below.
  3. cwd resolves to exactly one pinned workspace → select it, print the one-line note.
  4. Pins exist, cwd matched none (or matched several), shell is interactive → numbered selector, listing each pinned project with its three resolved volume names. Empty input selects nothing and stays on the legacy path.
  5. Same, but non-interactive or opted out → bold red message naming what was found and what to do, then return 1. Never prompt, never block.

Re-derivation on cwd change

A selection made at source time is a tier-2 export, and tier 2 beats cwd derivation at tier 3. Left alone, a choice made once would silently outrank the workspace the user is standing in an hour later — the "stale correct guess" risk #72 names, made stickier by being an explicit export.

Decision: when cwd moves into a different pinned workspace, the selection is re-derived.

This requires distinguishing a value we set from one the user set, or re-derivation would trample an explicit choice:

  • When the selector (or cwd derivation) sets MACKAS_PROJECT_SELECT, it also records _MACKAS_PROJECT_PROVISIONAL=<name>.
  • Re-derivation acts only when MACKAS_PROJECT_SELECT is non-empty and equals _MACKAS_PROJECT_PROVISIONAL. If the user set the variable by hand — marker absent, or values diverged — it is never touched.
  • Moving into a different pinned workspace updates both, and prints a one-line note naming the new project and its volumes. Moving somewhere unpinned changes nothing: the current selection persists rather than being cleared, because "I cd'd to ~ for a moment" must not silently drop the build environment.

Mechanism: re-derive at use, not on every cd. The kas-container() shell function env.sh already defines runs on every hand-typed invocation and can re-derive immediately before dispatch. This costs nothing when idle, needs no shell hooks, and behaves identically under bash and zsh. A chpwd/PROMPT_COMMAND hook is a possible later addition purely to surface the note at cd time, and should not be a dependency — hooks are per-shell, intrusive, and differ between bash and zsh.

Constraints that shape the implementation

  • env.sh is sourced, and by zsh on a stock Mac (see the generator comment in mackas). No exit, no die — either kills the user's terminal; only return. No bashisms, and specifically not select, whose prompt behaviour differs between bash 3.2 and zsh. A numbered list plus read is portable.
  • Never block a non-interactive shell. README.md tells people to source this in every shell, so it lands in .zshrc: a prompt would fire on every new terminal, tmux pane and split, and would hang outright where there is no TTY (git hooks, scp, cron). Gate on case $- in *i* and [ -t 0 ], plus a MACKAS_ENV_NO_PROMPT=1 opt-out.
  • Colour reuses the existing gate (NO_COLOR, non-tty, TERM=dumb) rather than hardcoding escapes, or red bytes end up in logs and pipes.
  • Candidate configs are grepped, never sourced, consistent with mackas projects.

Not an error: no pinned projects

The red message fires only when pinned configs exist and none matched. With zero pins there is nothing to select and nothing is wrong — that user is on the fully supported "do nothing" path, and erroring would put a red error in every shell of every existing user for correctly using the tool. Zero pins stays silent and byte-identical to today.

Sketch

_mackas_select_project() {
    [ -n "${MACKAS_PROJECT_SELECT:-}" ] && return 0   # explicit always wins

    local d="$HOME/.config/mackas/projects" pins
    pins=$(ls "$d"/*.conf 2>/dev/null) || return 0  # no pins: silent, legacy
    [ -n "$pins" ] || return 0

    _mackas_derive_from_pwd && return 0             # cwd resolved it

    case $- in *i*) ;; *) _mackas_env_warn; return 1 ;; esac
    { [ -t 0 ] && [ -t 2 ]; } || { _mackas_env_warn; return 1; }
    [ "${MACKAS_ENV_NO_PROMPT:-0}" = 1 ] && { _mackas_env_warn; return 1; }

    # numbered list + read; empty input selects nothing
}

Tests

  • Zero pinned configs: sourcing is byte-identical to today, no prompt, no output, no non-zero return. Mutation-test this — it is the assertion protecting every existing user.
  • Pins exist, cwd inside one: selected without prompting, note printed once.
  • Pins exist, cwd matches none, non-interactive: no prompt, message on stderr, return 1, shell survives (assert the shell is still alive afterwards — a stray exit is the failure mode).
  • MACKAS_ENV_NO_PROMPT=1 suppresses the prompt in an interactive shell.
  • A pre-set MACKAS_PROJECT_SELECT survives sourcing untouched.
  • Re-derivation: provisional value updates on moving into a different pinned workspace; a hand-set value does not; moving somewhere unpinned leaves the selection intact.
  • Sourced under both bash 3.2 and zsh.
  • With NO_COLOR=1 and with stderr redirected to a file, no escape sequences appear.

Size: M. Risk: medium — it runs in the user's own interactive shell, where the failure modes are hangs and killed terminals rather than a bad build.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions