nParse+plugin registry

Publish a plugin

A plugin is a Python package with a manifest, built and released by your own repository. This registry never holds your code: it holds a plugin's id, who owns it, and a pointer to the release artifact you published, together with a sha256 this server computed itself from the bytes it downloaded.

Publishing is a step in your release pipeline, not a pull request against anybody's repository. The whole path is below, in order.

Start from the template

The plugin template (prokopto-dev/nparse-plugin-template) is a working plugin with the manifest, the layout and a release workflow already in it. Use it as a GitHub template repository and you start from something that builds.

Writing one from scratch is fine too. What this registry needs from a build is one artifact at a public https URL and a version string your plugin declares.

The path, in order

  1. Start from the template. Build your plugin, and make your release produce one artifact — normally a zip attached to a GitHub Release.
  2. Claim your plugin id. An id is first come, permanent, and never recycled: it names your plugin in every installed copy on every user's machine, so it is never released or reassigned, not even after a delisting. Claiming it registers the id to your account and makes you its owner. It does not list anything.

    Sign in with GitHub, then claim the id with the Claim a plugin id form on your account page. GitHub is the only identity this registry accepts, because the artifacts live on GitHub Releases and the ownership records it was migrated from are GitHub handles.

    A token cannot do this step, however scoped, and that is deliberate. Claiming is session-only — POST /api/v1/plugins for a machine, the account page's form for you — because a deployment credential that could register new ids would grow its own reach every time it was used. Claim the id before you mint the token: a token is pinned to an id, and publishing to an id nobody has claimed is answered 404, whatever the token carries.

    Do not ask somebody else to claim it for you. The account whose session sends the request becomes the owner — the claim takes the caller's own id and there is no on-behalf-of field — and an id is permanent, so that is not something anybody can correct afterwards. It has to be handed over instead: they add you as an owner, you mint your own token, and only then do they remove themselves. That needs you to have signed in here at least once anyway, so signing in and claiming it yourself is strictly less work.

  3. Mint a scoped token. On your account, mint a personal access token with plugin:publish and nothing else, pinned to that one plugin id. The pin is the half that contains a leak: a token stolen from one repository's CI can publish to one plugin.

    The secret is shown exactly once and is never recoverable — what is stored is a keyed hash of it. If you lose it, revoke it and mint another.

  4. Store it as a repository secret. In your plugin repository, Settings → Secrets and variables → Actions, as REGSERVE_TOKEN. It goes to the reusable workflow through secrets:, never through an input and never into a run: block.
  5. Tag a release. Your workflow builds the artifact, uploads it to the GitHub Release, and then calls the registry's reusable workflow with the plugin id, the version your plugin declares, the artifact URL and its sha256.
  6. The reusable workflow publishes. One job in your pipeline. It sends an idempotency key derived from your repository, id and version, so a re-run replays the original answer instead of burning a version that can only be used once.
      publish:
        needs: build
        uses: prokopto-dev/nparse-plugin-regserve/.github/workflows/publish-plugin.yml@5b0d87f7666f20c0c188b7208ad2738bd55c10d7 # v0.3.0
        with:
          plugin-id: your-plugin-id
          version: ${{ needs.build.outputs.version }}
          artifact-url: https://github.com/${{ github.repository }}/releases/download/${{ github.ref_name }}/your_plugin.zip
          artifact-sha256: ${{ needs.build.outputs.sha256 }}
        secrets:
          registry-token: ${{ secrets.REGSERVE_TOKEN }}

Your first release waits for a human, and that is not a failure

A new plugin id always goes to human review, whatever your trust level. The first appearance of an id is where impersonation is caught, and nothing bypasses it — not a trusted account, not a second attempt.

So the first run of your publish job normally reports state: pending and succeeds with a warning. The release is durably recorded and its version is claimed; it is simply not in the index yet. An author who expects a green tick to mean "live" reads that as a broken publish, and it is not one. Later version bumps of an already-approved plugin can publish without a human, which is what trust levels govern.

There is a third outcome worth knowing before you meet it: verified: false, meaning the registry could not download your artifact at all. It waits for review and is honest about having checked nothing. The usual cause is ordering — a GitHub Release asset does not exist for an anonymous caller until the release is published, so a draft release gives the registry a 404.

Pin the workflow to a commit, not to a tag and not to a branch

The uses: line above ends in a 40-character commit SHA, with the release it belongs to written beside it. That is not fussiness: a reusable workflow runs inside your job, with your publish token, so the ref you copy from us decides what gets to run next to that secret.

@main would mean your pipeline changes whenever this repository does. And @v0.3.0 is not a pin either — a tag is a movable label, so one git tag -f and a force-push here would change what every pipeline that copied it runs on its next release, with no diff to review and nothing to notice. A commit SHA cannot be moved, and that is the whole property worth buying.

The comment is what makes it readable: # v0.3.0 says which release you are on when forty hex characters do not, and a pin you cannot place is a pin you will never update. This repository pins every action it uses the same way, and it is a fair rule to hold us to.

The rest of it