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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ packages.

StackForge requires Node.js 20 or newer. Its build, template checks, and CLI
smoke tests run in CI on both Node.js 20 and the primary Node.js 22 runtime.
The full contributor `pnpm run release:check` also requires Python 3.11 or
newer to install and import the generated `python-api` projects. StackForge
checks `python3.13`, `python3.12`, `python3.11`, then `python3`; set
`STACKFORGE_PYTHON` to select another compatible interpreter explicitly.

## Status

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
"check": "tsc --noEmit",
"package-manager:check": "node scripts/check-package-manager.mjs",
"docs:check": "node scripts/check-docs.mjs",
"python:check": "bash scripts/test-resolve-python.sh",
"start": "node dist/index.js",
"check:templates": "pnpm build && node scripts/check-template-registry.mjs",
"smoke:init": "bash scripts/smoke-init.sh",
"release:check": "pnpm run package-manager:check && pnpm run docs:check && pnpm run build && pnpm test && pnpm run smoke && pnpm run package:smoke",
"release:check": "pnpm run package-manager:check && pnpm run docs:check && pnpm run python:check && pnpm run build && pnpm test && pnpm run smoke && pnpm run package:smoke",
"test": "pnpm run build && node scripts/check-template-registry.mjs",
"smoke": "pnpm run smoke:init",
"package:smoke": "pnpm run build && node scripts/package-smoke.mjs"
Expand Down
35 changes: 35 additions & 0 deletions scripts/resolve-python.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -euo pipefail

minimum="3.11"

is_supported() {
"$1" -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else 1)' >/dev/null 2>&1
}

if [ -n "${STACKFORGE_PYTHON:-}" ]; then
if ! command -v "$STACKFORGE_PYTHON" >/dev/null 2>&1; then
echo "StackForge: STACKFORGE_PYTHON '$STACKFORGE_PYTHON' was not found; set it to a Python >= $minimum interpreter." >&2
exit 1
fi
candidate="$(command -v "$STACKFORGE_PYTHON")"
if ! is_supported "$candidate"; then
echo "StackForge: STACKFORGE_PYTHON '$STACKFORGE_PYTHON' must be Python >= $minimum." >&2
exit 1
fi
printf '%s\n' "$candidate"
exit 0
fi

for name in python3.13 python3.12 python3.11 python3; do
if command -v "$name" >/dev/null 2>&1; then
candidate="$(command -v "$name")"
if is_supported "$candidate"; then
printf '%s\n' "$candidate"
exit 0
fi
fi
done

echo "StackForge: Python >= $minimum is required for the python-api smoke test. Install it or set STACKFORGE_PYTHON to a compatible interpreter." >&2
exit 1
3 changes: 2 additions & 1 deletion scripts/smoke-init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,8 @@ grep -q 'name = "my-api"' 'My API/pyproject.toml'
node "$repo_root/dist/index.js" init python-api '123 Weird.API!' > python-punctuated.json
test -f '123 Weird.API!/src/_123_weird_api/main.py'
grep -q 'name = "123-weird-api"' '123 Weird.API!/pyproject.toml'
python3 -m venv python-venv
python_interpreter="$("$repo_root/scripts/resolve-python.sh")"
"$python_interpreter" -m venv python-venv
python-venv/bin/python -m pip install './My API' './123 Weird.API!'
python-venv/bin/python -c 'from my_api.main import app; from _123_weird_api.main import app as numbered_app; assert app.title == "My API"; assert numbered_app.title == "123 Weird.API!"'
node "$repo_root/dist/index.js" init next-app web-app > next.json
Expand Down
39 changes: 39 additions & 0 deletions scripts/test-resolve-python.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/bash
set -euo pipefail

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT

make_python() {
name="$1"
supported="$2"
cat > "$tmp_dir/$name" <<EOF
#!/bin/bash
exit $supported
EOF
chmod +x "$tmp_dir/$name"
}

# A compatible versioned interpreter wins even when the unversioned one is old.
make_python python3.12 0
make_python python3 1
resolved="$(PATH="$tmp_dir" /bin/bash "$repo_root/scripts/resolve-python.sh")"
[ "$resolved" = "$tmp_dir/python3.12" ]

# An explicit incompatible interpreter is rejected with an actionable message.
if PATH="$tmp_dir" STACKFORGE_PYTHON=python3 /bin/bash "$repo_root/scripts/resolve-python.sh" > "$tmp_dir/out" 2> "$tmp_dir/err"; then
echo "incompatible configured Python unexpectedly succeeded" >&2
exit 1
fi
grep -q 'STACKFORGE_PYTHON.*Python >= 3.11' "$tmp_dir/err"

# With no compatible candidate, resolution fails independently of the host Python.
rm -f "$tmp_dir/python3.12"
if PATH="$tmp_dir" /bin/bash "$repo_root/scripts/resolve-python.sh" > "$tmp_dir/out" 2> "$tmp_dir/err"; then
echo "missing compatible Python unexpectedly succeeded" >&2
exit 1
fi
grep -q 'Install it or set STACKFORGE_PYTHON' "$tmp_dir/err"

echo "resolve-python tests ok"
Loading