Skip to content

Commit ec3dd1e

Browse files
authored
new mb cli (#15)
1 parent 1f39fdc commit ec3dd1e

13 files changed

Lines changed: 331 additions & 24 deletions

‎.dockerignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,9 @@ action.yml
33
LICENSE
44
Makefile
55
readme.md
6+
CLAUDE.md
7+
VERSION
68
.gitignore
79
.github
10+
docs
11+
example
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
name: metablock-web action instructions
3+
applyTo: "**"
4+
description: Development guidance for the metablock web deployment github action - layout, the metablock client it wraps, inputs and CI.
5+
---
6+
7+
# metablock-web
8+
9+
A github action that ships a static bundle to an **html block** on
10+
[metablock](https://api.metablock.io/v1/docs) cloud — it deploys site content,
11+
not backend blocks, and block configuration belongs to the sibling
12+
`metablock-ingress` action. There is no application code in this repo: the
13+
action is a thin docker wrapper around the `metablock` CLI, so almost every
14+
change is to `Dockerfile`, `action.yml` or the workflows.
15+
16+
The repository was renamed from `metablock-action` to `metablock-web`; old
17+
references to the previous name are stale, not a second repo.
18+
19+
## Layout
20+
21+
| Path | Purpose |
22+
|---|---|
23+
| `Dockerfile` | installs the pinned `metablock[cli]` release the action runs |
24+
| `action.yml` | the action definition — inputs and the `metablock ship` argv |
25+
| `example/` | a vite/react app built and deployed as the CI smoke test |
26+
| `VERSION` | the action version, single source of truth for `make release` |
27+
| `docs/release-notes.md` | release bodies, published by the release workflow |
28+
29+
## The client it wraps
30+
31+
The action runs
32+
`metablock ship <bundle> --token ... --block-id ... --org ... --env ... --name ...`
33+
from the [metablock](https://pypi.org/project/metablock/) python client,
34+
developed in the sibling `metablock-py` repo. Two things follow from that:
35+
36+
- **The client version is pinned in the `Dockerfile`.** It used to track
37+
whatever was latest at image build time, which meant a client release changed
38+
the behaviour of every existing action tag. Bump the pin deliberately and cut
39+
a release for it.
40+
- **Every CLI option must have a matching input in `action.yml`.** When the CLI
41+
grows an option the action should expose, add the input and forward it in
42+
`args`. Inputs are passed unconditionally, so an unset input arrives as an
43+
empty string; the CLI treats that as "fall back to the environment variable"
44+
(`token or METABLOCK_API_TOKEN`), which is what makes the env-variable
45+
alternatives in the readme work. Do not add a default that would defeat that
46+
fallback — `bundle` and `env` have defaults on purpose, because the action's
47+
defaults differ from the CLI's (the CLI ships to `prod` unless told
48+
otherwise, the action defaults to `stage`).
49+
50+
Since version 2 of the client, the organization is selected by the
51+
`x-metablock-org-id` header rather than in the URL, so deployments to a block
52+
owned by an organization need `org` (or `METABLOCK_ORG_ID`) and fail with a
53+
`422` without it.
54+
55+
## CI
56+
57+
`build.yml` runs on every push: it builds the image, then builds `example/` and
58+
runs the action from the checkout (`uses: ./`) to deploy it — to `stage` on a
59+
branch, to `prod` on `main`. That job deploys for real, so it needs the
60+
`METABLOCK_API_TOKEN` and `METABLOCK_ORG_ID` secrets and it is a live test — a
61+
failure may reflect API state rather than a bug here.
62+
63+
`release.yml` is tag-driven, see [release.instructions.md](./release.instructions.md).
64+
65+
## Conventions
66+
67+
- Keep the readme's input list in step with `action.yml`; it has drifted before
68+
(it documented a `BUNDLE_LOCATION` variable the CLI does not read).
69+
- Bump `VERSION` and add release notes in the same change as the behaviour
70+
change, so a release is only ever a tag push.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
applyTo: 'Makefile'
3+
---
4+
5+
# Makefile Conventions
6+
7+
- Keep all targets sorted alphabetically.
8+
- `help` target is the exception and should be the first target in the file.
9+
- targets should be separated by a one blank line only.
10+
- Each target should have a one-line description, starting with `##`, that describes what the target does. This description is used by the `help` target to generate documentation for all targets.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
applyTo: 'VERSION,docs/release-notes.md,.github/workflows/release.yml,Makefile'
3+
---
4+
5+
# Release Instructions
6+
7+
Releases are driven by `v*` git tags. Pushing a tag triggers
8+
`.github/workflows/release.yml`, which checks the tag against `VERSION`, builds
9+
and deploys `example/` as a smoke test, then extracts the matching `## vX.Y.Z`
10+
section from `docs/release-notes.md` and publishes it as the GitHub Release body
11+
(re-runs update the existing release instead of failing). Finally it moves the
12+
floating major tag (`v5` for a `v5.x.y` release) to the released commit, so
13+
consumers pinned to `@v5` pick the release up.
14+
15+
## Cutting a release
16+
17+
1. Bump `VERSION`. That is the single source of truth — the release workflow
18+
refuses to publish a tag that does not match it. Note this versions the
19+
*action*, not the `metablock` client it installs; the client pin lives in the
20+
`Dockerfile` and moves independently.
21+
2. Add a `## vX.Y.Z` section at the top of `docs/release-notes.md` with the
22+
notes for the release. The header text is matched verbatim by the workflow's
23+
`awk` extractor, so it must be `## vX.Y.Z` exactly (no trailing dash, no
24+
title after the version). The release workflow fails if this section is
25+
missing.
26+
3. Commit and merge to `main`; let the `build` workflow pass.
27+
4. From `main`, run `make release` — it reads `VERSION`, asks for confirmation,
28+
then creates an annotated `vX.Y.Z` tag and pushes it. The `release` workflow
29+
takes it from there.
30+
31+
Do not move the major tag by hand — the workflow owns it, and a manual push
32+
will be overwritten by the next release.
33+
34+
Note the smoke test deploys the example bundle through the live metablock API,
35+
so a release needs valid `METABLOCK_API_TOKEN` and `METABLOCK_ORG_ID` secrets
36+
and will fail if the API is down.
37+
38+
## Release-notes conventions
39+
40+
The `## vX.Y.Z` section in `docs/release-notes.md` is published verbatim as the
41+
GitHub Release body, so it must follow these conventions:
42+
43+
- Open with a one-paragraph summary describing the theme of the release. If the
44+
release contains breaking changes, point readers to the **Breaking changes**
45+
section in that paragraph. A bump of the `metablock` client pin is worth
46+
naming in that paragraph, since it is what changes the action's behaviour.
47+
- Group entries under H3 subsections in this order: `### Breaking changes`,
48+
`### New features`, `### Improvements and fixes`,
49+
`### Documentation and assets`. Omit any subsection that has no entries.
50+
- Every PR reference must be a markdown link of the form
51+
`[#NN](https://github.com/quantmind/metablock-web/pull/NN)` — never a bare
52+
`(#NN)`. GitHub's auto-linking only works in some contexts, and the explicit
53+
URL works everywhere. When one entry references multiple PRs, list them
54+
comma-separated inside one set of parentheses, each as its own link.
55+
- Build the PR list by running `git log vPREV..HEAD --oneline` against the
56+
previous release tag and following each squashed-merge commit back to its PR.
57+
Cross-check with `gh pr list --state merged --base main` for any PRs merged
58+
since the previous tag.
59+
- End the section with a
60+
`[Full changelog](https://github.com/quantmind/metablock-web/compare/vPREV...vX.Y.Z)`
61+
link comparing the new tag against the previous one.

‎.github/workflows/build.yml‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,20 @@ jobs:
99

1010
steps:
1111
- name: checkout repo
12-
uses: actions/checkout@v4
12+
uses: actions/checkout@v6
1313
- run: make image
1414

1515
test:
1616
runs-on: ubuntu-latest
1717
env:
1818
METABLOCK_API_TOKEN: ${{ secrets.METABLOCK_API_TOKEN }}
19+
METABLOCK_ORG_ID: ${{ secrets.METABLOCK_ORG_ID }}
1920
METABLOCK_BLOCK_ID: 85ef26bff9f0454aa6ff0516b6d52420
2021

2122
steps:
2223
- name: checkout repo
2324
uses: actions/checkout@v6
24-
- name: build example
25+
- name: setup node
2526
uses: actions/setup-node@v6
2627
with:
2728
node-version: 22

‎.github/workflows/release.yml‎

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
name: release
2+
3+
# Triggered by `v*` tags (push one with `make release`). Checks the tag against
4+
# VERSION, smoke-tests the action by deploying the example bundle to stage, then
5+
# extracts the matching section from docs/release-notes.md and publishes it as
6+
# the GitHub Release body. Re-runs idempotently update an existing release
7+
# rather than failing. Finally the floating major tag (v5) is moved to the
8+
# released commit so `uses: quantmind/metablock-web@v5` picks the release up.
9+
10+
on:
11+
push:
12+
tags:
13+
- "v*"
14+
15+
permissions:
16+
contents: write
17+
18+
jobs:
19+
release:
20+
runs-on: ubuntu-latest
21+
timeout-minutes: 10
22+
env:
23+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25+
METABLOCK_API_TOKEN: ${{ secrets.METABLOCK_API_TOKEN }}
26+
METABLOCK_ORG_ID: ${{ secrets.METABLOCK_ORG_ID }}
27+
METABLOCK_BLOCK_ID: 85ef26bff9f0454aa6ff0516b6d52420
28+
29+
steps:
30+
- uses: actions/checkout@v6
31+
- name: Check tag matches VERSION
32+
run: |
33+
TAG="${GITHUB_REF_NAME}"
34+
VERSION="v$(cat VERSION)"
35+
if [ "$TAG" != "$VERSION" ]; then
36+
echo "Tag ${TAG} does not match VERSION (${VERSION})" >&2
37+
exit 1
38+
fi
39+
- name: setup node
40+
uses: actions/setup-node@v6
41+
with:
42+
node-version: 22
43+
- name: build example
44+
run: make build-example
45+
- name: stage deployment
46+
uses: ./
47+
with:
48+
env: stage
49+
bundle: example/dist
50+
- name: Extract release notes
51+
run: |
52+
TAG="${GITHUB_REF_NAME}"
53+
awk -v tag="## ${TAG}" '
54+
$0 == tag { in_section = 1; next }
55+
/^## / && in_section { exit }
56+
in_section { print }
57+
' docs/release-notes.md > /tmp/notes.md
58+
if [ ! -s /tmp/notes.md ]; then
59+
echo "No section '## ${TAG}' found in docs/release-notes.md" >&2
60+
exit 1
61+
fi
62+
echo "--- extracted notes for ${TAG} ---"
63+
cat /tmp/notes.md
64+
- name: Publish GitHub Release
65+
run: |
66+
TAG="${GITHUB_REF_NAME}"
67+
if gh release view "$TAG" >/dev/null 2>&1; then
68+
gh release edit "$TAG" --notes-file /tmp/notes.md
69+
else
70+
gh release create "$TAG" --title "$TAG" --notes-file /tmp/notes.md
71+
fi
72+
- name: Move major tag
73+
run: |
74+
TAG="${GITHUB_REF_NAME}"
75+
MAJOR="${TAG%%.*}"
76+
git tag -f "$MAJOR" "$GITHUB_SHA"
77+
git push origin -f "$MAJOR"

‎CLAUDE.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@readme.md
2+
@.github/instructions/action.instructions.md
3+
@.github/instructions/makefile.instructions.md
4+
@.github/instructions/release.instructions.md

‎Dockerfile‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
FROM python:3.13-slim
1+
FROM python:3.14-slim
22

33
WORKDIR /action
44

5-
RUN pip install metablock[cli]==1.1
5+
RUN pip install "metablock[cli]==2.0.0"

‎Makefile‎

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,30 @@
1-
.PHONY: help update
2-
31
.PHONY: help
42
help:
53
@echo ======================================================================================
64
@fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//'
75
@echo ======================================================================================
86

7+
.PHONY: build-example
8+
build-example: ## build the example bundle deployed by CI
9+
@cd example && npm i && npm run build
910

1011
.PHONY: image
11-
image: ## Build image
12-
@docker build -t metablock-action .
12+
image: ## build the action docker image
13+
@docker build -t metablock-web .
14+
15+
.PHONY: release
16+
release: ## tag current version (from VERSION) and push
17+
$(eval VERSION := $(shell cat VERSION))
18+
@read -p "Tagging with v$(VERSION), are you sure? [Y/n] " ans; \
19+
ans=$${ans:-Y}; \
20+
if [ "$$ans" = "Y" ] || [ "$$ans" = "y" ]; then \
21+
git tag -a v$(VERSION) -m "v$(VERSION)" && git push origin v$(VERSION); \
22+
else \
23+
echo "Aborted."; \
24+
fi
1325

1426
.PHONY: terminal
15-
terminal: ## Terminal
27+
terminal: ## open a shell in the action image
1628
@docker run -it --rm \
1729
-v $(PWD):/action \
18-
metablock-action bash
19-
20-
21-
.PHONY: build-example
22-
build-example: ## Build example
23-
@cd example && npm i && npm run build
30+
metablock-web bash

‎VERSION‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
5.1.0

0 commit comments

Comments
 (0)