Documentation / Développement / Guides pratiques / Publish to the registry

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.yaml exists, with name and version set (libs/plugin-cli/src/registry/registry.ts, publishPlugin reads 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-plugin available: through the scaffold's node_modules/.bin (npm run …) or via npx @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:

  1. Bump version in plugin.yaml (semver). This is the only place the version comes from — there is no --version flag on publish. A change of a dependsOn range is a change of plugin.yaml and needs a bump too. Which part to bump is in Release and version.
  2. Rebuild: npm run build copies the bumped manifest into dist/.
  3. 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.

97 documents7 sectionssource : /docs · généré au build