@biffo/cli 0.162.0 → 0.162.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Publishes this plugin's entry to the Biffo plugin registry.
|
|
2
|
+
#
|
|
3
|
+
# Without this, a plugin is invisible. The portal's plugin store reads a static
|
|
4
|
+
# `plugins.json` from the public `keiranholloway/biffo-plugins-registry` repo,
|
|
5
|
+
# and adding an entry used to be a manual step that nothing performed — so the
|
|
6
|
+
# registry sat empty from the day it was created while real plugins ran in
|
|
7
|
+
# production, and every instance's store read "No plugins available yet".
|
|
8
|
+
# Nobody had forgotten a step; there was no step. This is the step.
|
|
9
|
+
#
|
|
10
|
+
# It runs when `biffo.plugin.json` changes on `dev`, and republishes the entry
|
|
11
|
+
# from that manifest via `scripts/upsert_plugin.py` in the registry repo.
|
|
12
|
+
#
|
|
13
|
+
# ## What it does and does not overwrite
|
|
14
|
+
#
|
|
15
|
+
# `description`, `tags` and `ui_components` in the registry are **marketplace
|
|
16
|
+
# copy** — written to sell the plugin to a human browsing the store, which is a
|
|
17
|
+
# different job from this manifest's `description`, written for engineers. The
|
|
18
|
+
# registry preserves those three once set, seeding them from the manifest only
|
|
19
|
+
# on a plugin's first publish. `version`, `repo`, `required_core_version` and
|
|
20
|
+
# `api_routes` track this manifest on every run. `status` is an operator
|
|
21
|
+
# decision and is never changed by a publish.
|
|
22
|
+
#
|
|
23
|
+
# ## It needs a token
|
|
24
|
+
#
|
|
25
|
+
# The registry is a different repo, so the job-scoped GITHUB_TOKEN cannot write
|
|
26
|
+
# to it. Until `REGISTRY_PUBLISH_TOKEN` is set on this repository the job warns
|
|
27
|
+
# and skips — deliberately not a failure, so a missing token never reds an
|
|
28
|
+
# otherwise good merge, but deliberately not silent either, because silence is
|
|
29
|
+
# how the registry went stale in the first place.
|
|
30
|
+
#
|
|
31
|
+
# Create a fine-grained PAT with `contents: write` on the registry repo, then:
|
|
32
|
+
# gh secret set REGISTRY_PUBLISH_TOKEN
|
|
33
|
+
#
|
|
34
|
+
# If your plugin's repo is **public**, you may not need this at all: the
|
|
35
|
+
# registry also runs a pull-based sync that re-derives every entry listed in its
|
|
36
|
+
# `sources.json` from the source manifest, with no credential anywhere. Add your
|
|
37
|
+
# plugin there and the store converges within a week of any version bump. The
|
|
38
|
+
# token only upgrades that from eventually-correct to immediately-correct.
|
|
39
|
+
name: Publish to plugin registry
|
|
40
|
+
|
|
41
|
+
on:
|
|
42
|
+
push:
|
|
43
|
+
branches: [dev]
|
|
44
|
+
paths:
|
|
45
|
+
- biffo.plugin.json
|
|
46
|
+
workflow_dispatch:
|
|
47
|
+
|
|
48
|
+
jobs:
|
|
49
|
+
publish:
|
|
50
|
+
name: Upsert registry entry
|
|
51
|
+
runs-on: ${{ vars.RUNNER_LABEL || 'ubuntu-latest' }}
|
|
52
|
+
steps:
|
|
53
|
+
- name: Check out this plugin
|
|
54
|
+
uses: actions/checkout@v4
|
|
55
|
+
with:
|
|
56
|
+
path: plugin
|
|
57
|
+
|
|
58
|
+
- name: Check for the registry token
|
|
59
|
+
id: token
|
|
60
|
+
env:
|
|
61
|
+
REGISTRY_PUBLISH_TOKEN: ${{ secrets.REGISTRY_PUBLISH_TOKEN }}
|
|
62
|
+
run: |
|
|
63
|
+
if [ -z "${REGISTRY_PUBLISH_TOKEN}" ]; then
|
|
64
|
+
echo "::warning title=Registry not updated::REGISTRY_PUBLISH_TOKEN is not set on this repository, so this plugin's entry in the Biffo plugin registry was NOT updated. The portal marketplace will keep showing the previously published version. See this workflow's header comment."
|
|
65
|
+
echo "present=false" >> "$GITHUB_OUTPUT"
|
|
66
|
+
else
|
|
67
|
+
echo "present=true" >> "$GITHUB_OUTPUT"
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
- name: Check out the registry
|
|
71
|
+
if: steps.token.outputs.present == 'true'
|
|
72
|
+
uses: actions/checkout@v4
|
|
73
|
+
with:
|
|
74
|
+
repository: keiranholloway/biffo-plugins-registry
|
|
75
|
+
token: ${{ secrets.REGISTRY_PUBLISH_TOKEN }}
|
|
76
|
+
path: registry
|
|
77
|
+
|
|
78
|
+
- name: Set up Python
|
|
79
|
+
if: steps.token.outputs.present == 'true'
|
|
80
|
+
uses: actions/setup-python@v5
|
|
81
|
+
with:
|
|
82
|
+
python-version: '3.13'
|
|
83
|
+
|
|
84
|
+
- name: Install jsonschema
|
|
85
|
+
if: steps.token.outputs.present == 'true'
|
|
86
|
+
run: pip install jsonschema
|
|
87
|
+
|
|
88
|
+
- name: Upsert this plugin's entry
|
|
89
|
+
if: steps.token.outputs.present == 'true'
|
|
90
|
+
working-directory: registry
|
|
91
|
+
run: |
|
|
92
|
+
set -euo pipefail
|
|
93
|
+
python3 scripts/upsert_plugin.py \
|
|
94
|
+
--manifest "../plugin/biffo.plugin.json" \
|
|
95
|
+
--repo "${{ github.server_url }}/${{ github.repository }}"
|
|
96
|
+
|
|
97
|
+
- name: Commit and push
|
|
98
|
+
if: steps.token.outputs.present == 'true'
|
|
99
|
+
working-directory: registry
|
|
100
|
+
run: |
|
|
101
|
+
set -euo pipefail
|
|
102
|
+
if git diff --quiet -- plugins.json; then
|
|
103
|
+
echo "Registry entry already current — nothing to publish."
|
|
104
|
+
exit 0
|
|
105
|
+
fi
|
|
106
|
+
version="$(python3 -c "import json;print(json.load(open('../plugin/biffo.plugin.json'))['version'])")"
|
|
107
|
+
name="$(python3 -c "import json;print(json.load(open('../plugin/biffo.plugin.json'))['name'])")"
|
|
108
|
+
git config user.name "github-actions[bot]"
|
|
109
|
+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
110
|
+
git add plugins.json
|
|
111
|
+
git commit -m "feat(registry): publish ${name}@${version}"
|
|
112
|
+
# Another plugin may have published between checkout and now.
|
|
113
|
+
git pull --rebase origin main
|
|
114
|
+
git push origin HEAD:main
|
|
@@ -1,15 +1,40 @@
|
|
|
1
1
|
name: Release
|
|
2
2
|
|
|
3
|
-
# Runs on every push of a `v*` tag:
|
|
4
|
-
#
|
|
3
|
+
# Runs on every push of a `v*` tag: checks that every version this plugin
|
|
4
|
+
# declares agrees with the tag, builds the sdist/wheel, and cuts a GitHub
|
|
5
|
+
# Release. Publishing to PyPI is opt-in — see below.
|
|
5
6
|
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
7
|
+
# ## Three versions have to agree
|
|
8
|
+
#
|
|
9
|
+
# A plugin declares its version twice, and the tag is a third:
|
|
10
|
+
#
|
|
11
|
+
# - `pyproject.toml` -> the Python package
|
|
12
|
+
# - `biffo.plugin.json` -> what the plugin registry publishes, and therefore
|
|
13
|
+
# the version the portal's plugin store shows
|
|
14
|
+
# - the `v*` tag itself
|
|
15
|
+
#
|
|
16
|
+
# The manifest one is the easy one to forget, and it fails in the worst way
|
|
17
|
+
# available: silently. Tagging v0.2.0 with a stale manifest gives you a v0.2.0
|
|
18
|
+
# release, a 0.2.0 package, and a plugin store still advertising 0.1.0 —
|
|
19
|
+
# indefinitely, with nothing anywhere reporting a problem. The registry sync
|
|
20
|
+
# faithfully re-derives whatever the manifest says, so it propagates the stale
|
|
21
|
+
# value rather than catching it. Both files are therefore checked against the
|
|
22
|
+
# tag below, and a mismatch fails the release rather than shipping a lie to the
|
|
23
|
+
# store.
|
|
24
|
+
#
|
|
25
|
+
# ## PyPI publishing is opt-in
|
|
26
|
+
#
|
|
27
|
+
# No plugin scaffolded from this skeleton has a PyPI project or publishing
|
|
28
|
+
# credential. Rather than ship a workflow whose last step always fails — which
|
|
29
|
+
# only teaches people that a red release means nothing, and is how a real
|
|
30
|
+
# failure later goes unnoticed — the publish step is gated on the
|
|
31
|
+
# `PYPI_PUBLISH` repository variable and skips with a notice.
|
|
32
|
+
#
|
|
33
|
+
# To enable for YOUR plugin: create the PyPI project, configure a trusted
|
|
34
|
+
# publisher (OIDC — no stored secret to rotate or leak; the `id-token: write`
|
|
35
|
+
# permission below is already in place for it), then
|
|
36
|
+
# `gh variable set PYPI_PUBLISH --body true`. See README.md's
|
|
37
|
+
# "Releasing" section.
|
|
13
38
|
#
|
|
14
39
|
# Note this is about *this plugin's* own distribution. The SDK it depends
|
|
15
40
|
# on, biffo-plugin-sdk, has its own release path upstream —
|
|
@@ -18,6 +43,11 @@ name: Release
|
|
|
18
43
|
# "biffo-plugin-sdk dependency" section for what that means for `uv sync`
|
|
19
44
|
# before the SDK's first release lands.
|
|
20
45
|
#
|
|
46
|
+
# Registry publishing is NOT done here. `publish-registry.yml` owns that,
|
|
47
|
+
# triggered by `biffo.plugin.json` changing on `dev` — i.e. when the version is
|
|
48
|
+
# merged, not when it is tagged. The marketplace should reflect `dev`, and
|
|
49
|
+
# tagging is an independent decision.
|
|
50
|
+
#
|
|
21
51
|
# Prefer PyPI Trusted Publishing (OIDC) over a long-lived PYPI_API_TOKEN
|
|
22
52
|
# when you set this up: it needs no stored secret to rotate or leak. The
|
|
23
53
|
# `id-token: write` permission below is already in place for it, and
|
|
@@ -44,11 +74,12 @@ env:
|
|
|
44
74
|
|
|
45
75
|
jobs:
|
|
46
76
|
release:
|
|
47
|
-
name:
|
|
77
|
+
name: Verify, Build & Release
|
|
48
78
|
runs-on: ${{ vars.RUNNER_LABEL || 'ubuntu-latest' }}
|
|
49
|
-
environment:
|
|
50
|
-
|
|
51
|
-
|
|
79
|
+
# No `environment: pypi` here: a job-level environment gates the WHOLE job,
|
|
80
|
+
# and this job now verifies and releases whether or not it publishes. If you
|
|
81
|
+
# enable PyPI and want environment protection on it, split publishing into
|
|
82
|
+
# its own job (passing dist/ as an artifact) and put the environment there.
|
|
52
83
|
steps:
|
|
53
84
|
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
|
|
54
85
|
with:
|
|
@@ -72,13 +103,24 @@ jobs:
|
|
|
72
103
|
id: version
|
|
73
104
|
run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
|
74
105
|
|
|
75
|
-
- name: Verify pyproject.toml
|
|
106
|
+
- name: Verify pyproject.toml and biffo.plugin.json match the tag
|
|
107
|
+
env:
|
|
108
|
+
TAG_VERSION: ${{ steps.version.outputs.version }}
|
|
76
109
|
run: |
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
110
|
+
set -euo pipefail
|
|
111
|
+
project_version="$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")"
|
|
112
|
+
manifest_version="$(python3 -c "import json; print(json.load(open('biffo.plugin.json'))['version'])")"
|
|
113
|
+
failed=0
|
|
114
|
+
if [ "$project_version" != "$TAG_VERSION" ]; then
|
|
115
|
+
echo "::error file=pyproject.toml::pyproject.toml version ($project_version) does not match tag $GITHUB_REF_NAME"
|
|
116
|
+
failed=1
|
|
117
|
+
fi
|
|
118
|
+
if [ "$manifest_version" != "$TAG_VERSION" ]; then
|
|
119
|
+
echo "::error file=biffo.plugin.json::biffo.plugin.json version ($manifest_version) does not match tag $GITHUB_REF_NAME. The plugin registry publishes this field, so releasing with it stale would leave the portal's plugin store advertising $manifest_version."
|
|
120
|
+
failed=1
|
|
81
121
|
fi
|
|
122
|
+
[ "$failed" -eq 0 ] || exit 1
|
|
123
|
+
echo "pyproject.toml, biffo.plugin.json and the tag all agree on $TAG_VERSION"
|
|
82
124
|
|
|
83
125
|
- name: Install python-semantic-release
|
|
84
126
|
run: uv tool install python-semantic-release
|
|
@@ -89,13 +131,12 @@ jobs:
|
|
|
89
131
|
- name: Build distribution
|
|
90
132
|
run: uv build
|
|
91
133
|
|
|
92
|
-
# PYPI PUBLISH — see the file-level comment above.
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
# until a PyPI project + trusted publisher (or PYPI_API_TOKEN) is set
|
|
97
|
-
# up for this specific plugin package.
|
|
134
|
+
# PYPI PUBLISH — opt-in; see the file-level comment above. Off until
|
|
135
|
+
# `PYPI_PUBLISH` is set, so a plugin with no PyPI project (which is every
|
|
136
|
+
# plugin, on the day it is scaffolded) gets a green release rather than
|
|
137
|
+
# one that always fails at the last step.
|
|
98
138
|
- name: Publish to PyPI
|
|
139
|
+
if: vars.PYPI_PUBLISH == 'true'
|
|
99
140
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
100
141
|
with:
|
|
101
142
|
# Prefer PyPI's trusted publishing (OIDC, via the `id-token: write`
|
|
@@ -107,6 +148,11 @@ jobs:
|
|
|
107
148
|
# up for this project yet.
|
|
108
149
|
skip-existing: true
|
|
109
150
|
|
|
151
|
+
- name: Note that PyPI publishing is not enabled
|
|
152
|
+
if: vars.PYPI_PUBLISH != 'true'
|
|
153
|
+
run: |
|
|
154
|
+
echo "::notice title=PyPI publish skipped::The PYPI_PUBLISH repository variable is not set to 'true', so the built distribution was not uploaded to PyPI. The GitHub Release below still carries it. See this workflow's header comment to enable."
|
|
155
|
+
|
|
110
156
|
- name: Create GitHub Release
|
|
111
157
|
uses: softprops/action-gh-release@v2
|
|
112
158
|
with:
|
|
@@ -219,14 +219,59 @@ equivalent `github_branch_protection` resource. If you're setting this up
|
|
|
219
219
|
by hand via the GitHub UI instead: **Settings → Branches → Add branch
|
|
220
220
|
protection rule**, pattern `main`, and set each field above to match.
|
|
221
221
|
|
|
222
|
-
##
|
|
223
|
-
|
|
224
|
-
`release.yml` triggers on `v*` tag pushes
|
|
225
|
-
the tagged commit, verifies the
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
222
|
+
## Releasing
|
|
223
|
+
|
|
224
|
+
`release.yml` triggers on `v*` tag pushes: it re-runs the full CI gate against
|
|
225
|
+
the tagged commit, verifies the versions agree, builds the sdist/wheel, and
|
|
226
|
+
cuts a GitHub Release.
|
|
227
|
+
|
|
228
|
+
### Three versions have to agree
|
|
229
|
+
|
|
230
|
+
Your plugin declares its version twice, and the tag is a third:
|
|
231
|
+
|
|
232
|
+
| where | what reads it |
|
|
233
|
+
| --- | --- |
|
|
234
|
+
| `pyproject.toml` `project.version` | the Python package |
|
|
235
|
+
| `biffo.plugin.json` `version` | the plugin registry — **and therefore the version the portal's plugin store shows** |
|
|
236
|
+
| the `v*` tag | the GitHub Release |
|
|
237
|
+
|
|
238
|
+
`release.yml` checks all three and fails the release on any mismatch.
|
|
239
|
+
|
|
240
|
+
The manifest one is the one to watch. It is easy to forget and it fails
|
|
241
|
+
silently: tag `v0.2.0` with a stale manifest and you get a `v0.2.0` release, a
|
|
242
|
+
`0.2.0` package, and a plugin store still advertising `0.1.0` — indefinitely,
|
|
243
|
+
with nothing anywhere reporting a problem. The registry sync re-derives
|
|
244
|
+
whatever the manifest says, so it propagates the stale value rather than
|
|
245
|
+
catching it. Bump both.
|
|
246
|
+
|
|
247
|
+
Note that the registry entry is published by `publish-registry.yml` when
|
|
248
|
+
`biffo.plugin.json` changes on `dev`, not by this workflow on a tag — the
|
|
249
|
+
marketplace reflects `dev`, and tagging is an independent decision.
|
|
250
|
+
|
|
251
|
+
### Getting into the plugin store
|
|
252
|
+
|
|
253
|
+
`publish-registry.yml` publishes your entry to the Biffo plugin registry, which
|
|
254
|
+
is what the portal's plugin store reads. It needs a `REGISTRY_PUBLISH_TOKEN`
|
|
255
|
+
secret (a fine-grained PAT with `contents: write` on the registry repo) and
|
|
256
|
+
warns-and-skips without one, so check its header comment before assuming your
|
|
257
|
+
plugin will appear.
|
|
258
|
+
|
|
259
|
+
If your repo is public you may not need the token: the registry also runs a
|
|
260
|
+
credential-free pull-based sync over the repos listed in its `sources.json`.
|
|
261
|
+
Adding yours there is enough.
|
|
262
|
+
|
|
263
|
+
### PyPI publishing is opt-in
|
|
264
|
+
|
|
265
|
+
The publish step is gated on a `PYPI_PUBLISH` repository variable and skips
|
|
266
|
+
with a notice until you set it, so a freshly-scaffolded plugin gets a green
|
|
267
|
+
release instead of one that always fails at the last step. The built
|
|
268
|
+
distribution still ships on the GitHub Release either way.
|
|
269
|
+
|
|
270
|
+
To enable it for your plugin:
|
|
271
|
+
|
|
272
|
+
1. Create the PyPI project.
|
|
273
|
+
2. Configure a **trusted publisher** (OIDC) for this repo at
|
|
274
|
+
`https://pypi.org/manage/project/<name>/settings/publishing/` — preferred
|
|
275
|
+
over a long-lived `PYPI_API_TOKEN`, since there is no stored secret to
|
|
276
|
+
rotate or leak. The `id-token: write` permission is already in place.
|
|
277
|
+
3. `gh variable set PYPI_PUBLISH --body true`
|