diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..76599ed --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @jasenc7 diff --git a/.github/ISSUE_TEMPLATE/issue.md b/.github/ISSUE_TEMPLATE/issue.md new file mode 100644 index 0000000..2064c20 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue.md @@ -0,0 +1,44 @@ +## name: Issue Report + +about: Report an issue in pyr.\ +title: "[ISSUE] "\ +labels: issue + +### **Describe the issue** + +A clear and concise description of what the issue is. + +--- + +### **To Reproduce** + +Steps to reproduce the behavior: + +1. Run `pyr ...` +2. See error + +**Expected behavior:**\ +What should happen? + +**Actual behavior:**\ +What happens instead? + +--- + +### **Environment** + +- OS: [e.g., macOS 13.4, Windows 11 ARM64, Ubuntu 22.04] +- `pyr --version`: [e.g., 0.1.0] +- Python version: [e.g., CPython 3.11.6] +- Deno version (if relevant): [e.g., 2.7.0] + +--- + +### **Additional Context** + +Add any other context about the problem here (e.g., logs, screenshots). + +--- + +**Note:** If this is a **feature request**, open a +[Discussion](https://github.com/jasenc7/pyr/discussions) instead. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..bfb0f00 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,80 @@ +## name: Question + +about: Ask a question or request support - we'll see how we can help! title: "[Q] "\ +labels: question + +### **Question** + +Describe what you’re trying to do and what’s happening. + +**What are you trying to do?**\ +_(e.g., "I’m trying to use pyr on Windows ARM64...")_ + +**What’s happening?**\ +_(e.g., "I get an error when running `pyr init`...")_ + +**What have you tried?**\ +_(e.g., "I checked the docs and ran `pyr --version`...")_ + +--- + +**Example:** + +> _"I’m trying to use pyr on Windows ARM64. When I run `pyr init`, I see [error]. I expected +> [result] but got [result]. I’ve tried [steps]."_ + +--- + +### **Environment** + +- OS: [e.g., macOS 13.4, Windows 11 ARM64, Ubuntu 22.04] +- `pyr --version`: [e.g., 0.1.0] +- Python version: [e.g., CPython 3.11.6] +- Deno version (if relevant): [e.g., 2.7.0] + +--- + +### **Additional Context** + +Add any other context about the problem here (e.g., logs, screenshots). + +--- + +**Note:** For issues, open an [Issue](https://github.com/jasenc7/pyr/issues). For feature requests, +use [Discussions](https://github.com/jasenc7/pyr/discussions). + +## name: Question or Support + +about: Ask a question or request help with pyr.\ +title: "[Q] "\ +labels: question + +### **Your Question** + +Describe what you’re trying to do and what’s happening. + +**Example:** + +> _"I’m trying to use pyr on Windows ARM64. When I run `pyr init`, I see [error]. I expected +> [result] but got [result]. I’ve tried [steps]."_ + +--- + +### **Environment (if relevant)** + +- OS: [e.g., Windows 11 ARM64, macOS 13.4] +- `pyr --version`: [e.g., 0.1.0] +- Python version: [e.g., CPython 3.14.4] + +--- + +### **What to Expect** + +- **Bugs:** If this is a bug, we’ll convert it to an issue. +- **Features:** If this is a feature request, we’ll move it to + [Discussions](https://github.com/jasenc7/pyr/discussions). +- **Questions:** We’ll answer here or redirect to Discussions for broader conversation. + +--- + +**Tip:** For bugs or feature requests, use the dedicated templates instead. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..342a357 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## v0.1.0 (2026-04-14) + +- Initial release diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4226df9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,154 @@ +# Contributing to pyr + +Thanks for your interest! pyr is a minimal tool, and contributions should focus on reducing ceremony +for Python projects. + +--- + +## **Reporting Bugs** + +Open an [issue](https://github.com/jasenc7/pyr/issues) with: + +- Steps to reproduce. +- Expected/actual behavior. +- OS, Python version, and `pyr --version`. + +--- + +## **Suggesting Features** + +Open a [Discussion](https://github.com/jasenc7/pyr/discussions) first. pyr’s scope is intentionally +narrow: + +- Does it reduce ceremony for Python projects? +- Would it make sense for `pip` or `venv` to do this?\ + If not, it’s likely out of scope. + +--- + +## **Pull Requests** + +1. Fork the repo and create a branch. +2. Run checks: + +```sh +deno fmt +deno lint +deno check +deno run test +``` + +3. Open a PR with: + +- A clear description. +- Reference to any related issues. + +--- + +## **Code Style** + +- **TypeScript:** Use `deno fmt`. +- **Shell scripts:** Use `shellcheck`. + +--- + +## **Development Setup** + +```sh +git clone https://github.com/jasenc7/pyr.git +cd pyr +deno fmt +deno lint +deno check +deno run test +``` + +### **Cross-Compile Locally** + +```sh +# Apple Silicon +deno compile --target aarch64-apple-darwin main.ts +# Windows x86 +deno compile --target x86_64-pc-windows-msvc main.ts +# Linux x86 +deno compile --target x86_64-unknown-linux-gnu main.ts +# Linux ARM +deno compile --target aarch64-unknown-linux-gnu main.ts +# Apple Intel x86 +deno compile --target x86_64-apple-darwin main.ts +``` + +### **Site Development** + +```sh +cd site +deno run dev +``` + +The site runs in Vite with HMR. + +--- + +## **Git Hooks (Optional)** + +To automate local checks, add these scripts to `.git/hooks/` and make them executable: + +### **1. Pre-Commit Hook** + +Save as `.git/hooks/pre-commit`: + +```sh +#!/bin/sh +set -eu +echo "🔍 Running pre-commit checks..." +deno fmt --check +deno lint +deno check +deno run test +``` + +### **2. Pre-Push Hook** + +Save as `.git/hooks/pre-push`: + +```sh +#!/bin/sh +set -eu +echo "🔍 Running pre-push checks..." +deno compile --target aarch64-apple-darwin main.ts +deno compile --target x86_64-pc-windows-msvc main.ts +deno compile --target x86_64-unknown-linux-gnu main.ts +deno compile --target aarch64-unknown-linux-gnu main.ts +deno compile --target x86_64-apple-darwin main.ts +``` + +### **3. Commit Message Hook** + +Save as `.git/hooks/commit-msg`: + +```sh +#!/bin/sh +set -eu +if ! grep -qE '^(feat|fix|docs|style|refactor|perf|test|chore|revert)!:? .{1,72}' "$1"; then + echo "❌ Commit message does not follow conventional format." + echo " Use: type(scope): description (e.g., 'feat: add Windows ARM64 support')" + exit 1 +fi +``` + +**Make hooks executable:** + +```sh +chmod +x .git/hooks/pre-commit .git/hooks/pre-push .git/hooks/commit-msg +``` + +**Skip hooks if needed:** + +```sh +git commit --no-verify +git push --no-verify +``` + +--- + +**Note:** pyr is not an app—it’s a tool. Keep changes minimal and focused. diff --git a/README.md b/README.md index 9344b11..d64f782 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# [pyrun.dev](http://pyrun.dev) +# [pyr](http://pyrun.dev) -**Python without the ceremony.** -A project manager that bootstraps its own runtime, manages your venv, and gets out of the way. +**Python without the ceremony.**\ +A project manager that bootstraps its own runtime, manages your venv, and gets out of the way.\ Six commands. One honest lockfile. -[Website](https://pyrun.dev) -[Docs](https://pyrun.dev/docs) -[App Convention](https://pyrun.dev/app-convention) -[Blog: Why pyr?](https://jasencarroll.com/python-project-manager.html) +[Website](https://pyrun.dev)\ +[Docs](https://pyrun.dev/docs)\ +[App Convention](https://pyrun.dev/app-convention)\ +[Blog: Why pyr?](https://jasencarroll.com/python-project-manager.html)\ [Blog: How pyr Works](https://jasencarroll.com/how-pyr-works.html) --- @@ -47,11 +47,13 @@ pyr upgrade --python # Update the managed CPython --- -## **Why pyrun?** +## **Why pyr?** - **Zero system deps:** Bootstraps its own CPython. No brew, no apt, no pyenv. -- **Six commands:** `init`, `run`, `add`, `remove`, `sync`, `upgrade`. No `activate`, no `pip freeze`. -- **Honest lockfile:** `pyproject.toml` is the source of truth. `requirements.txt` is a generated, fully-pinned lock. +- **Six commands:** `init`, `run`, `add`, `remove`, `sync`, `upgrade`. No `activate`, no + `pip freeze`. +- **Honest lockfile:** `pyproject.toml` is the source of truth. `requirements.txt` is a generated, + fully-pinned lock. - **Self-updating:** `pyr upgrade` updates the tool. `pyr upgrade --python` updates the runtime. - **Not written in Python:** The tool that manages Python shouldn’t need Python to install. @@ -78,7 +80,8 @@ myapp/ ## **How It Works** - **Bootstrapping:** Downloads a standalone CPython into `~/.pyr/python` on first use. -- **Venv Management:** Creates a project-local `.venv` from the managed Python. Rebuilds automatically if the Python version changes. +- **Venv Management:** Creates a project-local `.venv` from the managed Python. Rebuilds + automatically if the Python version changes. - **Dependency Resolution:** Delegates to `pip`. `requirements.txt` is a generated lockfile. - **TOML Surgery:** Edits `pyproject.toml` without destroying comments or formatting. - **Self-Upgrades:** Replaces the running binary with the latest release. @@ -94,7 +97,8 @@ For a deep dive, see: > The thing that manages Python shouldn’t be Python. -`pyr` is a single compiled binary. It drives `pip` and the standalone CPython runtime; it doesn’t depend on them to install itself. +`pyr` is a single compiled binary. It drives `pip` and the standalone CPython runtime; it doesn’t +depend on them to install itself. --- diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..86f7e21 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,37 @@ +# pyr roadmap + +This roadmap outlines goals for pyr. Priorities are driven by user feedback, maintainability, and +the Python ecosystem’s needs. + +--- + +### **1. Stability & Polish** + +- **Checksum Verification:** Enforce checksum verification in install scripts. + +### **2. Platform Support** + +- **Windows ARM64:** Track [Deno’s ARM64 support](https://deno.com/blog/v2.7) and add native Windows + ARM64 binaries once `deno compile` supports it. + - _Status:_ Blocked on Deno. Workaround: Document x86_64 emulation. + - Open to expiremental build with Bun. + +--- + +## **Non-Goals** + +- **Package Management / Packaging:** pyr is not a package manager. It's a project / app manager. + See `pip`. +- **Plugins** pyr is not a runtime. it will not have plugins for lint, test, deploy, etc. + +--- + +## **How to Influence the Roadmap** + +- **Open an issue** for feature requests or bugs. +- **Vote on discussions** to show interest in a specific feature. +- **Contribute code** (see [CONTRIBUTING.md](CONTRIBUTING.md)). + +--- + +**Last Updated:** April 2026 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..c47fc51 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,4 @@ +# Security Policy + +Report vulnerabilities by [emailing me](jasen@jasencarroll.com) or opening a **private** +[GitHub Security Advisory](https://github.com/jasenc7/pyr/security/advisories/new). diff --git a/site/assets/install.ps1 b/site/assets/install.ps1 index f51485c..048d412 100644 --- a/site/assets/install.ps1 +++ b/site/assets/install.ps1 @@ -1,26 +1,25 @@ -#Requires -Version 5.1 +<# +.SYNOPSIS +Installs pyr on Windows. +#> +Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" $Repo = "jasenc7/pyr" -$InstallDir = if ($env:PYR_HOME) { Join-Path $env:PYR_HOME "bin" } else { Join-Path $env:USERPROFILE ".pyr\bin" } - -$arch = (Get-CimInstance Win32_Processor).Architecture -switch ($arch) { - 9 { $target = "windows-x86_64" } # x64 - 12 { $target = "windows-aarch64" } # ARM64 - default { - Write-Error "unsupported arch: $arch" - exit 1 - } +$PyrHome = if ($env:PYR_HOME) { $env:PYR_HOME } else { Join-Path $env:USERPROFILE ".pyr" } +$InstallDir = Join-Path $PyrHome "bin" +$PythonDir = Join-Path $PyrHome "python\bin" + +switch ($env:PROCESSOR_ARCHITECTURE) { + "AMD64" { $target = "windows-x86_64" } + "ARM64" { $target = "windows-aarch64" } + default { Write-Error "unsupported arch: $env:PROCESSOR_ARCHITECTURE"; exit 1 } } $Url = "https://github.com/$Repo/releases/latest/download/pyr-$target.zip" - Write-Host "installing pyr..." - $tmp = Join-Path ([System.IO.Path]::GetTempPath()) ([System.Guid]::NewGuid().ToString()) New-Item -ItemType Directory -Path $tmp | Out-Null - try { $zipPath = Join-Path $tmp "pyr.zip" Invoke-WebRequest -Uri $Url -OutFile $zipPath -UseBasicParsing @@ -29,19 +28,28 @@ try { if (-not (Test-Path $InstallDir)) { New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null } - $src = Join-Path $tmp "pyr.exe" $dst = Join-Path $InstallDir "pyr.exe" Move-Item -Path $src -Destination $dst -Force - Write-Host "installed to $dst" # check PATH $userPath = [Environment]::GetEnvironmentVariable("Path", "User") - if ($userPath -notlike "*$InstallDir*") { + $parts = if ($userPath) { $userPath.Split(';', [StringSplitOptions]::RemoveEmptyEntries) } else { @() } + $hasInstall = $parts -icontains $InstallDir + $hasPython = $parts -icontains $PythonDir + + if (-not ($hasInstall -and $hasPython)) { + $missing = @() + if (-not $hasInstall) { $missing += $InstallDir } + if (-not $hasPython) { $missing += $PythonDir } + $toPrepend = $missing -join ';' + + Write-Host "" + Write-Host "add to your user PATH (run in PowerShell):" + Write-Host " [Environment]::SetEnvironmentVariable('Path', '$toPrepend;' + [Environment]::GetEnvironmentVariable('Path','User'), 'User')" Write-Host "" - Write-Host "add to your user PATH:" - Write-Host " [Environment]::SetEnvironmentVariable('Path', `"$InstallDir;`" + [Environment]::GetEnvironmentVariable('Path','User'), 'User')" + Write-Host "then restart your shell." } } finally { diff --git a/site/assets/install.sh b/site/assets/install.sh index 027a15e..249549e 100644 --- a/site/assets/install.sh +++ b/site/assets/install.sh @@ -1,8 +1,9 @@ #!/bin/sh -set -e +set -euo pipefail REPO="jasenc7/pyr" INSTALL_DIR="${PYR_HOME:-$HOME/.pyr}/bin" +PYTHON_DIR="${PYR_HOME:-$HOME/.pyr}/python/bin" main() { command -v unzip >/dev/null 2>&1 || { echo "unzip is required"; exit 1; } @@ -41,11 +42,12 @@ main() { # check PATH case ":$PATH:" in - *":${INSTALL_DIR}:"*) ;; + *":${INSTALL_DIR}:"*":${PYTHON_DIR}:"*) ;; + *":${PYTHON_DIR}:"*":${INSTALL_DIR}:"*) ;; *) echo "" echo "add to your shell profile:" - echo " export PATH=\"${INSTALL_DIR}:\$PATH\"" + echo " export PATH=\"${INSTALL_DIR}:${PYTHON_DIR}:\$PATH\"" ;; esac } diff --git a/site/routes/_app.tsx b/site/routes/_app.tsx index 808bbd1..f18c77b 100644 --- a/site/routes/_app.tsx +++ b/site/routes/_app.tsx @@ -14,11 +14,10 @@ export default function App({ Component, url }: PageProps) { data-cf-beacon={JSON.stringify({ token: "69eb3aa5f4874e21a287c7de86cfea48", })} - > - + > - pyrun - Python without the ceremony + pyr - Python without the ceremony