Skills als eigene Bereiche dokumentieren und das ZIP über ein Gitea-Release ausliefern.
This commit is contained in:
1 parent
b57c458919
commit
55e93e7660
17 files changed
+436
-215
No files matched your search
@@ -18,13 +18,13 @@ if (Test-Path $dir) {
|
||||
New-Item -ItemType Directory -Force -Path $SkillsDir | Out-Null
|
||||
Copy-Item -Path (Join-Path $Root 'templates/skill') -Destination $dir -Recurse
|
||||
$today = Get-Date -Format 'yyyy-MM-dd'
|
||||
foreach ($f in @('SKILL.md', 'CHANGELOG.md')) {
|
||||
foreach ($f in @('SKILL.md', 'README.md', 'CHANGELOG.md')) {
|
||||
$p = Join-Path $dir $f
|
||||
$t = (Read-Text $p).Replace('SKILL_NAME', $Name).Replace('DATUM', $today)
|
||||
Write-Text $p $t
|
||||
}
|
||||
|
||||
& (Join-Path $PSScriptRoot 'update-readme.ps1')
|
||||
& (Join-Path $PSScriptRoot 'update-index.ps1')
|
||||
|
||||
Write-Host "Angelegt: skills/$Name"
|
||||
Write-Host "Weiter: SKILL.md ausfuellen, dann scripts\validate.ps1 -Name $Name"
|
||||
Write-Host "Weiter: SKILL.md und README.md ausfuellen, dann scripts\validate.ps1 -Name $Name"
|
||||
@@ -25,12 +25,12 @@ fi
|
||||
mkdir -p "$SKILLS_DIR"
|
||||
cp -R "$ROOT/templates/skill" "$dir"
|
||||
today="$(date +%Y-%m-%d)"
|
||||
for f in "$dir/SKILL.md" "$dir/CHANGELOG.md"; do
|
||||
for f in "$dir/SKILL.md" "$dir/README.md" "$dir/CHANGELOG.md"; do
|
||||
sed -e "s/SKILL_NAME/$name/g" -e "s/DATUM/$today/g" "$f" > "$f.tmp"
|
||||
mv "$f.tmp" "$f"
|
||||
done
|
||||
|
||||
bash "$(dirname "$0")/update-readme.sh"
|
||||
bash "$(dirname "$0")/update-index.sh"
|
||||
|
||||
echo "Angelegt: skills/$name"
|
||||
echo "Weiter: SKILL.md ausfüllen, dann scripts/validate.sh $name"
|
||||
echo "Weiter: SKILL.md und README.md ausfüllen, dann scripts/validate.sh $name"
|
||||
+6
-5
@@ -1,5 +1,6 @@
|
||||
# Prueft und verpackt alle Skills (oder nur den angegebenen) als dist\<name>-<version>.zip.
|
||||
# Im ZIP liegt der Ordner <name>/ auf oberster Ebene, CHANGELOG.md wird nicht mitverpackt.
|
||||
# Prueft und verpackt alle Skills (oder nur den angegebenen) als dist\<name>-v<version>.zip.
|
||||
# Im ZIP liegt der Ordner <name>/ auf oberster Ebene.
|
||||
# README.md und CHANGELOG.md werden nicht mitverpackt.
|
||||
# Pfade im ZIP immer mit "/" (Compress-Archive in Windows PowerShell 5.1 schreibt "\",
|
||||
# das bricht den Upload). Deshalb System.IO.Compression direkt.
|
||||
# Aufruf: scripts\package.ps1 [-Name <skill-name>]
|
||||
@@ -14,12 +15,12 @@ Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
|
||||
if ($Name) { $names = @($Name) } else { $names = @(Get-SkillNames) }
|
||||
New-Item -ItemType Directory -Force -Path $DistDir | Out-Null
|
||||
$exclude = @('CHANGELOG.md', '.DS_Store', 'Thumbs.db')
|
||||
$exclude = @('CHANGELOG.md', 'README.md', '.DS_Store', 'Thumbs.db')
|
||||
|
||||
foreach ($n in $names) {
|
||||
$src = (Get-Item (Join-Path $SkillsDir $n)).FullName
|
||||
$version = Get-SkillVersion $src
|
||||
$zipPath = Join-Path $DistDir "$n-$version.zip"
|
||||
$zipPath = Join-Path $DistDir "$n-v$version.zip"
|
||||
if (Test-Path $zipPath) { Remove-Item $zipPath }
|
||||
|
||||
$files = @(Get-ChildItem -Path $src -Recurse -File -Force |
|
||||
@@ -38,7 +39,7 @@ foreach ($n in $names) {
|
||||
finally { $zip.Dispose() }
|
||||
|
||||
Write-Host ''
|
||||
Write-Host "dist/$n-$version.zip"
|
||||
Write-Host "dist/$n-v$version.zip"
|
||||
$read = [IO.Compression.ZipFile]::OpenRead($zipPath)
|
||||
try {
|
||||
foreach ($e in $read.Entries) { Write-Host ('{0,9} {1}' -f $e.Length, $e.FullName) }
|
||||
|
||||
+6
-5
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# Prüft und verpackt alle Skills (oder nur den angegebenen) als dist/<name>-<version>.zip.
|
||||
# Im ZIP liegt der Ordner <name>/ auf oberster Ebene, CHANGELOG.md wird nicht mitverpackt.
|
||||
# Prüft und verpackt alle Skills (oder nur den angegebenen) als dist/<name>-v<version>.zip.
|
||||
# Im ZIP liegt der Ordner <name>/ auf oberster Ebene.
|
||||
# README.md und CHANGELOG.md werden nicht mitverpackt.
|
||||
# Aufruf: scripts/package.sh [<skill-name>]
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
@@ -17,17 +18,17 @@ mkdir -p "$DIST_DIR"
|
||||
|
||||
for name in "${names[@]}"; do
|
||||
version="$(skill_version "$SKILLS_DIR/$name")"
|
||||
zip_path="$DIST_DIR/$name-$version.zip"
|
||||
zip_path="$DIST_DIR/$name-v$version.zip"
|
||||
rm -f "$zip_path"
|
||||
|
||||
tmp="$(mktemp -d)"
|
||||
mkdir "$tmp/$name"
|
||||
(cd "$SKILLS_DIR/$name" && tar cf - --exclude=CHANGELOG.md --exclude=.DS_Store --exclude=Thumbs.db .) \
|
||||
(cd "$SKILLS_DIR/$name" && tar cf - --exclude=CHANGELOG.md --exclude=README.md --exclude=.DS_Store --exclude=Thumbs.db .) \
|
||||
| (cd "$tmp/$name" && tar xf -)
|
||||
(cd "$tmp" && zip -qrX "$zip_path" "$name")
|
||||
rm -rf "$tmp"
|
||||
|
||||
echo
|
||||
echo "dist/$name-$version.zip"
|
||||
echo "dist/$name-v$version.zip"
|
||||
unzip -l "$zip_path"
|
||||
done
|
||||
@@ -0,0 +1,109 @@
|
||||
# Baut das ZIP eines Skills und legt damit ein Gitea-Release an.
|
||||
# Kein Actions-Runner. Der Tag <name>/v<version> muss lokal und auf dem Server liegen.
|
||||
# Token: Umgebungsvariable GITEA_TOKEN (persoenliches Zugangs-Token, nicht ins Repo).
|
||||
# Aufruf: scripts\release.ps1 -Name <skill-name>
|
||||
param([Parameter(Mandatory = $true)][string]$Name)
|
||||
. (Join-Path $PSScriptRoot 'lib.ps1')
|
||||
|
||||
$Token = $env:GITEA_TOKEN
|
||||
if (-not $Token) {
|
||||
Write-Host 'GITEA_TOKEN fehlt. Persoenliches Zugangs-Token mit Schreibrecht (write:repository), nur in der Umgebung.'
|
||||
exit 1
|
||||
}
|
||||
|
||||
$dir = Join-Path $SkillsDir $Name
|
||||
if (-not (Test-Path $dir -PathType Container)) {
|
||||
Write-Host "Ordner skills/$Name existiert nicht."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$ver = Get-SkillVersion $dir
|
||||
if (-not $ver) {
|
||||
Write-Host "CHANGELOG von $Name hat keine Version '## x.y.z (YYYY-MM-DD)'."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$tag = "$Name/v$ver"
|
||||
git -C $Root rev-parse --verify --quiet "refs/tags/$tag" | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "Tag $tag fehlt lokal. Setzen, nicht verschieben: git tag $tag"
|
||||
exit 1
|
||||
}
|
||||
|
||||
& (Join-Path $PSScriptRoot 'package.ps1') -Name $Name
|
||||
if ($LASTEXITCODE -ne 0) { exit 1 }
|
||||
|
||||
$zipName = "$Name-v$ver.zip"
|
||||
$zipPath = Join-Path $DistDir $zipName
|
||||
if (-not (Test-Path $zipPath)) {
|
||||
Write-Host "ZIP fehlt: dist/$zipName"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$on = $false
|
||||
$acc = New-Object System.Collections.Generic.List[string]
|
||||
foreach ($l in ((Read-Text (Join-Path $dir 'CHANGELOG.md')) -split "`n")) {
|
||||
if (-not $on) {
|
||||
if ($l.StartsWith("## $ver ")) { $on = $true }
|
||||
continue
|
||||
}
|
||||
if ($l.StartsWith('## ')) { break }
|
||||
$acc.Add($l)
|
||||
}
|
||||
$notes = (($acc -join "`n").Trim())
|
||||
|
||||
$api = 'https://git.gundel.dev/api/v1/repos/dg/claude-skills'
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
|
||||
function Invoke-Gitea([string]$Method, [string]$Url, [byte[]]$Body, [string]$ContentType) {
|
||||
$req = [Net.HttpWebRequest]::Create($Url)
|
||||
$req.Method = $Method
|
||||
$req.Headers.Add('Authorization', "token $Token")
|
||||
$req.Accept = 'application/json'
|
||||
if ($null -ne $Body) {
|
||||
$req.ContentType = $ContentType
|
||||
$req.ContentLength = $Body.LongLength
|
||||
$stream = $req.GetRequestStream()
|
||||
$stream.Write($Body, 0, $Body.Length)
|
||||
$stream.Close()
|
||||
}
|
||||
try {
|
||||
$resp = $req.GetResponse()
|
||||
}
|
||||
catch [System.Net.WebException] {
|
||||
$errResp = $_.Exception.Response
|
||||
$detail = ''
|
||||
if ($errResp) {
|
||||
$reader = New-Object IO.StreamReader($errResp.GetResponseStream())
|
||||
$detail = $reader.ReadToEnd()
|
||||
$reader.Close()
|
||||
}
|
||||
throw "API-Fehler $Url : $detail"
|
||||
}
|
||||
$reader = New-Object IO.StreamReader($resp.GetResponseStream(), $Utf8NoBom)
|
||||
$text = $reader.ReadToEnd()
|
||||
$reader.Close()
|
||||
$resp.Close()
|
||||
return $text
|
||||
}
|
||||
|
||||
$payload = @{
|
||||
tag_name = $tag
|
||||
name = "$Name $ver"
|
||||
body = $notes
|
||||
} | ConvertTo-Json -Compress
|
||||
try {
|
||||
$created = Invoke-Gitea 'POST' "$api/releases" $Utf8NoBom.GetBytes($payload) 'application/json; charset=utf-8' | ConvertFrom-Json
|
||||
}
|
||||
catch {
|
||||
Write-Host $_
|
||||
Write-Host "Tag auf dem Server? git push origin $tag"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$assetUrl = "$api/releases/$($created.id)/assets?name=$([Uri]::EscapeDataString($zipName))"
|
||||
$asset = Invoke-Gitea 'POST' $assetUrl ([IO.File]::ReadAllBytes($zipPath)) 'application/octet-stream' | ConvertFrom-Json
|
||||
|
||||
if ($created.html_url) { Write-Host $created.html_url }
|
||||
if ($asset.browser_download_url) { Write-Host $asset.browser_download_url }
|
||||
Write-Host "Release $tag angelegt."
|
||||
Executable
+93
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env bash
|
||||
# Baut das ZIP eines Skills und legt damit ein Gitea-Release an.
|
||||
# Kein Actions-Runner. Der Tag <name>/v<version> muss lokal und auf dem Server liegen.
|
||||
# Token: Umgebungsvariable GITEA_TOKEN (persönliches Zugangs-Token, nicht ins Repo).
|
||||
# Aufruf: scripts/release.sh <skill-name>
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
|
||||
name="${1:-}"
|
||||
if [[ -z "$name" ]]; then
|
||||
echo "Aufruf: scripts/release.sh <skill-name>" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${GITEA_TOKEN:-}" ]]; then
|
||||
echo "GITEA_TOKEN fehlt. Persönliches Zugangs-Token mit Schreibrecht (write:repository), nur in der Umgebung." >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v python3 >/dev/null 2>&1 || ! command -v curl >/dev/null 2>&1; then
|
||||
echo "release.sh braucht curl und python3. Unter Windows: scripts\\release.ps1" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dir="$SKILLS_DIR/$name"
|
||||
version="$(skill_version "$dir")"
|
||||
if [[ -z "$version" ]]; then
|
||||
echo "CHANGELOG von $name hat keine Version '## x.y.z (YYYY-MM-DD)'." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tag="$name/v$version"
|
||||
if ! git -C "$ROOT" rev-parse --verify --quiet "refs/tags/$tag" >/dev/null; then
|
||||
echo "Tag $tag fehlt lokal. Setzen, nicht verschieben: git tag $tag" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bash "$(dirname "$0")/package.sh" "$name"
|
||||
|
||||
zip_name="$name-v$version.zip"
|
||||
zip_path="$DIST_DIR/$zip_name"
|
||||
[[ -f "$zip_path" ]] || { echo "ZIP fehlt: dist/$zip_name" >&2; exit 1; }
|
||||
|
||||
notes="$(mktemp)"
|
||||
payload="$(mktemp)"
|
||||
trap 'rm -f "$notes" "$payload"' EXIT
|
||||
awk -v v="$version" '
|
||||
index($0, "## " v " ") == 1 { on = 1; next }
|
||||
/^## / && on { exit }
|
||||
on { print }' "$dir/CHANGELOG.md" > "$notes"
|
||||
|
||||
TAG="$tag" TITLE="$name $version" NOTES="$notes" python3 -c '
|
||||
import json, os
|
||||
print(json.dumps({
|
||||
"tag_name": os.environ["TAG"],
|
||||
"name": os.environ["TITLE"],
|
||||
"body": open(os.environ["NOTES"], encoding="utf-8").read().strip(),
|
||||
}))' > "$payload"
|
||||
|
||||
api="https://git.gundel.dev/api/v1/repos/dg/claude-skills"
|
||||
resp="$(mktemp)"
|
||||
code="$(curl -sS -o "$resp" -w '%{http_code}' -X POST "$api/releases" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-binary @"$payload")"
|
||||
if [[ "$code" != "201" ]]; then
|
||||
echo "API $code" >&2
|
||||
cat "$resp" >&2
|
||||
echo "Tag auf dem Server? git push origin $tag" >&2
|
||||
rm -f "$resp"
|
||||
exit 1
|
||||
fi
|
||||
created="$(cat "$resp")"
|
||||
rm -f "$resp"
|
||||
rel_id="$(CREATED="$created" python3 -c 'import json,os; print(json.loads(os.environ["CREATED"])["id"])')"
|
||||
html="$(CREATED="$created" python3 -c 'import json,os; print(json.loads(os.environ["CREATED"]).get("html_url",""))')"
|
||||
|
||||
asset_resp="$(mktemp)"
|
||||
code="$(curl -sS -o "$asset_resp" -w '%{http_code}' -X POST "$api/releases/$rel_id/assets?name=$zip_name" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/octet-stream" \
|
||||
--data-binary @"$zip_path")"
|
||||
if [[ "$code" != "201" ]]; then
|
||||
echo "API $code" >&2
|
||||
cat "$asset_resp" >&2
|
||||
rm -f "$asset_resp"
|
||||
exit 1
|
||||
fi
|
||||
asset="$(cat "$asset_resp")"
|
||||
rm -f "$asset_resp"
|
||||
download="$(ASSET="$asset" python3 -c 'import json,os; print(json.loads(os.environ["ASSET"]).get("browser_download_url",""))')"
|
||||
|
||||
[[ -n "$html" ]] && echo "$html"
|
||||
[[ -n "$download" ]] && echo "$download"
|
||||
echo "Release $tag angelegt."
|
||||
@@ -1,9 +1,10 @@
|
||||
# Erzeugt die Skill-Tabelle in README.md zwischen <!-- skills:start --> und <!-- skills:end -->
|
||||
# neu. Mit -Check wird nur geprueft, ob die Tabelle aktuell ist (Exit-Code 1, wenn nicht).
|
||||
# Aufruf: scripts\update-readme.ps1 [-Check]
|
||||
# Aufruf: scripts\update-index.ps1 [-Check]
|
||||
param([switch]$Check)
|
||||
. (Join-Path $PSScriptRoot 'lib.ps1')
|
||||
|
||||
$RepoWeb = 'https://git.gundel.dev/dg/claude-skills'
|
||||
$readme = Join-Path $Root 'README.md'
|
||||
$current = Read-Text $readme
|
||||
$lines = $current -split "`n"
|
||||
@@ -14,7 +15,7 @@ if ($start -lt 0 -or $end -lt $start) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
$table = @('| Skill | Beschreibung | Version |', '|---|---|---|')
|
||||
$table = @('| Skill | Kurzbeschreibung | Version | Doku | Download |', '|---|---|---|---|---|')
|
||||
foreach ($n in (Get-SkillNames)) {
|
||||
$dir = Join-Path $SkillsDir $n
|
||||
$desc = ''
|
||||
@@ -27,7 +28,8 @@ foreach ($n in (Get-SkillNames)) {
|
||||
}
|
||||
}
|
||||
$desc = $desc.Replace('|', '\|')
|
||||
$table += ('| [{0}](skills/{0}/) | {1} | {2} |' -f $n, $desc, (Get-SkillVersion $dir))
|
||||
$ver = Get-SkillVersion $dir
|
||||
$table += ('| [{0}](skills/{0}/) | {1} | {2} | [README](skills/{0}/README.md) | [{0}-v{2}.zip]({3}/releases/tag/{0}/v{2}) |' -f $n, $desc, $ver, $RepoWeb)
|
||||
}
|
||||
|
||||
$newLines = @()
|
||||
@@ -38,7 +40,7 @@ $new = $newLines -join "`n"
|
||||
|
||||
if ($Check) {
|
||||
if ($new -cne $current) {
|
||||
Write-Host 'README.md ist nicht aktuell - scripts\update-readme.ps1 ausfuehren und committen.'
|
||||
Write-Host 'README.md ist nicht aktuell - scripts\update-index.ps1 ausfuehren und committen.'
|
||||
exit 1
|
||||
}
|
||||
Write-Host 'README.md ist aktuell.'
|
||||
@@ -1,10 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# Erzeugt die Skill-Tabelle in README.md zwischen <!-- skills:start --> und <!-- skills:end -->
|
||||
# neu. Mit --check wird nur geprüft, ob die Tabelle aktuell ist (Exit-Code 1, wenn nicht).
|
||||
# Aufruf: scripts/update-readme.sh [--check]
|
||||
# Aufruf: scripts/update-index.sh [--check]
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/lib.sh"
|
||||
|
||||
REPO_WEB="https://git.gundel.dev/dg/claude-skills"
|
||||
readme="$ROOT/README.md"
|
||||
check=0
|
||||
[[ "${1:-}" == "--check" ]] && check=1
|
||||
@@ -17,16 +18,18 @@ fi
|
||||
table="$(mktemp)"
|
||||
trap 'rm -f "$table"' EXIT
|
||||
{
|
||||
echo "| Skill | Beschreibung | Version |"
|
||||
echo "|---|---|---|"
|
||||
echo "| Skill | Kurzbeschreibung | Version | Doku | Download |"
|
||||
echo "|---|---|---|---|---|"
|
||||
while IFS= read -r name; do
|
||||
[[ -z "$name" ]] && continue
|
||||
desc=""
|
||||
ver="$(skill_version "$SKILLS_DIR/$name")"
|
||||
if fm="$(frontmatter "$SKILLS_DIR/$name/SKILL.md" 2>/dev/null)"; then
|
||||
desc="$(fm_value "$fm" description || true)"
|
||||
fi
|
||||
desc="${desc//|/\\|}"
|
||||
printf '| [%s](skills/%s/) | %s | %s |\n' "$name" "$name" "$desc" "$(skill_version "$SKILLS_DIR/$name")"
|
||||
printf '| [%s](skills/%s/) | %s | %s | [README](skills/%s/README.md) | [%s-v%s.zip](%s/releases/tag/%s/v%s) |\n' \
|
||||
"$name" "$name" "$desc" "$ver" "$name" "$name" "$ver" "$REPO_WEB" "$name" "$ver"
|
||||
done < <(skill_names)
|
||||
} > "$table"
|
||||
|
||||
@@ -37,7 +40,7 @@ new="$(tr -d '\r' < "$readme" | awk -v t="$table" '
|
||||
|
||||
if [[ $check -eq 1 ]]; then
|
||||
if [[ "$new" != "$(tr -d '\r' < "$readme")" ]]; then
|
||||
echo "README.md ist nicht aktuell – scripts/update-readme.sh ausführen und committen." >&2
|
||||
echo "README.md ist nicht aktuell – scripts/update-index.sh ausführen und committen." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "README.md ist aktuell."
|
||||
@@ -41,6 +41,10 @@ function Test-Skill([string]$N) {
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path (Join-Path $dir 'README.md'))) {
|
||||
$errs += 'README.md fehlt'
|
||||
}
|
||||
|
||||
if (-not (Test-Path (Join-Path $dir 'CHANGELOG.md'))) {
|
||||
$errs += 'CHANGELOG.md fehlt'
|
||||
}
|
||||
|
||||
@@ -42,6 +42,10 @@ validate_skill() {
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -f "$dir/README.md" ]]; then
|
||||
errs+=("README.md fehlt")
|
||||
fi
|
||||
|
||||
if [[ ! -f "$dir/CHANGELOG.md" ]]; then
|
||||
errs+=("CHANGELOG.md fehlt")
|
||||
else
|
||||
|
||||
Reference in new issue
Block a user