Merge pull request 'Add the release workflow (MIL-004 task 5)' (#80) from mil-004-release-workflow into main
Validate workflows / validate (push) Successful in 15s
TirSystem/github-action: Publish wiki / publish-wiki (push) Successful in 11s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s

This commit was merged in pull request #80.
This commit is contained in:
2026-10-10 17:02:00 +02:00
9 changed files with 523 additions and 13 deletions
+41
View File
@@ -0,0 +1,41 @@
name: Release
# Releases this repository (ADR-0006, ADR-0007, MIL-004 task 5). It is started
# by hand with the version to release; it creates the version tag, the release
# page from the changelog entry and moves the major tag, and only when the
# commit is the tip of main, its validation run is green and the changelog has
# an entry for the version. dry_run defaults to true: it checks and prints and
# writes nothing, so a release is a deliberate second run with dry_run false.
on:
workflow_dispatch:
inputs:
version:
description: "Version to release, MAJOR.MINOR.PATCH without a leading v"
required: true
dry_run:
description: "true checks only; false creates the tag, the release page and the major tag"
required: false
default: "true"
concurrency:
group: release
cancel-in-progress: false
jobs:
release:
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- name: Check out the repository
uses: actions/checkout@v4
- name: Check and publish the release
env:
GITEA_API_URL: ${{ gitea.api_url }}
SOURCE_REPOSITORY: ${{ gitea.repository }}
RELEASE_CREDENTIALS: ${{ secrets.TOKEN_FOR_GITEA_RELEASE || github.token }}
RELEASE_VERSION: ${{ inputs.version }}
RELEASE_DRY_RUN: ${{ inputs.dry_run }}
RELEASE_SHA: ${{ gitea.sha }}
run: python3 tools/release.py
+4
View File
@@ -25,6 +25,10 @@ tagged the workflows are delivered from `main` and are not pinned.
### Added ### Added
- Workflow `release.yml`: started by hand with a version, it releases only the tip of
`main` whose validation run is green and whose changelog entry exists, creates
the version tag, the release page and the major tag, and does nothing but
check until `dry_run` is set to `false`.
- Scoped workflow `sync-github-metadata.yml`: copies the description and topics - Scoped workflow `sync-github-metadata.yml`: copies the description and topics
of a Gitea repository to its GitHub push mirror on every push to `main`, by of a Gitea repository to its GitHub push mirror on every push to `main`, by
hand and daily at 03:17 UTC. A repository without a GitHub mirror is skipped hand and daily at 03:17 UTC. A repository without a GitHub mirror is skipped
+1 -1
View File
@@ -68,7 +68,7 @@ Rows that say Implemented or Guides written exist today. The rest is planned and
| Onboard a repository, manage runners, diagnose failures | Guides written ([onboarding](guides/onboarding.md), [troubleshooting](guides/troubleshooting.md)); scoped delivery to other repositories not yet verified; brief use cases open (MIL-002) | | Onboard a repository, manage runners, diagnose failures | Guides written ([onboarding](guides/onboarding.md), [troubleshooting](guides/troubleshooting.md)); scoped delivery to other repositories not yet verified; brief use cases open (MIL-002) |
| Publish a repository wiki from `wiki/` (opt-in, delivered to every organization) | Implemented in `.gitea/scoped_workflows/publish-wiki.yml`, with offline tests; the pilot in a second repository is pending (MIL-006) | | Publish a repository wiki from `wiki/` (opt-in, delivered to every organization) | Implemented in `.gitea/scoped_workflows/publish-wiki.yml`, with offline tests; the pilot in a second repository is pending (MIL-006) |
| Shared quality checks (Python and shell) | Implemented as the reusable workflow `.gitea/workflows/quality-checks.yml`, with offline tests and a trial in a second organization; adoption in RepoFoundry is in review ([guide](guides/quality-checks.md)) | | Shared quality checks (Python and shell) | Implemented as the reusable workflow `.gitea/workflows/quality-checks.yml`, with offline tests and a trial in a second organization; adoption in RepoFoundry is in review ([guide](guides/quality-checks.md)) |
| Versioned release and publishing | Release process, changelog and upgrade guide defined; the first release `v1.0.0` and the release workflow are pending (MIL-004) ([guide](guides/upgrading.md)) | | Versioned release and publishing | Release process, changelog and upgrade guide defined; the release workflow exists; the first release `v1.0.0` is pending (MIL-004) ([guide](guides/upgrading.md)) |
### 🔄 Sync GitHub metadata ### 🔄 Sync GitHub metadata
+17 -7
View File
@@ -5,7 +5,8 @@ is validated, versioned and announced, and how consumers pin a version. The
decisions are in ADR-0006; this guide is the checklist. It is not published decisions are in ADR-0006; this guide is the checklist. It is not published
on the wiki. on the wiki.
Until the release workflow exists (MIL-004 task 5) the steps are done by hand. Steps 5 and 6 are done by the **Release** workflow (`.gitea/workflows/release.yml`,
`tools/release.py`); the manual commands below are the fallback.
## What a version is ## What a version is
@@ -34,17 +35,26 @@ Until the release workflow exists (MIL-004 task 5) the steps are done by hand.
new major version, and a new major version needs a Breaking entry. new major version, and a new major version needs a Breaking entry.
4. **Get the approval** of S01 for the release, in the pull request that adds 4. **Get the approval** of S01 for the release, in the pull request that adds
the changelog entry. the changelog entry.
5. **Tag.** Create `vMAJOR.MINOR.PATCH` on the approved commit, then move 5. **Run the Release workflow in dry-run mode.** In the Actions tab of this
`vMAJOR` to it: repository start **Release** on `main` with the version and `dry_run`
`true`. It checks that the commit is the tip of `main`, that its validation
run is green, that the changelog has an entry for the version and that the
tag does not exist, and writes nothing.
6. **Release.** Start **Release** again with `dry_run` `false`. It creates the
tag `vMAJOR.MINOR.PATCH`, the release page with the text of
`python tools/changelog.py notes x.y.z`, and moves `vMAJOR` to the commit
when this is the highest version of its major. The page on Gitea is the
only destination (ADR-0007); nothing is attached to it. The workflow uses
the token of its own run; if Gitea refuses it, store `TOKEN_FOR_GITEA_RELEASE`
as a secret with write access to this repository only.
By hand, the same steps are:
```bash ```bash
git tag -a v1.2.0 -m "v1.2.0" <commit> git tag -a v1.2.0 -m "v1.2.0" <commit>
git push origin v1.2.0 git push origin v1.2.0
git tag -f v1 <commit> && git push -f origin v1 git tag -f v1 <commit> && git push -f origin v1
``` ```
6. **Announce.** Create the release page for the tag with the text of and create the release page with the notes of the changelog entry.
`python tools/changelog.py notes x.y.z`, and say whether consumers must act.
The release page of this repository on Gitea is the only destination
(ADR-0007); nothing is attached to it.
7. **Verify.** One repository pinned to the previous exact tag still runs the 7. **Verify.** One repository pinned to the previous exact tag still runs the
same code; one repository on the major tag receives the release. same code; one repository on the major tag receives the release.
+184
View File
@@ -0,0 +1,184 @@
"""Offline tests for tools/release.py and .gitea/workflows/release.yml."""
import pytest
import yaml
from workflow_source import REPO_ROOT, load_script
release = load_script(REPO_ROOT / "tools" / "release.py", "release")
WORKFLOW = REPO_ROOT / ".gitea" / "workflows" / "release.yml"
SHA = "a" * 40
CONTEXT = "Validate workflows / validate (push)"
CHANGELOG = """# Changelog
## [Unreleased]
### Added
- Something later.
## [1.1.0] - 2026-11-01
### Added
- A workflow.
## [1.0.0] - 2026-10-20
### Added
- First release.
"""
class FakeGitea:
"""Answers the calls of the release script and records the writes."""
def __init__(self, tip=SHA, status="success", tags=(), context=CONTEXT):
self.tip = tip
self.status = status
self.tags = list(tags)
self.context = context
self.writes = []
def __call__(self, url, method="GET", headers=None, body=None):
path = url.split("?")[0]
if method != "GET":
self.writes.append((method, path.rsplit("/repos/o/r", 1)[-1], body))
return None
if path.endswith("/branches/main"):
return {"commit": {"id": self.tip}}
if path.endswith(f"/commits/{SHA}/status"):
if self.status is None:
return {"statuses": []}
return {"statuses": [{"context": self.context, "status": self.status}]}
if path.endswith("/tags"):
return [{"name": name} for name in self.tags] if "page=1" in url else []
raise AssertionError(f"unexpected request {method} {url}")
@pytest.fixture
def env(tmp_path, monkeypatch):
changelog = tmp_path / "CHANGELOG.md"
changelog.write_text(CHANGELOG, encoding="utf-8")
values = {
"GITEA_API_URL": "https://git.example/api/v1",
"SOURCE_REPOSITORY": "o/r",
"RELEASE_CREDENTIALS": "t" * 40,
"RELEASE_VERSION": "1.1.0",
"RELEASE_SHA": SHA,
"RELEASE_DRY_RUN": "false",
"CHANGELOG_FILE": str(changelog),
}
for key, value in values.items():
monkeypatch.setenv(key, value)
monkeypatch.delenv("RELEASE_BRANCH", raising=False)
monkeypatch.delenv("VALIDATION_CONTEXT", raising=False)
return monkeypatch
def run(gitea):
return release.main(request=gitea)
def test_green_commit_with_entry_creates_release_and_major_tag(env):
gitea = FakeGitea(tags=["v1.0.0", "v1"])
assert run(gitea) == 0
methods = [(m, p) for m, p, _ in gitea.writes]
assert methods == [("POST", "/releases"), ("DELETE", "/tags/v1"), ("POST", "/tags")]
release_body = gitea.writes[0][2]
assert release_body["tag_name"] == "v1.1.0"
assert release_body["target_commitish"] == SHA
assert "### Added" in release_body["body"] and "A workflow." in release_body["body"]
assert gitea.writes[2][2]["tag_name"] == "v1" and gitea.writes[2][2]["target"] == SHA
def test_first_release_creates_the_major_tag_without_deleting(env):
env.setenv("RELEASE_VERSION", "1.0.0")
gitea = FakeGitea(tags=[])
assert run(gitea) == 0
assert [(m, p) for m, p, _ in gitea.writes] == [("POST", "/releases"), ("POST", "/tags")]
def test_failed_validation_run_creates_no_tag(env):
gitea = FakeGitea(status="failure")
with pytest.raises(release.WorkflowError) as error:
run(gitea)
assert error.value.category == "validation"
assert gitea.writes == []
def test_missing_validation_run_creates_no_tag(env):
gitea = FakeGitea(status=None)
with pytest.raises(release.WorkflowError) as error:
run(gitea)
assert "No validation run" in str(error.value)
assert gitea.writes == []
def test_commit_that_is_not_the_tip_is_refused(env):
gitea = FakeGitea(tip="b" * 40)
with pytest.raises(release.WorkflowError) as error:
run(gitea)
assert "not the tip" in str(error.value)
assert gitea.writes == []
def test_version_without_changelog_entry_is_refused(env):
env.setenv("RELEASE_VERSION", "1.2.0")
gitea = FakeGitea()
with pytest.raises(release.WorkflowError) as error:
run(gitea)
assert error.value.category == "validation"
assert gitea.writes == []
def test_existing_tag_is_never_moved(env):
gitea = FakeGitea(tags=["v1.1.0", "v1.0.0", "v1"])
with pytest.raises(release.WorkflowError) as error:
run(gitea)
assert "already exists" in str(error.value)
assert gitea.writes == []
def test_older_patch_does_not_move_the_major_tag(env):
env.setenv("RELEASE_VERSION", "1.0.0")
gitea = FakeGitea(tags=["v1.1.0", "v1"])
assert run(gitea) == 0
assert [(m, p) for m, p, _ in gitea.writes] == [("POST", "/releases")]
def test_dry_run_is_the_default_and_writes_nothing(env, capsys):
env.delenv("RELEASE_DRY_RUN")
gitea = FakeGitea(tags=["v1.0.0", "v1"])
assert run(gitea) == 0
assert gitea.writes == []
assert "Dry run" in capsys.readouterr().out
@pytest.mark.parametrize("version", ["v1.0.0", "1.0", "1.0.0-rc1", ""])
def test_malformed_version_is_a_configuration_error(env, version):
env.setenv("RELEASE_VERSION", version)
with pytest.raises(release.WorkflowError) as error:
run(FakeGitea())
assert error.value.category == "configuration"
def test_missing_token_is_a_configuration_error(env):
env.setenv("RELEASE_CREDENTIALS", " ")
with pytest.raises(release.WorkflowError) as error:
run(FakeGitea())
assert error.value.category == "configuration"
def test_workflow_runs_the_file_and_defaults_to_a_dry_run():
data = yaml.safe_load(WORKFLOW.read_text(encoding="utf-8"))
triggers = data.get("on", data.get(True))
inputs = triggers["workflow_dispatch"]["inputs"]
assert inputs["dry_run"]["default"] == "true"
job = data["jobs"]["release"]
assert job["permissions"] == {"contents": "write"}
runs = [step.get("run", "") for step in job["steps"]]
assert "python3 tools/release.py" in runs
+271
View File
@@ -0,0 +1,271 @@
"""Create a release of this repository: version tag, release page, major tag.
Run by .gitea/workflows/release.yml (MIL-004 task 5, ADR-0006, ADR-0007). It
publishes a release only when all of these hold, and stops before any write
when one does not:
- the version is MAJOR.MINOR.PATCH and CHANGELOG.md has an entry for it;
- the commit is the tip of the default branch;
- the validation run of that commit is green;
- the tag vMAJOR.MINOR.PATCH does not exist yet.
It then creates the release page (which creates the tag) from the changelog
entry and moves the major tag vMAJOR to the commit when the version is the
highest of its major version. It reads its settings from the environment:
GITEA_API_URL Gitea API base, for example https://host/api/v1
SOURCE_REPOSITORY owner/repo that is released
RELEASE_CREDENTIALS the token of the run, as clear text
RELEASE_VERSION the version, for example 1.0.0 (no leading v)
RELEASE_SHA the commit to release
RELEASE_DRY_RUN "true" (default) checks and prints, writes nothing
RELEASE_BRANCH the default branch (default: main)
VALIDATION_CONTEXT the status context that must be green
(default: Validate workflows / validate (push))
CHANGELOG_FILE default: CHANGELOG.md
Every failure is printed as ``ERROR: [category] message`` with the categories
configuration, validation, credentials, permissions and api; no secret value
is printed.
"""
import importlib.util
import json
import os
import re
import sys
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path
REQUEST_TIMEOUT_SECONDS = 30
DEFAULT_CONTEXT = "Validate workflows / validate (push)"
_VERSION = re.compile(r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$")
_TAG = re.compile(r"^v(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$")
class WorkflowError(RuntimeError):
"""A failure with a category.
configuration: a setting or the changelog is missing or malformed.
validation: the release is not allowed (commit, run, tag or changelog).
credentials: Gitea rejected the token (HTTP 401).
permissions: the token is valid but not allowed (HTTP 403).
api: any other API or network failure.
"""
def __init__(self, category, message):
super().__init__(f"[{category}] {message}")
self.category = category
self.detail = message
def load_changelog_module():
"""Import tools/changelog.py, which sits next to this file."""
path = Path(__file__).resolve().parent / "changelog.py"
spec = importlib.util.spec_from_file_location("changelog", path)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def request_json(url, method="GET", headers=None, body=None):
request = urllib.request.Request(
url,
data=json.dumps(body).encode("utf-8") if body is not None else None,
headers=headers or {},
method=method,
)
try:
with urllib.request.urlopen(request, timeout=REQUEST_TIMEOUT_SECONDS) as response:
content = response.read()
return json.loads(content) if content else None
except urllib.error.HTTPError as error:
target = url.split("?")[0]
detail = f"Gitea API request failed with HTTP {error.code} ({error.reason}) for {method} {target}"
if error.code == 401:
raise WorkflowError("credentials", f"{detail}. The token was rejected.") from None
if error.code == 403:
raise WorkflowError(
"permissions",
f"{detail}. The token lacks write access to the contents of the repository.",
) from None
raise WorkflowError("api", detail) from None
except (urllib.error.URLError, TimeoutError) as error:
raise WorkflowError("api", f"Gitea API is unreachable ({error}).") from None
def parse_version(raw):
"""Return the version as a tuple, or raise a configuration error."""
text = (raw or "").strip()
match = _VERSION.match(text)
if not match:
raise WorkflowError(
"configuration",
f"'{text}' is not a version MAJOR.MINOR.PATCH (write 1.0.0, without a leading v).",
)
return tuple(int(part) for part in match.groups())
def split_repository(full_name):
owner, separator, repo = (full_name or "").partition("/")
if not separator or not owner or not repo:
raise WorkflowError("configuration", "Could not determine the Gitea repository.")
return owner, repo
def release_notes(changelog_text, version, changelog):
"""Return the release page text for a version, or fail as a validation error."""
problems = changelog.validate(changelog_text)
if problems:
raise WorkflowError("validation", "CHANGELOG.md breaks its convention: " + "; ".join(problems))
try:
return changelog.notes(changelog_text, version)
except LookupError as error:
raise WorkflowError("validation", str(error)) from None
def list_tag_names(api, headers, request):
"""Return every tag name of the repository."""
names = []
page = 1
while True:
batch = request(f"{api}/tags?limit=50&page={page}", headers=headers)
if not batch:
return names
names += [item["name"] for item in batch]
if len(batch) < 50:
return names
page += 1
def check_commit(api, headers, request, sha, branch, context):
"""The commit is the tip of the branch and its validation run is green."""
tip = request(f"{api}/branches/{urllib.parse.quote(branch, safe='')}", headers=headers)
tip_sha = (tip or {}).get("commit", {}).get("id", "")
if tip_sha != sha:
raise WorkflowError(
"validation",
f"{sha[:7]} is not the tip of {branch} ({tip_sha[:7] or 'unknown'}); "
"release from the latest commit of the default branch.",
)
status = request(f"{api}/commits/{sha}/status", headers=headers) or {}
for item in status.get("statuses", []):
if item.get("context") == context:
if item.get("status") != "success":
raise WorkflowError(
"validation",
f"The validation run '{context}' of {sha[:7]} is {item.get('status')}, not success.",
)
return
raise WorkflowError(
"validation",
f"No validation run '{context}' was found for {sha[:7]}; wait for it or run it first.",
)
def plan_tags(tags, version):
"""Return (exact tag, major tag, whether the major tag moves)."""
exact = "v{}.{}.{}".format(*version)
major = f"v{version[0]}"
if exact in tags:
raise WorkflowError("validation", f"The tag {exact} already exists; tags are never moved.")
same_major = [
tuple(int(p) for p in m.groups())
for m in (_TAG.match(name) for name in tags)
if m and int(m.group(1)) == version[0]
]
moves = all(version > other for other in same_major)
return exact, major, moves
def publish(api, headers, request, sha, exact, major, moves, notes, major_exists):
"""Create the release page (and with it the tag), then move the major tag."""
request(
f"{api}/releases",
method="POST",
headers=headers,
body={
"tag_name": exact,
"target_commitish": sha,
"name": exact,
"body": notes,
"draft": False,
"prerelease": False,
},
)
if not moves:
return
if major_exists:
request(f"{api}/tags/{major}", method="DELETE", headers=headers)
request(
f"{api}/tags",
method="POST",
headers=headers,
body={"tag_name": major, "target": sha, "message": f"{major} points to {exact}"},
)
def main(request=request_json):
version = parse_version(os.environ.get("RELEASE_VERSION"))
owner, repo = split_repository(os.environ.get("SOURCE_REPOSITORY"))
sha = (os.environ.get("RELEASE_SHA") or "").strip()
if not re.fullmatch(r"[0-9a-f]{40}", sha):
raise WorkflowError("configuration", "RELEASE_SHA must be the 40-character commit to release.")
token = (os.environ.get("RELEASE_CREDENTIALS") or "").strip()
if not token:
raise WorkflowError(
"configuration",
"No token for the release: the run has none and TOKEN_FOR_GITEA_RELEASE is not set.",
)
dry_run = (os.environ.get("RELEASE_DRY_RUN") or "true").strip().lower() != "false"
branch = os.environ.get("RELEASE_BRANCH") or "main"
context = os.environ.get("VALIDATION_CONTEXT") or DEFAULT_CONTEXT
changelog_path = Path(os.environ.get("CHANGELOG_FILE") or "CHANGELOG.md")
changelog = load_changelog_module()
name = "{}.{}.{}".format(*version)
try:
text = changelog_path.read_text(encoding="utf-8")
except OSError as error:
raise WorkflowError("configuration", f"Cannot read {changelog_path}: {error}") from None
notes = release_notes(text, name, changelog)
api_url = os.environ["GITEA_API_URL"].rstrip("/")
api = f"{api_url}/repos/{urllib.parse.quote(owner, safe='')}/{urllib.parse.quote(repo, safe='')}"
headers = {
"Authorization": f"token {token}",
"Accept": "application/json",
"Content-Type": "application/json",
}
check_commit(api, headers, request, sha, branch, context)
tags = list_tag_names(api, headers, request)
exact, major, moves = plan_tags(tags, version)
print(f"Checks passed for {exact} on {sha[:7]}: changelog entry, tip of {branch}, green run, new tag.")
if dry_run:
print(
f"Dry run: would create the release page {exact}"
+ (f" and point {major} to it." if moves else f" and leave {major} where it is.")
)
return 0
publish(api, headers, request, sha, exact, major, moves, notes, major in tags)
print(f"Released {exact} at {sha[:7]}.")
print(
f"The major tag {major} now points to {exact}."
if moves
else f"The major tag {major} was not moved: a higher {version[0]}.x release exists."
)
return 0
if __name__ == "__main__":
try:
sys.exit(main())
except WorkflowError as error:
print(f"ERROR: {error}", file=sys.stderr)
sys.exit(1)
+2 -2
View File
@@ -1,4 +1,4 @@
<!-- translation of Capabilities.md, source sha256 c1d5b1f173ef --> <!-- translation of Capabilities.md, source sha256 94af1c3e6949 -->
> Dette er en oversættelse af [`Capabilities`](Capabilities). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres. > Dette er en oversættelse af [`Capabilities`](Capabilities). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
@@ -15,7 +15,7 @@ Rækker, der siger Implementeret eller Vejledninger er skrevet, findes i dag. Re
| Tilslutning af et repository, administration af runners, fejlfinding | Vejledninger er skrevet ([tilslutning](Onboarding.da), [fejlfinding](Troubleshooting.da)); scoped levering til andre repositories er ikke verificeret endnu; korte use cases er åbne (MIL-002) | | Tilslutning af et repository, administration af runners, fejlfinding | Vejledninger er skrevet ([tilslutning](Onboarding.da), [fejlfinding](Troubleshooting.da)); scoped levering til andre repositories er ikke verificeret endnu; korte use cases er åbne (MIL-002) |
| Publicering af et repositorys wiki fra `wiki/` (tilmelding, leveres til alle organisationer) | Implementeret i `.gitea/scoped_workflows/publish-wiki.yml`, med offline-tests; pilotforsøget i et andet repository afventer (MIL-006) | | Publicering af et repositorys wiki fra `wiki/` (tilmelding, leveres til alle organisationer) | Implementeret i `.gitea/scoped_workflows/publish-wiki.yml`, med offline-tests; pilotforsøget i et andet repository afventer (MIL-006) |
| Fælles kvalitetskontrol (Python og shell) | Implementeret som det genbrugelige workflow `.gitea/workflows/quality-checks.yml`, med offline-tests og et forsøg i en anden organisation; tilslutningen i RepoFoundry er til gennemgang ([vejledning](QualityChecks.da)) | | Fælles kvalitetskontrol (Python og shell) | Implementeret som det genbrugelige workflow `.gitea/workflows/quality-checks.yml`, med offline-tests og et forsøg i en anden organisation; tilslutningen i RepoFoundry er til gennemgang ([vejledning](QualityChecks.da)) |
| Versioneret udgivelse og publicering | Udgivelsesproces, changelog og opgraderingsvejledning er defineret; den første udgivelse `v1.0.0` og udgivelsesworkflowet mangler (MIL-004) ([vejledning](Upgrading.da)) | | Versioneret udgivelse og publicering | Udgivelsesproces, changelog og opgraderingsvejledning er defineret; udgivelsesworkflowet findes; den første udgivelse `v1.0.0` mangler (MIL-004) ([vejledning](Upgrading.da)) |
### Synkronisering af GitHub-metadata ### Synkronisering af GitHub-metadata
+1 -1
View File
@@ -15,7 +15,7 @@ Rows that say Implemented or Guides written exist today. The rest is planned and
| Onboard a repository, manage runners, diagnose failures | Guides written ([onboarding](Onboarding), [troubleshooting](Troubleshooting)); scoped delivery to other repositories not yet verified; brief use cases open (MIL-002) | | Onboard a repository, manage runners, diagnose failures | Guides written ([onboarding](Onboarding), [troubleshooting](Troubleshooting)); scoped delivery to other repositories not yet verified; brief use cases open (MIL-002) |
| Publish a repository wiki from `wiki/` (opt-in, delivered to every organization) | Implemented in `.gitea/scoped_workflows/publish-wiki.yml`, with offline tests; the pilot in a second repository is pending (MIL-006) | | Publish a repository wiki from `wiki/` (opt-in, delivered to every organization) | Implemented in `.gitea/scoped_workflows/publish-wiki.yml`, with offline tests; the pilot in a second repository is pending (MIL-006) |
| Shared quality checks (Python and shell) | Implemented as the reusable workflow `.gitea/workflows/quality-checks.yml`, with offline tests and a trial in a second organization; adoption in RepoFoundry is in review ([guide](QualityChecks)) | | Shared quality checks (Python and shell) | Implemented as the reusable workflow `.gitea/workflows/quality-checks.yml`, with offline tests and a trial in a second organization; adoption in RepoFoundry is in review ([guide](QualityChecks)) |
| Versioned release and publishing | Release process, changelog and upgrade guide defined; the first release `v1.0.0` and the release workflow are pending (MIL-004) ([guide](Upgrading)) | | Versioned release and publishing | Release process, changelog and upgrade guide defined; the release workflow exists; the first release `v1.0.0` is pending (MIL-004) ([guide](Upgrading)) |
### Sync GitHub metadata ### Sync GitHub metadata
+2 -2
View File
@@ -1,4 +1,4 @@
<!-- translation of Capabilities.md, source sha256 c1d5b1f173ef --> <!-- translation of Capabilities.md, source sha256 94af1c3e6949 -->
> Ini ialah terjemahan [`Capabilities`](Capabilities). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya. > Ini ialah terjemahan [`Capabilities`](Capabilities). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
@@ -15,7 +15,7 @@ Baris yang menyatakan Dilaksanakan atau Panduan telah ditulis wujud hari ini. Se
| Menyertakan repositori, mengurus runner, mendiagnosis kegagalan | Panduan telah ditulis ([menyertakan repositori](Onboarding.ms), [diagnosis](Troubleshooting.ms)); penghantaran scoped ke repositori lain belum disahkan; use case ringkas masih terbuka (MIL-002) | | Menyertakan repositori, mengurus runner, mendiagnosis kegagalan | Panduan telah ditulis ([menyertakan repositori](Onboarding.ms), [diagnosis](Troubleshooting.ms)); penghantaran scoped ke repositori lain belum disahkan; use case ringkas masih terbuka (MIL-002) |
| Menerbitkan wiki repositori daripada `wiki/` (pilih menyertai, dihantar ke setiap organisasi) | Dilaksanakan dalam `.gitea/scoped_workflows/publish-wiki.yml`, dengan ujian luar talian; juruterbang dalam repositori kedua belum dibuat (MIL-006) | | Menerbitkan wiki repositori daripada `wiki/` (pilih menyertai, dihantar ke setiap organisasi) | Dilaksanakan dalam `.gitea/scoped_workflows/publish-wiki.yml`, dengan ujian luar talian; juruterbang dalam repositori kedua belum dibuat (MIL-006) |
| Semakan kualiti bersama (Python dan shell) | Dilaksanakan sebagai aliran kerja boleh guna semula `.gitea/workflows/quality-checks.yml`, dengan ujian luar talian dan percubaan dalam organisasi kedua; penerimaan dalam RepoFoundry sedang disemak ([panduan](QualityChecks.ms)) | | Semakan kualiti bersama (Python dan shell) | Dilaksanakan sebagai aliran kerja boleh guna semula `.gitea/workflows/quality-checks.yml`, dengan ujian luar talian dan percubaan dalam organisasi kedua; penerimaan dalam RepoFoundry sedang disemak ([panduan](QualityChecks.ms)) |
| Keluaran berversi dan penerbitan | Proses keluaran, changelog dan panduan naik taraf telah ditakrifkan; keluaran pertama `v1.0.0` dan aliran kerja keluaran belum ada (MIL-004) ([panduan](Upgrading.ms)) | | Keluaran berversi dan penerbitan | Proses keluaran, changelog dan panduan naik taraf telah ditakrifkan; aliran kerja keluaran sudah ada; keluaran pertama `v1.0.0` belum ada (MIL-004) ([panduan](Upgrading.ms)) |
### Menyegerakkan metadata GitHub ### Menyegerakkan metadata GitHub