Publish to the registry
This recipe ships a built plugin to a plugin registry with
meridian-plugin publish, so operators can declare it in an instance manifest
by version range instead of hand-copying a dist/ folder. It assumes you
already have a working dist/ (see Build and bundle)
and a registry you can write to (see
Set up a plugin registry).
Prerequisites
- A built plugin:
dist/plugin.yamlexists, withnameandversionset (libs/plugin-cli/src/registry/registry.ts,publishPluginreads both from the manifest — nothing is taken from the command line). - A reachable registry — a GCS bucket (
gs://bucket[/prefix]) or a local/NFS directory — with write access for your identity. meridian-pluginavailable: through the scaffold'snode_modules/.bin(npm run …) or vianpx @meridian/plugin-cli.
1. Point the CLI at the registry
publish and versions both take the registry from --registry <url> or,
if that flag is absent, from the MERIDIAN_REGISTRY environment variable —
the flag always wins when both are set
(libs/plugin-cli/src/commands/publish.ts, registryUrl). Neither present is
a hard error (registre non configuré : passez --registry <url> ou définissez MERIDIAN_REGISTRY).
export MERIDIAN_REGISTRY=gs://meridian-plugins # or a local directory / file:// URL
# — or, per invocation:
meridian-plugin publish --registry gs://meridian-plugins
2. Publish the built plugin
cd vitals
npm run build # meridian-plugin build → dist/ (bundled, self-contained)
meridian-plugin publish # registry from MERIDIAN_REGISTRY, or add --registry <url>
publish [dir=.]… is convenient about where you point it
(publishCommand): if <dir>/dist/plugin.yaml exists, that dist/ is what
gets published; otherwise <dir> itself is published as-is. In practice this
means running the command from the plugin's project root (after build) and
running it from inside dist/ both work — meridian-plugin publish and
meridian-plugin publish dist are equivalent there. The directory argument
and --registry <value> can appear in either order on the command line.
Before writing anything, publish reads the manifest it is about to publish again
(publishCommand). When its sdk range accepts the CLI's plugin contract, or it has
none, the schema of that contract must accept the manifest and no key may be unknown to
it. Each schema error is printed as - <path> : <message>, each unknown key with its
path as build prints it, and the publish is refused, since a published version cannot
be corrected:
- @acme/vitals › contributes.nodes[0].config[0].option (key) : clé inconnue du contrat plugin 0.1.8, ignorée. Corriger ou retirer la clé.
✗ @acme/vitals@0.1.0 : 1 problème(s) du manifeste, publication refusée.
Fix the manifest, rebuild and publish again. A dist/ that the same CLI built has
already passed these checks; they matter for a dist/ edited by hand or built by an
older CLI. A manifest whose sdk range does not accept the CLI's contract belongs to a
newer contract and is published without them: publish it with a CLI of that contract.
Under the same condition, publish checks each dependsOn range against the target
registry (registryDependencyIssues): an invalid range is refused, and so is a
dependency published on that registry when all of its published versions are outside
the range. A prerelease counts only for a range that names one. A * range constrains
nothing:
- @acme/vitals › @acme/units (dependency) : toutes les versions de « @acme/units » publiées sur gs://meridian-plugins sont hors de la plage « ^0.3.0 ». Publier une version de « @acme/units » dans « ^0.3.0 » d'abord ou corriger la plage.
✗ @acme/vitals@0.2.0 : 1 problème(s) du manifeste, publication refusée.
✗ 1 plugin(s) refusé(s) sur 1, rien n'est publié.
A refusal exits 2; an error of the registry itself (network, credentials) exits 1.
A dependency absent from the registry is not checked, since it can come from the image (the core plugins are published nowhere), and a note says so without refusing the publish:
@acme/vitals@0.2.0 : 1 note(s) de dépendances :
- @acme/vitals › @posos/clinical (dependency) : aucune version de « @posos/clinical » publiée sur gs://meridian-plugins : plage « ^0.2.0 » non contrôlée.
When a plugin and a plugin it depends on change in the same release, publish them in
one command: publish orders the directories by dependsOn, a dependency first, and
checks each plugin against the registry plus the plugins published before it in the same
command. Every plugin is checked before anything is written; when one is refused,
nothing is published. The host resolves the pair together (see
Declare and install plugins).
meridian-plugin publish vitals units --registry gs://meridian-plugins
To run these checks without writing anything, add --dry-run: the command stops at
the same problems, the check that the version is not already published included, and
prints ✓ @acme/vitals@0.2.0 publiable sur gs://meridian-plugins (7 fichier(s), dry-run : rien n'est écrit).
for each plugin. The verdict is the one a publication would get: a dependency of the
same command counts for its dependents, in memory.
A successful publish reports the name, version and file count that landed on the store:
✓ @acme/vitals@0.1.0 publié sur gs://meridian-plugins (7 fichier(s)).
Internally, every file under dist/ is uploaded first, and plugin.yaml is
written last — its presence is the marker that a version is complete, so
a download in progress never sees a half-published version
(registry.ts, publishPlugin).
3. Verify what's published
List the versions the registry currently has for that name:
meridian-plugin versions @acme/vitals --registry gs://meridian-plugins
0.1.0
0.2.0
Versions print oldest first — resolved with the npm semver package
(compare), with any non-semver folder name pushed to the end rather than
failing the listing (registry.ts, listVersions). An empty result prints
@acme/vitals : aucune version publiée sur … rather than an error, which
still confirms the registry is reachable and readable.
To confirm an operator can actually pick it up, see
Declare and install plugins —
version ranges (^0.1.0, ~0.1.0, an exact version, *) are resolved
against this same list. The host takes the highest version in the instance range whose
dependsOn accepts the plugins it loads: a version that requires a core plugin version
the deployed image does not carry is not selected, and the previous one stays until that
image is deployed.
The resolution also sets aside a version whose sdk range does not admit the host's
plugin contract: a version that declares sdk: "^0.1.4" is not selected by a host on
contract 0.1.3, which takes the previous version of the range, or fails to boot when
none fits. A plugin found
on PLUGINS_PATH or through a path: entry is not resolved: the host compares its sdk
range with its own contract at load and refuses it when they do not match
(contrat plugin incompatible — plugin ignoré).
Re-release after a change (versions are immutable)
A published name@version can never be overwritten. Publishing the same
version again is refused outright:
✗ @acme/vitals@0.1.0 est déjà publié sur gs://meridian-plugins — les versions sont immuables, incrémentez.
(registry.ts, publishPlugin — the check is a plain existence test on that
version's plugin.yaml before anything is written.) To ship a new build:
- Bump
versioninplugin.yaml(semver). This is the only place the version comes from — there is no--versionflag onpublish. A change of adependsOnrange is a change ofplugin.yamland needs a bump too. Which part to bump is in Release and version. - Rebuild:
npm run buildcopies the bumped manifest intodist/. - Publish again. The new version is added; every previous one is untouched and still resolvable by any range that matched it before.
If a publish is interrupted
Because files land before plugin.yaml, an interrupted publish (network
drop, killed process) can leave stray files under that version's prefix even
though the version is not "complete" (no plugin.yaml yet, so versions
still won't list it). Retrying the exact same version isn't guaranteed to be
clean in that case — each file is written with a create-if-absent guard, so a
file that already landed makes the retry fail on that file rather than with
the friendly "already published" message above. If a retry fails oddly right
after an interruption, that's almost certainly why; publishing under a bumped
version sidesteps it entirely.
Related
- Build and bundle — produces the
dist/this recipe publishes. - Bundling and distribution —
why versions are immutable and what a published
dist/contains. - Set up a plugin registry — stand up the GCS bucket or directory this recipe publishes to, including the IAM/filesystem permissions publishing needs.
- Declare and install plugins — the operator side: adding the published name and a version range to an instance manifest.
- Environment variables —
MERIDIAN_REGISTRYand related. - Plugin manifest and
CLI reference — the
versionfield and the fullmeridian-plugincommand surface.