feat: web domain allowlist and markdown report rendering (#15) #59
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI (pythinker-code) | |
| on: | |
| pull_request: | |
| paths: | |
| - ".github/workflows/**" | |
| - "packages/**" | |
| - "src/**" | |
| - "tests/**" | |
| - "tests_e2e/**" | |
| - "tests_ai/**" | |
| - "web/**" | |
| - "pyproject.toml" | |
| - "uv.lock" | |
| push: | |
| branches: | |
| - main | |
| paths: | |
| - ".github/workflows/**" | |
| - "packages/**" | |
| - "src/**" | |
| - "tests/**" | |
| - "tests_e2e/**" | |
| - "tests_ai/**" | |
| - "web/**" | |
| - "pyproject.toml" | |
| - "uv.lock" | |
| env: | |
| NO_COLOR: "1" | |
| TERM: dumb | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" | |
| jobs: | |
| check: | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Python 3.14 | |
| id: setup-python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.14" | |
| allow-prereleases: true | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@v1 | |
| with: | |
| version: "0.8.5" | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| - name: Prepare building environment | |
| env: | |
| UV_PYTHON: ${{ steps.setup-python.outputs.python-path }} | |
| run: make prepare | |
| - name: Run checks | |
| env: | |
| UV_PYTHON: ${{ steps.setup-python.outputs.python-path }} | |
| run: make check-pythinker-code | |
| test: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| python-version: ["3.12", "3.13", "3.14"] | |
| runs-on: ubuntu-22.04 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Set up Python ${{ matrix.python-version }} | |
| id: setup-python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| allow-prereleases: true | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@v1 | |
| with: | |
| version: "0.8.5" | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| - name: Prepare building environment | |
| env: | |
| UV_PYTHON: ${{ steps.setup-python.outputs.python-path }} | |
| run: make prepare | |
| - name: Run tests | |
| env: | |
| UV_PYTHON: ${{ steps.setup-python.outputs.python-path }} | |
| PYTHONUTF8: "1" | |
| run: make test-pythinker-code | |
| build: | |
| strategy: | |
| # The release-blocking entries stay fail-fast. The experimental VS 2026 | |
| # probe below is opted out via continue-on-error so its failures do not | |
| # cancel sibling jobs. | |
| fail-fast: true | |
| matrix: | |
| include: | |
| - runner: ubuntu-22.04 | |
| target: x86_64-unknown-linux-gnu | |
| binary_path: dist/onefile/pythinker | |
| - runner: ubuntu-22.04-arm | |
| target: aarch64-unknown-linux-gnu | |
| binary_path: dist/onefile/pythinker | |
| - runner: macos-14 | |
| target: aarch64-apple-darwin | |
| binary_path: dist/onefile/pythinker | |
| - runner: windows-2022 | |
| target: x86_64-pc-windows-msvc | |
| binary_path: dist/onefile/pythinker.exe | |
| # Forward-compat probe for windows-latest -> VS 2026 migration | |
| # (rolling out 2026-06-08 to 2026-06-15). Same Rust target as the | |
| # release-blocking windows-2022 entry but built against VS 2026 / | |
| # MSVC v144 to surface toolchain regressions before windows-2022 | |
| # is deprecated. | |
| - runner: windows-2025-vs2026 | |
| target: x86_64-pc-windows-msvc | |
| binary_path: dist/onefile/pythinker.exe | |
| experimental: true | |
| runs-on: ${{ matrix.runner }} | |
| continue-on-error: ${{ matrix.experimental == true }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Install GNU Make (Windows) | |
| if: runner.os == 'Windows' | |
| run: choco install make -y | |
| - name: Set up Python 3.13 | |
| id: setup-python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.13" | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@v1 | |
| with: | |
| version: "0.8.5" | |
| enable-cache: true | |
| cache-dependency-glob: uv.lock | |
| - name: Set up Node.js (web build) | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| cache: "npm" | |
| cache-dependency-path: web/package-lock.json | |
| - name: Prepare building environment | |
| env: | |
| UV_PYTHON: ${{ steps.setup-python.outputs.python-path }} | |
| run: make prepare | |
| - name: Build standalone binary | |
| env: | |
| UV_PYTHON: ${{ steps.setup-python.outputs.python-path }} | |
| run: make build-bin | |
| - name: Smoke test binary --help | |
| shell: python | |
| run: | | |
| import os | |
| import subprocess | |
| import sys | |
| binary = os.path.abspath(os.environ["BINARY_PATH"]) | |
| result = subprocess.run([binary, "--help"], capture_output=True, text=True) | |
| if result.returncode != 0: | |
| print(f"Binary exited {result.returncode}", file=sys.stderr) | |
| print("--- stdout ---", file=sys.stderr) | |
| print(result.stdout, file=sys.stderr) | |
| print("--- stderr ---", file=sys.stderr) | |
| print(result.stderr, file=sys.stderr) | |
| raise SystemExit(result.returncode) | |
| if "Pythinker" not in result.stdout: | |
| print("--- stdout ---", file=sys.stderr) | |
| print(result.stdout, file=sys.stderr) | |
| raise SystemExit("'Pythinker' not found in --help output") | |
| env: | |
| BINARY_PATH: ${{ matrix.binary_path }} | |
| - name: Upload binary artifact | |
| # The experimental windows-2025-vs2026 probe shares its target string | |
| # with the release-blocking windows-2022 entry, so uploading would | |
| # collide on the artifact name. The probe only needs the build to | |
| # succeed; we do not consume its binary. | |
| if: success() && !matrix.experimental | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: pythinker-${{ matrix.target }} | |
| path: ${{ matrix.binary_path }} | |
| if-no-files-found: error | |
| retention-days: 7 | |
| release-validate: | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Set up Python 3.14 | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.14" | |
| allow-prereleases: true | |
| - name: Detect version bump | |
| id: version | |
| shell: python | |
| run: | | |
| import os | |
| import subprocess | |
| import tomllib | |
| output_path = os.environ["GITHUB_OUTPUT"] | |
| event_name = os.environ.get("GITHUB_EVENT_NAME", "") | |
| base_ref = os.environ.get("GITHUB_BASE_REF") | |
| if event_name != "pull_request" or not base_ref: | |
| with open(output_path, "a", encoding="utf-8") as output: | |
| output.write("bump=false\n") | |
| raise SystemExit(0) | |
| subprocess.run(["git", "fetch", "origin", base_ref, "--depth=1"], check=True) | |
| base_blob = subprocess.check_output( | |
| ["git", "show", f"origin/{base_ref}:pyproject.toml"], | |
| ) | |
| base_version = tomllib.loads(base_blob.decode())["project"]["version"] | |
| with open("pyproject.toml", "rb") as handle: | |
| head_version = tomllib.load(handle)["project"]["version"] | |
| bumped = base_version != head_version | |
| with open(output_path, "a", encoding="utf-8") as output: | |
| output.write(f"bump={'true' if bumped else 'false'}\n") | |
| output.write(f"base_version={base_version}\n") | |
| output.write(f"head_version={head_version}\n") | |
| - name: Show version bump info | |
| if: steps.version.outputs.bump == 'true' | |
| run: | | |
| echo "version bump: ${{ steps.version.outputs.base_version }} -> ${{ steps.version.outputs.head_version }}" | |
| - name: Check dependency versions | |
| if: steps.version.outputs.bump == 'true' | |
| run: | | |
| python scripts/check_pythinker_dependency_versions.py \ | |
| --root-pyproject pyproject.toml \ | |
| --pythinker-core-pyproject packages/pythinker-core/pyproject.toml \ | |
| --pythinker-host-pyproject packages/pythinker-host/pyproject.toml | |
| - name: Check pythinker-code version alignment | |
| if: steps.version.outputs.bump == 'true' | |
| env: | |
| EXPECTED_VERSION: ${{ steps.version.outputs.head_version }} | |
| run: | | |
| python scripts/check_version_tag.py \ | |
| --pyproject pyproject.toml \ | |
| --expected-version "$EXPECTED_VERSION" | |
| # Hard constraint: every pyproject version bump must be accompanied | |
| # by a README "What's New" section + pinned upgrade snippet and a | |
| # CHANGELOG entry. This is the same gate the release workflow | |
| # enforces at tag time, run here on the PR so we never tag a | |
| # version whose README/CHANGELOG drifted. | |
| - name: Verify README references the new version | |
| if: steps.version.outputs.bump == 'true' | |
| env: | |
| RELEASE_VERSION: ${{ steps.version.outputs.head_version }} | |
| run: | | |
| set -euo pipefail | |
| missing=() | |
| if ! grep -qF "What's New in ${RELEASE_VERSION}" README.md; then | |
| missing+=("a '## What\'s New in ${RELEASE_VERSION}' section") | |
| fi | |
| if ! grep -qF "pythinker-code==${RELEASE_VERSION}" README.md; then | |
| missing+=("a 'pip install --upgrade pythinker-code==${RELEASE_VERSION}' upgrade snippet") | |
| fi | |
| if [ "${#missing[@]}" -gt 0 ]; then | |
| printf '::error title=README out of sync with version bump %s::Update README.md in the same PR as the version bump. Missing: %s\n' \ | |
| "${RELEASE_VERSION}" "${missing[*]}" | |
| exit 1 | |
| fi | |
| echo "README.md references release ${RELEASE_VERSION}" | |
| - name: Verify CHANGELOG references the new version | |
| if: steps.version.outputs.bump == 'true' | |
| env: | |
| RELEASE_VERSION: ${{ steps.version.outputs.head_version }} | |
| run: | | |
| set -euo pipefail | |
| if ! grep -qE "^## ${RELEASE_VERSION} \\(" CHANGELOG.md; then | |
| printf '::error title=CHANGELOG out of sync with version bump %s::Add a "## %s (YYYY-MM-DD)" entry to CHANGELOG.md in the same PR as the version bump.\n' \ | |
| "${RELEASE_VERSION}" "${RELEASE_VERSION}" | |
| exit 1 | |
| fi | |
| echo "CHANGELOG.md has an entry for ${RELEASE_VERSION}" | |
| nix-test: | |
| strategy: | |
| fail-fast: true | |
| matrix: | |
| include: | |
| - runner: ubuntu-22.04 | |
| target: x86_64-unknown-linux-gnu | |
| binary_path: dist/pythinker | |
| - runner: ubuntu-22.04-arm | |
| target: aarch64-unknown-linux-gnu | |
| binary_path: dist/pythinker | |
| - runner: macos-14 | |
| target: aarch64-apple-darwin | |
| binary_path: dist/pythinker | |
| runs-on: ${{ matrix.runner }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Install Nix | |
| uses: DeterminateSystems/nix-installer-action@main | |
| - name: Run nix package | |
| run: nix run .#pythinker-code -- --version && nix run . -- --help |