Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 49 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Tests
name: Validate Demo

on:
push:
Expand All @@ -7,7 +7,55 @@ on:
jobs:
validate:
runs-on: macos-latest
env:
BASE_CACHE_DIR: ${{ github.workspace }}/.base-cache
BASE_SETUP_NOTIFY: "false"
PIP_DISABLE_PIP_VERSION_CHECK: "1"
steps:
- uses: actions/checkout@v4

- name: Install BATS
run: brew install bats-core

- name: Validate repository baseline
run: ./tests/validate.sh

- name: Run demo script tests
run: bats tests/demo_test.bats

- name: Check out Base
run: git clone --depth 1 https://github.com/codeforester/base.git ../base

- name: Detect Base demo support
id: base_demo
run: |
if ../base/bin/basectl demo --help >/dev/null 2>&1; then
echo "supported=true" >> "$GITHUB_OUTPUT"
else
echo "supported=false" >> "$GITHUB_OUTPUT"
echo "Base master does not support basectl demo yet; skipping full Base-backed validation."
fi

- name: Set up Base
if: steps.base_demo.outputs.supported == 'true'
run: ../base/bin/basectl setup base --no-notify

- name: Set up base-demo
if: steps.base_demo.outputs.supported == 'true'
run: ../base/bin/basectl setup base-demo --no-notify

- name: Check base-demo
if: steps.base_demo.outputs.supported == 'true'
run: ../base/bin/basectl check base-demo

- name: Doctor base-demo
if: steps.base_demo.outputs.supported == 'true'
run: ../base/bin/basectl doctor base-demo

- name: Test base-demo
if: steps.base_demo.outputs.supported == 'true'
run: ../base/bin/basectl test base-demo

- name: Run base-demo non-interactively
if: steps.base_demo.outputs.supported == 'true'
run: ../base/bin/basectl demo base-demo -- --non-interactive
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ and versions are tracked in the repo-root `VERSION` file.
- Initialized the repository with the Base-managed repo baseline.
- Added a Base manifest, Brewfile, activation source, example command, and
baseline validation script.
- Added the interactive `basectl demo base-demo` walkthrough and BATS coverage.
- Expanded GitHub Actions validation for the demo script and Base-backed demo
flow.
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ basectl check base-demo
basectl doctor base-demo
basectl run base-demo hello
basectl test base-demo
basectl demo base-demo
```

## Repository Shape
Expand All @@ -33,7 +34,11 @@ basectl test base-demo
- `Brewfile` is the Homebrew-owned place for ordinary macOS tools.
- `.base/activate.sh` demonstrates project activation state.
- `src/hello.sh` is a tiny command target for `basectl run`.
- `demo/demo.sh` is the interactive walkthrough.
- `tests/validate.sh` verifies that the repository baseline is intact.

The interactive `basectl demo base-demo` walkthrough lands in a follow-up change
after the Base demo command is available in a released Base version.
For CI or scripted validation, run the walkthrough without prompts:

```bash
basectl demo base-demo -- --non-interactive
```
4 changes: 4 additions & 0 deletions base_manifest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,8 @@ commands:
test:
command: ./tests/validate.sh

demo:
script: ./demo/demo.sh
description: Interactive walkthrough of Base-managed project conventions

artifacts: []
188 changes: 188 additions & 0 deletions demo/demo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
#!/usr/bin/env bash
set -euo pipefail

BASE_DEMO_PROJECT="${BASE_PROJECT:-base-demo}"
BASE_DEMO_ROOT="${BASE_PROJECT_ROOT:-}"
BASE_DEMO_BASECTL="${BASE_DEMO_BASECTL:-basectl}"
BASE_DEMO_NON_INTERACTIVE=0

demo_script_dir() {
cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P
}

demo_project_root() {
if [[ -n "$BASE_DEMO_ROOT" ]]; then
cd -- "$BASE_DEMO_ROOT" && pwd -P
return
fi

cd -- "$(demo_script_dir)/.." && pwd -P
}

BASE_DEMO_ROOT="$(demo_project_root)"

usage() {
cat <<'EOF'
Usage:
demo/demo.sh [--non-interactive] [-h|--help]

Run the base-demo interactive walkthrough.
EOF
}

parse_args() {
while (($#)); do
case "$1" in
--non-interactive)
BASE_DEMO_NON_INTERACTIVE=1
;;
-h|--help|help)
usage
return 2
;;
*)
printf 'ERROR: Unknown demo option %q\n' "$1" >&2
usage >&2
return 1
;;
esac
shift
done
}

pause() {
if [[ "$BASE_DEMO_NON_INTERACTIVE" == "1" || ! -t 0 ]]; then
return 0
fi

printf '\nPress Enter to continue...'
read -r _
printf '\n'
}

step() {
printf '\n== Step %s: %s ==\n\n' "$1" "$2"
}

run_command() {
printf ' $'
printf ' %q' "$@"
printf '\n'

if ! "$@"; then
printf '\nDemo step failed while running the command above.\n' >&2
printf 'Run it manually from %s, fix the issue, and retry the demo.\n' "$BASE_DEMO_ROOT" >&2
return 1
fi
}

capture_command() {
local output

printf ' $'
printf ' %q' "$@"
printf '\n'

if ! output="$("$@" 2>&1)"; then
printf '%s\n' "$output" >&2
printf '\nDemo step failed while running the command above.\n' >&2
return 1
fi

printf '%s\n' "$output"
}

require_contains() {
local label="$1"
local output="$2"
local expected="$3"

if [[ "$output" != *"$expected"* ]]; then
printf 'ERROR: Expected %s output to contain %q.\n' "$label" "$expected" >&2
return 1
fi
}

intro() {
printf '\nbase-demo Walkthrough\n\n'
printf 'This demo shows the smallest useful Base-managed project shape.\n'
printf 'Each step runs a real command or validates a real file in this repo.\n'
pause
}

project_shape_step() {
step 1 "Project Shape"
run_command test -f "$BASE_DEMO_ROOT/base_manifest.yaml"
run_command test -f "$BASE_DEMO_ROOT/Brewfile"
run_command test -x "$BASE_DEMO_ROOT/src/hello.sh"
run_command test -x "$BASE_DEMO_ROOT/tests/validate.sh"
pause
}

manifest_step() {
step 2 "Manifest Contracts"
run_command grep -n "name: base-demo" "$BASE_DEMO_ROOT/base_manifest.yaml"
run_command grep -n "hello: ./src/hello.sh" "$BASE_DEMO_ROOT/base_manifest.yaml"
run_command grep -n "command: ./tests/validate.sh" "$BASE_DEMO_ROOT/base_manifest.yaml"
run_command grep -n "script: ./demo/demo.sh" "$BASE_DEMO_ROOT/base_manifest.yaml"
pause
}

activation_step() {
step 3 "Project Activation Source"
# shellcheck source=/dev/null
source "$BASE_DEMO_ROOT/.base/activate.sh"
printf 'BASE_DEMO_ENV=%s\n' "${BASE_DEMO_ENV:-unset}"
require_contains "activation" "${BASE_DEMO_ENV:-}" "baseline"
pause
}

run_step() {
local output

step 4 "Declared Command"
output="$(capture_command "$BASE_DEMO_BASECTL" run "$BASE_DEMO_PROJECT" hello)"
printf '%s\n' "$output"
require_contains "run command" "$output" "hello from base-demo"
pause
}

test_step() {
local output

step 5 "Test Contract"
output="$(capture_command "$BASE_DEMO_BASECTL" test "$BASE_DEMO_PROJECT")"
printf '%s\n' "$output"
require_contains "test command" "$output" "Repository baseline is present."
pause
}

demo_step() {
local output

step 6 "Demo Contract"
output="$(capture_command "$BASE_DEMO_BASECTL" demo "$BASE_DEMO_PROJECT" --dry-run -- --non-interactive)"
printf '%s\n' "$output"
require_contains "demo command" "$output" "Would run demo"
pause
}

main() {
parse_args "$@" || {
local status=$?
[[ "$status" -eq 2 ]] && return 0
return "$status"
}

cd -- "$BASE_DEMO_ROOT"
intro
project_shape_step
manifest_step
activation_step
run_step
test_step
demo_step
printf '\nbase-demo walkthrough complete.\n'
}

main "$@"
68 changes: 68 additions & 0 deletions tests/demo_test.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env bats

setup() {
TEST_ROOT="$(cd "$BATS_TEST_DIRNAME/.." && pwd -P)"
TEST_TMPDIR="$(mktemp -d "${TMPDIR:-/tmp}/base-demo-test.XXXXXX")"
}

teardown() {
rm -rf "$TEST_TMPDIR"
}

@test "demo script is declared and executable" {
grep -Fq "script: ./demo/demo.sh" "$TEST_ROOT/base_manifest.yaml"
[ -x "$TEST_ROOT/demo/demo.sh" ]
}

@test "demo script prints help" {
run "$TEST_ROOT/demo/demo.sh" --help

[ "$status" -eq 0 ]
[[ "$output" == *"Run the base-demo interactive walkthrough."* ]]
}

@test "demo script runs in non-interactive mode" {
local fake_bin="$TEST_TMPDIR/bin"
local state_file="$TEST_TMPDIR/state"

mkdir -p "$fake_bin"
cat > "$fake_bin/basectl" <<'EOF'
#!/usr/bin/env bash
printf 'basectl %s\n' "$*" >> "${BASE_DEMO_TEST_STATE:?}"
case "$*" in
run\ base-demo\ hello)
printf 'hello from base-demo\n'
printf 'BASE_PROJECT=base-demo\n'
printf 'BASE_DEMO_ENV=%s\n' "${BASE_DEMO_ENV:-unset}"
;;
test\ base-demo)
printf 'Repository baseline is present.\n'
;;
demo\ base-demo\ --dry-run\ --\ --non-interactive)
printf '[DRY-RUN] Would run demo for project base-demo.\n'
;;
*)
printf 'unexpected basectl args: %s\n' "$*" >&2
exit 1
;;
esac
EOF
chmod +x "$fake_bin/basectl"

run env \
BASE_PROJECT=base-demo \
BASE_PROJECT_ROOT="$TEST_ROOT" \
BASE_DEMO_BASECTL="$fake_bin/basectl" \
BASE_DEMO_TEST_STATE="$state_file" \
"$TEST_ROOT/demo/demo.sh" --non-interactive

[ "$status" -eq 0 ]
[[ "$output" == *"base-demo Walkthrough"* ]]
[[ "$output" == *"BASE_DEMO_ENV=baseline"* ]]
[[ "$output" == *"hello from base-demo"* ]]
[[ "$output" == *"Repository baseline is present."* ]]
[[ "$output" == *"base-demo walkthrough complete."* ]]
grep -Fqx "basectl run base-demo hello" "$state_file"
grep -Fqx "basectl test base-demo" "$state_file"
grep -Fqx "basectl demo base-demo --dry-run -- --non-interactive" "$state_file"
}
9 changes: 8 additions & 1 deletion tests/validate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ required_files=(
Brewfile
.base/activate.sh
src/hello.sh
demo/demo.sh
tests/demo_test.bats
.github/workflows/tests.yml
)

Expand All @@ -21,7 +23,7 @@ for file in "${required_files[@]}"; do
}
done

for executable in tests/validate.sh .base/activate.sh src/hello.sh; do
for executable in tests/validate.sh .base/activate.sh src/hello.sh demo/demo.sh; do
[[ -x "$executable" ]] || {
printf 'Required file is not executable: %s\n' "$executable" >&2
exit 1
Expand All @@ -48,4 +50,9 @@ grep -Fq 'hello: ./src/hello.sh' base_manifest.yaml || {
exit 1
}

grep -Fq 'script: ./demo/demo.sh' base_manifest.yaml || {
printf 'base_manifest.yaml does not declare the demo script.\n' >&2
exit 1
}

printf 'Repository baseline is present.\n'
Loading