putitoutthere 0.1.42 → 0.1.44
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.
- package/CHANGELOG.md +106 -0
- package/MIGRATIONS.md +555 -0
- package/README.md +368 -91
- package/action.yml +5 -5
- package/dist/action.js +2 -2
- package/dist/action.js.map +1 -1
- package/dist/cli.d.ts +5 -7
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +11 -49
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +7 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +31 -14
- package/dist/config.js.map +1 -1
- package/dist/git.js +1 -1
- package/dist/git.js.map +1 -1
- package/dist/handlers/npm-platform.d.ts.map +1 -1
- package/dist/handlers/npm-platform.js +5 -1
- package/dist/handlers/npm-platform.js.map +1 -1
- package/dist/handlers/pypi.d.ts.map +1 -1
- package/dist/handlers/pypi.js +29 -6
- package/dist/handlers/pypi.js.map +1 -1
- package/dist/oidc-policy.d.ts +1 -1
- package/dist/oidc-policy.d.ts.map +1 -1
- package/dist/oidc-policy.js +10 -5
- package/dist/oidc-policy.js.map +1 -1
- package/dist/plan.d.ts +0 -1
- package/dist/plan.d.ts.map +1 -1
- package/dist/plan.js +19 -18
- package/dist/plan.js.map +1 -1
- package/dist/preflight.js +1 -1
- package/dist/preflight.js.map +1 -1
- package/package.json +5 -3
- package/dist/init.d.ts +0 -59
- package/dist/init.d.ts.map +0 -1
- package/dist/init.js +0 -181
- package/dist/init.js.map +0 -1
- package/dist/templates.d.ts +0 -43
- package/dist/templates.d.ts.map +0 -1
- package/dist/templates.js +0 -310
- package/dist/templates.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,152 +1,429 @@
|
|
|
1
1
|
# Put It Out There
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
A reusable GitHub Actions workflow that publishes packages to crates.io, PyPI,
|
|
4
|
+
and npm from one repo. OIDC-first, cascade-aware, polyglot. The consumer
|
|
5
|
+
surface is one config file plus ~10 lines of YAML calling
|
|
6
|
+
`uses: thekevinscott/putitoutthere/.github/workflows/release.yml@v1`.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
## Quickstart
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
### 1. Drop in `.github/workflows/release.yml`
|
|
10
11
|
|
|
11
|
-
```
|
|
12
|
-
|
|
12
|
+
```yaml
|
|
13
|
+
name: Release
|
|
14
|
+
|
|
15
|
+
on:
|
|
16
|
+
push:
|
|
17
|
+
branches: [main]
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
release:
|
|
21
|
+
uses: thekevinscott/putitoutthere/.github/workflows/release.yml@v1
|
|
22
|
+
permissions:
|
|
23
|
+
contents: write
|
|
24
|
+
id-token: write
|
|
13
25
|
```
|
|
14
26
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
contributors.
|
|
27
|
+
Pinned action versions, `plan → build → publish` orchestration, and GitHub
|
|
28
|
+
Release creation all live inside the reusable workflow.
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
Optional inputs — `with:` block at the call site:
|
|
20
31
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
| Input | Default | Use when |
|
|
33
|
+
|------------------|--------------|--------------------------------------------------------------------------|
|
|
34
|
+
| `environment` | `release` | Your GitHub deployment environment is named differently. |
|
|
35
|
+
| `node_version` | `24` | You need a specific Node version for `kind = "npm"` build steps. |
|
|
36
|
+
| `python_version` | `3.12` | You need a specific Python version for `kind = "pypi"` build steps. |
|
|
25
37
|
|
|
26
|
-
|
|
38
|
+
### 2. Drop in `putitoutthere.toml`
|
|
27
39
|
|
|
28
40
|
```toml
|
|
29
|
-
# putitoutthere.toml
|
|
30
41
|
[putitoutthere]
|
|
31
42
|
version = 1
|
|
32
43
|
|
|
33
44
|
[[package]]
|
|
34
45
|
name = "my-lib"
|
|
35
|
-
kind = "
|
|
46
|
+
kind = "pypi" # or "npm" | "crates"
|
|
36
47
|
path = "."
|
|
37
|
-
paths = ["src/**", "
|
|
48
|
+
paths = ["src/**", "pyproject.toml"]
|
|
49
|
+
build = "hatch" # required for kind = "pypi"
|
|
50
|
+
tag_format = "v{version}" # single-package repos often want this
|
|
38
51
|
```
|
|
39
52
|
|
|
40
|
-
`paths` are the globs that trigger a release
|
|
41
|
-
|
|
53
|
+
`paths` are the globs that trigger a release. Any commit touching a matching
|
|
54
|
+
file makes the package a candidate.
|
|
55
|
+
|
|
56
|
+
More config patterns are in [Configuration](#configuration) below.
|
|
57
|
+
|
|
58
|
+
### 3. Register trusted publishers
|
|
42
59
|
|
|
43
|
-
|
|
60
|
+
Each registry needs a one-time external setup so OIDC publishes work. See
|
|
61
|
+
[Trusted publishers](#trusted-publishers) below — three short lists, one per
|
|
62
|
+
registry.
|
|
44
63
|
|
|
45
|
-
|
|
64
|
+
### 4. Push a release
|
|
65
|
+
|
|
66
|
+
Merge to `main`. Default behavior: any package whose `paths` matched changed
|
|
67
|
+
files cascades and ships at `patch`. To bump `minor` or `major`:
|
|
46
68
|
|
|
47
69
|
```
|
|
48
70
|
fix: handle empty token lists
|
|
49
71
|
|
|
50
|
-
release:
|
|
72
|
+
release: minor
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
…in the merge commit body. See [Trailer](#trailer) below.
|
|
76
|
+
|
|
77
|
+
## Configuration
|
|
78
|
+
|
|
79
|
+
`putitoutthere.toml` lives at the repo root.
|
|
80
|
+
|
|
81
|
+
### `[putitoutthere]`
|
|
82
|
+
|
|
83
|
+
```toml
|
|
84
|
+
[putitoutthere]
|
|
85
|
+
version = 1 # required; only 1 is valid today
|
|
51
86
|
```
|
|
52
87
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
88
|
+
### `[[package]]` (one per releasable unit)
|
|
89
|
+
|
|
90
|
+
| Field | Type | Required | Notes |
|
|
91
|
+
|-----------------|----------|----------|---------------------------------------------------|
|
|
92
|
+
| `name` | string | yes | Unique across the config. |
|
|
93
|
+
| `kind` | enum | yes | `crates` \| `pypi` \| `npm`. |
|
|
94
|
+
| `path` | string | yes | Package working dir (`Cargo.toml` / `pyproject.toml` / `package.json` location). |
|
|
95
|
+
| `paths` | string[] | yes | Globs that cascade this package. |
|
|
96
|
+
| `depends_on` | string[] | no | Package names this one cascades on top of. |
|
|
97
|
+
| `first_version` | string | no | Default `0.1.0`. |
|
|
98
|
+
| `tag_format` | string | no | Template for the git tag. Default `"{name}-v{version}"`. Single-package repos often want `"v{version}"`. |
|
|
99
|
+
| `trust_policy` | table | no | Declared OIDC trust-policy expectations. See [Trusted publishers](#trusted-publishers). |
|
|
100
|
+
|
|
101
|
+
### `kind = "crates"`
|
|
102
|
+
|
|
103
|
+
| Field | Type | Notes |
|
|
104
|
+
|-----------------------|----------|------------------------------------------------------------|
|
|
105
|
+
| `crate` | string | Override `name` → crates.io name. |
|
|
106
|
+
| `features` | string[] | Pass through to `cargo publish --features`. |
|
|
107
|
+
| `no_default_features` | bool | Pass `--no-default-features` to `cargo publish` when true. |
|
|
108
|
+
|
|
109
|
+
### `kind = "pypi"`
|
|
110
|
+
|
|
111
|
+
| Field | Type | Notes |
|
|
112
|
+
|--------------|------------------------|----------------------------------------------------|
|
|
113
|
+
| `pypi` | string | Override `name` → PyPI registered name. |
|
|
114
|
+
| `build` | enum | `maturin` \| `setuptools` \| `hatch`. Required. |
|
|
115
|
+
| `targets` | (string \| object)[] | Required when `build = "maturin"`. Triples or `{ triple, runner }` objects. |
|
|
116
|
+
| `bundle_cli` | table | Opt-in: cross-compile a Rust CLI per target and stage it into each wheel. Only valid with `build = "maturin"`. See [Recipes → Rust CLI inside a PyPI wheel](#rust-cli-inside-a-pypi-wheel). |
|
|
117
|
+
|
|
118
|
+
### `kind = "npm"`
|
|
57
119
|
|
|
58
|
-
|
|
120
|
+
| Field | Type | Notes |
|
|
121
|
+
|-----------|------------------------|------------------------------------------------------|
|
|
122
|
+
| `npm` | string | Override `name` → npm name (for scoped packages). |
|
|
123
|
+
| `access` | enum | `public` \| `restricted`. Default `public`. |
|
|
124
|
+
| `tag` | string | dist-tag. Default `latest`. |
|
|
125
|
+
| `build` | enum | `napi` \| `bundled-cli`. Omitted = vanilla. See [Recipes → Bundled-CLI npm family](#bundled-cli-npm-family). |
|
|
126
|
+
| `targets` | (string \| object)[] | Required when `build ∈ {napi, bundled-cli}`. |
|
|
59
127
|
|
|
60
|
-
|
|
128
|
+
### Example: polyglot Rust library
|
|
61
129
|
|
|
62
|
-
|
|
63
|
-
mirrors a real polyglot shape: a Rust crate, a PyO3 Python wheel wrapping it,
|
|
64
|
-
and an npm CLI bundling the Rust binary. One `putitoutthere.toml` declares
|
|
65
|
-
all three:
|
|
130
|
+
One Rust crate feeds three artifacts:
|
|
66
131
|
|
|
67
132
|
```toml
|
|
68
133
|
[[package]]
|
|
69
|
-
name = "my-
|
|
134
|
+
name = "my-rust"
|
|
70
135
|
kind = "crates"
|
|
71
|
-
path = "
|
|
72
|
-
paths = ["
|
|
136
|
+
path = "crates/my-rust"
|
|
137
|
+
paths = ["crates/my-rust/**"]
|
|
73
138
|
|
|
74
139
|
[[package]]
|
|
75
|
-
name
|
|
76
|
-
kind
|
|
77
|
-
path
|
|
78
|
-
paths
|
|
79
|
-
build
|
|
80
|
-
|
|
140
|
+
name = "my-py"
|
|
141
|
+
kind = "pypi"
|
|
142
|
+
path = "py/my-py"
|
|
143
|
+
paths = ["py/my-py/**"]
|
|
144
|
+
build = "maturin"
|
|
145
|
+
targets = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin"]
|
|
146
|
+
depends_on = ["my-rust"]
|
|
81
147
|
|
|
82
148
|
[[package]]
|
|
83
|
-
name
|
|
84
|
-
kind
|
|
85
|
-
path
|
|
86
|
-
paths
|
|
87
|
-
build
|
|
88
|
-
|
|
149
|
+
name = "my-cli"
|
|
150
|
+
kind = "npm"
|
|
151
|
+
path = "packages/ts"
|
|
152
|
+
paths = ["packages/ts/**"]
|
|
153
|
+
build = "bundled-cli"
|
|
154
|
+
targets = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin"]
|
|
155
|
+
depends_on = ["my-rust"]
|
|
89
156
|
```
|
|
90
157
|
|
|
91
|
-
A change to `
|
|
92
|
-
|
|
93
|
-
|
|
158
|
+
A change to `crates/my-rust/` cascades: the crate ships, then the Python
|
|
159
|
+
wheels and npm family ship on top, version-bumped to match.
|
|
160
|
+
|
|
161
|
+
### Example: multi-package workspace
|
|
162
|
+
|
|
163
|
+
```toml
|
|
164
|
+
[[package]]
|
|
165
|
+
name = "@my/core"
|
|
166
|
+
kind = "npm"
|
|
167
|
+
path = "packages/core"
|
|
168
|
+
paths = ["packages/core/**"]
|
|
169
|
+
|
|
170
|
+
[[package]]
|
|
171
|
+
name = "@my/parser"
|
|
172
|
+
kind = "npm"
|
|
173
|
+
path = "packages/parser"
|
|
174
|
+
paths = ["packages/parser/**"]
|
|
175
|
+
depends_on = ["@my/core"]
|
|
176
|
+
```
|
|
94
177
|
|
|
95
|
-
##
|
|
178
|
+
## Trailer
|
|
96
179
|
|
|
97
|
-
|
|
98
|
-
|
|
180
|
+
The trailer is **optional**. Default behavior is `patch` whenever a package's
|
|
181
|
+
`paths` matched changed files.
|
|
99
182
|
|
|
100
|
-
|
|
183
|
+
Grammar:
|
|
101
184
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
185
|
+
```
|
|
186
|
+
release: <bump> [pkg1, pkg2, ...]
|
|
187
|
+
```
|
|
105
188
|
|
|
106
|
-
|
|
189
|
+
`<bump>` is `patch` | `minor` | `major` | `skip`. The optional package list
|
|
190
|
+
scopes a non-default bump to specific packages.
|
|
107
191
|
|
|
108
|
-
|
|
109
|
-
|
|
192
|
+
| Trailer | Effect |
|
|
193
|
+
|--------------------------|------------------------------------------------------------------------|
|
|
194
|
+
| *(none)* | Cascaded packages bump `patch`. |
|
|
195
|
+
| `release: minor` | Cascaded packages bump `minor`. |
|
|
196
|
+
| `release: major` | Cascaded packages bump `major`. |
|
|
197
|
+
| `release: skip` | No release this commit. Cascade ignored. |
|
|
198
|
+
| `release: minor [a, b]` | `a` and `b` bump `minor`; other cascaded packages stay at `patch`. |
|
|
110
199
|
|
|
111
|
-
|
|
200
|
+
The trailer matches anywhere in the commit body. If multiple `release:` lines
|
|
201
|
+
are present, the **last** one wins.
|
|
112
202
|
|
|
113
|
-
|
|
114
|
-
- [Rust + napi npm](./docs/guide/shapes/rust-napi.md) — crate + npm family (no PyPI)
|
|
115
|
-
- [Polyglot Rust library](./docs/guide/shapes/polyglot-rust.md) — all three registries from one core
|
|
116
|
-
- [Python wheels with C extensions](./docs/guide/shapes/python-cibuildwheel.md) — `cibuildwheel` for the `pillow`/`lxml`/`numpy` shape
|
|
203
|
+
## Cascade
|
|
117
204
|
|
|
118
|
-
|
|
205
|
+
A package cascades into the release plan when a commit changes any file
|
|
206
|
+
matching one of its `paths` globs since its last tag. If another package
|
|
207
|
+
declares `depends_on = ["this-package"]`, that package also cascades.
|
|
208
|
+
Transitively, DFS-ordered, with cycle detection at config-load.
|
|
119
209
|
|
|
120
|
-
|
|
121
|
-
|
|
210
|
+
Inside a single release, packages publish in topological order of their
|
|
211
|
+
`depends_on` graph. If your Python wrapper depends on a Rust crate, the
|
|
212
|
+
crate publishes first.
|
|
122
213
|
|
|
123
|
-
|
|
214
|
+
Each handler's first move on publish is `isPublished` — check the registry
|
|
215
|
+
for the target version. Already there → skip cleanly. Lets you re-run failed
|
|
216
|
+
releases without fighting registry-immutable-publish semantics.
|
|
124
217
|
|
|
125
218
|
## Trusted publishers
|
|
126
219
|
|
|
127
|
-
|
|
220
|
+
OIDC trusted publishers — the only auth path supported by the reusable
|
|
221
|
+
workflow. Long-lived registry tokens are not reachable through the workflow.
|
|
222
|
+
|
|
223
|
+
The fields each registry needs are the same: repository owner/name, workflow
|
|
224
|
+
filename (`release.yml`), and optionally a GitHub environment name.
|
|
225
|
+
|
|
226
|
+
### crates.io
|
|
227
|
+
|
|
228
|
+
1. Publish your crate once through the normal `cargo` flow so the crate
|
|
229
|
+
exists. (Trusted publishing needs a crate owner record.)
|
|
230
|
+
2. Go to `https://crates.io/crates/<crate>/settings` → **Trusted Publishing**
|
|
231
|
+
→ **Add**.
|
|
232
|
+
3. Fill in: repository owner, repository name, workflow filename
|
|
233
|
+
(`release.yml`), environment (optional).
|
|
234
|
+
|
|
235
|
+
### PyPI
|
|
236
|
+
|
|
237
|
+
1. Go to `https://pypi.org/manage/project/<name>/settings/publishing/` (or
|
|
238
|
+
**Publishing** on the project page).
|
|
239
|
+
2. Add a **GitHub** trusted publisher: owner, repo, workflow filename,
|
|
240
|
+
environment (optional).
|
|
241
|
+
3. Brand-new project? Use a [pending publisher](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/)
|
|
242
|
+
to skip the bootstrap token.
|
|
243
|
+
|
|
244
|
+
### npm
|
|
245
|
+
|
|
246
|
+
1. Publish at least one version of your package with a classic
|
|
247
|
+
`NODE_AUTH_TOKEN` so the package exists on the registry. (npm's trusted
|
|
248
|
+
publisher requires an existing package.)
|
|
249
|
+
2. Go to `https://www.npmjs.com/package/<name>/access` → **Require trusted
|
|
250
|
+
publisher**.
|
|
251
|
+
3. Fill in: repository, workflow filename, environment (optional).
|
|
252
|
+
4. Delete the bootstrap token.
|
|
253
|
+
|
|
254
|
+
### Catching trust-policy drift before release
|
|
255
|
+
|
|
256
|
+
Renaming `release.yml` to anything else, or renaming the environment, breaks
|
|
257
|
+
publish with an opaque HTTP 400 from the registry. To catch the rename
|
|
258
|
+
before it ships, declare the expected values in `putitoutthere.toml`:
|
|
128
259
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
260
|
+
```toml
|
|
261
|
+
[[package]]
|
|
262
|
+
name = "my-crate"
|
|
263
|
+
kind = "crates"
|
|
264
|
+
path = "crates/my-crate"
|
|
265
|
+
paths = ["crates/my-crate/**"]
|
|
266
|
+
|
|
267
|
+
[package.trust_policy]
|
|
268
|
+
workflow = "release.yml" # required; bare filename
|
|
269
|
+
environment = "release" # optional
|
|
270
|
+
repository = "my-org/my-crate" # optional
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
The engine diffs the declaration against the local workflow file and (in CI)
|
|
274
|
+
against `GITHUB_WORKFLOW_REF` before any registry call. For `kind = "crates"`,
|
|
275
|
+
a registry cross-check runs when `CRATES_IO_DOCTOR_TOKEN` is set — calls the
|
|
276
|
+
trusted-publishing read API and fails on mismatch. PyPI and npm don't expose
|
|
277
|
+
read APIs for trust policy, so the declared diff is the full gate there.
|
|
278
|
+
|
|
279
|
+
## Recipes
|
|
280
|
+
|
|
281
|
+
### Bundled-CLI npm family
|
|
282
|
+
|
|
283
|
+
Ship a compiled CLI as an npm per-platform family — `npm install -g my-cli`
|
|
284
|
+
gives users a working binary on PATH. The `esbuild` / `biome` distribution
|
|
285
|
+
shape.
|
|
286
|
+
|
|
287
|
+
Config:
|
|
288
|
+
|
|
289
|
+
```toml
|
|
290
|
+
[[package]]
|
|
291
|
+
name = "my-cli"
|
|
292
|
+
kind = "npm"
|
|
293
|
+
npm = "my-cli"
|
|
294
|
+
build = "bundled-cli"
|
|
295
|
+
path = "packages/ts-cli"
|
|
296
|
+
paths = ["packages/ts-cli/**", "crates/my-cli/**"]
|
|
297
|
+
targets = [
|
|
298
|
+
"x86_64-unknown-linux-gnu",
|
|
299
|
+
"aarch64-unknown-linux-gnu",
|
|
300
|
+
"x86_64-apple-darwin",
|
|
301
|
+
"aarch64-apple-darwin",
|
|
302
|
+
"x86_64-pc-windows-msvc",
|
|
303
|
+
]
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The engine publishes a per-platform sub-package per target
|
|
307
|
+
(`my-cli-<triple>`) plus a top-level whose `optionalDependencies` pin them
|
|
308
|
+
at the published version. npm's resolver installs exactly one sub-package
|
|
309
|
+
at consumer install time.
|
|
310
|
+
|
|
311
|
+
You author the launcher script that picks the right per-platform binary
|
|
312
|
+
once. `package.json`:
|
|
313
|
+
|
|
314
|
+
```json
|
|
315
|
+
{
|
|
316
|
+
"name": "my-cli",
|
|
317
|
+
"bin": { "my-cli": "bin/my-cli.js" }
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
`bin/my-cli.js`:
|
|
322
|
+
|
|
323
|
+
```js
|
|
324
|
+
#!/usr/bin/env node
|
|
325
|
+
const { spawnSync } = require('node:child_process');
|
|
326
|
+
const { platform, arch } = process;
|
|
327
|
+
|
|
328
|
+
const triples = {
|
|
329
|
+
'linux-x64': 'x86_64-unknown-linux-gnu',
|
|
330
|
+
'linux-arm64': 'aarch64-unknown-linux-gnu',
|
|
331
|
+
'darwin-x64': 'x86_64-apple-darwin',
|
|
332
|
+
'darwin-arm64': 'aarch64-apple-darwin',
|
|
333
|
+
'win32-x64': 'x86_64-pc-windows-msvc',
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const triple = triples[`${platform}-${arch}`];
|
|
337
|
+
if (!triple) {
|
|
338
|
+
console.error(`my-cli: unsupported platform ${platform}-${arch}`);
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const pkg = `my-cli-${triple}`;
|
|
343
|
+
const binary = require.resolve(`${pkg}/bin/my-cli${platform === 'win32' ? '.exe' : ''}`);
|
|
344
|
+
const result = spawnSync(binary, process.argv.slice(2), { stdio: 'inherit' });
|
|
345
|
+
process.exit(result.status ?? 1);
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Each per-platform sub-package needs its own npm trusted-publisher
|
|
349
|
+
registration (a policy on `my-cli` does not cover
|
|
350
|
+
`my-cli-x86_64-unknown-linux-gnu`).
|
|
351
|
+
|
|
352
|
+
### Rust CLI inside a PyPI wheel
|
|
353
|
+
|
|
354
|
+
`pip install my-lib` on any platform gets a working CLI on `PATH` without
|
|
355
|
+
the user installing a Rust toolchain. The `ruff` / `uv` / `pydantic-core`
|
|
356
|
+
pattern.
|
|
357
|
+
|
|
358
|
+
Config:
|
|
359
|
+
|
|
360
|
+
```toml
|
|
361
|
+
[[package]]
|
|
362
|
+
name = "my-py"
|
|
363
|
+
kind = "pypi"
|
|
364
|
+
build = "maturin"
|
|
365
|
+
path = "packages/python"
|
|
366
|
+
paths = ["packages/python/**", "crates/my-rust/**"]
|
|
367
|
+
targets = [
|
|
368
|
+
"x86_64-unknown-linux-gnu",
|
|
369
|
+
"aarch64-unknown-linux-gnu",
|
|
370
|
+
"x86_64-apple-darwin",
|
|
371
|
+
"aarch64-apple-darwin",
|
|
372
|
+
"x86_64-pc-windows-msvc",
|
|
373
|
+
]
|
|
374
|
+
depends_on = ["my-rust"]
|
|
375
|
+
|
|
376
|
+
[package.bundle_cli]
|
|
377
|
+
bin = "my-cli"
|
|
378
|
+
stage_to = "src/my_py/_binary"
|
|
379
|
+
crate_path = "crates/my-rust"
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
The reusable workflow cross-compiles the binary per target and stages it
|
|
383
|
+
into the package source tree before maturin runs. Your `pyproject.toml`
|
|
384
|
+
ties the staged binary into a `console_scripts` entry:
|
|
385
|
+
|
|
386
|
+
```toml
|
|
387
|
+
[project.scripts]
|
|
388
|
+
my-cli = "my_py._binary:entrypoint"
|
|
389
|
+
|
|
390
|
+
[tool.maturin]
|
|
391
|
+
include = ["src/my_py/_binary/**"]
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Launcher in `packages/python/src/my_py/_binary/__init__.py`:
|
|
395
|
+
|
|
396
|
+
```python
|
|
397
|
+
import os, sys
|
|
398
|
+
from pathlib import Path
|
|
399
|
+
|
|
400
|
+
def entrypoint():
|
|
401
|
+
here = Path(__file__).parent
|
|
402
|
+
binary = here / ("my-cli.exe" if os.name == "nt" else "my-cli")
|
|
403
|
+
if not binary.exists():
|
|
404
|
+
sys.stderr.write(f"my-cli binary not found at {binary}\n")
|
|
405
|
+
sys.exit(1)
|
|
406
|
+
os.execv(binary, [str(binary), *sys.argv[1:]])
|
|
407
|
+
```
|
|
132
408
|
|
|
133
|
-
|
|
134
|
-
still read as env vars if OIDC isn't available. `putitoutthere doctor`
|
|
135
|
-
reports which path is active.
|
|
409
|
+
## Dynamic-version PyPI gotcha
|
|
136
410
|
|
|
137
|
-
|
|
411
|
+
If your `pyproject.toml` uses `[project].dynamic = ["version"]` with
|
|
412
|
+
`hatch-vcs` or `setuptools-scm`, the build backend derives the version from
|
|
413
|
+
the latest git tag at build time — which is still the **previous** release
|
|
414
|
+
when the build runs. Without a handoff, the sdist ships as
|
|
415
|
+
`<pkg>-X.Y.Z.devN.tar.gz` instead of `<pkg>-X.Y.Z.tar.gz`.
|
|
138
416
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
*cascading releases*, not runtime version pinning.
|
|
144
|
-
- Not a build system. Handlers shell out to `cargo`, `uv`/`maturin`/`hatch`,
|
|
145
|
-
`npm` — standard toolchains only.
|
|
417
|
+
The reusable workflow sets `SETUPTOOLS_SCM_PRETEND_VERSION` to the planned
|
|
418
|
+
version on the build step, which both `setuptools-scm` and `hatch-vcs`
|
|
419
|
+
honor. Per-package variants like `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_<PKG>`
|
|
420
|
+
are silently ignored by `hatch-vcs`; only the global form works.
|
|
146
421
|
|
|
147
|
-
##
|
|
422
|
+
## Project layout
|
|
148
423
|
|
|
149
|
-
- [
|
|
150
|
-
- [
|
|
151
|
-
- [
|
|
152
|
-
- [
|
|
424
|
+
- [`CHANGELOG.md`](./CHANGELOG.md) — per-release changes.
|
|
425
|
+
- [`MIGRATIONS.md`](./MIGRATIONS.md) — per-version upgrade guide.
|
|
426
|
+
- [`notes/design-commitments.md`](./notes/design-commitments.md) — non-goals.
|
|
427
|
+
- [`notes/internals/`](./notes/internals/) — internal contracts (artifact
|
|
428
|
+
layout, runner setup) that the reusable workflow honors so consumers don't
|
|
429
|
+
have to.
|
package/action.yml
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
name: 'Put It Out There'
|
|
2
|
-
description: '
|
|
1
|
+
name: 'Put It Out There (internal)'
|
|
2
|
+
description: 'Internal step-level wrapper around the putitoutthere CLI. Consumers compose with the reusable workflow at .github/workflows/release.yml@v1, not with this action directly. See notes/design-commitments.md non-goal #10.'
|
|
3
3
|
author: 'Kevin Scott'
|
|
4
4
|
|
|
5
5
|
inputs:
|
|
6
6
|
command:
|
|
7
|
-
description: 'Which
|
|
7
|
+
description: 'Which CLI subcommand to run. Canonical values are `plan` and `publish`.'
|
|
8
8
|
required: false
|
|
9
9
|
default: 'plan'
|
|
10
10
|
dry_run:
|
|
@@ -12,13 +12,13 @@ inputs:
|
|
|
12
12
|
required: false
|
|
13
13
|
default: 'false'
|
|
14
14
|
fail_on_error:
|
|
15
|
-
description: 'If true, non-zero
|
|
15
|
+
description: 'If true, non-zero CLI exit codes fail the step.'
|
|
16
16
|
required: false
|
|
17
17
|
default: 'true'
|
|
18
18
|
|
|
19
19
|
outputs:
|
|
20
20
|
matrix:
|
|
21
|
-
description: 'JSON matrix emitted by `
|
|
21
|
+
description: 'JSON matrix emitted by `plan`. Output key is omitted when the plan resolves to zero rows or when command is anything other than plan; downstream jobs should coalesce with `|| ''[]'''.'
|
|
22
22
|
|
|
23
23
|
runs:
|
|
24
24
|
using: 'node24'
|
package/dist/action.js
CHANGED
|
@@ -13,7 +13,7 @@ export async function main() {
|
|
|
13
13
|
const dryRun = (process.env.INPUT_DRY_RUN ?? 'false').toLowerCase() === 'true';
|
|
14
14
|
const failOnError = (process.env.INPUT_FAIL_ON_ERROR ?? 'true').toLowerCase() !== 'false';
|
|
15
15
|
if (!command) {
|
|
16
|
-
process.stderr.write('
|
|
16
|
+
process.stderr.write('putitoutthere action: missing required input `command`\n');
|
|
17
17
|
process.exit(1);
|
|
18
18
|
}
|
|
19
19
|
const argv = ['node', 'putitoutthere', command];
|
|
@@ -22,7 +22,7 @@ export async function main() {
|
|
|
22
22
|
argv.push('--json');
|
|
23
23
|
const code = await run(argv);
|
|
24
24
|
if (code !== 0 && !failOnError) {
|
|
25
|
-
process.stderr.write(`
|
|
25
|
+
process.stderr.write(`putitoutthere action: ignoring non-zero exit (fail_on_error=false): ${code}\n`);
|
|
26
26
|
process.exit(0);
|
|
27
27
|
}
|
|
28
28
|
process.exit(code);
|
package/dist/action.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action.js","sourceRoot":"","sources":["../src/action.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC;IAChD,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC;IAC/E,MAAM,WAAW,GACf,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,MAAM,CAAC,CAAC,WAAW,EAAE,KAAK,OAAO,CAAC;IAExE,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,
|
|
1
|
+
{"version":3,"file":"action.js","sourceRoot":"","sources":["../src/action.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAE/B,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC;IAChD,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC;IAC/E,MAAM,WAAW,GACf,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,MAAM,CAAC,CAAC,WAAW,EAAE,KAAK,OAAO,CAAC;IAExE,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,0DAA0D,CAC3D,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;IAChD,IAAI,MAAM;QAAE,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACnC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAEpB,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC;IAC7B,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,uEAAuE,IAAI,IAAI,CAChF,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACrB,CAAC;AAED,oFAAoF;AACpF,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IACpD,KAAK,IAAI,EAAE,CAAC;AACd,CAAC"}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `putitoutthere` CLI entry.
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* init → TODO #20
|
|
7
|
-
* doctor → TODO #23
|
|
2
|
+
* `putitoutthere` CLI entry. Internal seam — the reusable workflow
|
|
3
|
+
* (`.github/workflows/release.yml`) invokes this. Not a consumer-
|
|
4
|
+
* facing surface; flags / help text are stable enough to test, but
|
|
5
|
+
* not promised externally. See `notes/design-commitments.md`.
|
|
8
6
|
*
|
|
9
7
|
* Global flags:
|
|
10
8
|
* --cwd <path> working directory (default: process.cwd())
|
|
11
9
|
* --config <path> path to putitoutthere.toml
|
|
12
10
|
* --dry-run for publish; no side effects
|
|
13
|
-
* --json
|
|
11
|
+
* --json machine-readable output
|
|
14
12
|
*/
|
|
15
13
|
export declare function run(argv: readonly string[]): Promise<number>;
|
|
16
14
|
//# sourceMappingURL=cli.d.ts.map
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AA6GH,wBAAsB,GAAG,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAyTlE"}
|