putitoutthere 0.2.12 → 0.2.14

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 CHANGED
@@ -32,6 +32,8 @@ are prefixed `**BREAKING**` and link to the matching section in
32
32
 
33
33
  ### Fixed
34
34
 
35
+ - **Bundled-CLI / napi npm consumers' `npm run build` step now sees `TARGET` and `BUILD` env vars on every matrix row.** The reusable workflow's `_matrix.yml` and `release.yml` previously ran the npm build step with no env block, so consumers' build scripts that read `process.env.TARGET` to know which triple to cross-compile saw `undefined` and either crashed or silently no-oped. Every per-platform matrix row then uploaded an empty `build/<triple>/` directory and `actions/upload-artifact@v7` flagged `No files were found with the provided path: ...`. The internal `e2e-fixture-job.yml` already passed `TARGET` / `BUILD` correctly — meaning the fixture suite passed but a real consumer's first publish still failed; an integration-tier divergence rather than a behavior bug per se. Both the build matrix step and the publish-job rebuild step in `release.yml` now set the env block. `_matrix.yml` exposes `TARGET=${{ matrix.target }}` / `BUILD=${{ matrix.build }}` per row; `release.yml`'s rebuild loop sets `TARGET=main BUILD=` per iteration (the publish-time rebuild only fires for the main package's row, since per-platform sub-packages stage from `artifacts/` via the engine's npm-platform handler). The README's [Bundled-CLI npm family](./README.md#bundled-cli-npm-family) recipe gained the consumer-side build-script contract that was previously missing — TARGET/BUILD vocabulary and a minimal `scripts/build.cjs` covering the simple single-workspace case. Hit in the wild on `thekevinscott/darkfactory`'s first release; tracked at #287. See [MIGRATIONS.md](./MIGRATIONS.md#npm-build-step-target-build-env-vars).
36
+
35
37
  - **pypi/maturin `[package.bundle_cli]` now actually ships the bundled binary inside published wheels.** The recipe was advertised as shipped in v0.2.0 (#217) — config parsing accepted `[package.bundle_cli]`, the planner attached it to per-target wheel rows, and `MIGRATIONS.md` named the two scaffolded build steps consumers should expect. None of those steps existed in `.github/workflows/_matrix.yml`. Consumers who declared the block (us, in `thekevinscott/dirsql`) shipped wheels missing the binary; `pip install <pkg> && <pkg> ...` failed at runtime with `FileNotFoundError`. The reusable workflow's build job now, for every per-target wheel row that carries `matrix.bundle_cli`: (1) `rustup target add ${{ matrix.target }}`, (2) `cargo build --release --target ${{ matrix.target }} --bin ${{ matrix.bundle_cli.bin }}` against `crate_path`, (3) copies the resulting binary into `${{ matrix.path }}/${{ matrix.bundle_cli.stage_to }}/` so maturin's `[tool.maturin].include` glob picks it up as wheel data, and (4) runs a permanent post-build wheel-content guard that opens the produced `.whl` and refuses to upload-artifact if `<stage_to>/<bin>` is missing. The guard is independent of staging — it catches any future regression where the cross-compile silently routes the binary to the wrong path. Consumers do not need to change their existing `[package.bundle_cli]` config or their `[tool.maturin].include` glob; the recipe just starts working. The cross-compile assumes the binary is buildable with a vanilla `cargo build --release --bin <bin>` (no `--features`, no env, no special flags); crates that gate the CLI behind a Cargo feature are not yet supported. The `.exe` suffix on Windows is handled. See [README → Recipes → Rust CLI inside a PyPI wheel](./README.md#rust-cli-inside-a-pypi-wheel) and [MIGRATIONS.md](./MIGRATIONS.md#bundle_cli-now-actually-stages-the-binary). #282.
36
38
 
37
39
  - **`putitoutthere.toml` validation now names common typos in the failure message.** A consumer integration shipped a config with `version` at the file root, `[[packages]]` (plural), `registry =` instead of `kind =`, and `files =` instead of `globs =`. The raw zod errors (`Invalid input: expected object, received undefined; ...; Unrecognized keys: "version", "packages"`) were opaque enough that the engine source had to be re-read to recover. A pre-pass in `parseConfig` now detects each of those four mistakes by name and emits a hint that pairs the wrong shape with the right one, e.g. `top-level table is \`[[packages]]\` (plural) but should be \`[[package]]\` (singular)`. README's [Drop in `putitoutthere.toml`](./README.md#2-drop-in-putitoutthere-toml) section grew a four-row "wrong → right" table covering the same four traps so the docs and the engine name them the same way; a new `[!IMPORTANT]` callout in [Drop in `.github/workflows/release.yml`](./README.md#1-drop-in-githubworkflowsreleaseyml) warns consumers off `push: branches: [main]` triggers on lane CI workflows (which fire duplicate runs against the merge commit and contend for runners with `release.yml`); `1b.` was promoted from "Optional" to "Recommended" since `build.yml` is the cheapest place to catch a malformed config before merge. See [MIGRATIONS.md](./MIGRATIONS.md#friendly-config-error-hints).
package/MIGRATIONS.md CHANGED
@@ -21,6 +21,58 @@ Each section covers five things, in order:
21
21
 
22
22
  ## Unreleased
23
23
 
24
+ ### npm build step: TARGET / BUILD env vars
25
+
26
+ **Summary.** The reusable workflow's `_matrix.yml` and `release.yml`
27
+ now set `TARGET=${{ matrix.target }}` and `BUILD=${{ matrix.build }}`
28
+ on the `matrix.kind == 'npm'` build step (and `TARGET=main BUILD=`
29
+ on `release.yml`'s publish-job rebuild loop, which only ever runs
30
+ for the main package's row). Bundled-CLI / napi consumers' build
31
+ scripts read these env vars to know which triple to cross-compile
32
+ and which build mode is active; without them, every per-platform
33
+ matrix row produced an empty `build/<triple>/` directory and
34
+ `actions/upload-artifact@v7` reported
35
+ `No files were found with the provided path: ...`. The internal
36
+ `e2e-fixture-job.yml` already passed `TARGET` / `BUILD` for the
37
+ `js-bundled-cli` fixture, so the fixture suite was green while the
38
+ shape it advertised was broken for real consumers. Tracked at #287;
39
+ hit in the wild on `thekevinscott/darkfactory`'s first release.
40
+
41
+ The README's [Bundled-CLI npm family](./README.md#bundled-cli-npm-family)
42
+ recipe gained the consumer-side build-script contract that was
43
+ previously missing: a documented `scripts/build.cjs` template
44
+ that reads `TARGET`, runs `rustup target add` + `cargo build`
45
+ + stage-into-`build/<triple>/`, and no-ops on
46
+ `TARGET=main`. Without that section, consumers had to read the
47
+ `js-bundled-cli` test fixture or experiment to discover the
48
+ contract.
49
+
50
+ **Required changes.** None for consumers whose existing build
51
+ script either ignored `TARGET` or used a different env var name —
52
+ those scripts now get the `TARGET` / `BUILD` exports too but
53
+ nothing forces them to read. To start using the contract, mirror
54
+ the README's `scripts/build.cjs` shape and reference it from
55
+ `package.json`'s `scripts.build` field. Existing fixtures and
56
+ the engine's contract are unchanged.
57
+
58
+ **Deprecations removed.** None.
59
+
60
+ **Behavior changes without code changes.** The npm build step
61
+ now runs with `TARGET` and `BUILD` set in its environment.
62
+ Build scripts that previously saw `undefined` and either crashed
63
+ or silently no-oped will now see a defined string. Vanilla npm
64
+ consumers (no `build = "bundled-cli" | "napi"`) see
65
+ `TARGET=main BUILD=` (or `BUILD=undefined` on rows that don't
66
+ declare a build mode); their build scripts that don't read either
67
+ var are unaffected.
68
+
69
+ **Verification.** A bundled-cli consumer following the README
70
+ recipe sees their per-platform matrix rows produce non-empty
71
+ artifacts: each `<pkg>-npm-<triple>` upload contains
72
+ `build/<triple>/<bin-name>`. The release run's `Upload artifact`
73
+ step no longer logs `No files were found with the provided path`
74
+ for any npm row.
75
+
24
76
  ### `[package.bundle_cli]` now actually stages the binary
25
77
 
26
78
  **Summary.** Wheels published from a maturin pypi package that
package/README.md CHANGED
@@ -483,6 +483,65 @@ const result = spawnSync(binary, process.argv.slice(2), { stdio: 'inherit' });
483
483
  process.exit(result.status ?? 1);
484
484
  ```
485
485
 
486
+ You also author a `build` script in `package.json` that does the
487
+ cross-compile. The reusable workflow runs the build matrix once
488
+ per `(target × main)` row, sets `TARGET=<triple>` /
489
+ `BUILD=bundled-cli` on the build step, and expects the script to
490
+ stage the compiled binary under `build/<triple>/<bin-name>` (with
491
+ `.exe` suffix on Windows). For every per-target row the engine
492
+ then packages `build/<triple>/` as the platform sub-package's
493
+ artifact; the `main` row is a no-op for the build script (the
494
+ launcher above is committed source). A minimal `package.json`:
495
+
496
+ ```json
497
+ {
498
+ "name": "my-cli",
499
+ "scripts": {
500
+ "build": "node scripts/build.cjs"
501
+ },
502
+ "bin": { "my-cli": "bin/my-cli.js" }
503
+ }
504
+ ```
505
+
506
+ `scripts/build.cjs`:
507
+
508
+ ```js
509
+ const { execFileSync, spawnSync } = require('node:child_process');
510
+ const { mkdirSync, copyFileSync } = require('node:fs');
511
+ const { join } = require('node:path');
512
+ const pkg = require('../package.json');
513
+
514
+ const target = process.env.TARGET;
515
+ // `target === 'main'` is the top-level row: nothing to compile,
516
+ // the launcher is committed source.
517
+ if (!target || target === 'main' || target === 'noarch') process.exit(0);
518
+
519
+ const binName = Object.keys(pkg.bin || {})[0] || pkg.name;
520
+ const ext = target.includes('windows') ? '.exe' : '';
521
+
522
+ execFileSync('rustup', ['target', 'add', target], { stdio: 'inherit' });
523
+ execFileSync(
524
+ 'cargo',
525
+ ['build', '--release', '--target', target, '--bin', binName],
526
+ { cwd: '../../crates/my-cli', stdio: 'inherit' },
527
+ );
528
+
529
+ const dir = join('build', target);
530
+ mkdirSync(dir, { recursive: true });
531
+ copyFileSync(
532
+ join('../../crates/my-cli/target', target, 'release', binName + ext),
533
+ join(dir, binName + ext),
534
+ );
535
+ ```
536
+
537
+ The build is consumer-owned because `cargo build` flags
538
+ (`--features`, env vars, alternate manifests, Zig-cc cross
539
+ toolchains) vary too much across crates to bake into the
540
+ workflow. For the simple "single workspace member, default
541
+ features" case the script above is the entire integration; tweak
542
+ it as your crate demands. `TARGET` and `BUILD` are the only env
543
+ vars the workflow guarantees.
544
+
486
545
  Each per-platform sub-package needs its own npm trusted-publisher
487
546
  registration (a policy on `my-cli` does not cover
488
547
  `my-cli-x86_64-unknown-linux-gnu`).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "putitoutthere",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "Polyglot release orchestrator for crates.io, PyPI, and npm",
5
5
  "license": "MIT",
6
6
  "repository": {