puba 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- puba-0.1.0/PKG-INFO +167 -0
- puba-0.1.0/README.md +158 -0
- puba-0.1.0/puba/__init__.py +0 -0
- puba-0.1.0/puba/build.py +32 -0
- puba-0.1.0/puba/checks.py +63 -0
- puba-0.1.0/puba/cli.py +48 -0
- puba-0.1.0/puba/git.py +11 -0
- puba-0.1.0/puba/pipeline.py +55 -0
- puba-0.1.0/puba/secrets.py +56 -0
- puba-0.1.0/puba.egg-info/PKG-INFO +167 -0
- puba-0.1.0/puba.egg-info/SOURCES.txt +15 -0
- puba-0.1.0/puba.egg-info/dependency_links.txt +1 -0
- puba-0.1.0/puba.egg-info/entry_points.txt +2 -0
- puba-0.1.0/puba.egg-info/requires.txt +1 -0
- puba-0.1.0/puba.egg-info/top_level.txt +1 -0
- puba-0.1.0/pyproject.toml +23 -0
- puba-0.1.0/setup.cfg +4 -0
puba-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: puba
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Safe Python package release CLI
|
|
5
|
+
Author: Griffin McManus
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: keyring>=23.0
|
|
9
|
+
|
|
10
|
+
# puba
|
|
11
|
+
|
|
12
|
+
**Safe Python Package Release CLI**
|
|
13
|
+
|
|
14
|
+
A minimal, dependency-free CLI to safely check, build, and publish Python packages without risking secrets.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- Ensure git working directory is clean before releasing
|
|
21
|
+
- Scan for secrets in the repo before publishing
|
|
22
|
+
- Run tests automatically (via `pytest` if installed)
|
|
23
|
+
- Build the package (`python -m build`)
|
|
24
|
+
- Safely upload to PyPI or TestPyPI
|
|
25
|
+
- Store your PyPI token securely in **macOS Keychain**, **Windows Credential Manager**, or Linux keyring
|
|
26
|
+
- Dry run mode for testing builds without uploading
|
|
27
|
+
- `checks` command to validate the repo without publishing
|
|
28
|
+
- No external dependencies required for CLI parsing
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
### 1️⃣ Store PyPI token securely
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
puba auth
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- On macOS → stored in Keychain
|
|
41
|
+
- On Windows → stored in Credential Manager
|
|
42
|
+
- On Linux → stored in system keyring if available
|
|
43
|
+
- Fallback → prompts you for input each time
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
### 2️⃣ Install optional dev tools (for tests, builds, and publishing)
|
|
48
|
+
|
|
49
|
+
If you want to run tests, build packages, or publish safely, install optional dependencies:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# zsh users: quote the extras to avoid glob errors
|
|
53
|
+
pip install ".[dev]"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- Installs: `pytest`, `build`, `twine`
|
|
57
|
+
- You can also install subsets:
|
|
58
|
+
```bash
|
|
59
|
+
pip install ".[test]" # only pytest
|
|
60
|
+
pip install ".[build]" # only build and twine
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
### 3️⃣ Run safety checks only
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
puba checks
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Checks performed:
|
|
72
|
+
|
|
73
|
+
- Git working directory is clean
|
|
74
|
+
- Secret scan (requires `gitleaks`)
|
|
75
|
+
- Runs tests (`pytest`)
|
|
76
|
+
- Builds the package
|
|
77
|
+
|
|
78
|
+
> No upload occurs in this step.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
### 4️⃣ Publish package
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
puba publish
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Options:**
|
|
89
|
+
|
|
90
|
+
- `--test` → Upload to **TestPyPI** instead of PyPI
|
|
91
|
+
- `--dry` → Run all checks and build without uploading
|
|
92
|
+
|
|
93
|
+
**Examples:**
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Standard release
|
|
97
|
+
puba publish
|
|
98
|
+
|
|
99
|
+
# Upload to TestPyPI
|
|
100
|
+
puba publish --test
|
|
101
|
+
|
|
102
|
+
# Dry run (check + build only)
|
|
103
|
+
puba publish --dry
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
### 5️⃣ Help
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
puba help
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Displays usage instructions and available commands.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Example Workflow
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
+----------------+
|
|
122
|
+
| puba auth |
|
|
123
|
+
+----------------+
|
|
124
|
+
|
|
|
125
|
+
v
|
|
126
|
+
+------------------+
|
|
127
|
+
| puba checks |
|
|
128
|
+
+------------------+
|
|
129
|
+
|
|
|
130
|
+
v
|
|
131
|
+
+-------------------+
|
|
132
|
+
| puba publish |
|
|
133
|
+
+-------------------+
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
1. Store token (one-time)
|
|
137
|
+
2. Check repo before release
|
|
138
|
+
3. Publish safely
|
|
139
|
+
|
|
140
|
+
Optional: test PyPI or dry-run:
|
|
141
|
+
```bash
|
|
142
|
+
puba publish --test
|
|
143
|
+
puba publish --dry
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Notes
|
|
149
|
+
|
|
150
|
+
- **Secrets are never committed** — always stored in system keyring or provided via environment variable `PYPI_TOKEN`.
|
|
151
|
+
- Works on **macOS, Windows, and Linux**.
|
|
152
|
+
- Can be integrated into **CI/CD pipelines** with environment variables:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
export PYPI_TOKEN=pypi-xxxxxxxxxxxx
|
|
156
|
+
puba publish --dry
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Recommended Tools (Optional)
|
|
162
|
+
|
|
163
|
+
- `pytest` → for running tests automatically
|
|
164
|
+
- `gitleaks` → for scanning secrets before publishing
|
|
165
|
+
- `build` → Python build system (`pip install build`)
|
|
166
|
+
- `twine` → for uploading packages (`pip install twine`)
|
|
167
|
+
|
puba-0.1.0/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# puba
|
|
2
|
+
|
|
3
|
+
**Safe Python Package Release CLI**
|
|
4
|
+
|
|
5
|
+
A minimal, dependency-free CLI to safely check, build, and publish Python packages without risking secrets.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Ensure git working directory is clean before releasing
|
|
12
|
+
- Scan for secrets in the repo before publishing
|
|
13
|
+
- Run tests automatically (via `pytest` if installed)
|
|
14
|
+
- Build the package (`python -m build`)
|
|
15
|
+
- Safely upload to PyPI or TestPyPI
|
|
16
|
+
- Store your PyPI token securely in **macOS Keychain**, **Windows Credential Manager**, or Linux keyring
|
|
17
|
+
- Dry run mode for testing builds without uploading
|
|
18
|
+
- `checks` command to validate the repo without publishing
|
|
19
|
+
- No external dependencies required for CLI parsing
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### 1️⃣ Store PyPI token securely
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
puba auth
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
- On macOS → stored in Keychain
|
|
32
|
+
- On Windows → stored in Credential Manager
|
|
33
|
+
- On Linux → stored in system keyring if available
|
|
34
|
+
- Fallback → prompts you for input each time
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
### 2️⃣ Install optional dev tools (for tests, builds, and publishing)
|
|
39
|
+
|
|
40
|
+
If you want to run tests, build packages, or publish safely, install optional dependencies:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# zsh users: quote the extras to avoid glob errors
|
|
44
|
+
pip install ".[dev]"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
- Installs: `pytest`, `build`, `twine`
|
|
48
|
+
- You can also install subsets:
|
|
49
|
+
```bash
|
|
50
|
+
pip install ".[test]" # only pytest
|
|
51
|
+
pip install ".[build]" # only build and twine
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
### 3️⃣ Run safety checks only
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
puba checks
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Checks performed:
|
|
63
|
+
|
|
64
|
+
- Git working directory is clean
|
|
65
|
+
- Secret scan (requires `gitleaks`)
|
|
66
|
+
- Runs tests (`pytest`)
|
|
67
|
+
- Builds the package
|
|
68
|
+
|
|
69
|
+
> No upload occurs in this step.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
### 4️⃣ Publish package
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
puba publish
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**Options:**
|
|
80
|
+
|
|
81
|
+
- `--test` → Upload to **TestPyPI** instead of PyPI
|
|
82
|
+
- `--dry` → Run all checks and build without uploading
|
|
83
|
+
|
|
84
|
+
**Examples:**
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Standard release
|
|
88
|
+
puba publish
|
|
89
|
+
|
|
90
|
+
# Upload to TestPyPI
|
|
91
|
+
puba publish --test
|
|
92
|
+
|
|
93
|
+
# Dry run (check + build only)
|
|
94
|
+
puba publish --dry
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
### 5️⃣ Help
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
puba help
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Displays usage instructions and available commands.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Example Workflow
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
+----------------+
|
|
113
|
+
| puba auth |
|
|
114
|
+
+----------------+
|
|
115
|
+
|
|
|
116
|
+
v
|
|
117
|
+
+------------------+
|
|
118
|
+
| puba checks |
|
|
119
|
+
+------------------+
|
|
120
|
+
|
|
|
121
|
+
v
|
|
122
|
+
+-------------------+
|
|
123
|
+
| puba publish |
|
|
124
|
+
+-------------------+
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
1. Store token (one-time)
|
|
128
|
+
2. Check repo before release
|
|
129
|
+
3. Publish safely
|
|
130
|
+
|
|
131
|
+
Optional: test PyPI or dry-run:
|
|
132
|
+
```bash
|
|
133
|
+
puba publish --test
|
|
134
|
+
puba publish --dry
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Notes
|
|
140
|
+
|
|
141
|
+
- **Secrets are never committed** — always stored in system keyring or provided via environment variable `PYPI_TOKEN`.
|
|
142
|
+
- Works on **macOS, Windows, and Linux**.
|
|
143
|
+
- Can be integrated into **CI/CD pipelines** with environment variables:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
export PYPI_TOKEN=pypi-xxxxxxxxxxxx
|
|
147
|
+
puba publish --dry
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Recommended Tools (Optional)
|
|
153
|
+
|
|
154
|
+
- `pytest` → for running tests automatically
|
|
155
|
+
- `gitleaks` → for scanning secrets before publishing
|
|
156
|
+
- `build` → Python build system (`pip install build`)
|
|
157
|
+
- `twine` → for uploading packages (`pip install twine`)
|
|
158
|
+
|
|
File without changes
|
puba-0.1.0/puba/build.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import shutil
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def build_package():
|
|
6
|
+
|
|
7
|
+
shutil.rmtree("dist", ignore_errors=True)
|
|
8
|
+
shutil.rmtree("build", ignore_errors=True)
|
|
9
|
+
|
|
10
|
+
subprocess.run(["python", "-m", "build"], check=True)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def upload_package(token, test=False):
|
|
14
|
+
|
|
15
|
+
repo = "testpypi" if test else "pypi"
|
|
16
|
+
|
|
17
|
+
subprocess.run(
|
|
18
|
+
[
|
|
19
|
+
"python",
|
|
20
|
+
"-m",
|
|
21
|
+
"twine",
|
|
22
|
+
"upload",
|
|
23
|
+
"--repository",
|
|
24
|
+
repo,
|
|
25
|
+
"dist/*",
|
|
26
|
+
"-u",
|
|
27
|
+
"__token__",
|
|
28
|
+
"-p",
|
|
29
|
+
token,
|
|
30
|
+
],
|
|
31
|
+
check=True,
|
|
32
|
+
)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import importlib.util
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
DEV_DEPS = ["build", "twine"]
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def check_installs():
|
|
9
|
+
missing = []
|
|
10
|
+
|
|
11
|
+
for tool in DEV_DEPS:
|
|
12
|
+
if importlib.util.find_spec(tool) is None:
|
|
13
|
+
missing.append(tool)
|
|
14
|
+
|
|
15
|
+
if not missing:
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
print(f"⚠️ Missing required tools: {', '.join(missing)}")
|
|
19
|
+
|
|
20
|
+
response = input("Install them now? [y/N]: ").strip().lower()
|
|
21
|
+
|
|
22
|
+
if response != "y":
|
|
23
|
+
print("Install manually with: pip install build twine")
|
|
24
|
+
sys.exit(1)
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
subprocess.check_call([sys.executable, "-m", "pip", "install", *missing])
|
|
28
|
+
print("✅ Dev tools installed.")
|
|
29
|
+
except subprocess.CalledProcessError:
|
|
30
|
+
print("❌ Failed to install dev tools.")
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def run_secret_scan():
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
subprocess.run(["gitleaks", "detect", "--no-git"], check=True)
|
|
38
|
+
except FileNotFoundError:
|
|
39
|
+
print("gitleaks not installed — skipping secret scan")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
import subprocess
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def run_secret_scan():
|
|
46
|
+
try:
|
|
47
|
+
subprocess.run(["gitleaks", "detect", "--no-git"], check=True)
|
|
48
|
+
except FileNotFoundError:
|
|
49
|
+
print("gitleaks not installed — skipping secret scan")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def run_tests():
|
|
53
|
+
"""Run pytest if available. Skip if no tests are found."""
|
|
54
|
+
try:
|
|
55
|
+
result = subprocess.run(["pytest"], capture_output=True, text=True)
|
|
56
|
+
if result.returncode == 5:
|
|
57
|
+
print("No tests found — skipping test step.")
|
|
58
|
+
elif result.returncode != 0:
|
|
59
|
+
raise RuntimeError(f"Tests failed with exit code {result.returncode}")
|
|
60
|
+
else:
|
|
61
|
+
print("Tests passed successfully.")
|
|
62
|
+
except FileNotFoundError:
|
|
63
|
+
print("pytest not installed — skipping tests.")
|
puba-0.1.0/puba/cli.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from .pipeline import release, run_checks_only
|
|
3
|
+
from .secrets import store_token
|
|
4
|
+
|
|
5
|
+
HELP_TEXT = """
|
|
6
|
+
pyrelease - Safe Python package release tool
|
|
7
|
+
|
|
8
|
+
Commands:
|
|
9
|
+
auth Store PyPI token securely
|
|
10
|
+
checks Run safety checks only (git, secrets, tests)
|
|
11
|
+
publish Build and upload your package safely
|
|
12
|
+
Options:
|
|
13
|
+
--test Upload to TestPyPI
|
|
14
|
+
--dry Run checks and build without uploading
|
|
15
|
+
help Show this help message
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main():
|
|
20
|
+
if len(sys.argv) < 2:
|
|
21
|
+
print(HELP_TEXT)
|
|
22
|
+
sys.exit(0)
|
|
23
|
+
|
|
24
|
+
cmd = sys.argv[1].lower()
|
|
25
|
+
options = sys.argv[2:]
|
|
26
|
+
|
|
27
|
+
if cmd == "auth":
|
|
28
|
+
store_token()
|
|
29
|
+
|
|
30
|
+
elif cmd == "checks":
|
|
31
|
+
run_checks_only()
|
|
32
|
+
|
|
33
|
+
elif cmd == "publish":
|
|
34
|
+
test = "--test" in options
|
|
35
|
+
dry = "--dry" in options
|
|
36
|
+
release(test=test, dry=dry)
|
|
37
|
+
|
|
38
|
+
elif cmd == "help":
|
|
39
|
+
print(HELP_TEXT)
|
|
40
|
+
|
|
41
|
+
else:
|
|
42
|
+
print(f"Unknown command: {cmd}\n")
|
|
43
|
+
print(HELP_TEXT)
|
|
44
|
+
sys.exit(1)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
if __name__ == "__main__":
|
|
48
|
+
main()
|
puba-0.1.0/puba/git.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from .git import ensure_clean_repo
|
|
2
|
+
from .checks import run_secret_scan, run_tests, check_installs
|
|
3
|
+
from .build import build_package, upload_package
|
|
4
|
+
from .secrets import get_pypi_token
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def release(test=False, dry=False):
|
|
8
|
+
print(f"Checking installs...")
|
|
9
|
+
check_installs()
|
|
10
|
+
|
|
11
|
+
print("Checking git state...")
|
|
12
|
+
ensure_clean_repo()
|
|
13
|
+
|
|
14
|
+
print("Scanning for secrets...")
|
|
15
|
+
run_secret_scan()
|
|
16
|
+
|
|
17
|
+
print("Running tests...")
|
|
18
|
+
run_tests()
|
|
19
|
+
|
|
20
|
+
print("Building package...")
|
|
21
|
+
build_package()
|
|
22
|
+
|
|
23
|
+
if dry:
|
|
24
|
+
print("Dry run complete")
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
token = get_pypi_token()
|
|
28
|
+
|
|
29
|
+
print("Uploading package...")
|
|
30
|
+
upload_package(token, test)
|
|
31
|
+
|
|
32
|
+
print("Release complete")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def run_checks_only():
|
|
36
|
+
"""
|
|
37
|
+
Run safety checks without uploading package.
|
|
38
|
+
Useful for CI or pre-release verification.
|
|
39
|
+
"""
|
|
40
|
+
print(f"Checking installs...")
|
|
41
|
+
check_installs()
|
|
42
|
+
|
|
43
|
+
print("Checking git state...")
|
|
44
|
+
ensure_clean_repo()
|
|
45
|
+
|
|
46
|
+
print("Scanning for secrets...")
|
|
47
|
+
run_secret_scan()
|
|
48
|
+
|
|
49
|
+
print("Running tests...")
|
|
50
|
+
run_tests()
|
|
51
|
+
|
|
52
|
+
print("Building package...")
|
|
53
|
+
build_package()
|
|
54
|
+
|
|
55
|
+
print("All checks passed. Package is ready for release.")
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import keyring
|
|
3
|
+
import getpass
|
|
4
|
+
import platform
|
|
5
|
+
|
|
6
|
+
SERVICE = "puba"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_pypi_token():
|
|
10
|
+
"""
|
|
11
|
+
Retrieve the PyPI token safely.
|
|
12
|
+
Order:
|
|
13
|
+
1. Environment variable
|
|
14
|
+
2. Keyring
|
|
15
|
+
3. Prompt user
|
|
16
|
+
"""
|
|
17
|
+
# 1. Env var
|
|
18
|
+
token = os.getenv("PYPI_TOKEN")
|
|
19
|
+
if token:
|
|
20
|
+
print("Using PyPI token from environment variable.")
|
|
21
|
+
return token
|
|
22
|
+
|
|
23
|
+
# 2. Keyring
|
|
24
|
+
token = keyring.get_password(SERVICE, "puba")
|
|
25
|
+
if token:
|
|
26
|
+
backend = keyring.get_keyring()
|
|
27
|
+
print(f"Using PyPI token stored in {backend}.")
|
|
28
|
+
return token
|
|
29
|
+
|
|
30
|
+
# 3. Prompt
|
|
31
|
+
token = getpass.getpass("Enter PyPI token: ")
|
|
32
|
+
return token
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def store_token():
|
|
36
|
+
"""
|
|
37
|
+
Prompt the user for PyPI token and store it securely.
|
|
38
|
+
"""
|
|
39
|
+
token = getpass.getpass("Enter PyPI token: ")
|
|
40
|
+
|
|
41
|
+
keyring.set_password(SERVICE, "puba", token)
|
|
42
|
+
|
|
43
|
+
system = platform.system()
|
|
44
|
+
|
|
45
|
+
if system == "Darwin":
|
|
46
|
+
location = "macOS Keychain"
|
|
47
|
+
elif system == "Windows":
|
|
48
|
+
location = "Windows Credential Manager"
|
|
49
|
+
elif system == "Linux":
|
|
50
|
+
location = "Linux Keyring backend (may vary)"
|
|
51
|
+
else:
|
|
52
|
+
location = "Unknown secure store"
|
|
53
|
+
|
|
54
|
+
print(
|
|
55
|
+
f"PyPI token stored securely in {location}. You can now run 'pyrelease publish' without re-entering it."
|
|
56
|
+
)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: puba
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Safe Python package release CLI
|
|
5
|
+
Author: Griffin McManus
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: keyring>=23.0
|
|
9
|
+
|
|
10
|
+
# puba
|
|
11
|
+
|
|
12
|
+
**Safe Python Package Release CLI**
|
|
13
|
+
|
|
14
|
+
A minimal, dependency-free CLI to safely check, build, and publish Python packages without risking secrets.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Features
|
|
19
|
+
|
|
20
|
+
- Ensure git working directory is clean before releasing
|
|
21
|
+
- Scan for secrets in the repo before publishing
|
|
22
|
+
- Run tests automatically (via `pytest` if installed)
|
|
23
|
+
- Build the package (`python -m build`)
|
|
24
|
+
- Safely upload to PyPI or TestPyPI
|
|
25
|
+
- Store your PyPI token securely in **macOS Keychain**, **Windows Credential Manager**, or Linux keyring
|
|
26
|
+
- Dry run mode for testing builds without uploading
|
|
27
|
+
- `checks` command to validate the repo without publishing
|
|
28
|
+
- No external dependencies required for CLI parsing
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
### 1️⃣ Store PyPI token securely
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
puba auth
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- On macOS → stored in Keychain
|
|
41
|
+
- On Windows → stored in Credential Manager
|
|
42
|
+
- On Linux → stored in system keyring if available
|
|
43
|
+
- Fallback → prompts you for input each time
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
### 2️⃣ Install optional dev tools (for tests, builds, and publishing)
|
|
48
|
+
|
|
49
|
+
If you want to run tests, build packages, or publish safely, install optional dependencies:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# zsh users: quote the extras to avoid glob errors
|
|
53
|
+
pip install ".[dev]"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
- Installs: `pytest`, `build`, `twine`
|
|
57
|
+
- You can also install subsets:
|
|
58
|
+
```bash
|
|
59
|
+
pip install ".[test]" # only pytest
|
|
60
|
+
pip install ".[build]" # only build and twine
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
### 3️⃣ Run safety checks only
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
puba checks
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Checks performed:
|
|
72
|
+
|
|
73
|
+
- Git working directory is clean
|
|
74
|
+
- Secret scan (requires `gitleaks`)
|
|
75
|
+
- Runs tests (`pytest`)
|
|
76
|
+
- Builds the package
|
|
77
|
+
|
|
78
|
+
> No upload occurs in this step.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
### 4️⃣ Publish package
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
puba publish
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Options:**
|
|
89
|
+
|
|
90
|
+
- `--test` → Upload to **TestPyPI** instead of PyPI
|
|
91
|
+
- `--dry` → Run all checks and build without uploading
|
|
92
|
+
|
|
93
|
+
**Examples:**
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Standard release
|
|
97
|
+
puba publish
|
|
98
|
+
|
|
99
|
+
# Upload to TestPyPI
|
|
100
|
+
puba publish --test
|
|
101
|
+
|
|
102
|
+
# Dry run (check + build only)
|
|
103
|
+
puba publish --dry
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
### 5️⃣ Help
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
puba help
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Displays usage instructions and available commands.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Example Workflow
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
+----------------+
|
|
122
|
+
| puba auth |
|
|
123
|
+
+----------------+
|
|
124
|
+
|
|
|
125
|
+
v
|
|
126
|
+
+------------------+
|
|
127
|
+
| puba checks |
|
|
128
|
+
+------------------+
|
|
129
|
+
|
|
|
130
|
+
v
|
|
131
|
+
+-------------------+
|
|
132
|
+
| puba publish |
|
|
133
|
+
+-------------------+
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
1. Store token (one-time)
|
|
137
|
+
2. Check repo before release
|
|
138
|
+
3. Publish safely
|
|
139
|
+
|
|
140
|
+
Optional: test PyPI or dry-run:
|
|
141
|
+
```bash
|
|
142
|
+
puba publish --test
|
|
143
|
+
puba publish --dry
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Notes
|
|
149
|
+
|
|
150
|
+
- **Secrets are never committed** — always stored in system keyring or provided via environment variable `PYPI_TOKEN`.
|
|
151
|
+
- Works on **macOS, Windows, and Linux**.
|
|
152
|
+
- Can be integrated into **CI/CD pipelines** with environment variables:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
export PYPI_TOKEN=pypi-xxxxxxxxxxxx
|
|
156
|
+
puba publish --dry
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Recommended Tools (Optional)
|
|
162
|
+
|
|
163
|
+
- `pytest` → for running tests automatically
|
|
164
|
+
- `gitleaks` → for scanning secrets before publishing
|
|
165
|
+
- `build` → Python build system (`pip install build`)
|
|
166
|
+
- `twine` → for uploading packages (`pip install twine`)
|
|
167
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
puba/__init__.py
|
|
4
|
+
puba/build.py
|
|
5
|
+
puba/checks.py
|
|
6
|
+
puba/cli.py
|
|
7
|
+
puba/git.py
|
|
8
|
+
puba/pipeline.py
|
|
9
|
+
puba/secrets.py
|
|
10
|
+
puba.egg-info/PKG-INFO
|
|
11
|
+
puba.egg-info/SOURCES.txt
|
|
12
|
+
puba.egg-info/dependency_links.txt
|
|
13
|
+
puba.egg-info/entry_points.txt
|
|
14
|
+
puba.egg-info/requires.txt
|
|
15
|
+
puba.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
keyring>=23.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
puba
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "puba"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Safe Python package release CLI"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [{name = "Griffin McManus"}]
|
|
7
|
+
requires-python = ">=3.9"
|
|
8
|
+
dependencies = [
|
|
9
|
+
"keyring>=23.0" # secure token storage (runtime dependency)
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
[project.scripts]
|
|
13
|
+
puba = "puba.cli:main"
|
|
14
|
+
|
|
15
|
+
# Optional dependencies for development, testing, and publishing
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = [
|
|
20
|
+
"setuptools>=61.0",
|
|
21
|
+
"wheel>=0.37"
|
|
22
|
+
]
|
|
23
|
+
build-backend = "setuptools.build_meta"
|
puba-0.1.0/setup.cfg
ADDED