Skip to content

[v3] feat: publish npm packages and the Gradle plugin, rename runner to @plugwright/runner - #55

Merged
Drownek merged 10 commits into
Drownek:v3-devfrom
monikon22:pr/6-publishing
Aug 27, 2026
Merged

[v3] feat: publish npm packages and the Gradle plugin, rename runner to @plugwright/runner#55
Drownek merged 10 commits into
Drownek:v3-devfrom
monikon22:pr/6-publishing

Conversation

@monikon22

Copy link
Copy Markdown
Contributor

Sixth and last PR in the series (#46), on top of #52. Rebased onto v3-dev at 1dbef83.
Kotlin compiles (plugwright-bundle:compileKotlin), and tsc --noEmit is clean in
runner-package, auth-authme-package and console-rcon-package.

This wires up the four artifacts the project ships — three npm packages and the Gradle
plugin — for an actual release, and finishes the scope decision from #48: the runner moves
from @drownek/plugwright to @plugwright/runner.

What ships

Lockstep versioning. scripts/bump-version.js now bumps all three npm packages, not just
runner-package, and refreshes the plugin packages' lockfiles afterward — npm version writes
each package's own version but doesn't touch a linked dependency's version as it's recorded in
a lockfile, so an install is needed to pick up the runner's new number everywhere it's copied.

npm publishing. scripts/publish.js publishes all three packages in dependency order
(runner first, so a consumer resolving a plugin package finds the runner already there). It
reaches npmjs.com by default and a private registry when PLUGWRIGHT_NPM_REGISTRY (or
--registry) is set — the registry and its credentials live in the environment rather than in
a package manifest, so pointing at a private mirror for one run doesn't leak into the public
release. release.yml runs it with --provenance against npmjs.com.

Gradle plugin publishing. plugwright-bundle/build.gradle.kts gets publishToPublicRepository
(Gradle Plugin Portal, on by default) and publishToPrivateRepository (an arbitrary Maven
repository, on only once plugwright.publish.url / PLUGWRIGHT_PUBLISH_URL is set). Both are
switches on the underlying publish task rather than wrappers that skip themselves, since a
wrapper skipping itself still lets whatever it dependsOn run.

Fixed alongside: the bundle module had implementation(project(":plugwright-core")) etc. for
-core, -local and -external, which Gradle writes into the published POM as runtime
dependencies on Maven coordinates that exist in no repository. Every consumer resolving the
plugin from a repository — public or private — got Could not find io.github.drownek:plugwright-core.
They're compileOnly now, matching plugwright-api, since all four are merged into the
bundle's own jar and none is published separately.

.env.example documents every variable both scripts read, and docs/publishing.mdx covers
the two publish paths end to end — public release and pointing everything at a private registry.

The rename. @drownek/plugwright@plugwright/runner, everywhere: both packages'
package.json/lockfiles, every import across the runner, the two plugins, the docs, and the
example. The Gradle plugin id stays io.github.drownek.plugwright, the Kotlin package stays
me.drownek.plugwright, and repository.url still points at Drownek/plugwright — this is
only about the npm name. README.md gets an upgrade note for anyone still on 2.x.

Needs from you before merge

Two things from the #48 discussion that are still open and now block this PR's own CI job:

  1. NPM_TOKEN GitHub secret. release.yml wires it as NODE_AUTH_TOKEN for the first
    publish of each @plugwright package, per the plan from [v3] feat: reference plugin packages, and the runner surface they need #48 — a granular token scoped to
    the org, since a trusted publisher can't be configured for a package name that doesn't
    exist yet. Without it the release workflow's npm step fails outright on the first tag push.
  2. npm deprecate @drownek/plugwright, pointing at @plugwright/runner, once 3.0
    actually publishes. Not urgent for this merge, but easy to lose track of once the new name
    is live and the old one just goes quiet instead of pointing anywhere.

Both are on your side — I don't have publish access to either scope.

Testing

npm run publish:packages -- --dry-run packs and validates all three without sending anything.
./gradlew publishToPublicRepository and publishToPrivateRepository were exercised against a
throwaway local Maven repo (plugwright.publish.url=file://...) to confirm the POM resolves
now that the three inner modules are compileOnly.

Merging

This is the last PR in the series — squash and merge is fine here, unlike the previous five.

After this

Opened two follow-up issues from the #46 discussion: #53 (replacing player reuse with
environment-scoped identity and describe.serial()) and #54 (the three bugs you found
reviewing #52). I'll start on both once this merges.

auth-authme and console-rcon sat at 1.0.0 while the runner they are written
against had moved on several versions, because the bump script only ever knew
about runner-package. Nothing said which runner a given plugin package was
built for.

They now share one version. A plugin package is not useful without the runner,
so a version pair that has to be looked up is a cost with nothing on the other
side of it.
The plugin only went to the Gradle Plugin Portal, which is unreachable from a
network that does not let builds out to the internet. Such an organisation had
no supported way to get the plugin at all.

`publishAllPublicationsToPlugwrightRepository` now deploys both the plugin jar
and its marker to whatever repository `plugwright.publish.url` names, with
credentials from `plugwright.publish.user` and `.password`. All three also read
from PLUGWRIGHT_PUBLISH_URL, _USER and _PASSWORD, which is the shape a CI job
already has its secrets in.

None of the three appear in this repository. A URL here would tie a public
build to one company's servers; a password here would be a password in version
control. With no URL set the repository is not declared at all, so a build that
does not opt in publishes exactly where it did before.
…oosing

The release workflow published one of the three packages, and only to npmjs. The
other two — the AuthMe and RCON reference plugins — had no publish path at all,
and an organisation that mirrors its dependencies had none either.

`npm run publish:packages` now sends all three wherever it is pointed. Given no
configuration that is npmjs, which is what a release is; given a registry and
credentials in the environment it is that registry instead. The two cases differ
only in where the request goes and how it is authenticated, so they share a
script rather than each growing one.

Nothing about a private registry is written into the packages. A
`publishConfig.registry` in a package.json would send the public release there
too, so the URL and the credentials come from the environment, and the
credentials go into a temporary npm config outside the working tree that is
deleted whether the publish worked or not.
…optional

The plugin could already go to the Gradle Plugin Portal or to a maven repository
of the build's choosing, but the two were reached by tasks that look nothing
alike — `publishPlugins` against
`publishAllPublicationsToPlugwrightRepository`. Documenting a release meant
documenting two vocabularies.

`publishToPublicRepository` and `publishToPrivateRepository` wrap what was
already there. Neither destination is mandatory, and neither being available is
a failure: the private one is off until `plugwright.publish.url` names a
repository, and without one the task succeeds, publishes nothing and says why.
Most checkouts have no private repository, so a build script or a CI job can name
the task unconditionally instead of guarding every call.

Either can also be switched off outright, for when the implicit rule gets it
wrong — a fork that publishes only inside a company wants the public one off, and
a machine that holds the private URL for *resolving* may still want to publish
nowhere:

    plugwright.publish.public.enabled    default true
    plugwright.publish.private.enabled   default: on when a URL is set

The switch goes on the wrapped task rather than the wrapper. `onlyIf` skips the
task it is set on and nothing it depends on, so a wrapper that skipped itself
would still have run the publish underneath it.
The workflow published `@drownek/plugwright` and stopped there, so
`@plugwright/auth-authme` and `@plugwright/console-rcon` — which a build asks
for by name as soon as it declares an AuthMe or RCON plugin — were never on
npmjs at all. Anyone following the docs got a 404 from `npm install`.

It now runs the same `npm run publish:packages` a maintainer would run locally,
which covers all three and keeps provenance on. The gradle step moves to
`publishToPublicRepository`, which is `publishPlugins` under the name the docs
use.
The registry URL and its credentials are deliberately not in any package.json or
build script, which leaves nowhere that says they exist. `.env.example` is that
place: it lists every variable both publish paths read, and says which of them a
public release needs, which is none of them.

`.env` itself is ignored, along with `*.local.md` for notes kept next to the
checked-in docs.
Neither destination was written down anywhere. The release workflow was the only
record of how a public release happens, and the private path — which exists
precisely so an organisation can run one without the public registries — had no
record at all beyond the environment variables the code reads.

`docs/publishing.mdx` covers both, alongside what carries a version and why the
tag comes before the publish.
The two reference plugins publish under `@plugwright`, so leaving the runtime
they load into on `@drownek/plugwright` splits the project across two npm scopes
for a reason no user could reconstruct. The org exists now, and 3.0 is already a
breaking release with build scripts being edited anyway, so this is the cheapest
moment to move.

Only the npm name changes. The Gradle plugin id stays `io.github.drownek.plugwright`,
the Kotlin package stays `me.drownek.plugwright`, and `repository.url` still points
at `Drownek/plugwright`, so provenance is unaffected. `@drownek/plugwright` keeps
its 2.x releases and wants an `npm deprecate` pointing at the new name once 3.0
ships.
Every `npm { }` example routes `@drownek` to a private mirror, a scope that stops
holding anything the moment the runner moves. `@plugwright` keeps the illustration
true: mirroring the project's own packages is the setup these examples are for.
A trusted publisher is configured per package, and every name under the new scope
is unpublished, so there is nothing for OIDC to authenticate against on the first
release. The publish step reads `NODE_AUTH_TOKEN` from an `NPM_TOKEN` secret — a
granular token scoped to the org — and the block comes out once each package exists
and `npm trust` can take over.

Provenance is unaffected: `--provenance` and `id-token: write` stay where they are,
and the attestation is signed from the job's OIDC token whichever credential does
the publishing.
@Drownek
Drownek merged commit 3bb591d into Drownek:v3-dev Aug 27, 2026
@Drownek

Drownek commented Aug 27, 2026

Copy link
Copy Markdown
Owner

Merged, will try to publish dev.0 release and will bump to dev.1 afterwards

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants