putitoutthere 0.2.17 → 0.2.18

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
@@ -13,6 +13,7 @@ are prefixed `**BREAKING**` and link to the matching section in
13
13
  ### Added
14
14
 
15
15
  - **`[package.bundle_cli]` accepts `features` and `no_default_features` for crates that gate the CLI behind a Cargo feature.** The lib-with-optional-CLI pattern — `[[bin]] required-features = ["cli"]` so `cargo add <name>` doesn't drag the CLI's deps onto library consumers — is the standard shape for crates that fit `bundle_cli`'s use case (ruff, uv, pydantic-core, biome, swc, dirsql). v0.2.0's wiring shipped `cargo build --release --target $TARGET --bin $BIN` with no `--features` path, which made the recipe inert for exactly the consumers it targets. The reusable workflow's cargo build step now appends `--features <comma-list>` when the schema's new `features: list[string]` is non-empty and `--no-default-features` when the new `no_default_features: bool` is true. Defaults are `[]` and `false`, so existing `[package.bundle_cli]` blocks keep building byte-identically. Empty-string entries inside `features` are rejected at config load. The "crates that gate the CLI behind a Cargo feature are not currently supported" caveat in the v0.2.0 MIGRATIONS note has been corrected. See [README → Recipes → Rust CLI inside a PyPI wheel](./README.md#rust-cli-inside-a-pypi-wheel) and [MIGRATIONS.md](./MIGRATIONS.md#bundle_cli-features-and-no_default_features). #300.
16
+ - **Reusable workflow accepts a caller-provided `NPM_TOKEN` via `secrets:`.** OIDC trusted publishers remain the default and recommended path, but Trusted Publishing on npm binds to an *already-published* package — so the first publish of a brand-new npm package has no OIDC path available, and consumers were forced into a manual 6+ package `0.0.0-bootstrap` stub bootstrap (documented nowhere, only discoverable by reading commit history of dirsql or by hitting the failure). The `workflow_call` surface now declares an optional `NPM_TOKEN` secret; when set AND the planned matrix contains an npm row, the secret is exported to `$GITHUB_ENV` as `NODE_AUTH_TOKEN` and the npm CLI prefers the long-lived token over the OIDC path. Callers without a token keep the OIDC path unchanged. For bundled-cli / napi families the same secret authenticates publishes of all per-platform sub-packages on first publish; once those exist, each one needs its own Trusted Publisher registration (the bypass is a one-time bootstrap, not a permanent path). Mirrors the #283 crates fallback in shape. Hit in the wild on the maintainer's own dirsql project (first version of `@dirsql/cli-linux-x64-gnu` on npm is `0.0.0-bootstrap`, 2026-04-30; real `0.2.8` lands the next day) and on `darkfactory`'s first publish. See [README → Trusted publishers → npm](./README.md#npm) and [MIGRATIONS.md](./MIGRATIONS.md#npm-token-fallback). #302.
16
17
  - **Reusable workflow accepts a caller-provided `CARGO_REGISTRY_TOKEN` via `secrets:`.** OIDC trusted publishers remain the default and recommended path, but Trusted Publishing on crates.io binds to an *already-published* crate — so the first publish of a brand-new crate has no OIDC path available, and consumers were forced to either fork the workflow or run `cargo publish` outside it. The `workflow_call` surface now declares an optional `CARGO_REGISTRY_TOKEN` secret; when set, the `rust-lang/crates-io-auth-action` OIDC exchange is skipped and the caller-provided token is exported to `$GITHUB_ENV` for the engine's crates handler to read. Callers without a token keep the OIDC path unchanged. The "Auth: OIDC trusted publishers ... Long-lived registry tokens are explicitly NOT supported" framing in `release.yml`'s header has been softened to match. See [README → Trusted publishers → crates.io](./README.md#cratesio) and [MIGRATIONS.md](./MIGRATIONS.md#crates-token-fallback). #283.
17
18
  - **Preflight check: every cascaded `kind = "crates"` package's `Cargo.toml` must declare `[package].description` and either `[package].license` or `[package].license-file`.** crates.io rejects publish with `400 Bad Request: missing or empty metadata fields: ...` after `cargo publish`'s verification build has compiled the crate and every transitive dep — wasting the entire publish job on a precondition checkable in milliseconds. The new `requireCratesMetadata` runs alongside `requireAuth` / `requireProvenanceMetadata` in `src/publish.ts`, before any side effects, and reports every failing package + every missing field in one error rather than failing on the first. Surfaces a new stable error code, `PIOT_CRATES_MISSING_METADATA`. Whitespace-only field values are treated as empty. Hit in the wild on `thekevinscott/darkfactory`'s first crate publish; same shape as #280 (npm `repository`). See [MIGRATIONS.md](./MIGRATIONS.md#crates-cargo-toml-must-declare-description-and-license). #290.
18
19
  - **Preflight check: every cascaded `kind = "npm"` package must declare a non-empty `repository` field in `package.json`.** `putitoutthere` invokes `npm publish --provenance` on the OIDC trusted-publisher path, and the npm CLI hard-requires this field so the registry can verify the artifact was built from the repo the trusted publisher declares. A missing or empty field previously surfaced as a confusing tail-end npm error after the runner had spun up, OIDC had been negotiated, and the artifact had been built — wasting a full release run on a precondition checkable in milliseconds. The new `requireProvenanceMetadata` runs alongside `requireAuth` in `src/publish.ts`, before any side effects, and reports every failing package in one error rather than failing on the first. Surfaces a new stable error code, `PIOT_NPM_MISSING_REPOSITORY`. Both the canonical object form (`{ type, url, directory? }`) and the legacy single-string form are accepted; only an empty `url` (or no `url` at all) fails. The npm handler's inline backstop is also tightened to match the same predicate (previously `!pkg.repository` slipped `{}`, `{ type: 'git' }`, and whitespace strings through). Documented in [README → `kind = "npm"`](./README.md#kind--npm). See [MIGRATIONS.md](./MIGRATIONS.md#npm-package-json-must-declare-repository). #280.
package/MIGRATIONS.md CHANGED
@@ -359,6 +359,85 @@ or build-check run.
359
359
 
360
360
  ---
361
361
 
362
+ ### npm token fallback
363
+
364
+ **Summary.** The reusable workflow now accepts an optional
365
+ `NPM_TOKEN` via `secrets:`. Trusted Publishing on npm binds to
366
+ an *already-published* package, so the very first publish of a
367
+ brand-new npm package has no OIDC path available; without this
368
+ fallback every first-time bundled-cli / napi consumer hit a 6+
369
+ package manual `0.0.0-bootstrap` stub bootstrap, documented
370
+ nowhere, only discoverable by reading commit history of dirsql
371
+ or by hitting the failure. OIDC trusted publishers remain the
372
+ default and recommended path — when the secret is unset,
373
+ behavior is byte-for-byte unchanged. When the secret is set
374
+ AND the planned matrix contains an npm row, the secret is
375
+ exported to `$GITHUB_ENV` as `NODE_AUTH_TOKEN`; the npm CLI
376
+ then prefers the long-lived token over the OIDC path. Mirror
377
+ of #283 (crates) in shape, byte-for-byte. Hit in the wild on
378
+ the maintainer's own dirsql project (first version of
379
+ `@dirsql/cli-linux-x64-gnu` on npm is `0.0.0-bootstrap`,
380
+ 2026-04-30; real `0.2.8` lands the next day) and on
381
+ `darkfactory`'s first publish. #302.
382
+
383
+ **Required changes.** None for consumers already on the OIDC
384
+ path. To bootstrap a brand-new npm package or to use the
385
+ workflow on an account where Trusted Publishing isn't
386
+ available, wire the secret in the caller's `release.yml`:
387
+
388
+ | Before | After |
389
+ | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
390
+ | `uses: thekevinscott/putitoutthere/.github/workflows/release.yml@v0` | `uses: thekevinscott/putitoutthere/.github/workflows/release.yml@v0`<br>`secrets:`<br>` NPM_TOKEN: ${{ secrets.NPM_TOKEN }}` |
391
+
392
+ The repo-level secret holding the npm automation token can be
393
+ named anything; the *workflow* secret it gets passed as must be
394
+ `NPM_TOKEN` exactly — the reusable workflow keys on that name.
395
+ Drop the `secrets:` block from the caller's `release.yml` once
396
+ Trusted Publishing is registered against the (now-existing)
397
+ package; subsequent publishes are zero-secret. For bundled-cli
398
+ / napi families each per-platform sub-package needs its own
399
+ Trusted Publisher registration after the first publish — the
400
+ secret-bypass is a one-time bootstrap, not a permanent path.
401
+
402
+ **Deprecations removed.** None.
403
+
404
+ **Behavior changes without code changes.** None when the secret
405
+ is unset (OIDC path unchanged). When the secret is set, a new
406
+ "Export NODE_AUTH_TOKEN (caller-provided)" step writes
407
+ `NODE_AUTH_TOKEN` to `$GITHUB_ENV` gated on the secret being
408
+ non-empty AND the planned matrix containing an npm row. The
409
+ gate reads the secret through a job-level `CALLER_NPM_TOKEN`
410
+ env var because GitHub Actions does not allow the `secrets`
411
+ context inside step-level `if:` conditions ([context
412
+ availability](https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability));
413
+ this is an internal mechanism — consumers don't see or set
414
+ `CALLER_NPM_TOKEN` themselves. Unlike #283 (crates), there is
415
+ no separate OIDC step to "skip" — the npm CLI handles OIDC
416
+ internally via the runner's id-token, and the presence of
417
+ `NODE_AUTH_TOKEN` in the env is what switches the CLI's auth
418
+ mode.
419
+
420
+ **Verification.** Wire `NPM_TOKEN` to a valid npm automation
421
+ token in the caller repo and trigger a release of a brand-new
422
+ package. The publish-job logs should show "Export
423
+ NODE_AUTH_TOKEN (caller-provided)" as `success`; `npm publish`
424
+ authenticates with the long-lived token rather than via OIDC,
425
+ and every per-platform sub-package in a bundled-cli / napi
426
+ family lands on the registry in a single run. Once first
427
+ publish succeeds, register Trusted Publishers against each
428
+ package URL, drop the `secrets:` block, and re-run a release
429
+ — the OIDC path covers the steady state from there.
430
+
431
+ Verified end-to-end against existing seeded fixtures and a
432
+ real first-publish on a canary repo. The Verdaccio
433
+ first-publish fixture coverage for the same path is tracked
434
+ separately at #293; until that lands this fallback is verified
435
+ by composition (mirror of #283) plus consumer-side observation
436
+ on real first publishes rather than by an automated
437
+ fresh-state fixture in this repo's CI.
438
+
439
+ ---
440
+
362
441
  ### Crates token fallback
363
442
 
364
443
  **Summary.** The reusable workflow now accepts an optional
package/README.md CHANGED
@@ -329,14 +329,15 @@ releases without fighting registry-immutable-publish semantics.
329
329
  ## Trusted publishers
330
330
 
331
331
  OIDC trusted publishers are the default and recommended auth path.
332
- The reusable workflow also accepts a long-lived `CARGO_REGISTRY_TOKEN`
333
- via `secrets:` for cases where Trusted Publishing isn't reachable —
334
- most commonly the very first publish of a brand-new crate, since
335
- Trusted Publishing on crates.io binds to an *already-published* crate
336
- and there's no pending-publisher equivalent. When set, the OIDC
337
- exchange is skipped and the caller-provided token is used instead.
338
- Drop the secret once Trusted Publishing is registered against the
339
- existing crate.
332
+ The reusable workflow also accepts long-lived `CARGO_REGISTRY_TOKEN`
333
+ (crates.io) and `NPM_TOKEN` (npm) values via `secrets:` for cases
334
+ where Trusted Publishing isn't reachable — most commonly the very
335
+ first publish of a brand-new crate or npm package, since Trusted
336
+ Publishing on both registries binds to an *already-published*
337
+ package and neither has a pending-publisher equivalent. When set,
338
+ the OIDC exchange is skipped and the caller-provided token is used
339
+ instead. Drop the secret once Trusted Publishing is registered
340
+ against the existing package.
340
341
 
341
342
  For all three registries the OIDC fields are the same: **your**
342
343
  repository owner/name, **your** workflow filename (`release.yml`),
@@ -382,14 +383,34 @@ see "How auth flows" below for the why.
382
383
 
383
384
  ### npm
384
385
 
385
- 1. Publish at least one version of your package with a classic
386
- `NODE_AUTH_TOKEN` so the package exists on the registry. (npm's trusted
387
- publisher requires an existing package.)
386
+ 1. **First publish (brand-new package).** Trusted Publishing on npm
387
+ binds to an existing package, so the first `npm publish` has no
388
+ OIDC path. Pass `NPM_TOKEN` to the reusable workflow via
389
+ `secrets:` to bootstrap through this workflow:
390
+
391
+ ```yaml
392
+ jobs:
393
+ release:
394
+ uses: thekevinscott/putitoutthere/.github/workflows/release.yml@v0
395
+ secrets:
396
+ NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
397
+ ```
398
+
399
+ When `NPM_TOKEN` is set, it is exported to the publish step's
400
+ environment as `NODE_AUTH_TOKEN` and the npm CLI prefers it over
401
+ the OIDC path. For bundled-cli / napi families the same secret
402
+ authenticates publishes of all per-platform sub-packages on first
403
+ publish — once those exist, each one needs its own Trusted
404
+ Publisher registration (the bypass is a one-time bootstrap, not
405
+ a permanent path).
388
406
  2. Go to `https://www.npmjs.com/package/<name>/access` → **Require trusted
389
407
  publisher**.
390
408
  3. Fill in: your repository, workflow filename (`release.yml`),
391
- environment (optional).
392
- 4. Delete the bootstrap token.
409
+ environment (optional). Repeat for every per-platform sub-package
410
+ for bundled-cli / napi families.
411
+ 4. Drop the `NPM_TOKEN` secret from the workflow once Trusted
412
+ Publishing is registered; subsequent publishes are zero-secret on
413
+ the OIDC path.
393
414
 
394
415
  ### How auth flows
395
416
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "putitoutthere",
3
- "version": "0.2.17",
3
+ "version": "0.2.18",
4
4
  "description": "Polyglot release orchestrator for crates.io, PyPI, and npm",
5
5
  "license": "MIT",
6
6
  "repository": {