envrcctl 0.2.1__tar.gz → 0.2.3__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.
- {envrcctl-0.2.1 → envrcctl-0.2.3}/.gitignore +4 -0
- envrcctl-0.2.1/README.md → envrcctl-0.2.3/PKG-INFO +25 -49
- envrcctl-0.2.1/PKG-INFO → envrcctl-0.2.3/README.md +7 -83
- envrcctl-0.2.3/pyproject.toml +67 -0
- {envrcctl-0.2.1 → envrcctl-0.2.3}/scripts/build_macos_auth_helper.sh +6 -1
- envrcctl-0.2.3/scripts/package_for_ship.sh +6 -0
- envrcctl-0.2.3/scripts/release_artifacts.py +286 -0
- {envrcctl-0.2.1 → envrcctl-0.2.3}/src/envrcctl/cli.py +15 -44
- envrcctl-0.2.3/tests/__init__.py +3 -0
- envrcctl-0.2.3/tests/conftest.py +15 -0
- envrcctl-0.2.3/tests/helpers/__init__.py +3 -0
- envrcctl-0.2.3/tests/helpers/cli_support.py +34 -0
- envrcctl-0.2.3/tests/test_audit.py +830 -0
- envrcctl-0.2.3/tests/test_audit_cli.py +364 -0
- envrcctl-0.2.3/tests/test_auth.py +181 -0
- envrcctl-0.2.3/tests/test_cli.py +81 -0
- envrcctl-0.2.3/tests/test_cli_doctor.py +213 -0
- envrcctl-0.2.3/tests/test_cli_errors.py +485 -0
- envrcctl-0.2.3/tests/test_cli_eval.py +84 -0
- envrcctl-0.2.3/tests/test_cli_exec.py +361 -0
- envrcctl-0.2.3/tests/test_cli_helpers.py +150 -0
- envrcctl-0.2.3/tests/test_cli_inject.py +222 -0
- envrcctl-0.2.3/tests/test_cli_migrate.py +50 -0
- envrcctl-0.2.3/tests/test_cli_secret_get.py +304 -0
- envrcctl-0.2.3/tests/test_cli_secret_list.py +66 -0
- envrcctl-0.2.3/tests/test_cli_secret_set_unset.py +138 -0
- envrcctl-0.2.3/tests/test_command_runner.py +51 -0
- envrcctl-0.2.3/tests/test_envrc.py +147 -0
- envrcctl-0.2.3/tests/test_keychain.py +609 -0
- envrcctl-0.2.3/tests/test_main.py +30 -0
- envrcctl-0.2.3/tests/test_managed_block.py +68 -0
- envrcctl-0.2.3/tests/test_secrets.py +186 -0
- envrcctl-0.2.3/tests/test_secretservice.py +91 -0
- envrcctl-0.2.3/tests/test_smoke.py +4 -0
- envrcctl-0.2.1/pyproject.toml +0 -31
- {envrcctl-0.2.1 → envrcctl-0.2.3}/LICENSE +0 -0
- {envrcctl-0.2.1 → envrcctl-0.2.3}/completions/envrcctl.bash +0 -0
- {envrcctl-0.2.1 → envrcctl-0.2.3}/completions/envrcctl.fish +0 -0
- {envrcctl-0.2.1 → envrcctl-0.2.3}/completions/envrcctl.zsh +0 -0
- {envrcctl-0.2.1 → envrcctl-0.2.3}/scripts/generate_completions.py +0 -0
- {envrcctl-0.2.1 → envrcctl-0.2.3}/scripts/macos/envrcctl-macos-auth.swift +0 -0
- {envrcctl-0.2.1 → envrcctl-0.2.3}/src/envrcctl/__init__.py +0 -0
- {envrcctl-0.2.1 → envrcctl-0.2.3}/src/envrcctl/audit.py +0 -0
- {envrcctl-0.2.1 → envrcctl-0.2.3}/src/envrcctl/auth.py +0 -0
- {envrcctl-0.2.1 → envrcctl-0.2.3}/src/envrcctl/command_runner.py +0 -0
- {envrcctl-0.2.1 → envrcctl-0.2.3}/src/envrcctl/envrc.py +0 -0
- /envrcctl-0.2.1/src/envrcctl/envrcctl-macos-auth → /envrcctl-0.2.3/src/envrcctl/envrcctl-macos-auth.bak +0 -0
- {envrcctl-0.2.1 → envrcctl-0.2.3}/src/envrcctl/errors.py +0 -0
- {envrcctl-0.2.1 → envrcctl-0.2.3}/src/envrcctl/keychain.py +0 -0
- {envrcctl-0.2.1 → envrcctl-0.2.3}/src/envrcctl/main.py +0 -0
- {envrcctl-0.2.1 → envrcctl-0.2.3}/src/envrcctl/managed_block.py +0 -0
- {envrcctl-0.2.1 → envrcctl-0.2.3}/src/envrcctl/secrets.py +0 -0
- {envrcctl-0.2.1 → envrcctl-0.2.3}/src/envrcctl/secretservice.py +0 -0
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: envrcctl
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
Summary: Manage .envrc with managed blocks and OS-backed secrets.
|
|
5
|
+
Project-URL: Homepage, https://github.com/rioriost/envrcctl
|
|
6
|
+
Project-URL: Issues, https://github.com/rioriost/envrcctl/issues
|
|
7
|
+
Project-URL: Repository, https://github.com/rioriost/envrcctl
|
|
8
|
+
Author-email: Rio Fujita <rio_github@rio.st>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Python: >=3.14
|
|
12
|
+
Requires-Dist: typer>=0.24.1
|
|
13
|
+
Provides-Extra: test
|
|
14
|
+
Requires-Dist: bandit>=1.7.10; extra == 'test'
|
|
15
|
+
Requires-Dist: pytest-cov>=7; extra == 'test'
|
|
16
|
+
Requires-Dist: pytest>=9; extra == 'test'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
1
19
|
# envrcctl
|
|
2
20
|
|
|
3
21
|
envrcctl is a CLI tool that manages `.envrc` files safely through a managed
|
|
@@ -34,23 +52,17 @@ It is designed for macOS first, with Linux support via SecretService.
|
|
|
34
52
|
Tap and install:
|
|
35
53
|
|
|
36
54
|
```sh
|
|
37
|
-
brew tap rioriost/
|
|
55
|
+
brew tap rioriost/tap
|
|
38
56
|
brew install envrcctl
|
|
39
57
|
```
|
|
40
58
|
|
|
41
|
-
|
|
42
|
-
prebuilt Apple Silicon macOS authentication helper from a GitHub release
|
|
43
|
-
`tar.gz` asset instead of compiling it at install time.
|
|
44
|
-
|
|
45
|
-
This Homebrew path is therefore intended for:
|
|
59
|
+
This Homebrew path is intended for:
|
|
46
60
|
|
|
47
61
|
- Apple Silicon (`arm64`) Macs
|
|
48
62
|
- macOS installs that should not require a full Xcode.app build dependency
|
|
49
63
|
|
|
50
64
|
Intel Macs are not a target for this Homebrew distribution path.
|
|
51
65
|
|
|
52
|
-
After release, Homebrew will download the release from GitHub.
|
|
53
|
-
|
|
54
66
|
Install direnv with Homebrew:
|
|
55
67
|
|
|
56
68
|
```sh
|
|
@@ -69,42 +81,25 @@ pipx install envrcctl
|
|
|
69
81
|
uv tool install envrcctl
|
|
70
82
|
```
|
|
71
83
|
|
|
72
|
-
###
|
|
73
|
-
|
|
74
|
-
```sh
|
|
75
|
-
git clone <REPO_URL>
|
|
76
|
-
cd envrcctl
|
|
77
|
-
uv sync
|
|
78
|
-
uv run python -m envrcctl.main --help
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### Build the macOS auth helper manually (macOS only)
|
|
84
|
+
### About the macOS auth helper (Apple Silicon macOS only)
|
|
82
85
|
|
|
83
86
|
The macOS device owner authentication flow requires a native helper named
|
|
84
87
|
`envrcctl-macos-auth`.
|
|
85
88
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
release `tar.gz` asset, so you should not need to compile it yourself in the
|
|
89
|
-
common case.
|
|
89
|
+
Homebrew on Apple Silicon is intended to install this helper automatically, so
|
|
90
|
+
you should not need to build it yourself in the common case.
|
|
90
91
|
|
|
91
92
|
Manual helper installation is still useful when:
|
|
92
93
|
|
|
93
|
-
- you are running from source
|
|
94
|
-
- you are developing on this repository
|
|
95
94
|
- you want to place the helper in a custom location
|
|
96
95
|
- you are not using the Apple Silicon Homebrew distribution path
|
|
97
96
|
|
|
98
|
-
|
|
99
|
-
`envrcctl-macos-auth-arm64.tar.gz` and to contain an executable named
|
|
100
|
-
`envrcctl-macos-auth`.
|
|
101
|
-
|
|
102
|
-
If you are building the helper yourself, place the binary at either:
|
|
97
|
+
If you are building the helper yourself, use Apple Silicon (`arm64`) macOS and place the binary at either:
|
|
103
98
|
|
|
104
99
|
- `src/envrcctl/envrcctl-macos-auth`
|
|
105
100
|
- or a custom path set via `ENVRCCTL_MACOS_AUTH_HELPER`
|
|
106
101
|
|
|
107
|
-
Example build flow:
|
|
102
|
+
Example build flow on Apple Silicon macOS:
|
|
108
103
|
|
|
109
104
|
```sh
|
|
110
105
|
swiftc -O -framework LocalAuthentication -framework Security \
|
|
@@ -113,20 +108,6 @@ swiftc -O -framework LocalAuthentication -framework Security \
|
|
|
113
108
|
chmod +x src/envrcctl/envrcctl-macos-auth
|
|
114
109
|
```
|
|
115
110
|
|
|
116
|
-
You can also use the repository build script:
|
|
117
|
-
|
|
118
|
-
```sh
|
|
119
|
-
sh scripts/build_macos_auth_helper.sh
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
If you want to write the helper to a custom location, pass the source and output paths explicitly:
|
|
123
|
-
|
|
124
|
-
```sh
|
|
125
|
-
sh scripts/build_macos_auth_helper.sh \
|
|
126
|
-
scripts/macos/envrcctl-macos-auth.swift \
|
|
127
|
-
/usr/local/bin/envrcctl-macos-auth
|
|
128
|
-
```
|
|
129
|
-
|
|
130
111
|
If you install the helper elsewhere, set:
|
|
131
112
|
|
|
132
113
|
```sh
|
|
@@ -373,12 +354,7 @@ uv run python scripts/generate_completions.py
|
|
|
373
354
|
- Audit integrity is based on a hash chain and can be checked with `envrcctl audit verify`
|
|
374
355
|
- The tool refuses to write to world-writable `.envrc`
|
|
375
356
|
|
|
376
|
-
## Development
|
|
377
357
|
|
|
378
|
-
```sh
|
|
379
|
-
uv sync
|
|
380
|
-
.venv/bin/envrcctl --help
|
|
381
|
-
```
|
|
382
358
|
|
|
383
359
|
## Acknowledgements
|
|
384
360
|
|
|
@@ -1,37 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: envrcctl
|
|
3
|
-
Version: 0.2.1
|
|
4
|
-
Summary: Manage .envrc with managed blocks and OS-backed secrets.
|
|
5
|
-
License: MIT License
|
|
6
|
-
|
|
7
|
-
Copyright (c) 2026 Rio Fujita
|
|
8
|
-
|
|
9
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
-
in the Software without restriction, including without limitation the rights
|
|
12
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
-
furnished to do so, subject to the following conditions:
|
|
15
|
-
|
|
16
|
-
The above copyright notice and this permission notice shall be included in all
|
|
17
|
-
copies or substantial portions of the Software.
|
|
18
|
-
|
|
19
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
-
SOFTWARE.
|
|
26
|
-
License-File: LICENSE
|
|
27
|
-
Requires-Python: >=3.14
|
|
28
|
-
Requires-Dist: typer>=0.24.1
|
|
29
|
-
Provides-Extra: test
|
|
30
|
-
Requires-Dist: bandit>=1.7.10; extra == 'test'
|
|
31
|
-
Requires-Dist: pytest-cov>=7.0.0; extra == 'test'
|
|
32
|
-
Requires-Dist: pytest>=9.0.2; extra == 'test'
|
|
33
|
-
Description-Content-Type: text/markdown
|
|
34
|
-
|
|
35
1
|
# envrcctl
|
|
36
2
|
|
|
37
3
|
envrcctl is a CLI tool that manages `.envrc` files safely through a managed
|
|
@@ -68,23 +34,17 @@ It is designed for macOS first, with Linux support via SecretService.
|
|
|
68
34
|
Tap and install:
|
|
69
35
|
|
|
70
36
|
```sh
|
|
71
|
-
brew tap rioriost/
|
|
37
|
+
brew tap rioriost/tap
|
|
72
38
|
brew install envrcctl
|
|
73
39
|
```
|
|
74
40
|
|
|
75
|
-
|
|
76
|
-
prebuilt Apple Silicon macOS authentication helper from a GitHub release
|
|
77
|
-
`tar.gz` asset instead of compiling it at install time.
|
|
78
|
-
|
|
79
|
-
This Homebrew path is therefore intended for:
|
|
41
|
+
This Homebrew path is intended for:
|
|
80
42
|
|
|
81
43
|
- Apple Silicon (`arm64`) Macs
|
|
82
44
|
- macOS installs that should not require a full Xcode.app build dependency
|
|
83
45
|
|
|
84
46
|
Intel Macs are not a target for this Homebrew distribution path.
|
|
85
47
|
|
|
86
|
-
After release, Homebrew will download the release from GitHub.
|
|
87
|
-
|
|
88
48
|
Install direnv with Homebrew:
|
|
89
49
|
|
|
90
50
|
```sh
|
|
@@ -103,42 +63,25 @@ pipx install envrcctl
|
|
|
103
63
|
uv tool install envrcctl
|
|
104
64
|
```
|
|
105
65
|
|
|
106
|
-
###
|
|
107
|
-
|
|
108
|
-
```sh
|
|
109
|
-
git clone <REPO_URL>
|
|
110
|
-
cd envrcctl
|
|
111
|
-
uv sync
|
|
112
|
-
uv run python -m envrcctl.main --help
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
### Build the macOS auth helper manually (macOS only)
|
|
66
|
+
### About the macOS auth helper (Apple Silicon macOS only)
|
|
116
67
|
|
|
117
68
|
The macOS device owner authentication flow requires a native helper named
|
|
118
69
|
`envrcctl-macos-auth`.
|
|
119
70
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
release `tar.gz` asset, so you should not need to compile it yourself in the
|
|
123
|
-
common case.
|
|
71
|
+
Homebrew on Apple Silicon is intended to install this helper automatically, so
|
|
72
|
+
you should not need to build it yourself in the common case.
|
|
124
73
|
|
|
125
74
|
Manual helper installation is still useful when:
|
|
126
75
|
|
|
127
|
-
- you are running from source
|
|
128
|
-
- you are developing on this repository
|
|
129
76
|
- you want to place the helper in a custom location
|
|
130
77
|
- you are not using the Apple Silicon Homebrew distribution path
|
|
131
78
|
|
|
132
|
-
|
|
133
|
-
`envrcctl-macos-auth-arm64.tar.gz` and to contain an executable named
|
|
134
|
-
`envrcctl-macos-auth`.
|
|
135
|
-
|
|
136
|
-
If you are building the helper yourself, place the binary at either:
|
|
79
|
+
If you are building the helper yourself, use Apple Silicon (`arm64`) macOS and place the binary at either:
|
|
137
80
|
|
|
138
81
|
- `src/envrcctl/envrcctl-macos-auth`
|
|
139
82
|
- or a custom path set via `ENVRCCTL_MACOS_AUTH_HELPER`
|
|
140
83
|
|
|
141
|
-
Example build flow:
|
|
84
|
+
Example build flow on Apple Silicon macOS:
|
|
142
85
|
|
|
143
86
|
```sh
|
|
144
87
|
swiftc -O -framework LocalAuthentication -framework Security \
|
|
@@ -147,20 +90,6 @@ swiftc -O -framework LocalAuthentication -framework Security \
|
|
|
147
90
|
chmod +x src/envrcctl/envrcctl-macos-auth
|
|
148
91
|
```
|
|
149
92
|
|
|
150
|
-
You can also use the repository build script:
|
|
151
|
-
|
|
152
|
-
```sh
|
|
153
|
-
sh scripts/build_macos_auth_helper.sh
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
If you want to write the helper to a custom location, pass the source and output paths explicitly:
|
|
157
|
-
|
|
158
|
-
```sh
|
|
159
|
-
sh scripts/build_macos_auth_helper.sh \
|
|
160
|
-
scripts/macos/envrcctl-macos-auth.swift \
|
|
161
|
-
/usr/local/bin/envrcctl-macos-auth
|
|
162
|
-
```
|
|
163
|
-
|
|
164
93
|
If you install the helper elsewhere, set:
|
|
165
94
|
|
|
166
95
|
```sh
|
|
@@ -407,12 +336,7 @@ uv run python scripts/generate_completions.py
|
|
|
407
336
|
- Audit integrity is based on a hash chain and can be checked with `envrcctl audit verify`
|
|
408
337
|
- The tool refuses to write to world-writable `.envrc`
|
|
409
338
|
|
|
410
|
-
## Development
|
|
411
339
|
|
|
412
|
-
```sh
|
|
413
|
-
uv sync
|
|
414
|
-
.venv/bin/envrcctl --help
|
|
415
|
-
```
|
|
416
340
|
|
|
417
341
|
## Acknowledgements
|
|
418
342
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "envrcctl"
|
|
3
|
+
version = "0.2.3"
|
|
4
|
+
description = "Manage .envrc with managed blocks and OS-backed secrets."
|
|
5
|
+
readme = { file = "README.md", content-type = "text/markdown" }
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.14"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Rio Fujita", email = "rio_github@rio.st" },
|
|
10
|
+
]
|
|
11
|
+
dependencies = [
|
|
12
|
+
"typer>=0.24.1",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[tool.hatch.build.targets.wheel]
|
|
16
|
+
packages = ["src/envrcctl"]
|
|
17
|
+
|
|
18
|
+
[tool.hatch.build.targets.sdist]
|
|
19
|
+
include = [
|
|
20
|
+
"src/**",
|
|
21
|
+
"tests/**",
|
|
22
|
+
"scripts/**",
|
|
23
|
+
"completions/**",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"pyproject.toml",
|
|
27
|
+
]
|
|
28
|
+
exclude = [
|
|
29
|
+
"src/envrcctl/envrcctl-macos-auth",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/rioriost/envrcctl"
|
|
34
|
+
Issues = "https://github.com/rioriost/envrcctl/issues"
|
|
35
|
+
Repository = "https://github.com/rioriost/envrcctl"
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
envrcctl = "envrcctl.main:main"
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
test = [
|
|
42
|
+
"pytest>=9",
|
|
43
|
+
"pytest-cov>=7",
|
|
44
|
+
"bandit>=1.7.10",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[dependency-groups]
|
|
48
|
+
dev = [
|
|
49
|
+
"build>=1.2.2",
|
|
50
|
+
"twine>=6.1.0",
|
|
51
|
+
"ruff>=0.12.0",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[build-system]
|
|
55
|
+
requires = ["hatchling>=1.25.0"]
|
|
56
|
+
build-backend = "hatchling.build"
|
|
57
|
+
|
|
58
|
+
[tool.pytest.ini_options]
|
|
59
|
+
testpaths = ["tests"]
|
|
60
|
+
python_files = ["test_*.py"]
|
|
61
|
+
|
|
62
|
+
[tool.ruff]
|
|
63
|
+
line-length = 100
|
|
64
|
+
target-version = "py314"
|
|
65
|
+
|
|
66
|
+
[tool.ruff.lint]
|
|
67
|
+
select = ["E", "F", "I", "B", "UP"]
|
|
@@ -11,6 +11,11 @@ if [ "$(uname -s)" != "Darwin" ]; then
|
|
|
11
11
|
exit 1
|
|
12
12
|
fi
|
|
13
13
|
|
|
14
|
+
if [ "$(uname -m)" != "arm64" ]; then
|
|
15
|
+
echo "This helper only supports Apple Silicon (arm64) macOS." >&2
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
14
19
|
if ! command -v swiftc >/dev/null 2>&1; then
|
|
15
20
|
echo "swiftc not found. Install Xcode Command Line Tools first." >&2
|
|
16
21
|
exit 1
|
|
@@ -24,7 +29,7 @@ fi
|
|
|
24
29
|
|
|
25
30
|
mkdir -p "$(dirname "$OUTPUT_PATH")"
|
|
26
31
|
|
|
27
|
-
echo "Building macOS auth helper..."
|
|
32
|
+
echo "Building macOS auth helper for Apple Silicon (arm64)..."
|
|
28
33
|
echo " source: $SWIFT_SOURCE"
|
|
29
34
|
echo " output: $OUTPUT_PATH"
|
|
30
35
|
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import hashlib
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import tarfile
|
|
10
|
+
import tempfile
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def run(cmd: list[str], *, cwd: Path) -> None:
|
|
15
|
+
print("+", " ".join(cmd))
|
|
16
|
+
subprocess.run(cmd, cwd=cwd, check=True)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def sha256_file(path: Path) -> str:
|
|
20
|
+
digest = hashlib.sha256()
|
|
21
|
+
with path.open("rb") as fh:
|
|
22
|
+
for chunk in iter(lambda: fh.read(1024 * 1024), b""):
|
|
23
|
+
digest.update(chunk)
|
|
24
|
+
return digest.hexdigest()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def project_root() -> Path:
|
|
28
|
+
return Path(__file__).resolve().parents[1]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def project_version(pyproject_path: Path) -> str:
|
|
32
|
+
for line in pyproject_path.read_text(encoding="utf-8").splitlines():
|
|
33
|
+
stripped = line.strip()
|
|
34
|
+
if stripped.startswith("version = "):
|
|
35
|
+
value = stripped.split("=", 1)[1].strip()
|
|
36
|
+
if value.startswith('"') and value.endswith('"'):
|
|
37
|
+
return value[1:-1]
|
|
38
|
+
if value.startswith("'") and value.endswith("'"):
|
|
39
|
+
return value[1:-1]
|
|
40
|
+
raise RuntimeError(f"Could not find project version in {pyproject_path}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def require_command(name: str) -> None:
|
|
44
|
+
if shutil.which(name) is None:
|
|
45
|
+
raise RuntimeError(f"Required command not found in PATH: {name}")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def ensure_macos_arm64() -> None:
|
|
49
|
+
if sys.platform != "darwin":
|
|
50
|
+
raise RuntimeError("This script must run on macOS.")
|
|
51
|
+
machine = getattr(__import__("os"), "uname")().machine
|
|
52
|
+
if machine != "arm64":
|
|
53
|
+
raise RuntimeError("This script only supports Apple Silicon (arm64) macOS.")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def dist_dir(repo_root: Path) -> Path:
|
|
57
|
+
return repo_root / "dist"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def helper_output_path(repo_root: Path) -> Path:
|
|
61
|
+
return repo_root / "src" / "envrcctl" / "envrcctl-macos-auth"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def generate_completions(repo_root: Path) -> None:
|
|
65
|
+
require_command("uv")
|
|
66
|
+
run(["uv", "run", "python", "scripts/generate_completions.py"], cwd=repo_root)
|
|
67
|
+
|
|
68
|
+
expected = [
|
|
69
|
+
repo_root / "completions" / "envrcctl.bash",
|
|
70
|
+
repo_root / "completions" / "envrcctl.zsh",
|
|
71
|
+
repo_root / "completions" / "envrcctl.fish",
|
|
72
|
+
]
|
|
73
|
+
missing = [path for path in expected if not path.exists()]
|
|
74
|
+
if missing:
|
|
75
|
+
joined = ", ".join(str(path) for path in missing)
|
|
76
|
+
raise RuntimeError(f"Completion generation did not create expected files: {joined}")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def sync_dev_environment(repo_root: Path) -> None:
|
|
80
|
+
require_command("uv")
|
|
81
|
+
run(["uv", "sync", "--extra", "test", "--group", "dev"], cwd=repo_root)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def clean_dist(repo_root: Path) -> None:
|
|
85
|
+
out = dist_dir(repo_root)
|
|
86
|
+
if out.exists():
|
|
87
|
+
shutil.rmtree(out)
|
|
88
|
+
out.mkdir(parents=True, exist_ok=True)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def build_python_artifacts(repo_root: Path) -> tuple[Path, Path]:
|
|
92
|
+
require_command("uv")
|
|
93
|
+
run(["uv", "build"], cwd=repo_root)
|
|
94
|
+
|
|
95
|
+
version = project_version(repo_root / "pyproject.toml")
|
|
96
|
+
sdist = dist_dir(repo_root) / f"envrcctl-{version}.tar.gz"
|
|
97
|
+
wheel = dist_dir(repo_root) / f"envrcctl-{version}-py3-none-any.whl"
|
|
98
|
+
|
|
99
|
+
if not sdist.exists():
|
|
100
|
+
raise RuntimeError(f"Expected sdist was not created: {sdist}")
|
|
101
|
+
if not wheel.exists():
|
|
102
|
+
raise RuntimeError(f"Expected wheel was not created: {wheel}")
|
|
103
|
+
|
|
104
|
+
return sdist, wheel
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def build_helper_binary(repo_root: Path) -> Path:
|
|
108
|
+
ensure_macos_arm64()
|
|
109
|
+
run(["sh", "scripts/build_macos_auth_helper.sh"], cwd=repo_root)
|
|
110
|
+
|
|
111
|
+
helper_path = helper_output_path(repo_root)
|
|
112
|
+
if not helper_path.exists():
|
|
113
|
+
raise RuntimeError(f"Expected helper binary was not created: {helper_path}")
|
|
114
|
+
if not helper_path.is_file():
|
|
115
|
+
raise RuntimeError(f"Helper path is not a file: {helper_path}")
|
|
116
|
+
return helper_path
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def package_helper_archive(repo_root: Path, version: str, helper_binary: Path) -> Path:
|
|
120
|
+
archive_path = dist_dir(repo_root) / f"envrcctl-macos-auth-{version}-arm64.tar.gz"
|
|
121
|
+
|
|
122
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
123
|
+
stage_dir = Path(tmpdir)
|
|
124
|
+
staged_binary = stage_dir / "envrcctl-macos-auth"
|
|
125
|
+
shutil.copy2(helper_binary, staged_binary)
|
|
126
|
+
staged_binary.chmod(0o755)
|
|
127
|
+
|
|
128
|
+
with tarfile.open(archive_path, "w:gz") as tf:
|
|
129
|
+
tf.add(staged_binary, arcname="envrcctl-macos-auth")
|
|
130
|
+
|
|
131
|
+
if not archive_path.exists():
|
|
132
|
+
raise RuntimeError(f"Expected helper archive was not created: {archive_path}")
|
|
133
|
+
|
|
134
|
+
return archive_path
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def formula_content(
|
|
138
|
+
*,
|
|
139
|
+
version: str,
|
|
140
|
+
source_sha256: str,
|
|
141
|
+
helper_sha256: str,
|
|
142
|
+
homepage: str,
|
|
143
|
+
license_name: str,
|
|
144
|
+
) -> str:
|
|
145
|
+
release_base = f"{homepage}/releases/download/{version}"
|
|
146
|
+
source_url = f"{release_base}/envrcctl-{version}.tar.gz"
|
|
147
|
+
helper_url = f"{release_base}/envrcctl-macos-auth-{version}-arm64.tar.gz"
|
|
148
|
+
|
|
149
|
+
return f"""class Envrcctl < Formula
|
|
150
|
+
include Language::Python::Virtualenv
|
|
151
|
+
|
|
152
|
+
desc "Manage .envrc with managed blocks and OS-backed secrets"
|
|
153
|
+
homepage "{homepage}"
|
|
154
|
+
url "{source_url}"
|
|
155
|
+
sha256 "{source_sha256}"
|
|
156
|
+
license "{license_name}"
|
|
157
|
+
|
|
158
|
+
depends_on "python@3.12"
|
|
159
|
+
|
|
160
|
+
on_macos do
|
|
161
|
+
on_arm do
|
|
162
|
+
resource "envrcctl-macos-auth-arm64" do
|
|
163
|
+
url "{helper_url}"
|
|
164
|
+
sha256 "{helper_sha256}"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def install
|
|
170
|
+
venv = virtualenv_create(libexec, "python3.12")
|
|
171
|
+
venv.pip_install buildpath
|
|
172
|
+
|
|
173
|
+
bin.install_symlink libexec/"bin/envrcctl"
|
|
174
|
+
|
|
175
|
+
if OS.mac? && Hardware::CPU.arm?
|
|
176
|
+
resource("envrcctl-macos-auth-arm64").stage do
|
|
177
|
+
bin.install "envrcctl-macos-auth"
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
bash_completion.install "completions/envrcctl.bash" => "envrcctl"
|
|
182
|
+
zsh_completion.install "completions/envrcctl.zsh" => "_envrcctl"
|
|
183
|
+
fish_completion.install "completions/envrcctl.fish"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
test do
|
|
187
|
+
assert_predicate bin/"envrcctl", :exist?
|
|
188
|
+
assert_match version.to_s, shell_output("#{bin}/envrcctl --version")
|
|
189
|
+
if OS.mac? && Hardware::CPU.arm?
|
|
190
|
+
assert_predicate bin/"envrcctl-macos-auth", :exist?
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def write_formula(repo_root: Path, formula_text: str, formula_dir: Path | None) -> Path:
|
|
198
|
+
target_dir = formula_dir or (repo_root.parent / "homebrew-tap" / "Formula")
|
|
199
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
200
|
+
formula_path = target_dir / "envrcctl.rb"
|
|
201
|
+
formula_path.write_text(formula_text, encoding="utf-8")
|
|
202
|
+
return formula_path
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def parse_args() -> argparse.Namespace:
|
|
206
|
+
parser = argparse.ArgumentParser(
|
|
207
|
+
description=(
|
|
208
|
+
"Build envrcctl release artifacts: sync dev dependencies, generate completions, "
|
|
209
|
+
"build Python artifacts, build the Apple Silicon helper, package the helper tarball, "
|
|
210
|
+
"and write a Homebrew formula. This is the canonical script entrypoint behind "
|
|
211
|
+
"`make release-artifacts`."
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
parser.add_argument(
|
|
215
|
+
"--homepage",
|
|
216
|
+
default="https://github.com/rioriost/envrcctl",
|
|
217
|
+
help="Project homepage / GitHub repository URL.",
|
|
218
|
+
)
|
|
219
|
+
parser.add_argument(
|
|
220
|
+
"--license",
|
|
221
|
+
dest="license_name",
|
|
222
|
+
default="MIT",
|
|
223
|
+
help="Homebrew formula license identifier.",
|
|
224
|
+
)
|
|
225
|
+
parser.add_argument(
|
|
226
|
+
"--formula-dir",
|
|
227
|
+
type=Path,
|
|
228
|
+
default=None,
|
|
229
|
+
help="Directory to write envrcctl.rb into. Defaults to ../homebrew-tap/Formula.",
|
|
230
|
+
)
|
|
231
|
+
return parser.parse_args()
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def main() -> int:
|
|
235
|
+
args = parse_args()
|
|
236
|
+
repo_root = project_root()
|
|
237
|
+
version = project_version(repo_root / "pyproject.toml")
|
|
238
|
+
|
|
239
|
+
print(f"Building release artifacts for envrcctl {version}")
|
|
240
|
+
print(f"Repository root: {repo_root}")
|
|
241
|
+
|
|
242
|
+
sdist_path = dist_dir(repo_root) / f"envrcctl-{version}.tar.gz"
|
|
243
|
+
wheel_path = dist_dir(repo_root) / f"envrcctl-{version}-py3-none-any.whl"
|
|
244
|
+
helper_archive = dist_dir(repo_root) / f"envrcctl-macos-auth-{version}-arm64.tar.gz"
|
|
245
|
+
|
|
246
|
+
if not sdist_path.exists() or not wheel_path.exists() or not helper_archive.exists():
|
|
247
|
+
sync_dev_environment(repo_root)
|
|
248
|
+
generate_completions(repo_root)
|
|
249
|
+
clean_dist(repo_root)
|
|
250
|
+
sdist_path, wheel_path = build_python_artifacts(repo_root)
|
|
251
|
+
helper_binary = build_helper_binary(repo_root)
|
|
252
|
+
helper_archive = package_helper_archive(repo_root, version, helper_binary)
|
|
253
|
+
else:
|
|
254
|
+
print("Reusing existing release artifacts from dist/")
|
|
255
|
+
|
|
256
|
+
source_sha256 = sha256_file(sdist_path)
|
|
257
|
+
helper_sha256 = sha256_file(helper_archive)
|
|
258
|
+
|
|
259
|
+
formula_path = write_formula(
|
|
260
|
+
repo_root,
|
|
261
|
+
formula_content(
|
|
262
|
+
version=version,
|
|
263
|
+
source_sha256=source_sha256,
|
|
264
|
+
helper_sha256=helper_sha256,
|
|
265
|
+
homepage=args.homepage,
|
|
266
|
+
license_name=args.license_name,
|
|
267
|
+
),
|
|
268
|
+
args.formula_dir,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
print()
|
|
272
|
+
print("Artifacts built successfully:")
|
|
273
|
+
print(f"- sdist: {sdist_path}")
|
|
274
|
+
print(f"- wheel: {wheel_path}")
|
|
275
|
+
print(f"- helper: {helper_archive}")
|
|
276
|
+
print(f"- formula: {formula_path}")
|
|
277
|
+
print()
|
|
278
|
+
print("SHA256:")
|
|
279
|
+
print(f"- envrcctl-{version}.tar.gz: {source_sha256}")
|
|
280
|
+
print(f"- envrcctl-macos-auth-{version}-arm64.tar.gz: {helper_sha256}")
|
|
281
|
+
|
|
282
|
+
return 0
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
if __name__ == "__main__":
|
|
286
|
+
raise SystemExit(main())
|