Chemenu 2.1.0 - deterministischer Wissenskompiler
Chemenu kompiliert Rohnotizen zu einem verlinkten, quellengebundenen Wiki: raw/ -> types/ + tools/ -> kb/ -> reports/. Was mechanisch ist, macht tools/wikitool; was Urteil braucht, macht ein Agent unter Contracts, deren Grenzen in Code durchgesetzt sind statt im Prompt. Dieser Commit ist der Startpunkt der oeffentlichen Historie. Die vorherige Entwicklung fand in einer privaten Instanz statt und ist nicht Teil dieses Repositorys; ihre Erzaehlung steht vollstaendig in CHANGES.md, das mit 44 Eintraegen von 0.1.0 bis 2.1.0 erhalten geblieben ist. Der mitgelieferte Korpus ist ein Testbett und eine Demo: 170 Seiten ueber den Stack selbst - Gates, Lint, Versionierung, Suche, das Wiki-Muster. Er dokumentiert das Werkzeug mit den eigenen Mitteln des Werkzeugs. Lizenz: AGPL-3.0 fuer den Stack (tools/, types/), CC-BY-4.0 fuer die Inhalte. Die Grenze zwischen beiden ist der Dateiplan, den dist export berechnet - siehe NOTICE.
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
# CI for the wiki stack.
|
||||
#
|
||||
# One job, stopping at the first failure - the stack has no artifact to build
|
||||
# and nothing to deploy, so the pipeline's whole job is "does the machinery
|
||||
# still hold together, and does the distribution it produces still work".
|
||||
#
|
||||
# Runner: `linux-docker` is one of this Gitea instance's three routing labels
|
||||
# (alongside `container-builder` and `k3s-deploy`). The job image is named
|
||||
# explicitly rather than inherited from the runner's label mapping, which is
|
||||
# not documented anywhere: `debian:trixie-slim` is the base the instance's
|
||||
# container-build workflows already use, and Trixie's python3 is 3.13, past
|
||||
# the 3.11 floor `doctor` enforces.
|
||||
#
|
||||
# Pinning that image makes `nodejs` this workflow's own responsibility.
|
||||
# `actions/checkout` is a JavaScript action, and act_runner runs it with `node`
|
||||
# *inside the job container* - a slim Debian has none, and the job dies with
|
||||
# exit 127 before any step of ours runs. The shape below (apt `nodejs` first,
|
||||
# then `checkout@v7`) is the one proven on this instance by
|
||||
# torben/gitea-mcp@ci-build, workflow `ci-build.yaml`, runs 42-45.
|
||||
#
|
||||
# Triggers: content commits are excluded. `publish` touches kb/, raw/ and work/
|
||||
# and never the stack, so running the suite for them would be pure noise. The
|
||||
# exclusions are deliberately literal rather than a `!**/CONTRACT.md` negation,
|
||||
# whose support in Gitea's filter matching is unverified: every pattern here
|
||||
# names content, so anything unanticipated still triggers CI. The list is
|
||||
# repeated rather than shared through a YAML anchor for the same reason -
|
||||
# GitHub's parser rejects anchors outright, and Gitea's is not documented to
|
||||
# accept them. `kb/CONTRACT.md` is absent on purpose: it is a stack file that
|
||||
# happens to live under a content directory, and it must keep its CI.
|
||||
#
|
||||
# That the filter works is now observed, not assumed (Gitea issue #11): commit
|
||||
# f916376 published only kb/ and raw/ paths and produced no run at all, while
|
||||
# the stack commits on either side of it (adfa220, 40adbb7) each produced two.
|
||||
# Gitea evaluates these patterns the way GitHub does. Do not re-derive this.
|
||||
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- 'kb/*/**'
|
||||
- 'kb/index.md'
|
||||
- 'kb/log.md'
|
||||
- 'kb/provenance.md'
|
||||
- 'raw/*/**'
|
||||
- 'work/*/**'
|
||||
- 'reports/*/**'
|
||||
pull_request:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- 'kb/*/**'
|
||||
- 'kb/index.md'
|
||||
- 'kb/log.md'
|
||||
- 'kb/provenance.md'
|
||||
- 'raw/*/**'
|
||||
- 'work/*/**'
|
||||
- 'reports/*/**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
verify:
|
||||
runs-on: linux-docker
|
||||
container:
|
||||
image: debian:trixie-slim
|
||||
env:
|
||||
# Scope the Iteration Budget Gate to this run instead of letting it fall
|
||||
# back to the parent PID, and keep the trace out of the checkout so the
|
||||
# working tree stays clean for the ignore-rule checks.
|
||||
WIKITOOL_SESSION_ID: ci-${{ github.run_id }}
|
||||
WIKI_TRACE_DIR: /tmp/wikitool-trace
|
||||
DIST_DIR: /tmp/dist
|
||||
|
||||
steps:
|
||||
- name: System dependencies
|
||||
# `nodejs` is not for us - it is what act_runner needs to execute the
|
||||
# JavaScript action in the next step. It has to be installed before the
|
||||
# checkout, which is why this step comes first.
|
||||
run: |
|
||||
set -eu
|
||||
apt-get update -qq
|
||||
apt-get install -y --no-install-recommends \
|
||||
python3 python3-venv git nodejs ripgrep ca-certificates
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
# The version gate diffs against the pushed range's base, so the
|
||||
# shallow default clone is not enough.
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Tool environment
|
||||
run: |
|
||||
set -eu
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
python3 -m venv tools/.venv
|
||||
tools/.venv/bin/pip install --quiet --upgrade pip
|
||||
tools/.venv/bin/pip install --quiet -r tools/requirements.txt
|
||||
# pytest-cov is CI-only: tools/requirements.txt describes what an
|
||||
# *instance* needs at runtime and ships with `dist export`, and an
|
||||
# instance does not measure this suite. Installed beside pytest for
|
||||
# the same reason pytest itself is.
|
||||
tools/.venv/bin/pip install --quiet pytest pytest-cov
|
||||
|
||||
- name: Tests
|
||||
# Not run with WIKI_TRACE=0: two telemetry tests assert that a trace is
|
||||
# written, and disabling the emitter globally fails them. The suite
|
||||
# redirects WIKI_TRACE_DIR per test on its own.
|
||||
#
|
||||
# One run, not two. This job used to be the only place the suite met a
|
||||
# machine with no global git configuration, which is how Gitea #8 was
|
||||
# found - two tests that silently read the developer's `git config
|
||||
# user.name`. That hole is now closed in the suite itself: the autouse
|
||||
# `hermetic_environment` fixture gives every test an empty HOME, no
|
||||
# git configuration and none of the tool's own environment, so this
|
||||
# container is no longer a special environment worth a second run.
|
||||
# See instructions/dev/testing-conventions.md.
|
||||
#
|
||||
# Coverage is reported, not enforced: there is deliberately no
|
||||
# `--cov-fail-under` yet (Gitea #10). The threshold gets set in its own
|
||||
# later commit, with the measured number as its justification - one
|
||||
# picked before the number is either too low to bite or too high to
|
||||
# survive the next honest commit, and the second kind gets lowered
|
||||
# instead of earned. Config: tools/.coveragerc.
|
||||
run: |
|
||||
set -eu
|
||||
cd tools
|
||||
.venv/bin/python -m pytest -q \
|
||||
--cov --cov-report=term --cov-report=xml --cov-report=html
|
||||
|
||||
- name: Coverage report
|
||||
# `always()`: a red suite is exactly when the per-module numbers are
|
||||
# worth reading, and the upload must not disappear with the failure.
|
||||
# v3, not v4 - v4 is restricted on this Gitea instance; v3 is what is
|
||||
# proven here (torben/gitea-mcp@ci-build, ci-build.yaml, runs
|
||||
# 42-45).
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: coverage-${{ github.run_id }}
|
||||
path: |
|
||||
tools/coverage.xml
|
||||
tools/htmlcov/
|
||||
retention-days: 14
|
||||
|
||||
- name: Verify the development tree
|
||||
run: |
|
||||
set -eu
|
||||
tools/wikitool docs verify
|
||||
tools/wikitool instructions verify
|
||||
tools/wikitool lint --fail-on-error
|
||||
|
||||
- name: Version gate
|
||||
# A stack change with no version bump cannot be released, because the
|
||||
# release would carry a change nobody named. Scoped to what
|
||||
# `dist export` actually ships as behaviour - prose docs and these
|
||||
# workflows are not in it, and a typo fix should not force a bump.
|
||||
env:
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
BEFORE_SHA: ${{ github.event.before }}
|
||||
run: |
|
||||
set -eu
|
||||
base="${BASE_SHA:-${BEFORE_SHA:-}}"
|
||||
case "$base" in
|
||||
""|0000000000000000000000000000000000000000)
|
||||
echo "No base commit to compare against - skipping the version gate."
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
if ! git cat-file -e "${base}^{commit}" 2>/dev/null; then
|
||||
echo "Base commit $base is not in this clone - skipping the version gate."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
changed="$(git diff --name-only "$base" HEAD)"
|
||||
stack="$(printf '%s\n' "$changed" \
|
||||
| grep -E '^(tools/|types/|instructions/|AGENTS\.md$|[^/]+/CONTRACT\.md$)' || true)"
|
||||
|
||||
if [ -z "$stack" ]; then
|
||||
echo "No stack paths touched - no version bump required."
|
||||
exit 0
|
||||
fi
|
||||
if printf '%s\n' "$changed" | grep -qx 'VERSION'; then
|
||||
echo "Stack changed, and VERSION moved to $(cat VERSION)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Stack paths changed without a VERSION bump:"
|
||||
printf '%s\n' "$stack" | sed 's/^/ /'
|
||||
echo ""
|
||||
echo 'Fix: tools/wikitool version bump --patch --title "<what changed>"'
|
||||
echo 'Then `docs verify` holds VERSION and CHANGES.md together.'
|
||||
exit 1
|
||||
|
||||
- name: Export the distribution
|
||||
run: tools/wikitool dist export "$DIST_DIR"
|
||||
|
||||
- name: The distribution works as a fresh instance
|
||||
# Replays instructions/setup-instance.md end to end, minus its four
|
||||
# interactive decision points. What this tests is the release artifact
|
||||
# as an artifact: the documented path from an unpacked export to a
|
||||
# verified instance. Running one `instructions verify` against the
|
||||
# export would only have re-checked the file it just copied.
|
||||
#
|
||||
# Personalization is stubbed the same way the identity is: the real
|
||||
# step interviews the user, so CI substitutes a fixed answer - here,
|
||||
# the template minus its sentinel line. That is deliberately the
|
||||
# cheapest thing `doctor`'s personalization check accepts, because
|
||||
# what is under test is that the export *carries* the templates, not
|
||||
# what a person would write into them.
|
||||
run: |
|
||||
set -eu
|
||||
cd "$DIST_DIR"
|
||||
git init -q -b main
|
||||
git config user.name "CI Instance"
|
||||
git config user.email "ci@example.invalid"
|
||||
for personal in USER SOUL; do
|
||||
grep -v 'wikitool:template-unfilled' "$personal.md.template" > "$personal.md"
|
||||
done
|
||||
python3 -m venv tools/.venv
|
||||
tools/.venv/bin/pip install --quiet -r tools/requirements.txt
|
||||
tools/wikitool instructions sync
|
||||
tools/wikitool index rebuild
|
||||
tools/wikitool sources rebuild-index
|
||||
tools/wikitool doctor
|
||||
tools/wikitool docs verify
|
||||
tools/wikitool instructions verify
|
||||
tools/wikitool lint --fail-on-error
|
||||
tools/wikitool version show
|
||||
# A fresh instance owes no migration: dist export declares its content
|
||||
# version, so `status` must answer rather than ask for a baseline.
|
||||
tools/wikitool migrate status
|
||||
@@ -0,0 +1,104 @@
|
||||
# Nightly drift check.
|
||||
#
|
||||
# CI (`ci.yml`) runs on push and deliberately ignores content paths, because
|
||||
# `publish` touches kb/, raw/ and work/ on every ingest and running the suite
|
||||
# for that is noise. That exclusion is observed to work (Gitea #11), which is
|
||||
# exactly why this file exists: since it landed, `lint --fail-on-error` no
|
||||
# longer runs when the *corpus* changes. A wiki that drifts into inconsistency
|
||||
# over a run of publishes would be seen by nobody.
|
||||
#
|
||||
# There is also drift that happens with no commit at all. `sources coverage`
|
||||
# starts reporting the moment a file appears under `raw/` without a source page
|
||||
# claiming it, and `migrate status` only answers when something asks.
|
||||
#
|
||||
# So: the same checks CI runs against the tree, on a clock instead of a push.
|
||||
# `lint --fail-on-error` is the reason; the rest costs seconds.
|
||||
#
|
||||
# Deliberately absent: `migrate verify --from <rev>`. It needs a comparison
|
||||
# revision that means something, and "yesterday" is not one - the invariant
|
||||
# diff answers "did *this migration* lose anything", not "did anything change
|
||||
# since yesterday". In normal operation a changed page is the desired outcome,
|
||||
# not a finding.
|
||||
#
|
||||
# Runner shape: identical to ci.yml, and for the same reason - `nodejs` is what
|
||||
# act_runner needs to execute the JavaScript `checkout` action *inside* the job
|
||||
# container, so it is installed before the checkout or the job dies with exit
|
||||
# 127. Do not re-derive this; see the header of ci.yml.
|
||||
#
|
||||
# Failure is meant to be visible without opening the Actions page. That is
|
||||
# Gitea's own run notification, not something this workflow builds: a job that
|
||||
# files its own issue needs an Actions token with issue-write and a dedup rule,
|
||||
# which is more machinery than a red run already carries.
|
||||
|
||||
name: Nightly
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# 03:17 UTC. Gitea evaluates cron in UTC and only for workflows on the
|
||||
# default branch, so this file has to live on `main` to fire at all - a
|
||||
# test branch proves nothing about it.
|
||||
- cron: '17 3 * * *'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
drift:
|
||||
runs-on: linux-docker
|
||||
container:
|
||||
image: debian:trixie-slim
|
||||
env:
|
||||
# Scope the Iteration Budget Gate to this run instead of letting it fall
|
||||
# back to the parent PID, and keep the trace out of the checkout so the
|
||||
# working tree stays clean.
|
||||
WIKITOOL_SESSION_ID: nightly-${{ github.run_id }}
|
||||
WIKI_TRACE_DIR: /tmp/wikitool-trace
|
||||
|
||||
steps:
|
||||
- name: System dependencies
|
||||
run: |
|
||||
set -eu
|
||||
apt-get update -qq
|
||||
apt-get install -y --no-install-recommends \
|
||||
python3 python3-venv git nodejs ripgrep ca-certificates
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Tool environment
|
||||
# More than `ci.yml`'s equivalent, because this job runs `doctor` and
|
||||
# `ci.yml` does not. `doctor` asks whether a *working instance* is
|
||||
# correctly configured, and a bare checkout is not one yet - it is the
|
||||
# fresh clone instructions/bootstrap.md describes. Two of its checks
|
||||
# answered for the container instead of for the repository on the first
|
||||
# run (#9): `git-identity` found no `user.name`, and `skills` found
|
||||
# nothing published, because `.agents/skills/` and `.claude/skills/`
|
||||
# are generated and deliberately not committed. So the bootstrap runs
|
||||
# first, and `doctor` then reports on an instance rather than on a
|
||||
# tarball. `instructions verify` needs the same, for the same reason.
|
||||
run: |
|
||||
set -eu
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
git config --global user.name "Nightly"
|
||||
git config --global user.email "nightly@example.invalid"
|
||||
python3 -m venv tools/.venv
|
||||
tools/.venv/bin/pip install --quiet --upgrade pip
|
||||
tools/.venv/bin/pip install --quiet -r tools/requirements.txt
|
||||
tools/wikitool instructions sync
|
||||
|
||||
- name: The instance is still correctly configured
|
||||
run: tools/wikitool doctor
|
||||
|
||||
- name: The stack still describes itself
|
||||
run: |
|
||||
set -eu
|
||||
tools/wikitool docs verify
|
||||
tools/wikitool instructions verify
|
||||
|
||||
- name: The corpus is still structurally sound
|
||||
# The one check push-driven CI no longer performs on a content commit.
|
||||
run: tools/wikitool lint --fail-on-error
|
||||
|
||||
- name: Every raw file is still claimed, and the content shape declared
|
||||
run: |
|
||||
set -eu
|
||||
tools/wikitool sources coverage
|
||||
tools/wikitool migrate status
|
||||
@@ -0,0 +1,142 @@
|
||||
# Publish a release when VERSION moves on main.
|
||||
#
|
||||
# The release artifact is exactly a `dist export` tree, packed with a top-level
|
||||
# directory: unpack it, run instructions/setup-instance.md, and there is a
|
||||
# working wiki instance - no checkout of this repo required. CI already proved
|
||||
# that path works before this workflow ever runs.
|
||||
#
|
||||
# The tag is created here, by CI, and never by an agent: AGENTS.md invariant 5
|
||||
# ("never call raw git commit/push") stays intact because nothing in a session
|
||||
# has to tag anything.
|
||||
#
|
||||
# Auth is `${{ gitea.token }}` - the short-lived per-job token this Gitea
|
||||
# instance issues - not a 1Password secret. Nothing here reaches outside the
|
||||
# instance, so the Zero-Trust secret path that the container-build workflows
|
||||
# use has nothing to carry.
|
||||
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- VERSION
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: linux-docker
|
||||
container:
|
||||
image: debian:trixie-slim
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
WIKITOOL_SESSION_ID: release-${{ github.run_id }}
|
||||
WIKI_TRACE_DIR: /tmp/wikitool-trace
|
||||
BUILD_DIR: /tmp/build
|
||||
# The address a *reader* uses. `github.server_url` is whatever the runner
|
||||
# registered against (an internal one here), which is right for the API
|
||||
# call below and wrong for a URL baked into every distributed instance.
|
||||
PUBLIC_BASE_URL: https://gitea.nehmer.net
|
||||
|
||||
steps:
|
||||
- name: System dependencies
|
||||
# `nodejs` is for act_runner, not for us - see the note in ci.yml.
|
||||
run: |
|
||||
set -eu
|
||||
apt-get update -qq
|
||||
apt-get install -y --no-install-recommends \
|
||||
python3 python3-venv git nodejs ripgrep ca-certificates curl jq tar gzip
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Tool environment
|
||||
run: |
|
||||
set -eu
|
||||
git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
python3 -m venv tools/.venv
|
||||
tools/.venv/bin/pip install --quiet --upgrade pip
|
||||
tools/.venv/bin/pip install --quiet -r tools/requirements.txt
|
||||
|
||||
- name: Resolve the version and refuse to re-release it
|
||||
id: version
|
||||
env:
|
||||
API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
|
||||
TOKEN: ${{ gitea.token }}
|
||||
run: |
|
||||
set -eu
|
||||
version="$(cat VERSION | tr -d '[:space:]')"
|
||||
tag="v${version}"
|
||||
echo "version=${version}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
status="$(curl -s -o /dev/null -w '%{http_code}' \
|
||||
-H "Authorization: token ${TOKEN}" "${API}/releases/tags/${tag}")"
|
||||
if [ "$status" = "200" ]; then
|
||||
echo "Release ${tag} already exists. VERSION was touched without being raised;"
|
||||
echo 'bump it with `tools/wikitool version bump` instead of re-releasing.'
|
||||
exit 1
|
||||
fi
|
||||
echo "No release ${tag} yet - proceeding."
|
||||
|
||||
- name: Release notes from CHANGES.md
|
||||
# `version notes` fails when the changelog has no entry for this
|
||||
# version, which is the last place that mistake can still be caught.
|
||||
run: |
|
||||
set -eu
|
||||
tools/wikitool docs verify
|
||||
tools/wikitool version notes > /tmp/release-notes.md
|
||||
cat /tmp/release-notes.md
|
||||
|
||||
- name: Build the distribution tarball
|
||||
id: build
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
TAG: ${{ steps.version.outputs.tag }}
|
||||
run: |
|
||||
set -eu
|
||||
name="chemenu-stack-${VERSION}"
|
||||
mkdir -p "$BUILD_DIR"
|
||||
tools/wikitool dist export "${BUILD_DIR}/${name}" \
|
||||
--source-repo "${PUBLIC_BASE_URL}/${GITHUB_REPOSITORY}" \
|
||||
--source-commit "${GITHUB_SHA}" \
|
||||
--release-url "${PUBLIC_BASE_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG}" \
|
||||
--update-url "${PUBLIC_BASE_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/latest"
|
||||
|
||||
tar -czf "${BUILD_DIR}/${name}.tar.gz" -C "$BUILD_DIR" "$name"
|
||||
( cd "$BUILD_DIR" && sha256sum "${name}.tar.gz" > "${name}.tar.gz.sha256" )
|
||||
cat "${BUILD_DIR}/${name}.tar.gz.sha256"
|
||||
echo "name=${name}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Publish the release
|
||||
env:
|
||||
API: ${{ github.server_url }}/api/v1/repos/${{ github.repository }}
|
||||
TOKEN: ${{ gitea.token }}
|
||||
TAG: ${{ steps.version.outputs.tag }}
|
||||
NAME: ${{ steps.build.outputs.name }}
|
||||
run: |
|
||||
set -eu
|
||||
# Creating the release creates the tag, pinned to this commit.
|
||||
payload="$(jq -n \
|
||||
--arg tag "$TAG" \
|
||||
--arg target "$GITHUB_SHA" \
|
||||
--arg name "$TAG" \
|
||||
--rawfile body /tmp/release-notes.md \
|
||||
'{tag_name: $tag, target_commitish: $target, name: $name, body: $body,
|
||||
draft: false, prerelease: false}')"
|
||||
|
||||
release="$(curl -sS -f -X POST "${API}/releases" \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$payload")"
|
||||
id="$(printf '%s' "$release" | jq -r '.id')"
|
||||
echo "Created release ${TAG} (id ${id})."
|
||||
|
||||
for asset in "${NAME}.tar.gz" "${NAME}.tar.gz.sha256"; do
|
||||
curl -sS -f -X POST "${API}/releases/${id}/assets?name=${asset}" \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
-F "attachment=@${BUILD_DIR}/${asset}" > /dev/null
|
||||
echo "Uploaded ${asset}."
|
||||
done
|
||||
|
||||
echo "Done: ${PUBLIC_BASE_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG}"
|
||||
Reference in New Issue
Block a user