From 506c1797a8657a30aaec3f7c638416cdbe9968ab Mon Sep 17 00:00:00 2001 From: Axel Meyer Date: Wed, 29 Apr 2026 23:37:38 +0200 Subject: [PATCH] =?UTF-8?q?chore:=20initial=20bootstrap=20=E2=80=94=205=20?= =?UTF-8?q?reusable=20workflows=20+=20scaffolding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Public-readable Tier-1 exception (alongside sporel-meta) for the practical reason that community pipelines need to call into these workflows without per-repo deploy-token setup. 5 reusable workflows moved from sporel-meta/.gitea/workflows/ to here (Plan-B.1: privacy-fix for sporel-meta which stays private with the proprietary design docs). Co-Authored-By: Claude Opus 4.7 (1M context) --- .gitea/workflows/reusable-c-lib.yml | 88 +++++++++++++++++++ .gitea/workflows/reusable-docs.yml | 43 +++++++++ .gitea/workflows/reusable-engine-launcher.yml | 59 +++++++++++++ .gitea/workflows/reusable-hybrid.yml | 83 +++++++++++++++++ .gitea/workflows/reusable-lua.yml | 70 +++++++++++++++ .gitignore | 4 + LICENSE | 27 ++++++ README.md | 80 +++++++++++++++++ 8 files changed, 454 insertions(+) create mode 100644 .gitea/workflows/reusable-c-lib.yml create mode 100644 .gitea/workflows/reusable-docs.yml create mode 100644 .gitea/workflows/reusable-engine-launcher.yml create mode 100644 .gitea/workflows/reusable-hybrid.yml create mode 100644 .gitea/workflows/reusable-lua.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md diff --git a/.gitea/workflows/reusable-c-lib.yml b/.gitea/workflows/reusable-c-lib.yml new file mode 100644 index 0000000..30e142f --- /dev/null +++ b/.gitea/workflows/reusable-c-lib.yml @@ -0,0 +1,88 @@ +# Reusable workflow: C-Lib (Cat 2 per ADR-0025) +# Build C-Lib gegen Engine-Release-Headers + Import-Lib → on-tag zip +name: C-Lib CI + +on: + workflow_call: + inputs: + engine_release_tag: + description: "Engine release to build against (e.g. 'v0.1.0' or 'latest')" + type: string + default: "latest" + build_type: + type: string + default: "Release" + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download engine release (headers + import lib) + shell: bash + env: + GITEA_API: "https://git.davoryn.de/api/v1" + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + set -euo pipefail + OS_TAG="${{ matrix.os }}" + OS_TAG="${OS_TAG//-latest/}" + TAG="${{ inputs.engine_release_tag }}" + if [ "$TAG" = "latest" ]; then + TAG=$(curl -sH "Authorization: token ${GITEA_TOKEN}" \ + "${GITEA_API}/repos/calic/sporel-engine/releases/latest" | \ + grep -oP '"tag_name":"\K[^"]+') + fi + ARTEFACT="sporel-engine-${TAG}-${OS_TAG}.zip" + mkdir -p engine-release + curl -sL -H "Authorization: token ${GITEA_TOKEN}" \ + -o "engine-release/${ARTEFACT}" \ + "https://git.davoryn.de/calic/sporel-engine/releases/download/${TAG}/${ARTEFACT}" + cd engine-release && unzip "${ARTEFACT}" && rm "${ARTEFACT}" + echo "SPOREL_ROOT=${{ github.workspace }}/engine-release" >> "$GITHUB_ENV" + + - name: Manifest validation (lib.json) + shell: bash + run: | + if [ -f lib.json ]; then + python3 -c "import json,sys; d=json.load(open('lib.json')); + assert d.get('id'), 'missing id'; assert d.get('version'), 'missing version'; + assert d.get('api'), 'missing api field (per ADR-0022)'; + print('lib.json OK:', d['id'], d['version'])" + else + echo "::warning::no lib.json found" + fi + + - name: README presence (per ADR-0026) + shell: bash + run: | + test -s README.md || (echo "::error::README.md missing or empty" && exit 1) + + - name: Configure CMake + run: cmake -B build -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DSPOREL_ROOT=${{ env.SPOREL_ROOT }} + + - name: Build + run: cmake --build build --config ${{ inputs.build_type }} --parallel + + - name: Package lib (on tag) + if: startsWith(github.ref, 'refs/tags/') + shell: bash + run: | + OS_TAG="${{ matrix.os }}" + OS_TAG="${OS_TAG//-latest/}" + ARTEFACT_NAME="${GITHUB_REPOSITORY##*/}-${GITHUB_REF_NAME}-${OS_TAG}.zip" + cmake --install build --prefix=${{ github.workspace }}/lib-install --config ${{ inputs.build_type }} + cd lib-install && zip -r "../${ARTEFACT_NAME}" . && cd .. + echo "ARTEFACT_NAME=${ARTEFACT_NAME}" >> "$GITHUB_ENV" + + - name: Upload to Gitea Release (on tag) + if: startsWith(github.ref, 'refs/tags/') + uses: akkuman/gitea-release-action@v1 + with: + files: ${{ env.ARTEFACT_NAME }} diff --git a/.gitea/workflows/reusable-docs.yml b/.gitea/workflows/reusable-docs.yml new file mode 100644 index 0000000..e319dce --- /dev/null +++ b/.gitea/workflows/reusable-docs.yml @@ -0,0 +1,43 @@ +# Reusable workflow: Meta / Docs / Tool (Cat 5 per ADR-0025) +# Markdown lint + internal link check +name: Docs CI + +on: + workflow_call: + inputs: + lint_paths: + description: "Glob of paths to lint" + type: string + default: "**/*.md" + ignore_paths: + description: "Glob of paths to skip (e.g. archive)" + type: string + default: "**/archive/**" + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Node + markdownlint-cli2 + run: | + sudo apt-get update -qq + sudo apt-get install -y nodejs npm + sudo npm install -g markdownlint-cli2 + + - name: Run markdownlint + run: | + markdownlint-cli2 "${{ inputs.lint_paths }}" "!${{ inputs.ignore_paths }}" || \ + (echo "::warning::markdown lint warnings (non-blocking)" && true) + + - name: Internal link check (lychee) + uses: lycheeverse/lychee-action@v2 + with: + args: --offline --no-progress --include-fragments "**/*.md" --exclude-path "**/archive/**" + fail: true + + - name: README presence (root) + run: | + test -s README.md || (echo "::warning::no README.md at repo root" && true) diff --git a/.gitea/workflows/reusable-engine-launcher.yml b/.gitea/workflows/reusable-engine-launcher.yml new file mode 100644 index 0000000..a727181 --- /dev/null +++ b/.gitea/workflows/reusable-engine-launcher.yml @@ -0,0 +1,59 @@ +# Reusable workflow: Engine + Launcher (Cat 1 per ADR-0025) +# C-Build (Windows + Linux) → smoke tests → on-tag: release artefacts +name: Engine + Launcher CI + +on: + workflow_call: + inputs: + build_type: + description: "CMake build type" + type: string + default: "Release" + smoke_test_script: + description: "Path to smoke-test script (relative to repo root); empty = skip" + type: string + default: "tests/smoke.sh" + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Configure CMake + run: cmake -B build -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} + + - name: Build + run: cmake --build build --config ${{ inputs.build_type }} --parallel + + - name: Smoke tests + if: hashFiles(inputs.smoke_test_script) != '' + shell: bash + run: | + chmod +x "${{ inputs.smoke_test_script }}" || true + "${{ inputs.smoke_test_script }}" + + - name: Install (on tag) + if: startsWith(github.ref, 'refs/tags/') + run: cmake --install build --prefix=${{ github.workspace }}/install --config ${{ inputs.build_type }} + + - name: Package release artefacts (on tag) + if: startsWith(github.ref, 'refs/tags/') + shell: bash + run: | + OS_TAG="${{ matrix.os }}" + OS_TAG="${OS_TAG//-latest/}" + ARTEFACT_NAME="${GITHUB_REPOSITORY##*/}-${GITHUB_REF_NAME}-${OS_TAG}.zip" + cd install && zip -r "../${ARTEFACT_NAME}" . && cd .. + echo "ARTEFACT_NAME=${ARTEFACT_NAME}" >> "$GITHUB_ENV" + + - name: Upload to Gitea Release (on tag) + if: startsWith(github.ref, 'refs/tags/') + uses: akkuman/gitea-release-action@v1 + with: + files: ${{ env.ARTEFACT_NAME }} diff --git a/.gitea/workflows/reusable-hybrid.yml b/.gitea/workflows/reusable-hybrid.yml new file mode 100644 index 0000000..f6f3e66 --- /dev/null +++ b/.gitea/workflows/reusable-hybrid.yml @@ -0,0 +1,83 @@ +# Reusable workflow: Hybrid C+Lua Module (Cat 4 per ADR-0025) +# Combines Cat 2 (C-build) + Cat 3 (Lua-lint) for modules with native code. +name: Hybrid Module CI + +on: + workflow_call: + inputs: + engine_release_tag: + type: string + default: "latest" + build_type: + type: string + default: "Release" + lint_target: + type: string + default: "lua/ scripts/ init.lua" + +jobs: + lint-lua: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install luacheck + run: sudo apt-get install -y luarocks && sudo luarocks install luacheck + - name: Run luacheck + run: luacheck ${{ inputs.lint_target }} --no-unused-args --no-self + - name: Manifest + README + LICENSE + run: | + for f in manifest.module manifest.core lib.json dependencies.json; do + [ -f "$f" ] && python3 -c "import json; json.load(open('$f'))" + done + test -s README.md && test -s LICENSE + + build-c: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - name: Download engine release + shell: bash + env: + GITEA_API: "https://git.davoryn.de/api/v1" + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + run: | + set -euo pipefail + OS_TAG="${{ matrix.os }}" + OS_TAG="${OS_TAG//-latest/}" + TAG="${{ inputs.engine_release_tag }}" + if [ "$TAG" = "latest" ]; then + TAG=$(curl -sH "Authorization: token ${GITEA_TOKEN}" \ + "${GITEA_API}/repos/calic/sporel-engine/releases/latest" | \ + grep -oP '"tag_name":"\K[^"]+') + fi + ARTEFACT="sporel-engine-${TAG}-${OS_TAG}.zip" + mkdir -p engine-release + curl -sL -H "Authorization: token ${GITEA_TOKEN}" \ + -o "engine-release/${ARTEFACT}" \ + "https://git.davoryn.de/calic/sporel-engine/releases/download/${TAG}/${ARTEFACT}" + cd engine-release && unzip "${ARTEFACT}" + echo "SPOREL_ROOT=${{ github.workspace }}/engine-release" >> "$GITHUB_ENV" + - name: Build + run: | + cmake -B build -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DSPOREL_ROOT=${{ env.SPOREL_ROOT }} + cmake --build build --config ${{ inputs.build_type }} --parallel + + package: + needs: [lint-lua, build-c] + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Package as zip (multi-OS-pre-built) + run: | + ARTEFACT="${GITHUB_REPOSITORY##*/}-${GITHUB_REF_NAME}.zip" + zip -r "$ARTEFACT" . -x "*.git/*" -x "*.gitea/*" -x "tests/*" -x "build/*" + echo "ARTEFACT_NAME=$ARTEFACT" >> "$GITHUB_ENV" + - name: Upload to Gitea Release + uses: akkuman/gitea-release-action@v1 + with: + files: ${{ env.ARTEFACT_NAME }} diff --git a/.gitea/workflows/reusable-lua.yml b/.gitea/workflows/reusable-lua.yml new file mode 100644 index 0000000..3fa60e6 --- /dev/null +++ b/.gitea/workflows/reusable-lua.yml @@ -0,0 +1,70 @@ +# Reusable workflow: Lua-Lib / Module (Cat 3 per ADR-0025) +# luacheck + JSON validation + on-tag zip +name: Lua CI + +on: + workflow_call: + inputs: + lint_target: + description: "Path glob to lint (default: lua/ + init.lua + scripts/)" + type: string + default: "lua/ scripts/ init.lua" + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install luacheck + run: | + sudo apt-get update -qq + sudo apt-get install -y luarocks + sudo luarocks install luacheck + + - name: Run luacheck + run: | + luacheck ${{ inputs.lint_target }} --no-unused-args --no-self || \ + (echo "::error::luacheck failures" && exit 1) + + - name: Manifest validation + shell: bash + run: | + for manifest in manifest.module manifest.core lib.json; do + if [ -f "$manifest" ]; then + python3 -c "import json; json.load(open('$manifest'))" || \ + (echo "::error::$manifest invalid JSON" && exit 1) + echo "$manifest: valid JSON" + fi + done + if [ -f dependencies.json ]; then + python3 -c "import json; d=json.load(open('dependencies.json')); assert 'required' in d or 'suggests' in d" + fi + + - name: README presence (per ADR-0026) + run: | + test -s README.md || (echo "::error::README.md missing or empty" && exit 1) + + - name: LICENSE presence (per ADR-0027) + run: | + test -s LICENSE || (echo "::error::LICENSE missing or empty" && exit 1) + + package: + needs: lint + if: startsWith(github.ref, 'refs/tags/') + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Package as zip + run: | + ARTEFACT="${GITHUB_REPOSITORY##*/}-${GITHUB_REF_NAME}.zip" + zip -r "$ARTEFACT" . -x "*.git/*" -x "*.gitea/*" -x "tests/*" + echo "ARTEFACT_NAME=$ARTEFACT" >> "$GITHUB_ENV" + + - name: Upload to Gitea Release + uses: akkuman/gitea-release-action@v1 + with: + files: ${{ env.ARTEFACT_NAME }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8d88296 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store +Thumbs.db +*.local +.env diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e692d42 --- /dev/null +++ b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2026 Calic. All rights reserved. + +This software is part of the Sporel platform — **Tier 1 (Official / +Proprietary)** content per the Three-Tier Licensing Model documented +in `meta/docs/architecture/licensing-model.md` and ratified by ADR-0027. + +This repository is **public-readable** as a deliberate Tier-1 +exception (alongside `sporel-meta`), so that community pipelines can +call into the reusable CI workflows without per-repo deploy-token +setup. Public-readability does NOT grant re-licensing rights — the +content remains all-rights-reserved. + +⚠ **WIP — Legal review required before public launch.** The terms +below reflect design intent only; the formalized license framework +will be finalized through legal counsel before the first public +release. Until then, this notice serves as a placeholder defending +the platform owner's rights against unintentional re-licensing. + +No license is granted to copy, modify, distribute, sublicense, or +otherwise use this software in any form without prior written +permission from the copyright holder. + +References: +- Tier 1 (this file): all rights reserved, proprietary +- Tier 2: bilateral contracts — see `licensing-model.md §4` +- Tier 3: CC BY-NC-SA 4.0 + asymmetric CLA — applies to + community-uploaded libs/modules/assets, not this repo diff --git a/README.md b/README.md new file mode 100644 index 0000000..f1b7ed7 --- /dev/null +++ b/README.md @@ -0,0 +1,80 @@ +# sporel-tool-ci + +Reusable Gitea-Actions workflows for the Sporel platform. + +> **Type:** Tool (CI/CD-Workflows) +> **Status:** Active +> **Tier:** Tier 1 (Official, **public-readable**) + +## Purpose + +Hosts the 5 reusable Gitea-Actions workflows used by every Sporel repo +per [ADR-0025](https://git.davoryn.de/calic/sporel-meta/src/branch/master/docs/adrs/0025-cicd-reusable-workflows.md): + +| Workflow | Category (per ADR-0019) | Used by | +|---|---|---| +| `reusable-engine-launcher.yml` | Cat 1 | `sporel-engine`, `sporel-launcher` | +| `reusable-c-lib.yml` | Cat 2 | `sporel-lib-core.` (compiled) | +| `reusable-lua.yml` | Cat 3 | `sporel-lib-` (Lua), `sporel-mod-` (pure Lua) | +| `reusable-hybrid.yml` | Cat 4 | `sporel-mod-` (Hybrid C+Lua) | +| `reusable-docs.yml` | Cat 5 | `sporel-meta`, `sporel-tool-*` | + +## Why public + +This repo is a **deliberate public-Tier-1 exception** (per +[`licensing-model.md`](https://git.davoryn.de/calic/sporel-meta/src/branch/master/docs/architecture/licensing-model.md)). +The two reasons: + +1. **Community pipelines** — Tier-3 community libs/mods need to call + into these workflows from their own repos. Forcing per-repo Deploy- + Token setup would defeat the DRY-CI value proposition entirely + (every contributor would need infrastructure permissions). +2. **Minimal IP exposure** — only YAML pipeline definitions live here, + no architecture docs, no proprietary source. The workflows describe + *how to build* but not *what to build* — that intelligence stays in + `sporel-engine` (private) and `sporel-meta` (private). + +Other Tier-1-public exception: `sporel-meta` (modder-readable docs, +when promoted post-legal-review). + +## Caller-Template Usage + +In each Sporel repo, `.gitea/workflows/release.yml` is a thin caller: + +```yaml +name: CI + +on: + push: + branches: [master] + tags: ['v*'] + pull_request: + +jobs: + ci: + uses: calic/sporel-tool-ci/.gitea/workflows/reusable-.yml@master + with: + + secrets: inherit +``` + +Caller-templates per category live in `sporel-meta/templates/workflow-callers/`. + +## Versioning + +Pin specific revisions for reproducibility: + +```yaml +uses: calic/sporel-tool-ci/.gitea/workflows/reusable-engine-launcher.yml@v0.1.0 +``` + +`@master` is convenient during active development; pin to tag for +production stability. + +## License + +**Proprietary — all rights reserved.** Tier 1 content per +ADR-0027. Public-readable for the practical reasons above; not +re-distributable. See `LICENSE`. + +⚠ WIP — legal review pending before public release.