pyfig 0.3.1 → 0.5.0

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.
Files changed (67) hide show
  1. package/README.md +116 -123
  2. package/SECURITY.md +2 -2
  3. package/dist/cjs/index.js +525 -888
  4. package/dist/cjs/index.js.map +15 -15
  5. package/dist/cjs/sync-resolver.js +482 -676
  6. package/dist/cjs/sync-resolver.js.map +13 -12
  7. package/dist/cli.js +7 -10
  8. package/dist/cli.js.map +1 -1
  9. package/dist/client.d.ts +3 -8
  10. package/dist/client.d.ts.map +1 -1
  11. package/dist/client.js +13 -11
  12. package/dist/client.js.map +1 -1
  13. package/dist/config.d.ts +9 -18
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/config.js +22 -59
  16. package/dist/config.js.map +1 -1
  17. package/dist/index.d.ts +5 -6
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +3 -4
  20. package/dist/index.js.map +1 -1
  21. package/dist/package-target.d.ts +7 -11
  22. package/dist/package-target.d.ts.map +1 -1
  23. package/dist/package-target.js +41 -57
  24. package/dist/package-target.js.map +1 -1
  25. package/dist/probes.d.ts +1 -19
  26. package/dist/probes.d.ts.map +1 -1
  27. package/dist/probes.js +13 -74
  28. package/dist/probes.js.map +1 -1
  29. package/dist/process.d.ts +1 -1
  30. package/dist/process.d.ts.map +1 -1
  31. package/dist/process.js +2 -1
  32. package/dist/process.js.map +1 -1
  33. package/dist/project-root.d.ts +1 -1
  34. package/dist/project-root.d.ts.map +1 -1
  35. package/dist/project-root.js +3 -64
  36. package/dist/project-root.js.map +1 -1
  37. package/dist/provision-lock.d.ts +10 -0
  38. package/dist/provision-lock.d.ts.map +1 -0
  39. package/dist/provision-lock.js +24 -0
  40. package/dist/provision-lock.js.map +1 -0
  41. package/dist/provision.d.ts +18 -6
  42. package/dist/provision.d.ts.map +1 -1
  43. package/dist/provision.js +297 -235
  44. package/dist/provision.js.map +1 -1
  45. package/dist/python-version.d.ts +10 -0
  46. package/dist/python-version.d.ts.map +1 -0
  47. package/dist/python-version.js +17 -0
  48. package/dist/python-version.js.map +1 -0
  49. package/dist/runners.d.ts +3 -7
  50. package/dist/runners.d.ts.map +1 -1
  51. package/dist/runners.js +35 -115
  52. package/dist/runners.js.map +1 -1
  53. package/dist/sync-resolver.js +4 -13
  54. package/dist/sync-resolver.js.map +1 -1
  55. package/dist/virtual-environment.d.ts +3 -1
  56. package/dist/virtual-environment.d.ts.map +1 -1
  57. package/dist/virtual-environment.js +8 -1
  58. package/dist/virtual-environment.js.map +1 -1
  59. package/package.json +9 -10
  60. package/dist/coordination.d.ts +0 -25
  61. package/dist/coordination.d.ts.map +0 -1
  62. package/dist/coordination.js +0 -187
  63. package/dist/coordination.js.map +0 -1
  64. package/dist/state.d.ts +0 -10
  65. package/dist/state.d.ts.map +0 -1
  66. package/dist/state.js +0 -62
  67. package/dist/state.js.map +0 -1
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
1
  # pyfig
2
2
 
3
- `pyfig` creates or reuses `<project-root>/.venv` and binds every Python and package subprocess to that environment from Bun or Node.js. A valid existing venv is the first choice. Otherwise Pyfig discovers a compatible interpreter, installs its checksum-verified uv and managed Python when necessary, and provisions the root venv without requiring Python or Node to be preinstalled alongside Bun.
3
+ `pyfig` prepares uv and a compatible Python virtual environment from Bun or Node.js. It prefers a valid uv already on the system, reuses a compatible venv, and searches system Python with downloads disabled before considering any managed Python download.
4
+
5
+ Pyfig never invokes standalone `pip`, never writes `pyproject.toml` or requirements files, and never creates a project-root `.venv`. When no compatible venv exists, its private environment is `<project-root>/node_modules/.pyfig/venv`.
4
6
 
5
7
  ## Install
6
8
 
@@ -8,156 +10,146 @@
8
10
  bun add pyfig
9
11
  ```
10
12
 
11
- ## Library
13
+ ## Use
12
14
 
13
15
  ```ts
14
16
  import { createPyfig } from 'pyfig';
15
17
 
16
18
  const pyfig = createPyfig({
17
- pythonRange: '>=3.11.0 <4.0.0',
18
- pipRange: '>=23.0.0',
19
- requireEmbedding: true, // require compatible CPython headers, library, and architecture
20
- requireGil: true, // reject free-threaded CPython ABIs
19
+ requiresPython: '>=3.11,<4',
20
+ requireEmbedding: true,
21
+ requireGil: true,
21
22
  });
22
- const environment = await pyfig.prepare();
23
23
 
24
+ const environment = await pyfig.prepare(
25
+ ['requests>=2.32'],
26
+ { extras: ['database'] },
27
+ );
28
+
29
+ console.log(environment.uv);
24
30
  console.log(environment.python);
25
- console.log(environment.packageManager);
31
+ console.log(environment.managed);
32
+
33
+ await pyfig.uv(
34
+ ['run', '--', 'python', 'script.py'],
35
+ { stdio: 'inherit' },
36
+ );
26
37
  ```
27
38
 
28
- Run Python, pip, and uv with standard argument arrays and child-process spawn options:
39
+ `prepare()` accepts PEP 508 dependency specifiers plus optional `extras`, `groups`, or `allExtras`. If the project has a `pyproject.toml`, uv resolves that read-only input together with a temporary dependency file. Pyfig installs the resolved result with `uv pip install -r`, which preserves unrelated packages. Temporary inputs are created outside the repository and removed afterward.
40
+
41
+ The same uv-only surface handles direct commands:
29
42
 
30
43
  ```ts
31
- import {
32
- pipSpawn,
33
- pipSpawnSync,
34
- pythonSpawn,
35
- pythonSpawnSync,
36
- uvSpawn,
37
- uvSpawnSync,
38
- } from 'pyfig';
39
-
40
- const python = await pythonSpawn(['script.py', '--verbose'], { stdio: 'inherit' });
41
- const pythonVersion = pythonSpawnSync(['--version'], { encoding: 'utf8' });
42
-
43
- const pip = await pipSpawn(['install', 'ruff'], { stdio: 'inherit' });
44
- const installed = pipSpawnSync(['list', '--format', 'json'], { encoding: 'utf8' });
45
-
46
- const uv = await uvSpawn(['python', 'list'], { stdio: 'inherit' });
47
- const uvVersion = uvSpawnSync(['--version'], { encoding: 'utf8' });
48
- ```
44
+ import { uvSpawn, uvSpawnSync } from 'pyfig';
49
45
 
50
- The asynchronous helpers return `Promise<ChildProcess>` because discovery or installation may precede the spawn. Synchronous helpers perform the same setup in a child launched with `process.execPath`, so Bun stays Bun and Node stays Node; no `node` executable is assumed. Python execution only requires the venv, not pip or uv. Package helpers additionally resolve uv and receive `VIRTUAL_ENV`, the venv scripts directory at the front of `PATH`, and the exact venv interpreter.
46
+ await uvSpawn(['pip', 'list'], { stdio: 'inherit' });
51
47
 
52
- Pass any `PyfigOptions` as the third argument when a helper needs a custom Python range, pip range, project root, cache directory, or installation policy.
48
+ const result = uvSpawnSync(
49
+ ['run', '--', 'python', '-c', 'import sys; print(sys.executable)'],
50
+ { encoding: 'utf8' },
51
+ );
52
+ ```
53
53
 
54
- Pyfig publishes matching ESM and CommonJS entry points. The root API intentionally contains only configuration constants, the scoped client, preparation helpers, spawn helpers, project-root discovery, public result types, and cache invalidation. Probe and lock internals are not package exports.
54
+ For `uv pip` and `uv run`, Pyfig selects the venv and injects its exact interpreter. Caller-supplied target overrides such as `--python`, `--system`, `--target`, `--prefix`, and `--user` are rejected. Inherited target-changing environment variables are removed. Other uv commands run with Pyfig's cache and system-Python preference but are otherwise unchanged.
55
55
 
56
- Defaults are Python `>=3.10.0 <4.0.0`, pip `>=20.3.0`, managed Python `3.12`, pinned uv `0.13.6`, and system uv `>=0.13.6 <1.0.0`.
56
+ The async spawn helper returns `Promise<ChildProcess>` because uv or environment setup may happen before spawning. The synchronous helper performs setup through `process.execPath`, so a Bun process remains Bun and does not assume a `node` command exists.
57
57
 
58
- Package mutation is always performed by uv, whose own target-environment lock protects concurrent installs and removals. When uv installation is explicitly disabled, the venv interpreter's `python -m pip` remains available for read-only commands such as `list`; mutable pip commands are rejected. Standalone `pip` and `pip3` executables are never discovered or executed.
58
+ ## Selection policy
59
59
 
60
- Native consumers can set `requireEmbedding: true` to require CPython, `Python.h`, a linkable library, and architecture/pointer-width compatibility with the JavaScript runtime. Probe results include implementation, architecture, pointer width, release level, base executable, and library kind. Set `requireGil: true` when an addon targets the standard GIL-enabled ABI.
60
+ Pyfig uses this order:
61
61
 
62
- Given the same `PyfigOptions`, `pipSpawn(['install', 'xyz'])` and `uvSpawn(['pip', 'install', 'xyz'])` resolve the same project root, `.venv`, and interpreter. Target-changing arguments such as `--python`/`-p`, `--system`, `--target`/`-t`, `--prefix`, and `--user` are rejected. Target-changing environment variables are removed, persistent package-manager configuration is disabled for these commands, and uv receives the exact venv interpreter through `UV_PYTHON`.
62
+ 1. A compatible configured or `PATH` uv.
63
+ 2. Pyfig's cached, checksum-verified uv.
64
+ 3. A compatible explicitly configured venv, active `VIRTUAL_ENV`, root `.venv`, or Pyfig-owned venv.
65
+ 4. Every installed system Python reported by uv, with downloads disabled, until one passes all capability checks.
66
+ 5. An already-cached uv-managed Python that passes the same checks.
67
+ 6. A managed Python download, only when installation policy allows it.
63
68
 
64
- Project-root discovery uses Pacwich for workspace repositories. Single-package projects recognize Bun, npm, pnpm, Yarn, uv, and `pyproject.toml` metadata before falling back to a Git root, the nearest package manifest, or the current directory. Set `projectRoot` or `PYFIG_PROJECT_ROOT` to make ownership explicit.
69
+ An explicit `python` option is strict: an unavailable or incompatible interpreter is an error. Incompatible external venvs are preserved and skipped. Only an incompatible environment under `node_modules/.pyfig/venv` may be rebuilt.
65
70
 
66
- ## Decision flows
71
+ ![Pyfig prefers existing uv, compatible venvs, and system Python before managed downloads](docs/diagrams/python-selection.png)
67
72
 
68
- ### Python, venv, and package manager
73
+ [Mermaid source](docs/diagrams/python-selection.mmd)
69
74
 
70
- ```mermaid
71
- graph TD;
72
- A[Ensure environment] --> V{Repository virtual environment usable};
73
- V -->|Yes| Q{Compatible uv available};
74
- V -->|No| B{Configured Python supplied};
75
- B -->|Valid| P[Select base Python];
76
- B -->|Invalid| X[Fail explicit configuration];
77
- B -->|No| C{Managed uv Python compatible};
78
- C -->|Yes| P;
79
- C -->|No| D{System Python compatible};
80
- D -->|Yes| P;
81
- D -->|No| E{Python installation allowed};
82
- E -->|No| X2[Fail without changes];
83
- E -->|Yes| U[Install pinned uv];
84
- U --> M[Install managed Python];
85
- M --> P;
75
+ Native consumers can set `requireEmbedding: true` to require CPython, `Python.h`, a linkable embedding library, and architecture/pointer-width compatibility with the JavaScript runtime. Set `requireGil: true` when the native addon requires the standard GIL-enabled ABI. These checks apply to system, existing-venv, cached, and downloaded interpreters alike.
86
76
 
87
- P --> W{uv available or installable};
88
- W -->|Yes| UVV[Create virtual environment with uv];
89
- W -->|No| SV[Create virtual environment with Python];
90
- UVV --> Q;
91
- SV --> Q;
77
+ ## Runtime dependency resolution
92
78
 
93
- Q -->|Yes| R[Use uv pip with selected Python];
94
- Q -->|No| S{Python module pip compatible};
95
- S -->|Read only| T[Use Python module pip];
96
- S -->|Mutation or missing| X3[Require uv or fail];
79
+ ```ts
80
+ await pyfig.prepare(
81
+ ['orjson>=3.10', 'httpx[http2]'],
82
+ {
83
+ extras: ['postgres'],
84
+ groups: ['runtime'],
85
+ },
86
+ );
97
87
  ```
98
88
 
99
- ### Direct uv spawning
100
-
101
- ```mermaid
102
- graph TD;
103
- A[Start uv helper] --> B{Compatible uv available};
104
- B -->|Yes| H[Spawn uv with pyfig cache environment];
105
- B -->|No| C{Automatic setup enabled};
106
- C -->|No| F[Fail without installing];
107
- C -->|Yes| I[Download verified platform uv archive];
108
- I --> J{Installed uv validates};
109
- J -->|No| K[Fail installation];
110
- J -->|Yes| H;
111
- ```
112
-
113
- ## CLI
89
+ Resolution follows the equivalent of:
114
90
 
115
91
  ```sh
116
- bunx --bun pyfig
117
- bunx --bun pyfig --check
118
- bunx --bun pyfig --python-range '>=3.12.0 <3.14.0' --pip-range '>=24.0.0'
119
- bunx --bun pyfig --require-embedding --require-gil
120
- bunx --bun pyfig --project-root /path/to/repository
121
- bunx --bun pyfig --json
92
+ uv pip compile pyproject.toml /temporary/runtime-dependencies.txt \
93
+ --extra postgres --group runtime \
94
+ --python /selected/venv/python \
95
+ --output-file /temporary/resolved-dependencies.txt
96
+
97
+ uv pip install \
98
+ --python /selected/venv/python \
99
+ -r /temporary/resolved-dependencies.txt
122
100
  ```
123
101
 
124
- `bunx --bun` runs the CLI with Bun itself and does not require a separate Node.js executable.
102
+ When no `pyproject.toml` exists, only the temporary dependency input is compiled. Extras and dependency groups require an existing `pyproject.toml`. Pyfig does not use `uv add` or `uv pip sync`: the former would edit project metadata, while the latter could remove unrelated packages from the venv.
103
+
104
+ ![Runtime dependencies are resolved in temporary files before installation](docs/diagrams/runtime-dependencies.png)
105
+
106
+ [Mermaid source](docs/diagrams/runtime-dependencies.mmd)
125
107
 
126
- `--check` prohibits virtual-environment creation and tool installation, and exits nonzero when no compatible environment exists.
108
+ ## Project root
109
+
110
+ Pyfig does not interpret `package.json`, workspace metadata, `pyproject.toml`, or lockfiles to choose ownership. Root precedence is:
111
+
112
+ 1. `projectRoot`
113
+ 2. `PYFIG_PROJECT_ROOT`
114
+ 3. nearest Git worktree marker above `INIT_CWD`, when set, otherwise the current directory
115
+ 4. that starting directory when no Git marker exists
116
+
117
+ Set `projectRoot` explicitly in reusable libraries and unusual workspace layouts.
127
118
 
128
119
  ## Public API
129
120
 
130
121
  | Export | Responsibility |
131
122
  | --- | --- |
132
- | `createPyfig(options)` | Bind one immutable policy object to preparation and spawn operations. |
133
- | `ensureEnvironment()` | Return the root venv Python and its safe package manager. |
134
- | `ensureVirtualEnvironment()` | Return the root venv Python without requiring package tooling. |
135
- | `ensureUv()` | Return a compatible system uv or the verified managed uv. |
136
- | `pythonSpawn[Sync]()` | Run the root venv interpreter. |
137
- | `pipSpawn[Sync]()` | Run package commands against the root venv; mutations require uv. |
138
- | `uvSpawn[Sync]()` | Run uv; direct `uv pip` calls receive the same strict target policy. |
139
- | `resolveProjectRoot()` | Resolve workspace, package-manager, Python, or Git ownership. |
140
- | `clearPyfigCaches()` | Invalidate realm-local validated resolution caches. |
141
-
142
- The standalone functions remain convenient for scripts. Long-lived consumers such as Pynect should prefer `createPyfig()` so every call shares one policy and the validated memoization path.
123
+ | `createPyfig(options)` | Create a scoped client with memoized base preparation. |
124
+ | `client.prepare(dependencies?, options?)` | Select a venv and optionally resolve/install runtime dependencies. |
125
+ | `client.pythonInfo()` | Return the selected interpreter and embedding metadata. |
126
+ | `client.uvInfo()` | Return the selected uv executable and version. |
127
+ | `client.uv()` / `client.uvSync()` | Spawn uv and pin pip/run commands to the selected venv. |
128
+ | `ensureEnvironment(options?, dependencies?, dependencyOptions?)` | Standalone preparation API. |
129
+ | `ensureUv(options?)` | Prefer existing uv, otherwise bootstrap verified uv when allowed. |
130
+ | `uvSpawn()` / `uvSpawnSync()` | Standalone uv spawn API. |
131
+ | `resolveProjectRoot(start?)` | Resolve the nearest Git root without reading manifests. |
132
+
133
+ The package exposes matching ESM and CommonJS entry points. Probe, download, lock, and command-policy internals are not exported.
143
134
 
144
135
  ## Configuration
145
136
 
146
- Precedence is API/CLI option, environment variable, then default.
137
+ Precedence is API or CLI option, environment variable, then default.
138
+
139
+ `requiresPython` uses the same PEP 440 specifier syntax as `[project].requires-python` in `pyproject.toml`. Comma-separated clauses, compatible releases (`~=`), exclusions (`!=`), wildcard equality (`==3.12.*`), arbitrary equality (`===`), and pre-release versions are supported. For example: `>=3.10,!=3.12.*,<4`.
147
140
 
148
141
  | API option | Environment variable | Default |
149
142
  | --- | --- | --- |
150
- | `python` | `PYFIG_PYTHON` | auto-detect |
151
- | `pythonRange` | `PYFIG_PYTHON_RANGE` | `>=3.10.0 <4.0.0` |
152
- | `pipRange` | `PYFIG_PIP_RANGE` | `>=20.3.0` |
143
+ | `python` | `PYFIG_PYTHON` | uv discovery |
144
+ | `requiresPython` | `PYFIG_REQUIRES_PYTHON` | `>=3.10,<4` |
153
145
  | `managedPython` | `PYFIG_MANAGED_PYTHON` | `3.12` |
154
146
  | `uvVersion` | `PYFIG_UV_VERSION` | `0.13.6` |
155
- | `uvRange` | `PYFIG_UV_RANGE` | `>=0.13.6 <1.0.0` |
147
+ | `uvRange` | `PYFIG_UV_RANGE` | `>=0.10.0 <1.0.0` |
156
148
  | `cacheDir` | `PYFIG_HOME` | OS cache directory |
157
- | `projectRoot` | `PYFIG_PROJECT_ROOT` | auto-detected from current directory |
149
+ | `projectRoot` | `PYFIG_PROJECT_ROOT` | `INIT_CWD`, Git root, or current directory |
158
150
  | `requireEmbedding` | `PYFIG_REQUIRE_EMBEDDING` | `false` |
159
151
  | `requireGil` | `PYFIG_REQUIRE_GIL` | `false` |
160
- | `autoInstall` | `PYFIG_AUTO_INSTALL` | `true` (fallback for the three policies below) |
152
+ | `autoInstall` | `PYFIG_AUTO_INSTALL` | `true` |
161
153
  | `installPython` | `PYFIG_INSTALL_PYTHON` | `autoInstall` |
162
154
  | `installUv` | `PYFIG_INSTALL_UV` | `autoInstall` |
163
155
  | `createVirtualEnvironment` | `PYFIG_CREATE_VIRTUAL_ENVIRONMENT` | `autoInstall` |
@@ -165,43 +157,44 @@ Precedence is API/CLI option, environment variable, then default.
165
157
  | `installTimeoutMs` | `PYFIG_INSTALL_TIMEOUT_MS` | `600000` |
166
158
  | `downloadTimeoutMs` | `PYFIG_DOWNLOAD_TIMEOUT_MS` | `300000` |
167
159
  | `coordinationTimeoutMs` | `PYFIG_COORDINATION_TIMEOUT_MS` | install + download + 60 seconds |
168
- | `cacheTtlMs` | `PYFIG_CACHE_TTL_MS` | `30000` (`0` disables completed-result caching) |
169
160
 
170
- Set `PYFIG_UV` to select an existing uv executable.
161
+ Set `PYFIG_UV` to choose an explicit uv executable. Set `PYFIG_AUTO_INSTALL=0` or use the CLI's `--check` flag on hosts where downloads and environment creation are prohibited.
171
162
 
172
- ## Platform support
163
+ ## CLI
173
164
 
174
- Pyfig bootstraps uv on x64 and arm64 Windows, macOS, and Linux. Linux uses Astral's fully static musl artifact, which also runs on glibc systems. The practical platform minimums follow uv: Windows 10 or Server 2016 and macOS 13; macOS 12 may work when `realpath` is installed. Other architectures fail before downloading an incorrect artifact.
165
+ ```sh
166
+ bunx --bun pyfig
167
+ bunx --bun pyfig --check
168
+ bunx --bun pyfig --requires-python '>=3.12,<3.14'
169
+ bunx --bun pyfig --require-embedding --require-gil
170
+ bunx --bun pyfig --project-root /path/to/repository
171
+ bunx --bun pyfig --json
172
+ ```
175
173
 
176
- Node.js 22 through 26 and Bun 1.2+ are supported. Bun-only hosts do not need a `node` executable because synchronous bridges relaunch `process.execPath`. Other JavaScript runtimes are outside the supported contract unless they provide compatible Node built-ins and process semantics.
174
+ `bunx --bun` runs the CLI with Bun and does not require a separate Node.js executable.
177
175
 
178
- ## Installation safety
176
+ ## Concurrency and installation safety
179
177
 
180
- Pyfig downloads the platform's version-pinned uv archive from Astral's GitHub release, streams it through a bounded request with environment-proxy support, verifies the adjacent SHA-256 checksum, validates the staged executable's exact version, and atomically publishes it into Pyfig's cache. It does not execute an installer script or modify shell profiles.
178
+ Pyfig downloads an exact uv release archive over HTTPS, verifies its adjacent SHA-256 checksum, validates the staged executable version, and atomically publishes it in Pyfig's cache. It does not execute installer scripts or modify shell profiles.
181
179
 
182
- Virtual-environment creation, uv installation, and managed Python installation share one coordination utility with this order:
180
+ Provisioning mutations use Loqkit's layered in-memory, worker, native-file-lock-when-available, and portable-file coordination with capability checks before and after acquisition. uv supplies its own target-environment lock for installs. Base client preparation is memoized; dependency preparation is intentionally rerun so uv can cheaply verify current inputs through its shared cache.
183
181
 
184
- 1. Check for a usable tool.
185
- 2. Queue behind the current JavaScript realm's mutex.
186
- 3. Use Node's cross-worker `LockManager` when the runtime provides it.
187
- 4. Use the system `flock` command when it is available and functional.
188
- 5. Acquire the portable heartbeat-based filesystem lease used by every runtime.
189
- 6. Check again and install only when the resource is still absent.
182
+ ## Platform support
190
183
 
191
- Long child operations are asynchronous so the filesystem lease heartbeat remains live. uv supplies the separate target-venv lock for package mutations. Resolution results are memoized for a short, configurable interval and revalidated against executable paths before reuse.
184
+ Pyfig bootstraps uv on x64 and arm64 Windows, macOS, and Linux. Linux uses Astral's static musl artifact, which also runs on glibc. Node.js 22.11+ on the 22.x line, Node.js 24.10 through 26.x, and Bun 1.2+ are supported. Other JavaScript runtimes may work when they provide compatible Node built-ins and process semantics.
192
185
 
193
186
  ## Publishing
194
187
 
195
- Pyfig publishes only from a `v<package-version>` Git tag through `.github/workflows/publish.yml`. The workflow validates the exact tag, rejects a version already present on npm, builds and audits one tarball, and publishes that same archive through npm Trusted Publishing with provenance. It uses the GitHub `npm` environment and does not accept an npm token.
188
+ Pyfig publishes from a `v<package-version>` Git tag through `.github/workflows/publish.yml`. The trusted-publishing workflow validates the tag, rejects an existing registry version, builds and audits one archive, and publishes that archive with provenance.
196
189
 
197
- Before pushing a release tag, commit the matching `package.json` version and run:
190
+ From a clean, synchronized `main` branch:
198
191
 
199
192
  ```sh
200
- bun install --frozen-lockfile
201
- bun run verify
202
- bun run release:check -- v0.3.0
193
+ bun run release
203
194
  ```
204
195
 
196
+ The local gate derives the next version from remote GitHub tags, verifies locally, audits the package, commits, tags, and atomically pushes. Use `bun run release -- minor`, `bun run release -- major`, or `bun run release -- --dry-run` as needed. Tag publishing intentionally does not repeat the local test suite.
197
+
205
198
  ## Development
206
199
 
207
200
  ```sh
package/SECURITY.md CHANGED
@@ -4,6 +4,6 @@ Pyfig can download Astral uv release archives and can ask uv to download Python
4
4
 
5
5
  The uv release URL is pinned to an exact validated version and fetched over HTTPS with bounded redirects and timeouts. Pyfig verifies the adjacent SHA-256 checksum, validates the staged executable's reported version, and atomically publishes it into its cache. It never executes an installer script or modifies shell profiles or the host `PATH`. Proxy variables are honored through a per-request dispatcher without logging proxy credentials.
6
6
 
7
- Provisioning uses realm, worker, optional native advisory, and portable filesystem coordination followed by a post-lock capability check. Package mutation uses uv's target-environment lock. Pyfig rejects package-manager arguments and removes environment/configuration values that could redirect an operation away from the repository `.venv`.
7
+ Provisioning uses in-process, worker, optional native advisory, and portable filesystem coordination followed by a post-lock capability check. Runtime dependency mutation uses uv's target-environment lock. Pyfig pins uv commands to the selected venv and removes configuration values that could redirect them. It reads an existing `pyproject.toml` only as a resolver input and never writes project manifests or requirements files.
8
8
 
9
- Executable paths, project roots, version ranges, package specifications, indexes, and proxy settings remain trusted configuration. Do not let an untrusted user choose `python`, `PYFIG_UV`, `cacheDir`, package arguments, or related environment variables in a privileged process. A checksum fetched from the same release protects transfer integrity, not a compromised upstream publisher.
9
+ Executable paths, project roots, version specifiers, package specifications, indexes, and proxy settings remain trusted configuration. Do not let an untrusted user choose `python`, `PYFIG_UV`, `cacheDir`, package arguments, or related environment variables in a privileged process. A checksum fetched from the same release protects transfer integrity, not a compromised upstream publisher.