putitoutthere 0.2.36 → 0.2.38
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 +2 -0
- package/MIGRATIONS.md +80 -0
- package/README.md +81 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,8 @@ are prefixed `**BREAKING**` and link to the matching section in
|
|
|
16
16
|
|
|
17
17
|
### Fixed
|
|
18
18
|
|
|
19
|
+
- Fixed: the reusable workflow's `Create GitHub Release(s) for new tag(s)` step no longer fails a fully successful publish when another tag in the consumer's repo moves mid-run. The step opened with a blanket, un-forced `git fetch --tags origin`, which made it depend on every tag in the repo — including moving major tags it never uses — so a consumer's promotion automation force-moving its floating `v0` between checkout and this step failed the job with `! [rejected] v0 -> v0 (would clobber existing tag)` after all registries were published and all per-package tags pushed (observed twice in two days on thekevinscott/testing-conventions; timelines in #436). A failed publish job is what consumer automation gates on, so the release was published but never promoted, and the job-level rerun then hard-failed on an empty plan. The fetch is dropped (checkout's `fetch-depth: 0` already fetched every remote tag, and the tags the step iterates were created locally by the engine in the same job), and each tag is now pushed ref-scoped and idempotently (`git push origin "refs/tags/$tag"`) before `gh release create` — which also completes the engine's deliberately warn-only tag push (#407) in the same run instead of hard-failing on it one step later (`tag ... exists locally but has not been pushed`, the 2026-07-08 variant). The step's inputs are now only refs it owns, so concurrent movement of any other tag cannot fail it. See [MIGRATIONS.md](./MIGRATIONS.md#github-release-creation-touches-only-the-releases-own-tags). #436. (verified by: unit/ubuntu-latest)
|
|
20
|
+
- Fixed: `kind = "npm"` `build = "napi"` releases now bake the planned release version into the compiled `.node`. `_matrix.yml` (and its e2e mirror `e2e-fixture-job.yml`) run a `write-crate-version` step on each per-triple napi row before `npm run build`, so `napi build` compiles the addon with the right `CARGO_PKG_VERSION` — matching the maturin `write-version` (#276) and bundled-cli `write-crate-version` (#366) pre-build bumps. Previously napi had no pre-build bump: the synthesized per-platform `package.json` carried `matrix.version`, but the `.node` inside it embedded whatever literal sat in the crate's `Cargo.toml`, so a library re-exposing the Rust core's `version()` through napi reported a version diverging from the published npm package. The bump targets `matrix.path` and runs only when a `Cargo.toml` is colocated there (the napi-rs single-crate default — the shape template-lib and the `js-napi` fixture use), resolving `version.workspace = true` to the workspace root via #428. A multi-mode package (`build = ["napi", "bundled-cli"]`) or one whose napi crate lives elsewhere has no `Cargo.toml` at the package path and is skipped with a `::notice::` — there is no per-napi `crate_path` config to point at a non-colocated crate yet, so that `.node` still embeds its on-disk version. The noarch `main` row is excluded (it compiles no `.node`). See [MIGRATIONS.md](./MIGRATIONS.md#napi-node-embeds-the-release-version). #429. (verified by: unit/ubuntu-latest)
|
|
19
21
|
- Fixed: `putitoutthere`'s pre-build version rewrite now follows Cargo **workspace version inheritance** — a crate that sources its version from `[workspace.package].version` via `version.workspace = true` (the idiomatic polyglot layout: one Rust core wrapped by a PyO3 wheel and a napi addon) is now bumped correctly instead of failing the release. The maturin `write-version` (#276) and npm/pypi bundled-cli `write-crate-version` (#366) steps both rewrote only a literal `[package].version` and threw `Cargo.toml: no [package].version field found` on an inheriting member, because the inherited version lives in a different file — the workspace root — that neither step read. So a release for that layout was blocked, not merely mis-versioned. The rewrite path now resolves the manifest first: a literal `[package].version` is bumped in place byte-for-byte as before, while an inheriting member walks up to the nearest ancestor `[workspace]` `Cargo.toml` and rewrites its `[workspace.package].version`. Unparseable or version-less manifests fall through to the prior literal-only path (`replaceCargoVersion`'s existing error), so single-crate layouts are byte-for-byte unchanged. New `src/find-workspace-root.ts`, `src/replace-workspace-package-version.ts`, and `src/write-resolved-cargo-version.ts`; `write-version.ts` / `write-crate-version.ts` now delegate to the resolver. See [MIGRATIONS.md](./MIGRATIONS.md#version-bump-follows-cargo-workspace-inheritance). #428. (verified by: unit/ubuntu-latest)
|
|
20
22
|
- Fixed: `putitoutthere publish` now writes a package's git tag when that version is already live on the registry but untagged, instead of skipping it silently. The publish loop's already-published branch (`isPublished` → true) returned *before* the tag-creation step, so a version that reached the registry on a half-failed run (the run died after publishing but before tagging — e.g. a cancelled downstream job) was left published-but-untagged and never self-healed: piot derives "last released version" from git tags, so on every later run the package looked unreleased, fell back to `first_version` (already live), skipped again, and stayed stuck forever — while its dependents kept bumping and publishing, opening unflagged version skew. The skip branch now calls a shared, idempotent `ensureTag` (new `src/ensure-tag.ts`; a no-op when the tag already exists, and reused by the normal post-publish tag step), so a stuck package self-heals on its next release run. See [MIGRATIONS.md](./MIGRATIONS.md#publish-path-auto-heal-missing-tags). #407. (verified by: integration, unit/ubuntu-latest)
|
|
21
23
|
- Fixed: a `kind = "pypi"` `build = "maturin"` package whose wheel is **Python-version-independent** — `[tool.maturin].bindings = "bin"` (a Rust-binary wheel tagged `py3-none`) or a pyo3 `abi3` / `abi3-pyXY` extension (a stable-ABI wheel tagged `cp3x-abi3`) — no longer fans its wheel build across every CPython version `requires-python` / `python_versions` resolves to. Such a wheel is byte-identical regardless of the interpreter that built it, so the fan produced N duplicate wheels: each fanned row uploaded under its own `-py<ver>` artifact, and the documented consumer `pypi-publish` recipe's `actions/download-artifact … merge-multiple: true` then extracted N copies of the same wheel filename onto one path concurrently — a torn write that failed `twine check` with `zipfile.BadZipFile` (observed on a `bindings = "bin"`, `requires-python = ">=3.9"` package: six identical wheels per platform, the x86_64-linux copy losing its extraction race while macOS/aarch64 won theirs). The planner (`src/plan.ts`) now collapses the fan to a single wheel row per target for these packages — built once on the newest resolved interpreter, keeping the historical unsuffixed `<pkg>-wheel-<triple>` artifact name — and the new `src/wheel-abi.ts` detects version independence from the package's real `pyproject.toml` (`[tool.maturin].bindings`, `[tool.maturin].features`) and `Cargo.toml` (an `abi3` / `abi3-pyXY` feature on the `pyo3` / `pyo3-ffi` dependency). Ordinary per-version extension modules (no abi3, no `bindings = "bin"`) still fan and keep their `-py<ver>` suffixes; the sdist row is unchanged. Also saves the redundant N−1 wheel builds. Detection is conservative — an unrecognized abi3 setup (workspace-inherited `pyo3`, a target-specific dependency table) falls back to fanning, never worse than before. See [README → `kind = "pypi"`](./README.md#kind--pypi) and [MIGRATIONS.md](./MIGRATIONS.md#pypi-version-independent-wheels-build-once). #401. (verified by: integration, unit/ubuntu-latest)
|
package/MIGRATIONS.md
CHANGED
|
@@ -21,6 +21,86 @@ Each section covers five things, in order:
|
|
|
21
21
|
|
|
22
22
|
## Unreleased
|
|
23
23
|
|
|
24
|
+
### GitHub Release creation touches only the release's own tags
|
|
25
|
+
|
|
26
|
+
**Summary.** The reusable workflow's `Create GitHub Release(s) for new
|
|
27
|
+
tag(s)` step no longer runs a blanket `git fetch --tags origin`, and now
|
|
28
|
+
pushes each of the release's tags ref-scoped (`git push origin
|
|
29
|
+
"refs/tags/$tag"`, idempotent) before `gh release create`. Previously the
|
|
30
|
+
un-forced fetch coupled the step to the state of **every** tag in the
|
|
31
|
+
consumer's repo: a floating major tag (e.g. `v0`) force-moved mid-run by
|
|
32
|
+
the consumer's own promotion automation failed the publish job with
|
|
33
|
+
`! [rejected] v0 -> v0 (would clobber existing tag)` *after* every
|
|
34
|
+
registry publish and per-package tag push had succeeded — and because
|
|
35
|
+
consumer automation gates promotion on this job's conclusion, the release
|
|
36
|
+
was published but never promoted, with no safe job-level rerun (the
|
|
37
|
+
replayed publish re-plans against the already-pushed tags and hard-fails
|
|
38
|
+
on an empty plan). Observed twice in two days on
|
|
39
|
+
thekevinscott/testing-conventions (#436). The ref-scoped push also
|
|
40
|
+
completes the engine's deliberately warn-only tag push (#407) within the
|
|
41
|
+
same run, closing the second observed variant (`tag ... exists locally
|
|
42
|
+
but has not been pushed`).
|
|
43
|
+
|
|
44
|
+
**Required changes.** None. The step is workflow-internal; no config,
|
|
45
|
+
input, or consumer-side YAML changes.
|
|
46
|
+
|
|
47
|
+
**Deprecations removed.** None.
|
|
48
|
+
|
|
49
|
+
**Behavior changes without code changes.**
|
|
50
|
+
|
|
51
|
+
- A publish run no longer fails when any tag it does not own (a floating
|
|
52
|
+
major tag, another run's tags) moves between checkout and Release
|
|
53
|
+
creation.
|
|
54
|
+
- A per-package tag that the engine created but could not push (its push
|
|
55
|
+
is warn-only, #407) is now pushed by this step in the same run, so the
|
|
56
|
+
GitHub Release is cut instead of the job failing one step later.
|
|
57
|
+
- A genuine conflict — the same version tag already on the remote at a
|
|
58
|
+
*different* commit — still fails loudly at the ref-scoped push. That
|
|
59
|
+
means two runs released the same version, which the
|
|
60
|
+
`putitoutthere-release-*` concurrency group exists to prevent.
|
|
61
|
+
|
|
62
|
+
**Verification.** Land two release-triggering pushes to `main` a few
|
|
63
|
+
minutes apart in a repo whose promotion automation force-moves a floating
|
|
64
|
+
major tag on release success (the thekevinscott/testing-conventions
|
|
65
|
+
shape): the second run's publish job completes green and its GitHub
|
|
66
|
+
Releases exist, where it previously failed at `Create GitHub Release(s)
|
|
67
|
+
for new tag(s)`. On any release run's log, the step shows a per-tag
|
|
68
|
+
`git push origin "refs/tags/<tag>"` and no `git fetch`.
|
|
69
|
+
|
|
70
|
+
### napi `.node` embeds the release version
|
|
71
|
+
|
|
72
|
+
**Summary.** `kind = "npm"` `build = "napi"` releases now rewrite the napi
|
|
73
|
+
crate's version to the planned release version before `napi build` compiles
|
|
74
|
+
the `.node`, mirroring the maturin (#276) and bundled-cli (#366) pre-build
|
|
75
|
+
bumps. Previously the compiled `.node` embedded whatever `[package].version`
|
|
76
|
+
literal sat on disk, so a library re-exposing the Rust core's `version()`
|
|
77
|
+
through napi reported a version diverging from the published npm package
|
|
78
|
+
(whose `package.json` version was already correct).
|
|
79
|
+
|
|
80
|
+
**Required changes.** None for the common case. The bump is a
|
|
81
|
+
workflow-internal step; a consumer whose napi crate's `Cargo.toml` is
|
|
82
|
+
colocated with `package.json` (the napi-rs single-crate default, including
|
|
83
|
+
the `version.workspace = true` workspace shape via #428) needs no config
|
|
84
|
+
change. A **multi-mode** package (`build = ["napi", "bundled-cli"]`) or one
|
|
85
|
+
whose napi crate lives outside the package directory has no `Cargo.toml` at
|
|
86
|
+
the package path — piot skips the bump there with a `::notice::` (no
|
|
87
|
+
per-napi `crate_path` config exists yet), so such a `.node` still embeds
|
|
88
|
+
its on-disk `CARGO_PKG_VERSION`. Until a `crate_path` field lands, bump that
|
|
89
|
+
crate's version yourself or colocate its manifest with `package.json`.
|
|
90
|
+
|
|
91
|
+
**Deprecations removed.** None.
|
|
92
|
+
|
|
93
|
+
**Behavior changes without code changes.** For a napi package, the
|
|
94
|
+
per-platform `.node` now reports the released version from any API sourced
|
|
95
|
+
on `CARGO_PKG_VERSION` (e.g. a Rust `version()` re-exported through napi).
|
|
96
|
+
The synthesized per-platform `package.json` version is unchanged (it was
|
|
97
|
+
already `matrix.version`). Non-napi npm packages are unaffected.
|
|
98
|
+
|
|
99
|
+
**Verification.** Release a napi package and read its Rust-sourced
|
|
100
|
+
`version()` from the installed addon (or inspect the crate manifest the
|
|
101
|
+
build rewrote): it matches the published npm version, not the pre-release
|
|
102
|
+
on-disk literal.
|
|
103
|
+
|
|
24
104
|
### Version bump follows Cargo workspace inheritance
|
|
25
105
|
|
|
26
106
|
**Summary.** piot's pre-build version rewrite — the maturin `write-version`
|
package/README.md
CHANGED
|
@@ -310,7 +310,7 @@ version = 1 # required; only 1 is valid today
|
|
|
310
310
|
| `npm` | string | Override `name` → npm name (for scoped packages). |
|
|
311
311
|
| `access` | enum | `public` \| `restricted`. Default `public`. |
|
|
312
312
|
| `tag` | string | dist-tag. Default `latest`. |
|
|
313
|
-
| `build` | string \| array | `"napi"` \| `"bundled-cli"` (single mode), or an array of entries (each: a bare mode string or `{ mode, name }` with a [name template](#multi-mode-npm-family)). Omitted = vanilla. See
|
|
313
|
+
| `build` | string \| array | `"napi"` \| `"bundled-cli"` (single mode), or an array of entries (each: a bare mode string or `{ mode, name }` with a [name template](#multi-mode-npm-family)). Omitted = vanilla. See Recipes → [napi](#napi-npm-family) / [Bundled-CLI](#bundled-cli-npm-family) npm family. |
|
|
314
314
|
| `targets` | (string \| object)[] | Required when `build` is set. |
|
|
315
315
|
| `[package.bundle_cli]` | sub-table | Declarative cross-compile for `build = "bundled-cli"` rows. Fields: `bin` (required), `crate_path` (default `"."`), `features` (default `[]`), `no_default_features` (default `false`). See [Recipes → Bundled-CLI npm family](#bundled-cli-npm-family). |
|
|
316
316
|
|
|
@@ -747,6 +747,86 @@ registration (a policy on `my-cli` does not cover
|
|
|
747
747
|
> No consumer-side change is required; you can keep the lockfile
|
|
748
748
|
> committed and the `optionalDependencies` declared.
|
|
749
749
|
|
|
750
|
+
### napi npm family
|
|
751
|
+
|
|
752
|
+
Ship a [napi-rs](https://napi.rs) Node addon (a `.node` native library) as
|
|
753
|
+
an npm per-platform family — consumers `import` the package and Node loads
|
|
754
|
+
the prebuilt binary for their platform. The `@node-rs/*` / `@swc/core`
|
|
755
|
+
distribution shape.
|
|
756
|
+
|
|
757
|
+
Config:
|
|
758
|
+
|
|
759
|
+
```toml
|
|
760
|
+
[[package]]
|
|
761
|
+
name = "my-addon"
|
|
762
|
+
kind = "npm"
|
|
763
|
+
path = "packages/node"
|
|
764
|
+
build = "napi"
|
|
765
|
+
globs = ["packages/node/**", "crates/core/**"]
|
|
766
|
+
targets = [
|
|
767
|
+
"x86_64-unknown-linux-gnu",
|
|
768
|
+
"aarch64-unknown-linux-gnu",
|
|
769
|
+
"x86_64-apple-darwin",
|
|
770
|
+
"aarch64-apple-darwin",
|
|
771
|
+
"x86_64-pc-windows-msvc",
|
|
772
|
+
]
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
The engine publishes a per-platform sub-package per target
|
|
776
|
+
(`my-addon-<triple>`) plus a top-level package whose `optionalDependencies`
|
|
777
|
+
pin them at the published version; napi-rs's loader resolves the matching
|
|
778
|
+
one at runtime. The reusable workflow fans the build across your `targets`
|
|
779
|
+
— one native runner per triple — so you never wire per-target steps
|
|
780
|
+
yourself.
|
|
781
|
+
|
|
782
|
+
**You own the build script.** Unlike `bundled-cli` — where the engine runs
|
|
783
|
+
the cross-compile for you — the napi toolchain stays consumer-owned. For
|
|
784
|
+
each per-target row the workflow runs your `package.json` `build` script
|
|
785
|
+
with `TARGET` set to that triple (and `BUILD=napi`); your script runs
|
|
786
|
+
`napi build` for it and stages the resulting `.node` under
|
|
787
|
+
`build/<triple>/`, the directory the engine packages per-platform
|
|
788
|
+
artifacts from:
|
|
789
|
+
|
|
790
|
+
```js
|
|
791
|
+
// scripts/build.cjs — invoked by your package.json "build" script
|
|
792
|
+
const target = process.env.TARGET;
|
|
793
|
+
// The noarch main row runs with TARGET=main (or unset) — nothing to build.
|
|
794
|
+
if (!target || target === 'main' || target === 'noarch') process.exit(0);
|
|
795
|
+
|
|
796
|
+
// napi-rs emits `<name>.<triple>.node`; stage it under build/<triple>/,
|
|
797
|
+
// e.g. napi build --release --target ${target} --output-dir build/${target}
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
The `main` (noarch) row carries no per-target binary — its build run is a
|
|
801
|
+
no-op for the `.node` and compiles only your TypeScript / JS.
|
|
802
|
+
|
|
803
|
+
> [!NOTE]
|
|
804
|
+
> **Cross-compilation is yours to arrange.** Each target builds on a
|
|
805
|
+
> native runner where one exists (`x86_64-linux` on `ubuntu-latest`,
|
|
806
|
+
> `aarch64-linux` on `ubuntu-24.04-arm`, darwin on `macos-latest`,
|
|
807
|
+
> windows on `windows-2022`), so the common triples need no cross
|
|
808
|
+
> toolchain — `napi build --target <native triple>` just works. For a
|
|
809
|
+
> target that isn't native to its runner (a `*-musl` triple, or an
|
|
810
|
+
> x86_64 macOS build on the arm64 runner), your build script owns
|
|
811
|
+
> `rustup target add <triple>` and any linker / C-toolchain setup. This
|
|
812
|
+
> is the one place napi differs from `bundled-cli`, where the engine
|
|
813
|
+
> performs the gnu→musl mapping and installs the musl toolchain for you.
|
|
814
|
+
|
|
815
|
+
Each per-platform sub-package needs its own npm trusted-publisher
|
|
816
|
+
registration (a policy on `my-addon` does not cover
|
|
817
|
+
`my-addon-x86_64-unknown-linux-gnu`) — same as the bundled-cli family
|
|
818
|
+
above.
|
|
819
|
+
|
|
820
|
+
The **first-publish lockfile chicken-and-egg** note under
|
|
821
|
+
[Bundled-CLI npm family](#bundled-cli-npm-family) applies identically to
|
|
822
|
+
napi families: the reusable workflow's strict installs self-heal, so you
|
|
823
|
+
can keep the lockfile committed and the `optionalDependencies` declared.
|
|
824
|
+
|
|
825
|
+
> [!NOTE]
|
|
826
|
+
> **Both modes at once.** A package that is *both* a `.node` addon and a
|
|
827
|
+
> CLI declares `build` as an array — see
|
|
828
|
+
> [Multi-mode npm family](#multi-mode-npm-family) below.
|
|
829
|
+
|
|
750
830
|
### Multi-mode npm family
|
|
751
831
|
|
|
752
832
|
For a package that is both a napi-rs Node addon (a `.node` library) **and**
|