putitoutthere 0.1.43 → 0.1.45
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 +115 -0
- package/MIGRATIONS.md +632 -0
- package/README.md +342 -94
- package/action.yml +5 -5
- package/dist/action.js +2 -2
- package/dist/action.js.map +1 -1
- package/dist/cascade.d.ts +1 -1
- package/dist/cascade.js +2 -2
- package/dist/cli.d.ts +9 -6
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +12 -426
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +13 -61
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +31 -45
- 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/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/dist/publish.d.ts +0 -10
- package/dist/publish.d.ts.map +1 -1
- package/dist/publish.js +6 -52
- package/dist/publish.js.map +1 -1
- package/dist/types.d.ts +1 -21
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +5 -3
- package/dist/auth.d.ts +0 -68
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js +0 -285
- package/dist/auth.js.map +0 -1
- package/dist/doctor.d.ts +0 -115
- package/dist/doctor.d.ts.map +0 -1
- package/dist/doctor.js +0 -292
- package/dist/doctor.js.map +0 -1
- 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/keyring.d.ts +0 -35
- package/dist/keyring.d.ts.map +0 -1
- package/dist/keyring.js +0 -88
- package/dist/keyring.js.map +0 -1
- package/dist/oidc-policy.d.ts +0 -145
- package/dist/oidc-policy.d.ts.map +0 -1
- package/dist/oidc-policy.js +0 -383
- package/dist/oidc-policy.js.map +0 -1
- package/dist/preflight-run.d.ts +0 -47
- package/dist/preflight-run.d.ts.map +0 -1
- package/dist/preflight-run.js +0 -197
- package/dist/preflight-run.js.map +0 -1
- package/dist/registries/crates-trust.d.ts +0 -69
- package/dist/registries/crates-trust.d.ts.map +0 -1
- package/dist/registries/crates-trust.js +0 -95
- package/dist/registries/crates-trust.js.map +0 -1
- package/dist/release.d.ts +0 -36
- package/dist/release.d.ts.map +0 -1
- package/dist/release.js +0 -102
- package/dist/release.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/dist/token-scope.d.ts +0 -65
- package/dist/token-scope.d.ts.map +0 -1
- package/dist/token-scope.js +0 -163
- package/dist/token-scope.js.map +0 -1
- package/dist/token.d.ts +0 -211
- package/dist/token.d.ts.map +0 -1
- package/dist/token.js +0 -719
- package/dist/token.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,155 +1,403 @@
|
|
|
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@v0`.
|
|
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@v0
|
|
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
|
-
|
|
48
|
+
globs = ["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
|
-
`
|
|
41
|
-
|
|
53
|
+
`globs` are the path globs that trigger a release. Any commit touching a
|
|
54
|
+
matching 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 `globs` 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
|
|
51
73
|
```
|
|
52
74
|
|
|
53
|
-
|
|
54
|
-
`release.yml` workflow runs `putitoutthere plan` against the trailer +
|
|
55
|
-
changed paths, then `putitoutthere publish` per matching package. Tags are
|
|
56
|
-
`{name}-v{version}`.
|
|
75
|
+
…in the merge commit body. See [Trailer](#trailer) below.
|
|
57
76
|
|
|
58
|
-
|
|
59
|
-
changed files cascades at `patch`. Use `release: minor` / `release: major`
|
|
60
|
-
to override the bump, or `release: skip` to suppress the release for that
|
|
61
|
-
commit.
|
|
77
|
+
## Configuration
|
|
62
78
|
|
|
63
|
-
|
|
79
|
+
`putitoutthere.toml` lives at the repo root.
|
|
64
80
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
81
|
+
### `[putitoutthere]`
|
|
82
|
+
|
|
83
|
+
```toml
|
|
84
|
+
[putitoutthere]
|
|
85
|
+
version = 1 # required; only 1 is valid today
|
|
86
|
+
```
|
|
87
|
+
|
|
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
|
+
| `globs` | string[] | yes | Path 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
|
+
|
|
100
|
+
### `kind = "crates"`
|
|
101
|
+
|
|
102
|
+
| Field | Type | Notes |
|
|
103
|
+
|-----------------------|----------|------------------------------------------------------------|
|
|
104
|
+
| `crate` | string | Override `name` → crates.io name. |
|
|
105
|
+
| `features` | string[] | Pass through to `cargo publish --features`. |
|
|
106
|
+
| `no_default_features` | bool | Pass `--no-default-features` to `cargo publish` when true. |
|
|
107
|
+
|
|
108
|
+
### `kind = "pypi"`
|
|
109
|
+
|
|
110
|
+
| Field | Type | Notes |
|
|
111
|
+
|--------------|------------------------|----------------------------------------------------|
|
|
112
|
+
| `pypi` | string | Override `name` → PyPI registered name. |
|
|
113
|
+
| `build` | enum | `maturin` \| `setuptools` \| `hatch`. Required. |
|
|
114
|
+
| `targets` | (string \| object)[] | Required when `build = "maturin"`. Triples or `{ triple, runner }` objects. |
|
|
115
|
+
| `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). |
|
|
116
|
+
|
|
117
|
+
### `kind = "npm"`
|
|
118
|
+
|
|
119
|
+
| Field | Type | Notes |
|
|
120
|
+
|-----------|------------------------|------------------------------------------------------|
|
|
121
|
+
| `npm` | string | Override `name` → npm name (for scoped packages). |
|
|
122
|
+
| `access` | enum | `public` \| `restricted`. Default `public`. |
|
|
123
|
+
| `tag` | string | dist-tag. Default `latest`. |
|
|
124
|
+
| `build` | enum | `napi` \| `bundled-cli`. Omitted = vanilla. See [Recipes → Bundled-CLI npm family](#bundled-cli-npm-family). |
|
|
125
|
+
| `targets` | (string \| object)[] | Required when `build ∈ {napi, bundled-cli}`. |
|
|
126
|
+
|
|
127
|
+
### Example: polyglot Rust library
|
|
128
|
+
|
|
129
|
+
One Rust crate feeds three artifacts:
|
|
69
130
|
|
|
70
131
|
```toml
|
|
71
132
|
[[package]]
|
|
72
|
-
name = "my-
|
|
133
|
+
name = "my-rust"
|
|
73
134
|
kind = "crates"
|
|
74
|
-
path = "
|
|
75
|
-
|
|
135
|
+
path = "crates/my-rust"
|
|
136
|
+
globs = ["crates/my-rust/**"]
|
|
76
137
|
|
|
77
138
|
[[package]]
|
|
78
|
-
name
|
|
79
|
-
kind
|
|
80
|
-
path
|
|
81
|
-
|
|
82
|
-
build
|
|
83
|
-
|
|
139
|
+
name = "my-py"
|
|
140
|
+
kind = "pypi"
|
|
141
|
+
path = "py/my-py"
|
|
142
|
+
globs = ["py/my-py/**"]
|
|
143
|
+
build = "maturin"
|
|
144
|
+
targets = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin"]
|
|
145
|
+
depends_on = ["my-rust"]
|
|
84
146
|
|
|
85
147
|
[[package]]
|
|
86
|
-
name
|
|
87
|
-
kind
|
|
88
|
-
path
|
|
89
|
-
|
|
90
|
-
build
|
|
91
|
-
|
|
148
|
+
name = "my-cli"
|
|
149
|
+
kind = "npm"
|
|
150
|
+
path = "packages/ts"
|
|
151
|
+
globs = ["packages/ts/**"]
|
|
152
|
+
build = "bundled-cli"
|
|
153
|
+
targets = ["x86_64-unknown-linux-gnu", "aarch64-apple-darwin"]
|
|
154
|
+
depends_on = ["my-rust"]
|
|
92
155
|
```
|
|
93
156
|
|
|
94
|
-
A change to `
|
|
95
|
-
|
|
96
|
-
just the npm package.
|
|
157
|
+
A change to `crates/my-rust/` cascades: the crate ships, then the Python
|
|
158
|
+
wheels and npm family ship on top, version-bumped to match.
|
|
97
159
|
|
|
98
|
-
|
|
160
|
+
### Example: multi-package workspace
|
|
99
161
|
|
|
100
|
-
|
|
101
|
-
|
|
162
|
+
```toml
|
|
163
|
+
[[package]]
|
|
164
|
+
name = "@my/core"
|
|
165
|
+
kind = "npm"
|
|
166
|
+
path = "packages/core"
|
|
167
|
+
globs = ["packages/core/**"]
|
|
102
168
|
|
|
103
|
-
|
|
169
|
+
[[package]]
|
|
170
|
+
name = "@my/parser"
|
|
171
|
+
kind = "npm"
|
|
172
|
+
path = "packages/parser"
|
|
173
|
+
globs = ["packages/parser/**"]
|
|
174
|
+
depends_on = ["@my/core"]
|
|
175
|
+
```
|
|
104
176
|
|
|
105
|
-
|
|
106
|
-
- [npm library](./docs/guide/shapes/npm-library.md) — one `package.json` to npm
|
|
107
|
-
- [Rust crate](./docs/guide/shapes/rust-crate.md) — one `Cargo.toml` to crates.io
|
|
177
|
+
## Trailer
|
|
108
178
|
|
|
109
|
-
**
|
|
179
|
+
The trailer is **optional**. Default behavior is `patch` whenever a package's
|
|
180
|
+
`globs` matched changed files.
|
|
110
181
|
|
|
111
|
-
|
|
112
|
-
- [npm workspace](./docs/guide/shapes/npm-workspace.md) — multiple npm packages, shared dependency graph
|
|
182
|
+
Grammar:
|
|
113
183
|
|
|
114
|
-
|
|
184
|
+
```
|
|
185
|
+
release: <bump> [pkg1, pkg2, ...]
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`<bump>` is `patch` | `minor` | `major` | `skip`. The optional package list
|
|
189
|
+
scopes a non-default bump to specific packages.
|
|
190
|
+
|
|
191
|
+
| Trailer | Effect |
|
|
192
|
+
|--------------------------|------------------------------------------------------------------------|
|
|
193
|
+
| *(none)* | Cascaded packages bump `patch`. |
|
|
194
|
+
| `release: minor` | Cascaded packages bump `minor`. |
|
|
195
|
+
| `release: major` | Cascaded packages bump `major`. |
|
|
196
|
+
| `release: skip` | No release this commit. Cascade ignored. |
|
|
197
|
+
| `release: minor [a, b]` | `a` and `b` bump `minor`; other cascaded packages stay at `patch`. |
|
|
198
|
+
|
|
199
|
+
The trailer matches anywhere in the commit body. If multiple `release:` lines
|
|
200
|
+
are present, the **last** one wins.
|
|
115
201
|
|
|
116
|
-
|
|
117
|
-
- [Rust + napi npm](./docs/guide/shapes/rust-napi.md) — crate + npm family (no PyPI)
|
|
118
|
-
- [Polyglot Rust library](./docs/guide/shapes/polyglot-rust.md) — all three registries from one core
|
|
119
|
-
- [Python wheels with C extensions](./docs/guide/shapes/python-cibuildwheel.md) — `cibuildwheel` for the `pillow`/`lxml`/`numpy` shape
|
|
202
|
+
## Cascade
|
|
120
203
|
|
|
121
|
-
|
|
204
|
+
A package cascades into the release plan when a commit changes any file
|
|
205
|
+
matching one of its `globs` since its last tag. If another package
|
|
206
|
+
declares `depends_on = ["this-package"]`, that package also cascades.
|
|
207
|
+
Transitively, DFS-ordered, with cycle detection at config-load.
|
|
122
208
|
|
|
123
|
-
|
|
124
|
-
|
|
209
|
+
Inside a single release, packages publish in topological order of their
|
|
210
|
+
`depends_on` graph. If your Python wrapper depends on a Rust crate, the
|
|
211
|
+
crate publishes first.
|
|
125
212
|
|
|
126
|
-
|
|
213
|
+
Each handler's first move on publish is `isPublished` — check the registry
|
|
214
|
+
for the target version. Already there → skip cleanly. Lets you re-run failed
|
|
215
|
+
releases without fighting registry-immutable-publish semantics.
|
|
127
216
|
|
|
128
217
|
## Trusted publishers
|
|
129
218
|
|
|
130
|
-
|
|
219
|
+
OIDC trusted publishers — the only auth path supported by the reusable
|
|
220
|
+
workflow. Long-lived registry tokens are not reachable through the workflow.
|
|
221
|
+
|
|
222
|
+
The fields each registry needs are the same: repository owner/name, workflow
|
|
223
|
+
filename (`release.yml`), and optionally a GitHub environment name.
|
|
224
|
+
|
|
225
|
+
### crates.io
|
|
226
|
+
|
|
227
|
+
1. Publish your crate once through the normal `cargo` flow so the crate
|
|
228
|
+
exists. (Trusted publishing needs a crate owner record.)
|
|
229
|
+
2. Go to `https://crates.io/crates/<crate>/settings` → **Trusted Publishing**
|
|
230
|
+
→ **Add**.
|
|
231
|
+
3. Fill in: repository owner, repository name, workflow filename
|
|
232
|
+
(`release.yml`), environment (optional).
|
|
233
|
+
|
|
234
|
+
### PyPI
|
|
131
235
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
236
|
+
1. Go to `https://pypi.org/manage/project/<name>/settings/publishing/` (or
|
|
237
|
+
**Publishing** on the project page).
|
|
238
|
+
2. Add a **GitHub** trusted publisher: owner, repo, workflow filename,
|
|
239
|
+
environment (optional).
|
|
240
|
+
3. Brand-new project? Use a [pending publisher](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/)
|
|
241
|
+
to skip the bootstrap token.
|
|
242
|
+
|
|
243
|
+
### npm
|
|
244
|
+
|
|
245
|
+
1. Publish at least one version of your package with a classic
|
|
246
|
+
`NODE_AUTH_TOKEN` so the package exists on the registry. (npm's trusted
|
|
247
|
+
publisher requires an existing package.)
|
|
248
|
+
2. Go to `https://www.npmjs.com/package/<name>/access` → **Require trusted
|
|
249
|
+
publisher**.
|
|
250
|
+
3. Fill in: repository, workflow filename, environment (optional).
|
|
251
|
+
4. Delete the bootstrap token.
|
|
252
|
+
|
|
253
|
+
## Recipes
|
|
254
|
+
|
|
255
|
+
### Bundled-CLI npm family
|
|
256
|
+
|
|
257
|
+
Ship a compiled CLI as an npm per-platform family — `npm install -g my-cli`
|
|
258
|
+
gives users a working binary on PATH. The `esbuild` / `biome` distribution
|
|
259
|
+
shape.
|
|
260
|
+
|
|
261
|
+
Config:
|
|
262
|
+
|
|
263
|
+
```toml
|
|
264
|
+
[[package]]
|
|
265
|
+
name = "my-cli"
|
|
266
|
+
kind = "npm"
|
|
267
|
+
npm = "my-cli"
|
|
268
|
+
build = "bundled-cli"
|
|
269
|
+
path = "packages/ts-cli"
|
|
270
|
+
globs = ["packages/ts-cli/**", "crates/my-cli/**"]
|
|
271
|
+
targets = [
|
|
272
|
+
"x86_64-unknown-linux-gnu",
|
|
273
|
+
"aarch64-unknown-linux-gnu",
|
|
274
|
+
"x86_64-apple-darwin",
|
|
275
|
+
"aarch64-apple-darwin",
|
|
276
|
+
"x86_64-pc-windows-msvc",
|
|
277
|
+
]
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
The engine publishes a per-platform sub-package per target
|
|
281
|
+
(`my-cli-<triple>`) plus a top-level whose `optionalDependencies` pin them
|
|
282
|
+
at the published version. npm's resolver installs exactly one sub-package
|
|
283
|
+
at consumer install time.
|
|
284
|
+
|
|
285
|
+
You author the launcher script that picks the right per-platform binary
|
|
286
|
+
once. `package.json`:
|
|
287
|
+
|
|
288
|
+
```json
|
|
289
|
+
{
|
|
290
|
+
"name": "my-cli",
|
|
291
|
+
"bin": { "my-cli": "bin/my-cli.js" }
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
`bin/my-cli.js`:
|
|
296
|
+
|
|
297
|
+
```js
|
|
298
|
+
#!/usr/bin/env node
|
|
299
|
+
const { spawnSync } = require('node:child_process');
|
|
300
|
+
const { platform, arch } = process;
|
|
301
|
+
|
|
302
|
+
const triples = {
|
|
303
|
+
'linux-x64': 'x86_64-unknown-linux-gnu',
|
|
304
|
+
'linux-arm64': 'aarch64-unknown-linux-gnu',
|
|
305
|
+
'darwin-x64': 'x86_64-apple-darwin',
|
|
306
|
+
'darwin-arm64': 'aarch64-apple-darwin',
|
|
307
|
+
'win32-x64': 'x86_64-pc-windows-msvc',
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const triple = triples[`${platform}-${arch}`];
|
|
311
|
+
if (!triple) {
|
|
312
|
+
console.error(`my-cli: unsupported platform ${platform}-${arch}`);
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const pkg = `my-cli-${triple}`;
|
|
317
|
+
const binary = require.resolve(`${pkg}/bin/my-cli${platform === 'win32' ? '.exe' : ''}`);
|
|
318
|
+
const result = spawnSync(binary, process.argv.slice(2), { stdio: 'inherit' });
|
|
319
|
+
process.exit(result.status ?? 1);
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Each per-platform sub-package needs its own npm trusted-publisher
|
|
323
|
+
registration (a policy on `my-cli` does not cover
|
|
324
|
+
`my-cli-x86_64-unknown-linux-gnu`).
|
|
325
|
+
|
|
326
|
+
### Rust CLI inside a PyPI wheel
|
|
327
|
+
|
|
328
|
+
`pip install my-lib` on any platform gets a working CLI on `PATH` without
|
|
329
|
+
the user installing a Rust toolchain. The `ruff` / `uv` / `pydantic-core`
|
|
330
|
+
pattern.
|
|
331
|
+
|
|
332
|
+
Config:
|
|
333
|
+
|
|
334
|
+
```toml
|
|
335
|
+
[[package]]
|
|
336
|
+
name = "my-py"
|
|
337
|
+
kind = "pypi"
|
|
338
|
+
build = "maturin"
|
|
339
|
+
path = "packages/python"
|
|
340
|
+
globs = ["packages/python/**", "crates/my-rust/**"]
|
|
341
|
+
targets = [
|
|
342
|
+
"x86_64-unknown-linux-gnu",
|
|
343
|
+
"aarch64-unknown-linux-gnu",
|
|
344
|
+
"x86_64-apple-darwin",
|
|
345
|
+
"aarch64-apple-darwin",
|
|
346
|
+
"x86_64-pc-windows-msvc",
|
|
347
|
+
]
|
|
348
|
+
depends_on = ["my-rust"]
|
|
349
|
+
|
|
350
|
+
[package.bundle_cli]
|
|
351
|
+
bin = "my-cli"
|
|
352
|
+
stage_to = "src/my_py/_binary"
|
|
353
|
+
crate_path = "crates/my-rust"
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
The reusable workflow cross-compiles the binary per target and stages it
|
|
357
|
+
into the package source tree before maturin runs. Your `pyproject.toml`
|
|
358
|
+
ties the staged binary into a `console_scripts` entry:
|
|
359
|
+
|
|
360
|
+
```toml
|
|
361
|
+
[project.scripts]
|
|
362
|
+
my-cli = "my_py._binary:entrypoint"
|
|
363
|
+
|
|
364
|
+
[tool.maturin]
|
|
365
|
+
include = ["src/my_py/_binary/**"]
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Launcher in `packages/python/src/my_py/_binary/__init__.py`:
|
|
369
|
+
|
|
370
|
+
```python
|
|
371
|
+
import os, sys
|
|
372
|
+
from pathlib import Path
|
|
373
|
+
|
|
374
|
+
def entrypoint():
|
|
375
|
+
here = Path(__file__).parent
|
|
376
|
+
binary = here / ("my-cli.exe" if os.name == "nt" else "my-cli")
|
|
377
|
+
if not binary.exists():
|
|
378
|
+
sys.stderr.write(f"my-cli binary not found at {binary}\n")
|
|
379
|
+
sys.exit(1)
|
|
380
|
+
os.execv(binary, [str(binary), *sys.argv[1:]])
|
|
381
|
+
```
|
|
135
382
|
|
|
136
|
-
|
|
137
|
-
still read as env vars if OIDC isn't available. `putitoutthere doctor`
|
|
138
|
-
reports which path is active.
|
|
383
|
+
## Dynamic-version PyPI gotcha
|
|
139
384
|
|
|
140
|
-
|
|
385
|
+
If your `pyproject.toml` uses `[project].dynamic = ["version"]` with
|
|
386
|
+
`hatch-vcs` or `setuptools-scm`, the build backend derives the version from
|
|
387
|
+
the latest git tag at build time — which is still the **previous** release
|
|
388
|
+
when the build runs. Without a handoff, the sdist ships as
|
|
389
|
+
`<pkg>-X.Y.Z.devN.tar.gz` instead of `<pkg>-X.Y.Z.tar.gz`.
|
|
141
390
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
*cascading releases*, not runtime version pinning.
|
|
147
|
-
- Not a build system. Handlers shell out to `cargo`, `uv`/`maturin`/`hatch`,
|
|
148
|
-
`npm` — standard toolchains only.
|
|
391
|
+
The reusable workflow sets `SETUPTOOLS_SCM_PRETEND_VERSION` to the planned
|
|
392
|
+
version on the build step, which both `setuptools-scm` and `hatch-vcs`
|
|
393
|
+
honor. Per-package variants like `SETUPTOOLS_SCM_PRETEND_VERSION_FOR_<PKG>`
|
|
394
|
+
are silently ignored by `hatch-vcs`; only the global form works.
|
|
149
395
|
|
|
150
|
-
##
|
|
396
|
+
## Project layout
|
|
151
397
|
|
|
152
|
-
- [
|
|
153
|
-
- [
|
|
154
|
-
- [
|
|
155
|
-
- [
|
|
398
|
+
- [`CHANGELOG.md`](./CHANGELOG.md) — per-release changes.
|
|
399
|
+
- [`MIGRATIONS.md`](./MIGRATIONS.md) — per-version upgrade guide.
|
|
400
|
+
- [`notes/design-commitments.md`](./notes/design-commitments.md) — non-goals.
|
|
401
|
+
- [`notes/internals/`](./notes/internals/) — internal contracts (artifact
|
|
402
|
+
layout, runner setup) that the reusable workflow honors so consumers don't
|
|
403
|
+
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@v0, 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/cascade.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* the last release, returns the packages that will release.
|
|
4
4
|
*
|
|
5
5
|
* Two passes (plan.md §11.1):
|
|
6
|
-
* 1. Direct match: every package whose `
|
|
6
|
+
* 1. Direct match: every package whose `globs` intersect the
|
|
7
7
|
* files changed since *its own* last tag is cascaded.
|
|
8
8
|
* 2. Transitive match: repeat until stable — any package whose
|
|
9
9
|
* `depends_on` list contains an already-cascaded package gets
|
package/dist/cascade.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* the last release, returns the packages that will release.
|
|
4
4
|
*
|
|
5
5
|
* Two passes (plan.md §11.1):
|
|
6
|
-
* 1. Direct match: every package whose `
|
|
6
|
+
* 1. Direct match: every package whose `globs` intersect the
|
|
7
7
|
* files changed since *its own* last tag is cascaded.
|
|
8
8
|
* 2. Transitive match: repeat until stable — any package whose
|
|
9
9
|
* `depends_on` list contains an already-cascaded package gets
|
|
@@ -35,7 +35,7 @@ export function computeCascade(packages, changedFilesByPackage) {
|
|
|
35
35
|
if (!files)
|
|
36
36
|
continue;
|
|
37
37
|
for (const f of files) {
|
|
38
|
-
if (matchesAny(p.
|
|
38
|
+
if (matchesAny(p.globs, f)) {
|
|
39
39
|
cascaded.add(p.name);
|
|
40
40
|
break;
|
|
41
41
|
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `putitoutthere` CLI entry.
|
|
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`.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* Commands:
|
|
8
|
+
* plan — compute and emit the release plan
|
|
9
|
+
* publish — execute the plan against the registries
|
|
10
|
+
* version — print CLI version
|
|
8
11
|
*
|
|
9
12
|
* Global flags:
|
|
10
13
|
* --cwd <path> working directory (default: process.cwd())
|
|
11
14
|
* --config <path> path to putitoutthere.toml
|
|
12
15
|
* --dry-run for publish; no side effects
|
|
13
|
-
* --json
|
|
16
|
+
* --json machine-readable output
|
|
14
17
|
*/
|
|
15
18
|
export declare function run(argv: readonly string[]): Promise<number>;
|
|
16
19
|
//# 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;;;;;;;;;;;;;;;;GAgBG;AA2DH,wBAAsB,GAAG,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA+FlE"}
|