feat!: publish-target plugin system (npm / jsr / pypi / vscode-marketplace / open-vsx) - #153
Open
theoephraim wants to merge 4 commits into
Open
feat!: publish-target plugin system (npm / jsr / pypi / vscode-marketplace / open-vsx)#153theoephraim wants to merge 4 commits into
theoephraim wants to merge 4 commits into
Conversation
…ilt-in) Packages can now publish to multiple targets via per-package publishTargets config and a root targets map (type defaults + named reusable instances). Targets execute per-package with per-target retry state in GitHub release metadata, shared artifacts (one .vsix -> marketplace + Open VSX), and a registry-level pre-publish idempotency guard. JSR publishing (publish-time jsr.json version sync, claim-first bootstrap detection) is modeled on Drake Costa's (@Saeris) setup in mirrordown. Legacy publishCommand/skipNpmPublish fields keep working via back-compat mapping in the resolver.
Python packages ride bumpy's package.json spine via a private stub package; the target syncs [project].version into pyproject.toml at publish time, builds with uv into an isolated per-version dist dir, and uploads via uv publish (PyPI trusted publishing / UV_PUBLISH_TOKEN). PyPI project name comes from pyproject.toml. Prereleases/snapshots are skipped (PEP 440 doesn't cover semver suffixes).
- prepare() lifecycle hook — publish-time version syncs (jsr.json, pyproject.toml) get a formal home instead of living in publish/buildArtifact - broken target config no longer breaks read-only commands: discovery records targetsError and publish flows refuse loudly instead - structured TargetOutcome.skipKind replaces magic reason strings coupled across pipeline and publish command - npmEffectiveRegistry() is the single registry fallback chain (options -> bumpy registry -> publishConfig.registry) for publish args, existence checks, prerelease counters, labels, and URLs - refusesPrivatePackages capability flag replaces hardcoded npm-type check in the resolver - jsr.json version sync uses the formatting-preserving updateJsonFields - per-target published checks run in parallel; shared stringArrayOption/ stringOption helpers de-triplicate option coercion
|
The changes in this PR will be included in the next version bump.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


What
Packages can now publish to multiple targets at once. Each target is an instance of a plugin implementing one interface (
src/core/targets/); built-ins:npm,custom(shell-command escape hatch),jsr,pypi,vscode-marketplace,open-vsx.Breaking (major bump):
publishCommand/checkPublished/skipNpmPublishare removed — bumpy fails with the migration when it sees them. Roottargetsentries no longer inherit from each other. A package's ownpackage.jsonmay only reference targets by name.State model
Three sources of "is this version out", with fixed precedence:
checkPublished) — truth whenever it can answer. Asked before every publish, so a lost draft never causes a duplicate publish, and how astagedtarget is promoted once approved.name@version— how packages with no queryable target are tracked.The tag marks the commit artifacts shipped from: it follows HEAD across failed attempts and freezes the first time anything ships (success or staged). The draft is finalized once every target is live. The pipeline only publishes; tagging/finalizing is orchestrated from its outcomes in one place.
Design highlights
npm succeeded + Open VSX failed→ the retry re-runs only Open VSX.Tblocks dependents onTonly (recorded as failed, retried next run) —app@jsrnever goes out referencing alib@jsrthat didn't land.npmStaged):publish()may report{ status: 'staged', ref }. The target is recorded 🟡 staged with the npm stage id, the draft stays a draft (no dead link, no prematurerelease: published), and the next publish run sees it live and finalizes. Snapshots never stage. Generic — any registry with an approval queue fits the same hook. (feat: mark staged publishes as staged in GH release +publish finalize#147'spublish finalizecommand becomes a thin wrapper over this reconciliation.).vsixuploads to both marketplaces, byte-identical.prepare()):jsr.json/pyproject.tomlversions sync from package.json at publish time — commit them as0.0.0and forget them. JSR identity comes fromjsr.json'sname(JSR scopes are a separate namespace).skipped; a package none of whose targets can take a release kind is dropped from the plan before any draft is opened.package.jsonexecutes or steers a publish — only name references.buildCommandand inline target definitions there requireallowCustomCommands. The root config is trusted.Validated against real setups
azureCredential: true) and gains per-registry retry.["npm", "jsr"].Credits
JSR publishing behavior is modeled on Drake Costa's setup in mirrordown — thanks Drake! 🙏
Test plan