Publishing
Release layout, the add-on library, versioning and updates.
Interface 0.2, and 0.1.
niles plugin checkand installing, updating and rolling back from GitHub releases are built (Niles 0.2.0), as described here. The add-on library (Browse) and install counts are built (Niles 0.5.0).
A plugin is published as a GitHub release on a public repo. Anyone with
the repo's URL can install it, and once the repo has the topic niles-addon
people also find it in the app, under Add-ons → Browse (see the add-on
library).
Release layout
The release Niles installs is the repo's latest release: not a draft, not
marked pre-release. (Somebody can install another by pasting its address,
https://github.com/you/your-repo/releases/tag/v0.2.0.) Its assets must
include:
| Asset | Required | What it is |
|---|---|---|
niles-integration.toml | yes | The manifest, at most 64 KiB. Its version is the release tag, with or without a v. |
integration.wasm | yes | The component, at most 64 MiB. |
logo.svg | if the manifest names one | The logo, at most 32 KiB. The asset is found by the file name of the manifest's logo, so logo = "assets/logo.svg" is the asset logo.svg. |
niles-installed | to be listed | A few bytes, at most 1 KiB, any content. Niles downloads it once per house after a fresh install, so its download count is your number of installs. |
Each cap holds whatever GitHub reports as the asset's size: a download is cut off at it.
Other assets are ignored by the install. The release notes (the release's body) are shown to the household when they are offered the update, so write them for people: what changed for them, not the commit log.
The workflow
The template's .github/workflows/release.yml builds and publishes on a
version tag:
name: release
on:
push:
tags: ["v*"]
permissions:
contents: write
env:
CRATE: niles_spotprice # the crate name, with _ for -
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-wasip2
- name: Tag matches the manifest
run: |
version=$(grep -m1 '^version' niles-integration.toml | cut -d '"' -f2)
if [ "v$version" != "$GITHUB_REF_NAME" ]; then
echo "Tag $GITHUB_REF_NAME but the manifest says $version" >&2
exit 1
fi
- name: Build
run: cargo build --release --target wasm32-wasip2
- name: Collect
run: |
cp "target/wasm32-wasip2/release/$CRATE.wasm" integration.wasm
# Niles downloads this once per house after a fresh install:
# its download count is your install count.
printf 'Niles counts installs of this add-on by this file.\n' > niles-installed
- name: Release
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "$GITHUB_REF_NAME" \
--title "$GITHUB_REF_NAME" \
--generate-notes \
integration.wasm niles-integration.toml logo.svg niles-installedOnce niles publishes release binaries, a niles plugin check . step before
Release is worth adding. niles plugin check --listing you/your-repo
checks a published release as the add-on library's listing check does.
For JavaScript, Python or Go, replace the toolchain and build steps with
jco componentize, componentize-py or tinygo build, and keep the rest.
Versioning your plugin
Semver, judged by what the household sees:
- Patch (0.1.0 → 0.1.1): fixes. Same tools, devices, settings and permissions.
- Minor (0.1.1 → 0.2.0): new tools, devices, settings or surfaces; new permissions.
- Major (0.2.0 → 1.0.0): anything that breaks what the household built on it: a device renamed or removed (their rules name it), a setting renamed (its value is lost), a tool renamed.
Never rename a device id lightly: rules, scenes and the app refer to it. Never
change the plugin's id: that is a different plugin to Niles, installed beside
the old one, with none of its settings, secrets or storage.
How updates reach people
- Niles asks each installed plugin's repo for its releases every 6 hours, first 2 minutes after it starts, and when someone presses Check for updates on its panel.
- The newest release whose tag is a higher semver version than the installed one shows as Update available, with its version, release notes and the change in permissions (what is added, what is removed). A pre-release is offered only to someone already on a pre-release, so mark betas as pre-releases on GitHub. Nothing updates by itself.
- Updating downloads the release and runs the same checks as an install, and the household sees the difference before they confirm. New required settings put the plugin in "Not set up" until they are filled in.
- The previous version is kept. If the new one traps, times out or will not
load within 10 minutes of the update, Niles puts the previous one back by
itself and says so on the panel;
initreturning an error does not count, since that is your plugin saying what is wrong. The household can also roll back by hand, once.
Storage and settings carry across updates. If your stored data's shape
changes, read the old shape too, or keep a version number in storage and
migrate in init.
The add-on library
The app lists what other people published under Add-ons → Browse, from
one file the registry repo (MarkNygaard/niles-addons) builds from GitHub's
public data. You don't upload anything; your repo's releases are the truth.
Being found
Give the repo the topic niles-addon, under the repo's About → Topics, or
with gh repo edit --add-topic niles-addon. The library picks it up within
six hours, as Unreviewed.
Getting listed
Open a pull request on MarkNygaard/niles-addons adding your repo to
addons.toml:
[[addon]]
repo = "you/your-repo"The check runs niles plugin check --listing you/your-repo on your latest
release (everything an install checks, plus niles-installed), and puts the
permissions your add-on asks for in its summary. Mark reads them and merges;
then it is Listed.
Listed means it passed the checks and someone read what it asks for. It isn't an audit of your code, later releases aren't reviewed again, and every household still approves its permissions on install.
Install counts
The library shows your installs: GitHub's download count of niles-installed,
summed over your releases. A preview, an update or installing the same
add-on again doesn't count, and nothing about the household is sent. It's a
total, not active installs: removals aren't counted. Without
niles-installed the library shows your component's downloads instead,
labelled as downloads, which counts previews and updates.
Leaving the library
Remove the topic, or open a pull request removing your entry. An add-on taken off the list keeps working where it's installed, and its panel says it is no longer listed.
Pinning: why a release cannot change under people
When somebody previews an install or an update, Niles downloads your assets, computes their sha256 and holds them for 15 minutes. Where GitHub reports a digest for an asset, the two must agree. Installing takes exactly those bytes, checked against the sha256 the preview showed, and keeps them in Niles's own database; nothing is downloaded again, at install or at any restart after. Replacing an asset on an existing release changes nothing for anyone who installed it; publish a new release instead.
Which Niles versions you support
interface = "0.3"in the manifest and the WIT your component was built with say which interface you target, and they must agree: Niles reads the version your component's imports carry, and refuses a component built for 0.3 under a manifest that says 0.2, or the reverse. A Niles that runs 0.2 runs any0.2.xcomponent; patch versions of the interface only add.min_nilesis the oldest Niles your plugin needs:"0.3.0"for interface 0.3, and later when you use a function or UI node added after that.
Interface versions
| Interface | Niles | What changed |
|---|---|---|
| 0.3 | 0.3.0 | Accounts and webhooks: the secrets and inbound interfaces, the exports authorize-url, on-callback and on-webhook, and the callback and webhook permissions. Nothing 0.2.1 had changed. |
| 0.2.1 | 0.2.1 | config.time-zone and devices.set-room-light. Any device class may report-action. Nothing 0.2.0 had changed. |
| 0.2.0 | 0.2.0 | The mqtt interface and the exports on-mqtt-message and on-mqtt-status. Nothing else. |
| 0.1 | 0.2.0 | The first. |
How a patch version stays compatible, both ways:
- Patch versions only add functions or interfaces. Nothing a component
already imports changes: no record, variant or signature. The WIT's
package version says the patch (
niles:integration@0.2.1); the manifest'sinterfacesays only"0.2". - A component built against an older patch runs unchanged. It imports
niles:integration/devices@0.2.0; Niles definesdevices@0.2.1, and the WebAssembly linker (wasmtime's) treats versions that differ only in the patch of a0.xas the same interface, so it links. The types are then checked function by function, for the functions the component imports only, and those have not changed. Niles's tests load a component built against 0.2.0 (relay.wasm) to hold it to that. - A component built against a newer patch needs a Niles that has it.
Built against 0.2.1 and calling
config.time-zone, it fails to load on a Niles that only has 0.2.0, naming the function it is missing. Raisemin_nilesto the first Niles with the patch, so the household is told before they install it rather than after.
Niles runs plugins built for the previous interfaces beside the current one
for at least six months, and their panel says "Built for an older Niles".
Moving a plugin from 0.2 to 0.3 takes a copy of the new
WIT, interface = "0.3",
min_niles = "0.3.0", and the three new exports, answering nothing if you
connect no account and have no webhook:
fn authorize_url(_callback_url: String, _state: String) -> Result<String, String> {
Err("This add-on connects no accounts.".into())
}
fn on_callback(_: String, _: Vec<(String, String)>, _: Option<String>) -> Result<String, String> {
Err("This add-on connects no accounts.".into())
}
fn on_webhook(_request: InboundRequest) -> InboundResponse {
InboundResponse { status: 404, content_type: None, body: Vec::new() }
}From 0.1, add the two MQTT exports as well, empty if you use no MQTT:
fn on_mqtt_message(_conn: u32, _topic: String, _payload: Vec<u8>) {}
fn on_mqtt_status(_conn: u32, _status: ConnectionStatus) {}The older interfaces are kept, frozen, as
niles-integration-0.2.wit and
niles-integration-0.1.wit.
Private repos, and GitHub's limit
Not yet. Installing from a private repo needs a GitHub token in Niles, which is planned but not specified. Without a token GitHub allows 60 API requests an hour from one address: a preview takes one or two, an update check one per plugin. Browsing the library takes none of them. When the limit is reached Niles says so, with the time it resets.