Skip to content

Latest commit

 

History

History
303 lines (224 loc) · 9.52 KB

File metadata and controls

303 lines (224 loc) · 9.52 KB

Base Execution Model

This document describes the current basectl execution contract. It is about what happens after a user invokes Base, not the future project-discovery or Python orchestration layers.

Public Entrypoint

bin/basectl is the public control-plane command for Base. Base exposes one public executable directory: $BASE_HOME/bin.

basectl is responsible for deciding what kind of invocation the user asked for. It then delegates runtime setup to base_init.sh.

At a high level, basectl can:

  • start a Base-enabled interactive Bash shell
  • run the umbrella Base command dispatcher
  • run a Base command implementation by convention
  • run an explicit Bash script path inside the Base runtime

Base Home Discovery

basectl derives BASE_HOME from its own location. In the normal layout, bin/basectl lives directly under $BASE_HOME/bin, so the parent of bin/ is Base home.

This is intentionally a filesystem-layout contract, not a Git contract. Base does not require $BASE_HOME to be the root of a Git repository. That keeps the same runtime model usable when Base is checked out as its own repo or embedded inside a larger repository.

BASE_HOME is the selected Base installation root. For Homebrew installs this should remain on Homebrew's stable opt path, such as /usr/local/opt/base/libexec or /opt/homebrew/opt/base/libexec, rather than a versioned Cellar path. It is not the developer workspace root, and it is not changed by ~/.base.d/config.yaml. Project-discovery commands use the configured workspace.root from ~/.base.d/config.yaml when present, then fall back to BASE_HOME's parent for source-checkout layouts.

The Base home check validates the expected Base files instead of checking for a .git directory.

Dispatch Order

When basectl starts, it uses this dispatch order:

  1. If no arguments are provided and stdin/stdout are attached to a terminal, start a Base-enabled interactive Bash shell with Base's project virtual environment activated, while preserving the caller's current directory.
  2. If the first argument is path-like or names an existing file, treat it as a Bash script path and run that script inside the Base runtime.
  3. If the first argument matches a Base command implementation by convention, run that command implementation.
  4. Otherwise, run the umbrella Base command dispatcher.

This ordering lets explicit script paths win over command names.

Script Arguments

A first argument is treated as a script when either condition is true:

  • it contains /, such as ./script.sh, scripts/deploy, or /tmp/base-task.sh
  • it is an existing file in the current directory, such as deploy.sh or deploy

Script files do not need a .sh extension. The script is sourced as Bash and must define a main function. After sourcing the script, basectl calls main "$@" with the remaining arguments.

For command naming, a trailing .sh is stripped from BASE_BASH_COMMAND_NAME. Other extensions are left intact.

Examples:

basectl ./scripts/deploy.sh prod
basectl scripts/deploy prod
basectl deploy.sh prod      # works if deploy.sh exists in the current directory

A script can also opt into Base with a shebang:

#!/usr/bin/env basectl

main() {
    local project="${1:-}"

    if [[ -z "$project" ]]; then
        print_error "Project name is required."
        return 2
    fi

    log_info "Checking project '$project'."
    run git status --short
}

In shebang mode, the script defines main but does not call main "$@" itself. The operating system runs basectl with the script path as an argument. basectl then:

  1. resolves BASE_HOME
  2. treats the script path as an explicit Bash script
  3. exports runtime metadata such as BASE_BASH_COMMAND_NAME, BASE_BASH_COMMAND_DIR, and BASE_BASH_COMMAND_SCRIPT
  4. sources base_init.sh, which loads Base runtime variables and the Bash standard library
  5. sources the script
  6. calls main with the remaining user arguments

This makes Base stdlib helpers such as log_info, print_error, fatal_error, run, assert_command_exists, and import_base_lib available without the script sourcing lib_std.sh directly.

Standalone Bash scripts that are not intended to run through Base should use the standalone base-bash-libs package instead of depending on Base's bundled fallback:

#!/usr/bin/env bash
base_bash_libs_prefix="$(brew --prefix codeforester/base/base-bash-libs)"
source "$base_bash_libs_prefix/libexec/lib/bash/std/lib_std.sh"

main() {
    run echo "hello"
}

main "$@"

Command Implementations

Base command implementations are found by convention:

$BASE_HOME/cli/bash/commands/<command>/<command>.sh

For example:

basectl example

loads:

$BASE_HOME/cli/bash/commands/example/example.sh

A command implementation is sourced as Bash and must define main.

Umbrella Base Command

The umbrella command implementation lives at:

$BASE_HOME/cli/bash/commands/basectl/basectl.sh

It owns the current Base subcommands. The canonical, always-current command list is basectl --help; this list summarizes the shipped public surface:

  • setup
  • check
  • clean
  • doctor
  • activate
  • test
  • build
  • run
  • demo
  • repo
  • release
  • logs
  • workspace
  • onboard
  • gh
  • config
  • update
  • projects list
  • update-profile
  • version
  • help

Subcommand modules for the umbrella command live under:

$BASE_HOME/cli/bash/commands/basectl/subcommands/

Public Command Launchers

Base-owned convenience commands in $BASE_HOME/bin should be tiny real launcher files, not symlinks. They delegate to basectl and keep the public command surface in one place.

Example for a hypothetical Base-owned Bash command:

#!/usr/bin/env bash
exec "$(dirname "$0")/basectl" example "$@"

The implementation still lives under cli/bash/commands/<command>/ with its local README and tests.

Optional utility CLIs such as caff and sort-in-place live in codeforester/base-platform-tools instead of Base core.

When base-platform-tools is checked out next to Base and contains both base_manifest.yaml and bin/, Base's shell startup snippets add its bin/ directory to PATH after $BASE_HOME/bin. Runtime project shells keep project bin/ entries behind both Base and Base Platform Tools.

Runtime Bootstrap

base_init.sh is the runtime bootstrap layer. It is sourced after basectl has decided what should run.

base_init.sh establishes the Base runtime contract, including:

  • exported Base environment variables such as BASE_HOME, BASE_BIN_DIR, BASE_BASH_COMMANDS_DIR, BASE_BASH_LIB_DIR, BASE_BASH_LIBS_DIR, and BASE_BASH_LIBS_SOURCE
  • OS and host metadata such as BASE_OS and BASE_HOST
  • the reusable Bash standard library, resolved from base-bash-libs when available and from Base's bundled fallback otherwise
  • import_base_lib, the convention-based helper for sourcing Base Bash libraries from the resolved reusable root with Base's bundled root as a fallback
  • PATH additions needed by Base runtime execution

The full variable list, ownership rules, readonly policy, and ~/.baserc behavior are documented in Runtime Environment.

Downstream Bash scripts should import Base Bash libraries with:

import_base_lib file/lib_file.sh

import_base_lib checks the resolved reusable library root first, then Base's bundled lib/bash root. It fails through Base standard error handling when the requested library cannot be found, so callers do not need to duplicate that check.

The standalone install path, Base resolution order, and bundled-library removal gate are documented in Base Bash Libraries.

Runtime Shell

Running basectl activate <project> starts an interactive Bash shell with the Base runtime loaded and the project virtual environment activated. Running basectl with no arguments in a terminal starts the Base project runtime while preserving the caller's current directory.

That shell uses Base's runtime rcfile:

$BASE_HOME/lib/bash/runtime/bashrc

The runtime rcfile sources base_init.sh, sources the user's ~/.bashrc once with guardrails, activates the project virtual environment, sources any manifest-declared activate.source scripts, and then sets the Base runtime prompt. This gives the user their normal interactive Bash behavior while also making Base stdlib functions such as import_base_lib available during user Bash startup. Base still owns the final runtime prompt.

Bash startup paths share lib/shell/baserc_guard.sh for safe ~/.baserc loading and Base-owned variable protection. See Runtime Environment for the exact variables users may set there.

Dotfile Boundary

The normal shell-startup snippets under lib/shell/ do not source base_init.sh. They only manage Bash/Zsh startup concerns, including:

  • deriving BASE_HOME for the managed snippet
  • adding $BASE_HOME/bin to PATH
  • adding an optional sibling base-platform-tools/bin to PATH
  • loading simple user preferences from ~/.baserc
  • enabling optional shell defaults when requested

The full Base runtime is loaded only through the basectl command path.

Current Non-Goals

The current execution model does not yet define:

  • Python command dispatch
  • project discovery
  • project activation
  • release automation around version numbers
  • Linux support beyond the current macOS implementation

Those features should build on this execution contract rather than bypass it.