Template
Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 657b122736 | |||
| bacb977fac | |||
| fec05ff9ac | |||
| ff156d3df8 | |||
| bc80d64de0 | |||
| ee2590e1e9 | |||
| 6e5322bd27 | |||
| 7a7d855d1e | |||
| 6a3e066b4b | |||
| 5d4ca460fb | |||
| e2d82f294e | |||
| 9a1c1cd37d | |||
| d316fe89b5 | |||
| 42f6730a4c | |||
| 03f41cc0d0 | |||
| 587c0fc5b7 | |||
| c4c008feff | |||
| bcb4698213 | |||
| a5c7dc4040 | |||
| 7b4a1533f7 | |||
| 98d2303ebd | |||
| c4fc5fd0b9 | |||
| 92b76c4a22 | |||
| 31718da51a | |||
| b51b8698d9 | |||
| f983d3771b | |||
| 9ed5e2a6ed | |||
| 0f72a807be | |||
| f117c0de0f | |||
| 27fc4dddcf | |||
| 5cea5c5b6b | |||
| 97183ea293 | |||
| 78cbb2704a | |||
| 5505a305cc | |||
| ab9112c234 | |||
| 5fe246908c | |||
| 1503cca367 | |||
| c5e5ca3261 | |||
| f8a105ec7a | |||
| d70de91d3d | |||
| 6fa062a61a | |||
| 36ce9234eb | |||
| 444d9fc732 | |||
| d4e3aa57d0 | |||
| 026e7266b6 | |||
| eff49e0260 | |||
| a2ad009425 | |||
| ba05fec731 | |||
| bdd843c8d3 | |||
| 256e45f092 | |||
| 77ca6c37d4 | |||
| d5714805f8 | |||
| 780bb86b1f | |||
| f323e69fe0 | |||
| 43670c9e22 | |||
| 0c60846ff6 | |||
| 0ce5dc1789 | |||
| 698b8e9b18 | |||
| 8af663df17 | |||
| 3e7f57c258 | |||
| e3e0a68a1c | |||
| f216bd0f7c | |||
| 4c23fc5ae5 | |||
| 6aee534334 | |||
| 8ecee813dd | |||
| d88e725cd3 | |||
| af60e0af7e | |||
| 1806f4f457 | |||
| 06a23f53cc | |||
| e8aea5bf6d | |||
| ab5dca295e | |||
| cf35adb1db | |||
| a5d777c62f | |||
| 5154d97bff | |||
| 591bfa76c3 | |||
| dd13aff38c | |||
| 2aaa79c0d2 | |||
| 9b15d342b1 | |||
| 521e4e4ba1 | |||
| edc3316ac8 | |||
| b9e4777d4a | |||
| 1e301e7401 | |||
| a9a01c322b | |||
| 79b6aedf76 | |||
| a91c05730e | |||
| 1c1b23c640 | |||
| 6f50658213 | |||
| 6a923039f8 | |||
| ee15d17974 | |||
| e558156b11 | |||
| 2a5aac40df | |||
| a254b02b86 | |||
| 5e20194dd0 | |||
| 5fdce27fe2 | |||
| 0de85a595f | |||
| 89c21dee45 | |||
| f2e3d2caa5 | |||
| 419504ee4b | |||
| 436fa138e8 | |||
| c606c3c453 | |||
| aad56fc91a |
+1
-1
@@ -41,7 +41,7 @@
|
|||||||
# CI and automation
|
# CI and automation
|
||||||
.github/ export-ignore
|
.github/ export-ignore
|
||||||
.github/workflows/ export-ignore
|
.github/workflows/ export-ignore
|
||||||
.gitea/ export-ignore
|
.mokogit/ export-ignore
|
||||||
.gitlab/ export-ignore
|
.gitlab/ export-ignore
|
||||||
|
|
||||||
# Development and tooling
|
# Development and tooling
|
||||||
|
|||||||
@@ -205,6 +205,14 @@ hypothesis/
|
|||||||
profile.ps1
|
profile.ps1
|
||||||
.mcp.json
|
.mcp.json
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# AI client working directories and instruction files
|
||||||
|
# ============================================================
|
||||||
|
.gemini/
|
||||||
|
.mokoai/
|
||||||
|
/GEMINI.md
|
||||||
|
/MOKOAI.md
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Local wiki clone (not version controlled)
|
# Local wiki clone (not version controlled)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# {{REPO_NAME}}
|
||||||
|
|
||||||
|
{{REPO_DESCRIPTION}}
|
||||||
|
|
||||||
|
> AI-client instructions for **Gemini**. Mirrors `.mokogit/CLAUDE.md` — keep CLAUDE.md, GEMINI.md, and MOKOAI.md in sync.
|
||||||
|
|
||||||
|
## First-Run Setup
|
||||||
|
|
||||||
|
> Before starting any work, complete these steps:
|
||||||
|
>
|
||||||
|
> 1. Replace all `{{REPO_NAME}}` placeholders with the actual repository name
|
||||||
|
> 2. Replace `{{REPO_DESCRIPTION}}` with a one-line project description
|
||||||
|
> 3. Replace `{{PLATFORM_TYPE}}` with the project type (e.g. `generic`, `npm`, `joomla`)
|
||||||
|
> 4. Replace `{{PRIMARY_LANGUAGE}}` with the dominant language
|
||||||
|
> 5. Set repository metadata via the MokoGIT **Metadata** field — repository **Settings → Metadata**, or the REST API `PUT /api/v1/repos/{owner}/{repo}/manifest` (identity, platform, language, package-type, entry-point). There is no metadata file in the repo.
|
||||||
|
> 6. Update the **Architecture** section for your project
|
||||||
|
> 7. Update the Commands section with actual build commands
|
||||||
|
> 8. Delete this setup block after completing all steps
|
||||||
|
> 9. Commit: `chore: initialize {{REPO_NAME}}`
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|---|---|
|
||||||
|
| **Platform** | {{PLATFORM_TYPE}} |
|
||||||
|
| **Language** | {{PRIMARY_LANGUAGE}} |
|
||||||
|
| **Branch** | develop on `dev`, merge to `main` (protected) |
|
||||||
|
| **Wiki** | [Wiki](https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/wiki) |
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make build # Build the project
|
||||||
|
make lint # Run linters
|
||||||
|
make validate # Validate structure
|
||||||
|
make release # Full release pipeline
|
||||||
|
make clean # Clean build artifacts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
<!-- Update this section for your project -->
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- **Never commit** `.gemini/`, `.mcp.json`, `TODO.md`, `*.min.css`/`*.min.js`
|
||||||
|
- **Attribution**: `Authored-by: Moko Consulting`
|
||||||
|
- **Workflow directory**: `.mokogit/` (not `.gitea/` or `.github/`)
|
||||||
|
- **Metadata**: set via the MokoGIT **Metadata** field (API / Settings → Metadata), never a committed manifest file
|
||||||
|
- **Wiki**: documentation lives in the MokoGIT wiki, not `docs/` files
|
||||||
|
- **Standards**: [MokoCLI](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)
|
||||||
@@ -7,11 +7,11 @@ contact_links:
|
|||||||
- name: 💬 Ask a Question
|
- name: 💬 Ask a Question
|
||||||
url: https://mokoconsulting.tech/
|
url: https://mokoconsulting.tech/
|
||||||
about: Get help or ask questions through our website
|
about: Get help or ask questions through our website
|
||||||
- name: 📚 MokoStandards Documentation
|
- name: 📚 MokoCLI Documentation
|
||||||
url: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
url: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
about: View our coding standards and best practices
|
about: View our coding standards and best practices
|
||||||
- name: 🔒 Report a Security Vulnerability
|
- name: 🔒 Report a Security Vulnerability
|
||||||
url: https://git.mokoconsulting.tech/mokoconsulting-tech/.github-private/security/advisories/new
|
url: https://git.mokoconsulting.tech/MokoConsulting/.github-private/security/advisories/new
|
||||||
about: Report security vulnerabilities privately (for critical issues)
|
about: Report security vulnerabilities privately (for critical issues)
|
||||||
- name: 💡 Community Discussions
|
- name: 💡 Community Discussions
|
||||||
url: https://github.com/orgs/mokoconsulting-tech/discussions
|
url: https://github.com/orgs/mokoconsulting-tech/discussions
|
||||||
+1
-1
@@ -42,7 +42,7 @@ Suggested text here
|
|||||||
<!-- Add any other context, screenshots, or references -->
|
<!-- Add any other context, screenshots, or references -->
|
||||||
|
|
||||||
## Standards Alignment
|
## Standards Alignment
|
||||||
- [ ] Follows MokoStandards documentation guidelines
|
- [ ] Follows MokoCLI documentation guidelines
|
||||||
- [ ] Uses en_US/en_GB localization
|
- [ ] Uses en_US/en_GB localization
|
||||||
- [ ] Includes proper SPDX headers where applicable
|
- [ ] Includes proper SPDX headers where applicable
|
||||||
|
|
||||||
+1
-1
@@ -37,7 +37,7 @@ If you have ideas about how this could be implemented, share them here:
|
|||||||
Add any other context, mockups, or screenshots about the feature request here.
|
Add any other context, mockups, or screenshots about the feature request here.
|
||||||
|
|
||||||
## Relevant Standards
|
## Relevant Standards
|
||||||
Does this relate to any standards in [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)?
|
Does this relate to any standards in [MokoCLI](https://git.mokoconsulting.tech/MokoConsulting/mokocli)?
|
||||||
- [ ] Accessibility (WCAG 2.1 AA)
|
- [ ] Accessibility (WCAG 2.1 AA)
|
||||||
- [ ] Localization (en_US/en_GB)
|
- [ ] Localization (en_US/en_GB)
|
||||||
- [ ] Security best practices
|
- [ ] Security best practices
|
||||||
+1
-1
@@ -45,4 +45,4 @@ Are there other ways to accomplish this today? If so, why is an MCP integration
|
|||||||
- [ ] API documentation is available and accessible
|
- [ ] API documentation is available and accessible
|
||||||
- [ ] API supports the required authentication method
|
- [ ] API supports the required authentication method
|
||||||
- [ ] I have tested the API endpoints manually
|
- [ ] I have tested the API endpoints manually
|
||||||
- [ ] The integration follows the Template-MCP architecture pattern
|
- [ ] The integration follows the Template-NPM architecture pattern
|
||||||
@@ -35,7 +35,7 @@ Use this template only for:
|
|||||||
<!-- Describe how this could be addressed -->
|
<!-- Describe how this could be addressed -->
|
||||||
|
|
||||||
## Standards Reference
|
## Standards Reference
|
||||||
Does this relate to security standards in [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)?
|
Does this relate to security standards in [MokoCLI](https://git.mokoconsulting.tech/MokoConsulting/mokocli)?
|
||||||
- [ ] SPDX license identifiers
|
- [ ] SPDX license identifiers
|
||||||
- [ ] Secret management
|
- [ ] Secret management
|
||||||
- [ ] Dependency security
|
- [ ] Dependency security
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# {{REPO_NAME}}
|
||||||
|
|
||||||
|
{{REPO_DESCRIPTION}}
|
||||||
|
|
||||||
|
> AI-client instructions for **MokoAI**. Mirrors `.mokogit/CLAUDE.md` — keep CLAUDE.md, GEMINI.md, and MOKOAI.md in sync.
|
||||||
|
|
||||||
|
## First-Run Setup
|
||||||
|
|
||||||
|
> Before starting any work, complete these steps:
|
||||||
|
>
|
||||||
|
> 1. Replace all `{{REPO_NAME}}` placeholders with the actual repository name
|
||||||
|
> 2. Replace `{{REPO_DESCRIPTION}}` with a one-line project description
|
||||||
|
> 3. Replace `{{PLATFORM_TYPE}}` with the project type (e.g. `generic`, `npm`, `joomla`)
|
||||||
|
> 4. Replace `{{PRIMARY_LANGUAGE}}` with the dominant language
|
||||||
|
> 5. Set repository metadata via the MokoGIT **Metadata** field — repository **Settings → Metadata**, or the REST API `PUT /api/v1/repos/{owner}/{repo}/manifest` (identity, platform, language, package-type, entry-point). There is no metadata file in the repo.
|
||||||
|
> 6. Update the **Architecture** section for your project
|
||||||
|
> 7. Update the Commands section with actual build commands
|
||||||
|
> 8. Delete this setup block after completing all steps
|
||||||
|
> 9. Commit: `chore: initialize {{REPO_NAME}}`
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|---|---|
|
||||||
|
| **Platform** | {{PLATFORM_TYPE}} |
|
||||||
|
| **Language** | {{PRIMARY_LANGUAGE}} |
|
||||||
|
| **Branch** | develop on `dev`, merge to `main` (protected) |
|
||||||
|
| **Wiki** | [Wiki](https://git.mokoconsulting.tech/MokoConsulting/{{REPO_NAME}}/wiki) |
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make build # Build the project
|
||||||
|
make lint # Run linters
|
||||||
|
make validate # Validate structure
|
||||||
|
make release # Full release pipeline
|
||||||
|
make clean # Clean build artifacts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
<!-- Update this section for your project -->
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
- **Never commit** `.mokoai/`, `.mcp.json`, `TODO.md`, `*.min.css`/`*.min.js`
|
||||||
|
- **Attribution**: `Authored-by: Moko Consulting`
|
||||||
|
- **Workflow directory**: `.mokogit/` (not `.gitea/` or `.github/`)
|
||||||
|
- **Metadata**: set via the MokoGIT **Metadata** field (API / Settings → Metadata), never a committed manifest file
|
||||||
|
- **Wiki**: documentation lives in the MokoGIT wiki, not `docs/` files
|
||||||
|
- **Standards**: [MokoCLI](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: moko-platform.Automation
|
# INGROUP: MokoCLI.Automation
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /.gitea/workflows/branch-protection.yml
|
# PATH: /.mokogit/workflows/branch-protection.yml
|
||||||
# BRIEF: Apply standardised branch protection rules to all governed repositories
|
# BRIEF: Apply standardised branch protection rules to all governed repositories
|
||||||
#
|
#
|
||||||
# +========================================================================+
|
# +========================================================================+
|
||||||
@@ -42,8 +42,8 @@ on:
|
|||||||
default: ''
|
default: ''
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITEA_URL: https://git.mokoconsulting.tech
|
GIT_URL: https://git.mokoconsulting.tech
|
||||||
GITEA_ORG: MokoConsulting
|
GIT_ORG: MokoConsulting
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -59,11 +59,11 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
API="${GITEA_URL}/api/v1"
|
API="${GIT_URL}/api/v1"
|
||||||
|
|
||||||
# Platform/standards/infra repos to exclude
|
# Platform/standards/infra repos to exclude
|
||||||
EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private MokoStandards moko-platform MokoTesting"
|
EXCLUDE="git-org-config org-profile git-private .mokogit-private mokocli MokoTesting"
|
||||||
EXCLUDE="$EXCLUDE MokoStandards-Template-Client MokoStandards-Template-Dolibarr MokoStandards-Template-Generic MokoStandards-Template-Joomla MokoDoliProjTemplate"
|
EXCLUDE="$EXCLUDE Template-Client Template-Dolibarr Template-Generic Template-Joomla MokoDoliProjTemplate"
|
||||||
|
|
||||||
if [ -n "${{ inputs.repos }}" ]; then
|
if [ -n "${{ inputs.repos }}" ]; then
|
||||||
# User-specified repos
|
# User-specified repos
|
||||||
@@ -75,7 +75,7 @@ jobs:
|
|||||||
while true; do
|
while true; do
|
||||||
BATCH=$(curl -sS \
|
BATCH=$(curl -sS \
|
||||||
-H "Authorization: token ${GA_TOKEN}" \
|
-H "Authorization: token ${GA_TOKEN}" \
|
||||||
"${API}/orgs/${GITEA_ORG}/repos?page=${PAGE}&limit=50" \
|
"${API}/orgs/${GIT_ORG}/repos?page=${PAGE}&limit=50" \
|
||||||
| jq -r '.[].name // empty')
|
| jq -r '.[].name // empty')
|
||||||
[ -z "$BATCH" ] && break
|
[ -z "$BATCH" ] && break
|
||||||
REPOS="$REPOS $BATCH"
|
REPOS="$REPOS $BATCH"
|
||||||
@@ -108,7 +108,7 @@ jobs:
|
|||||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||||
DRY_RUN: ${{ inputs.dry_run || 'false' }}
|
DRY_RUN: ${{ inputs.dry_run || 'false' }}
|
||||||
run: |
|
run: |
|
||||||
API="${GITEA_URL}/api/v1"
|
API="${GIT_URL}/api/v1"
|
||||||
REPOS="${{ steps.repos.outputs.repos }}"
|
REPOS="${{ steps.repos.outputs.repos }}"
|
||||||
|
|
||||||
SUCCESS=0
|
SUCCESS=0
|
||||||
@@ -215,7 +215,7 @@ jobs:
|
|||||||
curl -sS -o /dev/null -w "" \
|
curl -sS -o /dev/null -w "" \
|
||||||
-X DELETE \
|
-X DELETE \
|
||||||
-H "Authorization: token ${GA_TOKEN}" \
|
-H "Authorization: token ${GA_TOKEN}" \
|
||||||
"${API}/repos/${GITEA_ORG}/${REPO}/branch_protections/${ENCODED_NAME}" 2>/dev/null || true
|
"${API}/repos/${GIT_ORG}/${REPO}/branch_protections/${ENCODED_NAME}" 2>/dev/null || true
|
||||||
|
|
||||||
# Create rule
|
# Create rule
|
||||||
RESPONSE=$(curl -sS -w "\n%{http_code}" \
|
RESPONSE=$(curl -sS -w "\n%{http_code}" \
|
||||||
@@ -223,7 +223,7 @@ jobs:
|
|||||||
-H "Authorization: token ${GA_TOKEN}" \
|
-H "Authorization: token ${GA_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "$RULE" \
|
-d "$RULE" \
|
||||||
"${API}/repos/${GITEA_ORG}/${REPO}/branch_protections")
|
"${API}/repos/${GIT_ORG}/${REPO}/branch_protections")
|
||||||
|
|
||||||
HTTP=$(echo "$RESPONSE" | tail -1)
|
HTTP=$(echo "$RESPONSE" | tail -1)
|
||||||
BODY=$(echo "$RESPONSE" | sed '$d')
|
BODY=$(echo "$RESPONSE" | sed '$d')
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
# DEFGROUP: GitHub.Workflow
|
# DEFGROUP: GitHub.Workflow
|
||||||
# INGROUP: MokoStandards.Workflows.Shared
|
# INGROUP: MokoStandards.Workflows.Shared
|
||||||
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
||||||
# PATH: /.mokogitea/workflows/auto-assign.yml
|
# PATH: /.mokogit/workflows/auto-assign.yml
|
||||||
# VERSION: 04.06.00
|
# VERSION: 04.06.00
|
||||||
# BRIEF: Auto-assign jmiller to unassigned issues and PRs every 15 minutes
|
# BRIEF: Auto-assign jmiller to unassigned issues and PRs every 15 minutes
|
||||||
|
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: moko-platform.Release
|
# INGROUP: MokoCLI.Release
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /.mokogitea/workflows/auto-bump.yml
|
# PATH: /.mokogit/workflows/auto-bump.yml
|
||||||
# VERSION: 09.02.00
|
# VERSION: 09.02.00
|
||||||
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ on:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -40,27 +40,27 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
fetch-depth: 1
|
fetch-depth: 1
|
||||||
|
|
||||||
- name: Setup moko-platform tools
|
- name: Setup MokoCLI tools
|
||||||
run: |
|
run: |
|
||||||
if ! command -v composer &> /dev/null; then
|
if ! command -v composer &> /dev/null; then
|
||||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
if [ -d "/opt/moko-platform/cli" ]; then
|
if [ -d "/opt/mokocli/cli" ]; then
|
||||||
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
|
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
|
||||||
else
|
else
|
||||||
git clone --depth 1 --branch main --quiet \
|
git clone --depth 1 --branch main --quiet \
|
||||||
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
|
"https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
|
||||||
/tmp/moko-platform-api
|
/tmp/mokocli
|
||||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
||||||
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
|
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Bump version
|
- name: Bump version
|
||||||
run: |
|
run: |
|
||||||
php ${MOKO_CLI}/version_auto_bump.php \
|
php ${MOKO_CLI}/version_auto_bump.php \
|
||||||
--path . --branch "${GITHUB_REF_NAME}" \
|
--path . --branch "${GITHUB_REF_NAME}" \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
--token "${{ secrets.MOKOGIT_TOKEN }}" \
|
||||||
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
--repo-url "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
# PATH: /templates/workflows/shared/auto-dev-issue.yml.template
|
# PATH: /templates/workflows/shared/auto-dev-issue.yml.template
|
||||||
# VERSION: 04.06.00
|
# VERSION: 04.06.00
|
||||||
# BRIEF: Auto-create tracking issue with sub-issues for dev/rc branch workflow
|
# BRIEF: Auto-create tracking issue with sub-issues for dev/rc branch workflow
|
||||||
# NOTE: Synced via bulk-repo-sync to .mokogitea/workflows/auto-dev-issue.yml in all governed repos.
|
# NOTE: Synced via bulk-repo-sync to .mokogit/workflows/auto-dev-issue.yml in all governed repos.
|
||||||
|
|
||||||
name: "Universal: Dev/RC Branch Issue"
|
name: "Universal: Dev/RC Branch Issue"
|
||||||
|
|
||||||
@@ -0,0 +1,492 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
|
# INGROUP: MokoCLI.Release
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
|
# PATH: /templates/workflows/universal/auto-release.yml.template
|
||||||
|
# VERSION: 05.00.00
|
||||||
|
# BRIEF: Universal build & release � detects platform from manifest.xml
|
||||||
|
#
|
||||||
|
# +=======================================================================+
|
||||||
|
# | UNIVERSAL BUILD & RELEASE PIPELINE |
|
||||||
|
# +=======================================================================+
|
||||||
|
# | |
|
||||||
|
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
|
||||||
|
# | |
|
||||||
|
# | Platform-specific: |
|
||||||
|
# | joomla: XML manifest, type-prefixed packages |
|
||||||
|
# | dolibarr: mod*.class.php, update.txt, dev version reset |
|
||||||
|
# | generic: README-only, no update stream |
|
||||||
|
# | |
|
||||||
|
# +=======================================================================+
|
||||||
|
|
||||||
|
name: "Universal: Build & Release"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, closed]
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths-ignore:
|
||||||
|
- '.mokogit/workflows/**'
|
||||||
|
- '*.md'
|
||||||
|
- 'wiki/**'
|
||||||
|
- '.editorconfig'
|
||||||
|
- '.gitignore'
|
||||||
|
- '.gitattributes'
|
||||||
|
- '.gitmessage'
|
||||||
|
- 'LICENSE'
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
action:
|
||||||
|
description: 'Action to perform'
|
||||||
|
required: false
|
||||||
|
type: choice
|
||||||
|
default: release
|
||||||
|
options:
|
||||||
|
- release
|
||||||
|
- promote-rc
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
GIT_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||||
|
GIT_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# ── PR Opened → Rename branch to RC and build RC release ─────────────────────────
|
||||||
|
promote-rc:
|
||||||
|
name: Promote to RC
|
||||||
|
runs-on: release
|
||||||
|
if: >-
|
||||||
|
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
|
||||||
|
(github.event.action == 'synchronize' && github.event.pull_request.merged != true) ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Setup MokoCLI tools
|
||||||
|
env:
|
||||||
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
|
run: |
|
||||||
|
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
|
||||||
|
echo Using pre-installed /opt/mokocli
|
||||||
|
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo Falling back to fresh clone
|
||||||
|
if ! command -v composer > /dev/null 2>&1; then
|
||||||
|
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
rm -rf /tmp/mokocli
|
||||||
|
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
|
||||||
|
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
||||||
|
cd /tmp/mokocli
|
||||||
|
composer install --no-dev --no-interaction --quiet
|
||||||
|
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Rename branch to rc
|
||||||
|
run: |
|
||||||
|
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
|
AUTH="Authorization: token ${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
|
FROM="${{ github.event.pull_request.head.ref || 'dev' }}"
|
||||||
|
PR="${{ github.event.pull_request.number }}"
|
||||||
|
|
||||||
|
# Resolve the source branch HEAD commit.
|
||||||
|
SRC_JSON=$(curl -sf -H "$AUTH" "${API_BASE}/branches/${FROM}") \
|
||||||
|
|| { echo "::error::Source branch ${FROM} not found"; exit 1; }
|
||||||
|
SRC_SHA=$(printf '%s' "$SRC_JSON" | python3 -c "import sys, json; print(json.load(sys.stdin)['commit']['id'])" 2>/dev/null || true)
|
||||||
|
[ -n "$SRC_SHA" ] || { echo "::error::Could not resolve HEAD of ${FROM}"; exit 1; }
|
||||||
|
|
||||||
|
# Point rc at the source commit via git push. Git's git/refs PATCH API
|
||||||
|
# returns HTTP 405 on ANY protected branch (force or not, even for a user in
|
||||||
|
# the force-push allowlist), so it cannot move a protected rc. git push honors
|
||||||
|
# the push + force-push allowlists and creates rc if it is absent.
|
||||||
|
PUSH_URL="https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@${MOKOGIT_URL#https://}/${GIT_ORG}/${GIT_REPO}.git"
|
||||||
|
git config --global user.name "mokogit-actions[bot]"
|
||||||
|
git config --global user.email "actions@mokoconsulting.tech"
|
||||||
|
git fetch --no-tags "$PUSH_URL" "${FROM}"
|
||||||
|
git push --force "$PUSH_URL" "FETCH_HEAD:refs/heads/rc" \
|
||||||
|
|| { echo "::error::Failed to point rc at ${FROM} (${SRC_SHA}) via git push"; exit 1; }
|
||||||
|
echo "rc set to ${FROM} (${SRC_SHA})"
|
||||||
|
|
||||||
|
# Repoint the PR at rc, then delete the old source branch (non-fatal).
|
||||||
|
if [ -n "$PR" ]; then
|
||||||
|
curl -s -X PATCH -H "$AUTH" -H "Content-Type: application/json" \
|
||||||
|
"${API_BASE}/pulls/${PR}" -d '{"head":"rc"}' >/dev/null || true
|
||||||
|
fi
|
||||||
|
curl -s -X DELETE -H "$AUTH" "${API_BASE}/branches/${FROM}" >/dev/null || true
|
||||||
|
echo "Renamed ${FROM} -> rc"
|
||||||
|
|
||||||
|
- name: Checkout rc and configure git
|
||||||
|
run: |
|
||||||
|
git fetch origin rc
|
||||||
|
git checkout rc
|
||||||
|
git config --local user.email "mokogit-actions[bot]@mokoconsulting.tech"
|
||||||
|
git config --local user.name "mokogit-actions[bot]"
|
||||||
|
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
|
||||||
|
- name: Publish RC release
|
||||||
|
run: |
|
||||||
|
php ${MOKO_CLI}/release_publish.php \
|
||||||
|
--path . --stability rc --bump minor --branch rc \
|
||||||
|
--token "${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
|
|
||||||
|
- name: Update RC release notes from CHANGELOG.md
|
||||||
|
run: |
|
||||||
|
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
|
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
|
|
||||||
|
# Extract [Unreleased] section from changelog
|
||||||
|
NOTES=""
|
||||||
|
if [ -f "CHANGELOG.md" ]; then
|
||||||
|
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
|
||||||
|
fi
|
||||||
|
[ -z "$NOTES" ] && NOTES="Release candidate"
|
||||||
|
|
||||||
|
# Find the RC release and update its body
|
||||||
|
RELEASE_ID=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API_BASE}/releases/tags/release-candidate" \
|
||||||
|
| python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ -n "$RELEASE_ID" ]; then
|
||||||
|
python3 -c "
|
||||||
|
import json, urllib.request
|
||||||
|
body = open('/dev/stdin').read()
|
||||||
|
payload = json.dumps({'body': body}).encode()
|
||||||
|
req = urllib.request.Request(
|
||||||
|
'${API_BASE}/releases/${RELEASE_ID}',
|
||||||
|
data=payload, method='PATCH',
|
||||||
|
headers={
|
||||||
|
'Authorization': 'token ${TOKEN}',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
})
|
||||||
|
urllib.request.urlopen(req)
|
||||||
|
" <<< "$NOTES"
|
||||||
|
echo "RC release notes updated from CHANGELOG.md"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# ── Merged PR → Build & Release (or promote RC to stable) ─────────────────────────
|
||||||
|
release:
|
||||||
|
name: Build & Release Pipeline
|
||||||
|
runs-on: release
|
||||||
|
if: >-
|
||||||
|
github.event.pull_request.merged == true ||
|
||||||
|
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Configure git for bot pushes
|
||||||
|
run: |
|
||||||
|
git config --local user.email "mokogit-actions[bot]@mokoconsulting.tech"
|
||||||
|
git config --local user.name "mokogit-actions[bot]"
|
||||||
|
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
|
|
||||||
|
- name: Check for merge conflict markers
|
||||||
|
run: |
|
||||||
|
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
|
||||||
|
if [ -n "$CONFLICTS" ]; then
|
||||||
|
echo "::error::Merge conflict markers found — aborting release"
|
||||||
|
echo "## Release Blocked: Conflict Markers" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "No conflict markers found"
|
||||||
|
|
||||||
|
- name: Setup MokoCLI tools
|
||||||
|
env:
|
||||||
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
|
||||||
|
run: |
|
||||||
|
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
|
||||||
|
echo Using pre-installed /opt/mokocli
|
||||||
|
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo Falling back to fresh clone
|
||||||
|
if ! command -v composer > /dev/null 2>&1; then
|
||||||
|
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
rm -rf /tmp/mokocli
|
||||||
|
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
|
||||||
|
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
||||||
|
cd /tmp/mokocli
|
||||||
|
composer install --no-dev --no-interaction --quiet
|
||||||
|
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: "Detect platform"
|
||||||
|
id: platform
|
||||||
|
run: |
|
||||||
|
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
|
||||||
|
php ${MOKO_CLI}/manifest_read.php --path . --github-output 2>/dev/null || true
|
||||||
|
|
||||||
|
- name: "Determine version bump level"
|
||||||
|
id: bump
|
||||||
|
run: |
|
||||||
|
# Fix/patch branches: version was already bumped by pre-release, just strip suffix
|
||||||
|
# Feature/dev branches: bump minor for the new stable release
|
||||||
|
HEAD_REF="${{ github.event.pull_request.head.ref || 'dev' }}"
|
||||||
|
case "$HEAD_REF" in
|
||||||
|
fix/*|patch/*|hotfix/*|bugfix/*) BUMP="none" ;;
|
||||||
|
*) BUMP="minor" ;;
|
||||||
|
esac
|
||||||
|
echo "level=${BUMP}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Bump level: ${BUMP} (from branch: ${HEAD_REF})"
|
||||||
|
|
||||||
|
- name: "Publish stable release"
|
||||||
|
run: |
|
||||||
|
BUMP_FLAG=""
|
||||||
|
if [ "${{ steps.bump.outputs.level }}" != "none" ]; then
|
||||||
|
BUMP_FLAG="--bump ${{ steps.bump.outputs.level }}"
|
||||||
|
fi
|
||||||
|
php ${MOKO_CLI}/release_publish.php \
|
||||||
|
--path . --stability stable ${BUMP_FLAG} --branch main \
|
||||||
|
--token "${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
|
|
||||||
|
- name: "Read published version"
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "")
|
||||||
|
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||||
|
[ -z "$VERSION" ] && VERSION="00.00.00" && echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||||
|
if [[ "$PLATFORM" == joomla* ]]; then
|
||||||
|
echo "tag=stable" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "release_tag=stable" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "release_tag=v${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
|
fi
|
||||||
|
echo "branch=main" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Published version: ${VERSION}"
|
||||||
|
|
||||||
|
- name: "Create semver tag for non-Joomla repos"
|
||||||
|
id: semver
|
||||||
|
if: |
|
||||||
|
steps.version.outputs.skip != 'true' &&
|
||||||
|
!startsWith(steps.platform.outputs.platform, 'joomla')
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.version.outputs.version }}"
|
||||||
|
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
|
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
|
SEMVER_TAG="v${VERSION}"
|
||||||
|
|
||||||
|
echo "Creating semver tag: ${SEMVER_TAG}"
|
||||||
|
|
||||||
|
# Create the git tag via API
|
||||||
|
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \
|
||||||
|
-X POST -H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API_BASE}/tags" \
|
||||||
|
-d "{\"tag_name\":\"${SEMVER_TAG}\",\"target\":\"main\",\"message\":\"Release ${VERSION}\"}" 2>/dev/null || echo "000")
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
|
||||||
|
echo "Created semver tag: ${SEMVER_TAG}"
|
||||||
|
elif [ "$HTTP_CODE" = "409" ]; then
|
||||||
|
echo "Semver tag ${SEMVER_TAG} already exists (skipped)"
|
||||||
|
else
|
||||||
|
echo "::warning::Failed to create semver tag ${SEMVER_TAG} (HTTP ${HTTP_CODE})"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "semver_tag=${SEMVER_TAG}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Update release notes and promote changelog
|
||||||
|
run: |
|
||||||
|
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
|
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
|
|
||||||
|
# Get the stable release info (version and ID)
|
||||||
|
RELEASE_JSON=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API_BASE}/releases/tags/stable" 2>/dev/null || echo '{}')
|
||||||
|
RELEASE_ID=$(python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" <<< "$RELEASE_JSON" 2>/dev/null || true)
|
||||||
|
# Extract version from release name (e.g. "06.17.00" or "v06.17.00")
|
||||||
|
VERSION=$(python3 -c "
|
||||||
|
import json, sys, re
|
||||||
|
r = json.load(sys.stdin)
|
||||||
|
name = r.get('name', '')
|
||||||
|
m = re.search(r'(\d+\.\d+\.\d+)', name)
|
||||||
|
print(m.group(1) if m else '')
|
||||||
|
" <<< "$RELEASE_JSON" 2>/dev/null || true)
|
||||||
|
|
||||||
|
# Extract [Unreleased] section from changelog
|
||||||
|
NOTES=""
|
||||||
|
if [ -f "CHANGELOG.md" ]; then
|
||||||
|
NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md)
|
||||||
|
fi
|
||||||
|
[ -z "$NOTES" ] && NOTES="Stable release"
|
||||||
|
|
||||||
|
# Update release body via API
|
||||||
|
if [ -n "$RELEASE_ID" ]; then
|
||||||
|
python3 -c "
|
||||||
|
import json, urllib.request
|
||||||
|
body = open('/dev/stdin').read()
|
||||||
|
payload = json.dumps({'body': body}).encode()
|
||||||
|
req = urllib.request.Request(
|
||||||
|
'${API_BASE}/releases/${RELEASE_ID}',
|
||||||
|
data=payload, method='PATCH',
|
||||||
|
headers={
|
||||||
|
'Authorization': 'token ${TOKEN}',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
})
|
||||||
|
urllib.request.urlopen(req)
|
||||||
|
" <<< "$NOTES"
|
||||||
|
echo "Release notes updated from CHANGELOG.md"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Promote [Unreleased] → [version] in CHANGELOG.md and reset
|
||||||
|
if [ -n "$VERSION" ] && [ -f "CHANGELOG.md" ]; then
|
||||||
|
DATE=$(date +%Y-%m-%d)
|
||||||
|
python3 -c "
|
||||||
|
import sys
|
||||||
|
version, date = sys.argv[1], sys.argv[2]
|
||||||
|
content = open('CHANGELOG.md').read()
|
||||||
|
old = '## [Unreleased]'
|
||||||
|
new = f'## [Unreleased]\n\n## [{version}] --- {date}'
|
||||||
|
content = content.replace(old, new, 1)
|
||||||
|
open('CHANGELOG.md', 'w').write(content)
|
||||||
|
" "$VERSION" "$DATE"
|
||||||
|
git add CHANGELOG.md
|
||||||
|
git commit -m "chore: promote changelog [Unreleased] → [${VERSION}]" || true
|
||||||
|
git push origin main || true
|
||||||
|
echo "Changelog promoted: [Unreleased] → [${VERSION}]"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
|
||||||
|
- name: "Step 9: Mirror release to GitHub"
|
||||||
|
if: >-
|
||||||
|
steps.version.outputs.skip != 'true' &&
|
||||||
|
secrets.GH_MIRROR_TOKEN != ''
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
|
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||||
|
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||||
|
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
|
php ${MOKO_CLI}/release_mirror.php \
|
||||||
|
--version "$VERSION" --tag "$RELEASE_TAG" \
|
||||||
|
--token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
|
||||||
|
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
|
||||||
|
--branch main 2>&1 || true
|
||||||
|
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# -- STEP 10: Sync main branch to GitHub mirror ----------------------------
|
||||||
|
- name: "Step 10: Push main to GitHub mirror"
|
||||||
|
if: >-
|
||||||
|
steps.version.outputs.skip != 'true' &&
|
||||||
|
secrets.GH_MIRROR_TOKEN != ''
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||||
|
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
|
||||||
|
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
|
||||||
|
git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
|
||||||
|
git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
|
||||||
|
git fetch origin main --depth=1
|
||||||
|
git push github origin/main:refs/heads/main --force 2>/dev/null \
|
||||||
|
&& echo "main branch pushed to GitHub mirror" \
|
||||||
|
|| echo "WARNING: GitHub mirror push failed"
|
||||||
|
|
||||||
|
- name: "Step 11: Delete rc branch and recreate dev from main"
|
||||||
|
if: steps.version.outputs.skip != 'true'
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
|
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
|
|
||||||
|
# Delete rc branch (ephemeral — created by promote-rc)
|
||||||
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API_BASE}/branches/rc" 2>/dev/null \
|
||||||
|
&& echo "Deleted rc branch" || echo "rc branch not found"
|
||||||
|
|
||||||
|
# Delete dev branch
|
||||||
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
|
||||||
|
|
||||||
|
# Recreate dev from main (now includes version bump + changelog promotion)
|
||||||
|
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API_BASE}/branches" \
|
||||||
|
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
|
||||||
|
|
||||||
|
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
- name: "Step 12: Create version branch from main"
|
||||||
|
if: steps.version.outputs.skip != 'true'
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
|
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
|
BRANCH_NAME="version/${VERSION}"
|
||||||
|
MAIN_SHA=$(git rev-parse HEAD)
|
||||||
|
|
||||||
|
# Delete old version branch if it exists (same version re-release)
|
||||||
|
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}"
|
||||||
|
|
||||||
|
# Create version/XX.YY.ZZ from main
|
||||||
|
curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed"
|
||||||
|
|
||||||
|
echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# -- Dolibarr post-release: Reset dev version -----------------------------
|
||||||
|
- name: "Post-release: Reset dev version"
|
||||||
|
if: steps.version.outputs.skip != 'true'
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
|
php ${MOKO_CLI}/version_reset_dev.php \
|
||||||
|
--token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "${API_BASE}" \
|
||||||
|
--branch dev --path . 2>&1 || true
|
||||||
|
|
||||||
|
# -- Summary --------------------------------------------------------------
|
||||||
|
- name: Pipeline Summary
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||||
|
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||||
|
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
|
||||||
|
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
|
||||||
|
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Platform | \`${PLATFORM}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "| Release | [View](${MOKOGIT_URL}/${GIT_ORG}/${GIT_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
|
# INGROUP: MokoCLI.Universal
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
||||||
|
# PATH: /.mokogit/workflows/branch-cleanup.yml
|
||||||
|
# VERSION: 01.00.00
|
||||||
|
# BRIEF: Delete feature branches after PR merge
|
||||||
|
|
||||||
|
name: "Branch Cleanup"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
cleanup:
|
||||||
|
name: Delete merged branch
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: >-
|
||||||
|
github.event.pull_request.merged == true &&
|
||||||
|
github.event.pull_request.head.ref != 'dev' &&
|
||||||
|
github.event.pull_request.head.ref != 'main'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Delete source branch
|
||||||
|
# SECURITY: the PR head ref is attacker-controllable. Pass it (and the
|
||||||
|
# repo/token) through env so the Actions engine cannot splice it into the
|
||||||
|
# shell source, and validate it to a safe charset before use.
|
||||||
|
env:
|
||||||
|
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
API_BASE: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
if ! printf '%s' "${BRANCH}" | grep -Eq '^[A-Za-z0-9._/-]+$'; then
|
||||||
|
echo "::error::unsafe branch name; refusing to proceed"; exit 1
|
||||||
|
fi
|
||||||
|
API="${API_BASE}/api/v1/repos/${REPO}/branches"
|
||||||
|
# URL-encode the branch name's slashes (no PHP dependency on the runner)
|
||||||
|
ENCODED=$(printf '%s' "${BRANCH}" | sed 's|/|%2F|g')
|
||||||
|
|
||||||
|
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
||||||
|
-H "Authorization: token ${MOKOGIT_TOKEN}" \
|
||||||
|
"${API}/${ENCODED}" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ "$STATUS" = "204" ]; then
|
||||||
|
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ "$STATUS" = "404" ]; then
|
||||||
|
echo "Branch already deleted: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "::warning::Failed to delete branch ${BRANCH} (HTTP ${STATUS})"
|
||||||
|
fi
|
||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
# PATH: /templates/workflows/shared/changelog-validation.yml.template
|
# PATH: /templates/workflows/shared/changelog-validation.yml.template
|
||||||
# VERSION: 04.06.00
|
# VERSION: 04.06.00
|
||||||
# BRIEF: Validates CHANGELOG.md format and version consistency
|
# BRIEF: Validates CHANGELOG.md format and version consistency
|
||||||
# NOTE: Deployed to .mokogitea/workflows/changelog-validation.yml in governed repos.
|
# NOTE: Deployed to .mokogit/workflows/changelog-validation.yml in governed repos.
|
||||||
|
|
||||||
name: "Universal: Changelog Validation"
|
name: "Universal: Changelog Validation"
|
||||||
|
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
|
# INGROUP: MokoCLI.CI
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
|
||||||
|
# PATH: /.mokogit/workflows/ci-generic.yml
|
||||||
|
# VERSION: 01.00.00
|
||||||
|
# BRIEF: CI pipeline — lint, validate, and test for generic projects (PHP + Node.js)
|
||||||
|
|
||||||
|
name: "Generic: Project CI"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- dev
|
||||||
|
- dev/**
|
||||||
|
- rc/**
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# ── Lint & Validate ───────────────────────────────────────────────────
|
||||||
|
lint:
|
||||||
|
name: Lint & Validate
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Detect toolchain
|
||||||
|
id: detect
|
||||||
|
run: |
|
||||||
|
HAS_PHP=false
|
||||||
|
HAS_NODE=false
|
||||||
|
[ -f "composer.json" ] && HAS_PHP=true
|
||||||
|
[ -f "package.json" ] && HAS_NODE=true
|
||||||
|
echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Toolchain: PHP=$HAS_PHP Node=$HAS_NODE"
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
if: steps.detect.outputs.has_php == 'true'
|
||||||
|
run: |
|
||||||
|
if ! command -v php &> /dev/null; then
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
php -v
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
if: steps.detect.outputs.has_node == 'true'
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Install PHP dependencies
|
||||||
|
if: steps.detect.outputs.has_php == 'true'
|
||||||
|
run: |
|
||||||
|
if [ -f "composer.json" ]; then
|
||||||
|
composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Install Node.js dependencies
|
||||||
|
if: steps.detect.outputs.has_node == 'true'
|
||||||
|
run: |
|
||||||
|
if [ -f "package.json" ]; then
|
||||||
|
npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: PHP syntax check
|
||||||
|
if: steps.detect.outputs.has_php == 'true'
|
||||||
|
run: |
|
||||||
|
ERRORS=0
|
||||||
|
while IFS= read -r -d '' file; do
|
||||||
|
if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then
|
||||||
|
echo "::error file=${file}::PHP syntax error"
|
||||||
|
ERRORS=$((ERRORS + 1))
|
||||||
|
fi
|
||||||
|
done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -not -path "./node_modules/*" -print0)
|
||||||
|
|
||||||
|
echo "## PHP Lint" >> $GITHUB_STEP_SUMMARY
|
||||||
|
if [ "$ERRORS" -eq 0 ]; then
|
||||||
|
echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "${ERRORS} file(s) with syntax errors." >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: TypeScript/JavaScript lint
|
||||||
|
if: steps.detect.outputs.has_node == 'true'
|
||||||
|
run: |
|
||||||
|
if [ -f "node_modules/.bin/eslint" ]; then
|
||||||
|
npx eslint src/ --quiet 2>&1 || { echo "::error::ESLint errors found"; exit 1; }
|
||||||
|
echo "## ESLint" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "All files passed ESLint." >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ -f ".eslintrc.json" ] || [ -f ".eslintrc.js" ] || [ -f "eslint.config.js" ]; then
|
||||||
|
echo "::warning::ESLint config found but eslint not installed"
|
||||||
|
else
|
||||||
|
echo "No ESLint configured — skipping"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: TypeScript compile check
|
||||||
|
if: steps.detect.outputs.has_node == 'true'
|
||||||
|
run: |
|
||||||
|
if [ -f "tsconfig.json" ] && [ -f "node_modules/.bin/tsc" ]; then
|
||||||
|
npx tsc --noEmit 2>&1 || { echo "::error::TypeScript compilation errors"; exit 1; }
|
||||||
|
echo "## TypeScript" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "TypeScript compilation passed." >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: PHPStan static analysis
|
||||||
|
if: steps.detect.outputs.has_php == 'true'
|
||||||
|
run: |
|
||||||
|
if [ -f "phpstan.neon" ] && [ -f "vendor/bin/phpstan" ]; then
|
||||||
|
vendor/bin/phpstan analyse --no-progress 2>&1 || { echo "::warning::PHPStan found issues"; }
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Tests ─────────────────────────────────────────────────────────────
|
||||||
|
test:
|
||||||
|
name: Tests
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: lint
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Detect toolchain
|
||||||
|
id: detect
|
||||||
|
run: |
|
||||||
|
HAS_PHP=false
|
||||||
|
HAS_NODE=false
|
||||||
|
[ -f "composer.json" ] && HAS_PHP=true
|
||||||
|
[ -f "package.json" ] && HAS_NODE=true
|
||||||
|
echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Setup PHP
|
||||||
|
if: steps.detect.outputs.has_php == 'true'
|
||||||
|
run: |
|
||||||
|
if ! command -v php &> /dev/null; then
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
if: steps.detect.outputs.has_node == 'true'
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
[ -f "composer.json" ] && composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true
|
||||||
|
[ -f "package.json" ] && { npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true; }
|
||||||
|
|
||||||
|
- name: Run PHP tests
|
||||||
|
if: steps.detect.outputs.has_php == 'true'
|
||||||
|
run: |
|
||||||
|
if [ -f "vendor/bin/phpunit" ]; then
|
||||||
|
vendor/bin/phpunit --testdox 2>&1
|
||||||
|
echo "## PHPUnit" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Tests passed." >> $GITHUB_STEP_SUMMARY
|
||||||
|
elif [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then
|
||||||
|
echo "::warning::PHPUnit config found but phpunit not installed"
|
||||||
|
else
|
||||||
|
echo "No PHPUnit configured — skipping"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run Node.js tests
|
||||||
|
if: steps.detect.outputs.has_node == 'true'
|
||||||
|
run: |
|
||||||
|
if jq -e '.scripts.test' package.json > /dev/null 2>&1; then
|
||||||
|
npm test 2>&1
|
||||||
|
echo "## Node.js Tests" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Tests passed." >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "No test script in package.json — skipping"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build check
|
||||||
|
run: |
|
||||||
|
if [ -f "Makefile" ]; then
|
||||||
|
make build 2>&1 || echo "::warning::Build failed or not configured"
|
||||||
|
elif [ -f "package.json" ] && jq -e '.scripts.build' package.json > /dev/null 2>&1; then
|
||||||
|
npm run build 2>&1 || echo "::warning::Build failed"
|
||||||
|
fi
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
|
# INGROUP: MokoCLI.Universal
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
|
# PATH: /.mokogit/workflows/ci-issue-reporter.yml
|
||||||
|
# VERSION: 01.00.00
|
||||||
|
# BRIEF: Reusable workflow — creates/updates a MokoGIT issue when a CI gate fails.
|
||||||
|
# Clones MokoCLI and runs cli/ci_issue_reporter.sh.
|
||||||
|
|
||||||
|
name: "Universal: CI Issue Reporter"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
gate:
|
||||||
|
description: "CI gate name (e.g. PR Validation, Repository Health)"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
details:
|
||||||
|
description: "Human-readable failure description"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
severity:
|
||||||
|
description: "error or warning"
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: "error"
|
||||||
|
workflow:
|
||||||
|
description: "Workflow name for the issue title"
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
default: ""
|
||||||
|
secrets:
|
||||||
|
MOKOGIT_TOKEN:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
report:
|
||||||
|
name: "Report: ${{ inputs.gate }}"
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Clone MokoCLI
|
||||||
|
env:
|
||||||
|
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
run: |
|
||||||
|
MOKOGIT_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
||||||
|
git clone --depth 1 --filter=blob:none --sparse "${MOKOGIT_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
|
||||||
|
cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh
|
||||||
|
|
||||||
|
- name: Report CI failure
|
||||||
|
env:
|
||||||
|
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
run: |
|
||||||
|
chmod +x /tmp/mokocli/cli/ci_issue_reporter.sh
|
||||||
|
/tmp/mokocli/cli/ci_issue_reporter.sh \
|
||||||
|
--gate "${{ inputs.gate }}" \
|
||||||
|
--details "${{ inputs.details }}" \
|
||||||
|
--severity "${{ inputs.severity }}" \
|
||||||
|
--workflow "${{ inputs.workflow }}"
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: MokoStandards.Maintenance
|
# INGROUP: MokoCLI.Maintenance
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /.gitea/workflows/cleanup.yml
|
# PATH: /.mokogit/workflows/cleanup.yml
|
||||||
# VERSION: 01.00.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
|
# BRIEF: Scheduled cleanup — delete merged branches and old workflow runs
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
cleanup:
|
cleanup:
|
||||||
@@ -33,17 +33,17 @@ jobs:
|
|||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.GA_TOKEN }}
|
token: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
|
||||||
- name: Delete merged branches
|
- name: Delete merged branches
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo "=== Merged Branch Cleanup ==="
|
echo "=== Merged Branch Cleanup ==="
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${MOKOGIT_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
|
|
||||||
# List branches via API
|
# List branches via API
|
||||||
BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
|
BRANCHES=$(curl -sS -H "Authorization: token ${MOKOGIT_TOKEN}" \
|
||||||
"${API}/branches?limit=50" | jq -r '.[].name')
|
"${API}/branches?limit=50" | jq -r '.[].name')
|
||||||
|
|
||||||
DELETED=0
|
DELETED=0
|
||||||
@@ -56,7 +56,7 @@ jobs:
|
|||||||
# Check if branch is merged into main
|
# Check if branch is merged into main
|
||||||
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
|
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
|
||||||
echo " Deleting merged branch: ${BRANCH}"
|
echo " Deleting merged branch: ${BRANCH}"
|
||||||
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
|
curl -sS -X DELETE -H "Authorization: token ${MOKOGIT_TOKEN}" \
|
||||||
"${API}/branches/${BRANCH}" 2>/dev/null || true
|
"${API}/branches/${BRANCH}" 2>/dev/null || true
|
||||||
DELETED=$((DELETED + 1))
|
DELETED=$((DELETED + 1))
|
||||||
fi
|
fi
|
||||||
@@ -66,20 +66,20 @@ jobs:
|
|||||||
|
|
||||||
- name: Clean old workflow runs
|
- name: Clean old workflow runs
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
echo "=== Workflow Run Cleanup ==="
|
echo "=== Workflow Run Cleanup ==="
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${MOKOGIT_URL}/api/v1/repos/${{ github.repository }}"
|
||||||
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
||||||
|
|
||||||
# Get old completed runs
|
# Get old completed runs
|
||||||
RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
|
RUNS=$(curl -sS -H "Authorization: token ${MOKOGIT_TOKEN}" \
|
||||||
"${API}/actions/runs?status=completed&limit=50" | \
|
"${API}/actions/runs?status=completed&limit=50" | \
|
||||||
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
|
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
|
||||||
|
|
||||||
DELETED=0
|
DELETED=0
|
||||||
for RUN_ID in $RUNS; do
|
for RUN_ID in $RUNS; do
|
||||||
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
|
curl -sS -X DELETE -H "Authorization: token ${MOKOGIT_TOKEN}" \
|
||||||
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
|
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
|
||||||
DELETED=$((DELETED + 1))
|
DELETED=$((DELETED + 1))
|
||||||
done
|
done
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
|
# INGROUP: MokoCLI.Universal
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
|
# PATH: /.mokogit/workflows/workflow-sync-trigger.yml
|
||||||
|
# VERSION: 01.01.00
|
||||||
|
# BRIEF: Trigger workflow sync to live repos when a PR is merged to main
|
||||||
|
|
||||||
|
name: "Universal: Workflow Sync Trigger"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync:
|
||||||
|
name: Sync workflows to live repos
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: >-
|
||||||
|
startsWith(github.event.repository.name, 'Template-') &&
|
||||||
|
(github.event_name == 'workflow_dispatch' ||
|
||||||
|
(github.event.pull_request.merged == true &&
|
||||||
|
!contains(github.event.pull_request.title, '[skip sync]')))
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Determine platform from repo name
|
||||||
|
id: platform
|
||||||
|
run: |
|
||||||
|
REPO="${{ github.event.repository.name }}"
|
||||||
|
case "$REPO" in
|
||||||
|
Template-Joomla) PLATFORM="joomla" ;;
|
||||||
|
Template-Dolibarr) PLATFORM="dolibarr" ;;
|
||||||
|
Template-Go) PLATFORM="go" ;;
|
||||||
|
Template-NPM) PLATFORM="npm" ;;
|
||||||
|
Template-Generic) PLATFORM="" ;;
|
||||||
|
*) PLATFORM="" ;;
|
||||||
|
esac
|
||||||
|
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Platform: ${PLATFORM:-all}"
|
||||||
|
|
||||||
|
- name: Clone MokoCLI
|
||||||
|
env:
|
||||||
|
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
run: |
|
||||||
|
MOKOGIT_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
||||||
|
git clone --depth 1 "${MOKOGIT_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
|
||||||
|
|
||||||
|
- name: Install PHP
|
||||||
|
run: |
|
||||||
|
if ! command -v php &> /dev/null; then
|
||||||
|
apt-get update -qq && apt-get install -y -qq php-cli php-json php-curl > /dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
cd /tmp/mokocli
|
||||||
|
composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||||
|
|
||||||
|
- name: Run workflow sync
|
||||||
|
env:
|
||||||
|
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
run: |
|
||||||
|
ARGS="--token ${MOKOGIT_TOKEN}"
|
||||||
|
ARGS="${ARGS} --org ${{ vars.GITEA_ORG || github.repository_owner }}"
|
||||||
|
ARGS="${ARGS} --phase repos"
|
||||||
|
ARGS="${ARGS} --delete-orphans"
|
||||||
|
|
||||||
|
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||||
|
if [ -n "$PLATFORM" ]; then
|
||||||
|
ARGS="${ARGS} --platform-filter ${PLATFORM}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
php /tmp/mokocli/cli/workflow_sync.php ${ARGS}
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: MokoStandards.Security
|
# INGROUP: MokoCLI.Security
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /templates/workflows/gitleaks.yml.template
|
# PATH: /templates/workflows/gitleaks.yml.template
|
||||||
# VERSION: 01.00.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
|
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
|
||||||
@@ -25,10 +25,6 @@
|
|||||||
name: "Universal: Secret Scanning"
|
name: "Universal: Secret Scanning"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- 'dev/**'
|
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 5 * * 1' # Weekly Monday 05:00 UTC
|
- cron: '0 5 * * 1' # Weekly Monday 05:00 UTC
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@@ -38,7 +34,7 @@ permissions:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
||||||
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }}
|
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'git-security' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
gitleaks:
|
gitleaks:
|
||||||
@@ -3,8 +3,8 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: moko-platform.Automation
|
# INGROUP: MokoCLI.Automation
|
||||||
# VERSION: 01.00.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Auto-create feature branch when an issue is opened
|
# BRIEF: Auto-create feature branch when an issue is opened
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ permissions:
|
|||||||
issues: write
|
issues: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
create-branch:
|
create-branch:
|
||||||
@@ -27,14 +27,22 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Create branch and comment
|
- name: Create branch and comment
|
||||||
|
# SECURITY: never interpolate github.event.* into a run: script — the
|
||||||
|
# Actions engine splices the raw value into the shell source (command
|
||||||
|
# injection via a crafted issue title). Pass everything through env so
|
||||||
|
# the values arrive as ordinary shell variables that are not re-parsed.
|
||||||
|
env:
|
||||||
|
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
ISSUE_NUM: ${{ github.event.issue.number }}
|
||||||
|
ISSUE_TITLE: ${{ github.event.issue.title }}
|
||||||
run: |
|
run: |
|
||||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
TOKEN="${MOKOGIT_TOKEN}"
|
||||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
API="${MOKOGIT_URL}/api/v1/repos/${REPO}"
|
||||||
ISSUE_NUM="${{ github.event.issue.number }}"
|
|
||||||
ISSUE_TITLE="${{ github.event.issue.title }}"
|
|
||||||
|
|
||||||
# Build slug from title: lowercase, replace non-alnum with dash, trim
|
# Build slug from title: lowercase, replace non-alnum with dash, trim.
|
||||||
SLUG=$(echo "${ISSUE_TITLE}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | cut -c1-40)
|
# printf (not echo) so a title beginning with "-" is not read as flags.
|
||||||
|
SLUG=$(printf '%s' "${ISSUE_TITLE}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | cut -c1-40)
|
||||||
BRANCH="feature/${ISSUE_NUM}-${SLUG}"
|
BRANCH="feature/${ISSUE_NUM}-${SLUG}"
|
||||||
|
|
||||||
# Check dev branch exists
|
# Check dev branch exists
|
||||||
@@ -58,7 +66,7 @@ jobs:
|
|||||||
echo "Created branch: ${BRANCH}"
|
echo "Created branch: ${BRANCH}"
|
||||||
|
|
||||||
# Comment on issue with branch link
|
# Comment on issue with branch link
|
||||||
REPO_URL="${GITEA_URL}/${{ github.repository }}"
|
REPO_URL="${MOKOGIT_URL}/${REPO}"
|
||||||
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
|
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
|
||||||
|
|
||||||
curl -sf -X POST \
|
curl -sf -X POST \
|
||||||
@@ -3,10 +3,10 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: MokoStandards.Notifications
|
# INGROUP: MokoCLI.Notifications
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /.mokogitea/workflows/notify.yml
|
# PATH: /.mokogit/workflows/notify.yml
|
||||||
# VERSION: 01.00.00
|
# VERSION: 01.00.00
|
||||||
# BRIEF: Push notifications via ntfy on release success or workflow failure
|
# BRIEF: Push notifications via ntfy on release success or workflow failure
|
||||||
|
|
||||||
@@ -18,7 +18,6 @@ on:
|
|||||||
- "Joomla Build & Release"
|
- "Joomla Build & Release"
|
||||||
- "Joomla Extension CI"
|
- "Joomla Extension CI"
|
||||||
- "Deploy"
|
- "Deploy"
|
||||||
- "Cascade Main → Dev"
|
|
||||||
types:
|
types:
|
||||||
- completed
|
- completed
|
||||||
|
|
||||||
@@ -27,7 +26,7 @@ permissions:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
||||||
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-releases' }}
|
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'git-releases' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
notify:
|
notify:
|
||||||
@@ -32,7 +32,8 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: >-
|
if: >-
|
||||||
!contains(github.event.head_commit.message, '[skip ci]') &&
|
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||||
github.actor != 'github-actions[bot]'
|
github.actor != 'github-actions[bot]' &&
|
||||||
|
!startsWith(github.event.repository.name, 'Template-')
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
@@ -87,13 +88,13 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
# ── Version ──────────────────────────────────────────────────────
|
# ── Version ──────────────────────────────────────────────────────
|
||||||
- name: Setup moko-platform tools
|
- name: Setup MokoCLI tools
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
|
||||||
run: |
|
run: |
|
||||||
git clone --depth 1 --branch version/04 --quiet \
|
git clone --depth 1 --branch version/04 --quiet \
|
||||||
"https://x-access-token:${GH_TOKEN}@github.com/MokoConsulting/moko-platform.git" \
|
"https://x-access-token:${GH_TOKEN}@github.com/MokoConsulting/mokocli.git" \
|
||||||
/tmp/mokostandards
|
/tmp/mokostandards
|
||||||
cd /tmp/mokostandards
|
cd /tmp/mokostandards
|
||||||
composer install --no-dev --no-interaction --quiet
|
composer install --no-dev --no-interaction --quiet
|
||||||
@@ -22,6 +22,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [20, 22]
|
node-version: [20, 22]
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
name: "Publish to npm"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
GIT_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||||
|
GIT_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: >-
|
||||||
|
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||||
|
!contains(github.event.head_commit.message, '[skip publish]') &&
|
||||||
|
!startsWith(github.event.repository.name, 'Template-')
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
registry-url: 'https://registry.npmjs.org'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm install
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Auto-bump patch version
|
||||||
|
run: |
|
||||||
|
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
|
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
|
|
||||||
|
PKG_NAME=$(node -p "require('./package.json').name")
|
||||||
|
CURRENT=$(node -p "require('./package.json').version")
|
||||||
|
PUBLISHED=$(npm view "${PKG_NAME}@latest" version 2>/dev/null || echo "0.0.0")
|
||||||
|
|
||||||
|
if [ "$CURRENT" != "$PUBLISHED" ]; then
|
||||||
|
echo "Version ${CURRENT} not yet published, using as-is."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Bump locally to get the new version
|
||||||
|
npm version patch --no-git-tag-version
|
||||||
|
NEW_VER=$(node -p "require('./package.json').version")
|
||||||
|
echo "Bumping ${CURRENT} -> ${NEW_VER}"
|
||||||
|
|
||||||
|
# Push via MokoGIT API: branch + PR + merge
|
||||||
|
BRANCH="chore/npm-version-bump"
|
||||||
|
FILEPATH="package.json"
|
||||||
|
CONTENT=$(base64 -w 0 < package.json)
|
||||||
|
COMMIT_MSG="chore: bump to ${NEW_VER} [skip ci]"
|
||||||
|
|
||||||
|
# Get current file SHA on main
|
||||||
|
FILE_SHA=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API_BASE}/contents/${FILEPATH}?ref=main" \
|
||||||
|
| python3 -c "import json,sys; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
|
||||||
|
|
||||||
|
# Create chore branch from main
|
||||||
|
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API_BASE}/branches" \
|
||||||
|
-d "{\"new_branch_name\":\"${BRANCH}\",\"old_branch_name\":\"main\"}" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Get file SHA on chore branch (may differ if branch already existed)
|
||||||
|
BRANCH_SHA=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API_BASE}/contents/${FILEPATH}?ref=${BRANCH}" \
|
||||||
|
| python3 -c "import json,sys; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
|
||||||
|
[ -n "$BRANCH_SHA" ] && FILE_SHA="$BRANCH_SHA"
|
||||||
|
|
||||||
|
# Push package.json to chore branch
|
||||||
|
PAYLOAD="{\"content\":\"${CONTENT}\",\"message\":\"${COMMIT_MSG}\",\"branch\":\"${BRANCH}\",\"sha\":\"${FILE_SHA}\"}"
|
||||||
|
HTTP=$(curl -sf -o /dev/null -w "%{http_code}" -X PUT \
|
||||||
|
-H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$PAYLOAD" \
|
||||||
|
"${API_BASE}/contents/${FILEPATH}" 2>/dev/null || echo "ERR")
|
||||||
|
echo "File push: HTTP ${HTTP}"
|
||||||
|
|
||||||
|
# Create PR
|
||||||
|
PR_NUM=$(curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API_BASE}/pulls" \
|
||||||
|
-d "{\"title\":\"${COMMIT_MSG}\",\"head\":\"${BRANCH}\",\"base\":\"main\"}" \
|
||||||
|
| python3 -c "import json,sys; print(json.load(sys.stdin).get('number',''))" 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [ -n "$PR_NUM" ]; then
|
||||||
|
# Merge PR
|
||||||
|
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"${API_BASE}/pulls/${PR_NUM}/merge" \
|
||||||
|
-d "{\"Do\":\"merge\",\"merge_message_field\":\"${COMMIT_MSG}\"}" 2>/dev/null
|
||||||
|
echo "Version bumped via PR #${PR_NUM} (merged)"
|
||||||
|
else
|
||||||
|
echo "::warning::Could not create PR for version bump — publishing with current version"
|
||||||
|
fi
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
|
||||||
|
- name: Publish
|
||||||
|
run: npm publish --access public
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
@@ -19,6 +19,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
check-sdk:
|
check-sdk:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ !startsWith(github.event.repository.name, 'Template-') }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# Enforces branch merge policy:
|
||||||
|
# feature/* → dev only
|
||||||
|
# fix/* → dev only
|
||||||
|
# hotfix/* → dev or main (emergency)
|
||||||
|
# dev → main only
|
||||||
|
# alpha/* → dev only
|
||||||
|
# beta/* → dev only
|
||||||
|
# rc/* → main only
|
||||||
|
|
||||||
|
name: Branch Policy Check
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened, edited]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-target:
|
||||||
|
name: Verify merge target
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check branch policy
|
||||||
|
run: |
|
||||||
|
HEAD="${{ github.head_ref }}"
|
||||||
|
BASE="${{ github.base_ref }}"
|
||||||
|
|
||||||
|
echo "PR: ${HEAD} → ${BASE}"
|
||||||
|
|
||||||
|
ALLOWED=true
|
||||||
|
REASON=""
|
||||||
|
|
||||||
|
case "$HEAD" in
|
||||||
|
feature/*|feat/*)
|
||||||
|
if [ "$BASE" != "dev" ]; then
|
||||||
|
ALLOWED=false
|
||||||
|
REASON="Feature branches must target 'dev', not '${BASE}'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
fix/*|bugfix/*)
|
||||||
|
if [ "$BASE" != "dev" ]; then
|
||||||
|
ALLOWED=false
|
||||||
|
REASON="Fix branches must target 'dev', not '${BASE}'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
hotfix/*)
|
||||||
|
if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then
|
||||||
|
ALLOWED=false
|
||||||
|
REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
alpha/*|beta/*)
|
||||||
|
if [ "$BASE" != "dev" ]; then
|
||||||
|
ALLOWED=false
|
||||||
|
REASON="Pre-release branches must target 'dev', not '${BASE}'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
rc/*)
|
||||||
|
if [ "$BASE" != "main" ]; then
|
||||||
|
ALLOWED=false
|
||||||
|
REASON="Release candidate branches must target 'main', not '${BASE}'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
dev)
|
||||||
|
if [ "$BASE" != "main" ]; then
|
||||||
|
ALLOWED=false
|
||||||
|
REASON="Dev branch can only merge into 'main', not '${BASE}'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
if [ "$ALLOWED" = false ]; then
|
||||||
|
echo "::error::${REASON}"
|
||||||
|
echo ""
|
||||||
|
echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "${REASON}" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- \`feature/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- \`fix/*\` → \`dev\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- \`hotfix/*\` → \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- \`dev\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "- \`rc/*\` → \`main\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Branch policy: OK (${HEAD} → ${BASE})"
|
||||||
|
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -3,9 +3,9 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: moko-platform.CI
|
# INGROUP: MokoCLI.CI
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /templates/workflows/universal/pr-check.yml.template
|
# PATH: /templates/workflows/universal/pr-check.yml.template
|
||||||
# VERSION: 09.23.00
|
# VERSION: 09.23.00
|
||||||
# BRIEF: PR gate — branch policy + code validation before merge
|
# BRIEF: PR gate — branch policy + code validation before merge
|
||||||
@@ -96,6 +96,32 @@ jobs:
|
|||||||
echo "Branch policy: OK (${HEAD} → ${BASE})"
|
echo "Branch policy: OK (${HEAD} → ${BASE})"
|
||||||
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
|
echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
# ── Secret Scanning ──────────────────────────────────────────────────
|
||||||
|
gitleaks:
|
||||||
|
name: Secret Scan
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Install Gitleaks
|
||||||
|
run: |
|
||||||
|
GITLEAKS_VERSION="8.21.2"
|
||||||
|
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
|
||||||
|
| tar -xz -C /usr/local/bin gitleaks
|
||||||
|
|
||||||
|
- name: Scan PR commits for secrets
|
||||||
|
run: |
|
||||||
|
if gitleaks detect --source . --verbose \
|
||||||
|
--log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} 2>&1; then
|
||||||
|
echo "**No secrets detected.**" >> $GITHUB_STEP_SUMMARY
|
||||||
|
else
|
||||||
|
echo "::error::Potential secrets detected in PR commits"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Code Validation ────────────────────────────────────────────────────
|
# ── Code Validation ────────────────────────────────────────────────────
|
||||||
validate:
|
validate:
|
||||||
name: Validate PR
|
name: Validate PR
|
||||||
@@ -121,11 +147,12 @@ jobs:
|
|||||||
- name: Detect platform
|
- name: Detect platform
|
||||||
id: platform
|
id: platform
|
||||||
run: |
|
run: |
|
||||||
# Read platform from XML manifest (<platform> tag) or plain text fallback
|
# Platform comes from the MokoGIT metadata API (public GET).
|
||||||
PLATFORM=$(sed -n 's/.*<platform>\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1)
|
API="${GITHUB_SERVER_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${GITHUB_REPOSITORY}/metadata"
|
||||||
[ -z "$PLATFORM" ] && PLATFORM=$(cat .mokogitea/manifest.xml 2>/dev/null | tr -d '[:space:]')
|
PLATFORM="$(curl -sf "$API" 2>/dev/null | python3 -c "import sys, json; print(json.load(sys.stdin).get('platform') or '')" 2>/dev/null || true)"
|
||||||
[ -z "$PLATFORM" ] && PLATFORM="generic"
|
[ -z "$PLATFORM" ] && PLATFORM="generic"
|
||||||
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
|
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "Detected platform: $PLATFORM"
|
||||||
|
|
||||||
- name: Setup PHP
|
- name: Setup PHP
|
||||||
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
|
if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr'
|
||||||
@@ -256,10 +283,10 @@ jobs:
|
|||||||
for ELEMENT in name version description; do
|
for ELEMENT in name version description; do
|
||||||
grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; }
|
grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; }
|
||||||
done
|
done
|
||||||
# Block legacy raw/branch update server URLs on MokoGitea
|
# Block legacy raw/branch update server URLs on MokoGIT
|
||||||
RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogitea\|git\.mokoconsulting\.tech' || true)
|
RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogit\|git\.mokoconsulting\.tech' || true)
|
||||||
if [ -n "$RAW_URLS" ]; then
|
if [ -n "$RAW_URLS" ]; then
|
||||||
echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the Gitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
|
echo "::error::Manifest contains legacy raw/branch update server URL on MokoGIT. Use the MokoGIT Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)"
|
||||||
echo "$RAW_URLS"
|
echo "$RAW_URLS"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
@@ -470,39 +497,26 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Trigger RC pre-release
|
- name: Trigger RC pre-release
|
||||||
env:
|
env:
|
||||||
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
MOKOGIT_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
REPO: ${{ github.repository }}
|
REPO: ${{ github.repository }}
|
||||||
BRANCH: ${{ github.head_ref }}
|
BRANCH: ${{ github.head_ref }}
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
MOKOGIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
run: |
|
run: |
|
||||||
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
|
curl -s -X POST "${MOKOGIT_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${MOKOGIT_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
|
||||||
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
|
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
|
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# ── Issue Reporter ──────────────────────────────────────────────────────
|
# ── Issue Reporter ──────────────────────────────────────────────────────
|
||||||
report-issues:
|
report-issues:
|
||||||
name: Report Issues
|
name: Report Issues
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: [branch-policy, validate]
|
needs: [branch-policy, validate]
|
||||||
if: >-
|
if: >-
|
||||||
always() &&
|
always() &&
|
||||||
needs.validate.result == 'failure'
|
needs.validate.result == 'failure'
|
||||||
|
uses: ./.mokogit/workflows/ci-issue-reporter.yml
|
||||||
steps:
|
with:
|
||||||
- name: Checkout
|
gate: "PR Validation"
|
||||||
uses: actions/checkout@v4
|
workflow: "PR Check"
|
||||||
with:
|
severity: error
|
||||||
sparse-checkout: automation/ci-issue-reporter.sh
|
details: "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed."
|
||||||
sparse-checkout-cone-mode: false
|
secrets: inherit
|
||||||
|
|
||||||
- name: "File issue for PR validation failure"
|
|
||||||
env:
|
|
||||||
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
|
||||||
run: |
|
|
||||||
chmod +x automation/ci-issue-reporter.sh
|
|
||||||
./automation/ci-issue-reporter.sh \
|
|
||||||
--gate "PR Validation" \
|
|
||||||
--workflow "PR Check" \
|
|
||||||
--severity error \
|
|
||||||
--details "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed."
|
|
||||||
@@ -3,24 +3,27 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: moko-platform.Release
|
# INGROUP: MokoCLI.Release
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /templates/workflows/universal/pre-release.yml.template
|
# PATH: /templates/workflows/universal/pre-release.yml.template
|
||||||
# VERSION: 05.01.00
|
# VERSION: 05.02.00
|
||||||
# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch
|
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
|
||||||
|
|
||||||
name: "Universal: Pre-Release"
|
name: "Universal: Pre-Release"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
push:
|
||||||
types: [closed]
|
|
||||||
branches:
|
branches:
|
||||||
- dev
|
- dev
|
||||||
pull_request_target:
|
- 'fix/**'
|
||||||
types: [synchronize, opened, reopened]
|
- 'patch/**'
|
||||||
branches:
|
- 'hotfix/**'
|
||||||
- main
|
- 'bugfix/**'
|
||||||
|
- 'chore/**'
|
||||||
|
- alpha
|
||||||
|
- beta
|
||||||
|
- rc
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
stability:
|
stability:
|
||||||
@@ -37,60 +40,82 @@ permissions:
|
|||||||
contents: write
|
contents: write
|
||||||
|
|
||||||
env:
|
env:
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
GIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
GIT_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
GIT_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
|
name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})"
|
||||||
runs-on: release
|
runs-on: release
|
||||||
if: >-
|
if: >-
|
||||||
github.event_name == 'workflow_dispatch' ||
|
github.event_name == 'workflow_dispatch' ||
|
||||||
(github.event_name == 'pull_request' && github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'dev') ||
|
github.event_name == 'push'
|
||||||
(github.event_name == 'pull_request_target' && github.event.pull_request.base.ref == 'main')
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
token: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || '' }}
|
ref: ${{ github.ref_name }}
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
- name: Setup moko-platform tools
|
- name: Update submodules to main
|
||||||
|
run: |
|
||||||
|
git submodule foreach --quiet 'git checkout main && git pull --quiet origin main' 2>/dev/null || true
|
||||||
|
|
||||||
|
- name: Setup MokoCLI tools
|
||||||
env:
|
env:
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||||
run: |
|
run: |
|
||||||
# Use pre-installed /opt/moko-platform if available (updated by cron every 6h)
|
# Use pre-installed /opt/mokocli if available (updated by cron every 6h)
|
||||||
if [ -f “/opt/moko-platform/cli/version_bump.php” ] && [ -f “/opt/moko-platform/vendor/autoload.php” ]; then
|
if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/cli/manifest_element.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
|
||||||
echo “Using pre-installed /opt/moko-platform”
|
echo Using pre-installed /opt/mokocli
|
||||||
echo “MOKO_CLI=/opt/moko-platform/cli” >> “$GITHUB_ENV”
|
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
|
||||||
else
|
else
|
||||||
echo “Falling back to fresh clone”
|
echo Falling back to fresh clone
|
||||||
if ! command -v composer &> /dev/null; then
|
if ! command -v composer > /dev/null 2>&1; then
|
||||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
|
||||||
fi
|
fi
|
||||||
rm -rf /tmp/moko-platform-api
|
rm -rf /tmp/mokocli
|
||||||
git clone --depth 1 --branch main --quiet \
|
CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
|
||||||
“https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git” \
|
git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
|
||||||
/tmp/moko-platform-api
|
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
||||||
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
|
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
|
||||||
echo “MOKO_CLI=/tmp/moko-platform-api/cli” >> “$GITHUB_ENV”
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Detect platform
|
- name: Detect platform
|
||||||
id: platform
|
id: platform
|
||||||
run: |
|
run: |
|
||||||
|
# Auto-detect and update platform if not set in manifest
|
||||||
|
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
|
||||||
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||||
|
|
||||||
|
- name: Check platform eligibility (Joomla only)
|
||||||
|
id: eligibility
|
||||||
|
run: |
|
||||||
|
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||||
|
if [[ "$PLATFORM" == joomla* ]] || [[ "$PLATFORM" == "joomla" ]]; then
|
||||||
|
echo "proceed=true" >> "$GITHUB_OUTPUT"
|
||||||
|
else
|
||||||
|
echo "proceed=false" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "::notice::Platform '$PLATFORM' — non-Joomla, skipping pre-release auto-bump"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Resolve metadata and bump version
|
- name: Resolve metadata and bump version
|
||||||
id: meta
|
id: meta
|
||||||
|
if: steps.eligibility.outputs.proceed == 'true'
|
||||||
run: |
|
run: |
|
||||||
# Auto-detect stability: RC for PRs targeting main, else use input or default to development
|
# Auto-detect stability from branch name on push, or use input on dispatch
|
||||||
if [ "${{ github.event_name }}" = "pull_request_target" ] && [ "${{ github.event.pull_request.base.ref }}" = "main" ]; then
|
if [ "${{ github.event_name }}" = "push" ]; then
|
||||||
STABILITY="release-candidate"
|
case "${{ github.ref_name }}" in
|
||||||
|
rc) STABILITY="release-candidate" ;;
|
||||||
|
alpha) STABILITY="alpha" ;;
|
||||||
|
beta) STABILITY="beta" ;;
|
||||||
|
*) STABILITY="development" ;;
|
||||||
|
esac
|
||||||
else
|
else
|
||||||
STABILITY="${{ inputs.stability || 'development' }}"
|
STABILITY="${{ inputs.stability || 'development' }}"
|
||||||
fi
|
fi
|
||||||
@@ -118,15 +143,18 @@ jobs:
|
|||||||
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
|
--path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true
|
||||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||||
|
|
||||||
|
# Ensure licensing tags (updateservers, dlid) if enabled in manifest.xml
|
||||||
|
php ${MOKO_CLI}/manifest_licensing.php --path . --fix 2>/dev/null || true
|
||||||
|
|
||||||
# Append suffix for output
|
# Append suffix for output
|
||||||
if [ -n "$SUFFIX" ]; then
|
if [ -n "$SUFFIX" ]; then
|
||||||
VERSION="${VERSION}${SUFFIX}"
|
VERSION="${VERSION}${SUFFIX}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Commit version bump
|
# Commit version bump
|
||||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
git config --local user.email "mokogit-actions[bot]@mokoconsulting.tech"
|
||||||
git config --local user.name "gitea-actions[bot]"
|
git config --local user.name "mokogit-actions[bot]"
|
||||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||||
git add -A
|
git add -A
|
||||||
git diff --cached --quiet || {
|
git diff --cached --quiet || {
|
||||||
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
|
git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]"
|
||||||
@@ -136,12 +164,12 @@ jobs:
|
|||||||
# Auto-detect element via manifest_element.php
|
# Auto-detect element via manifest_element.php
|
||||||
php ${MOKO_CLI}/manifest_element.php \
|
php ${MOKO_CLI}/manifest_element.php \
|
||||||
--path . --version "$VERSION" --stability "$STABILITY" \
|
--path . --version "$VERSION" --stability "$STABILITY" \
|
||||||
--repo "${GITEA_REPO}" --github-output
|
--repo "${GIT_REPO}" --github-output
|
||||||
|
|
||||||
# Read back element outputs
|
# Read back element outputs
|
||||||
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||||
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2)
|
||||||
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GIT_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
|
||||||
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
[ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
||||||
|
|
||||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||||
@@ -155,20 +183,22 @@ jobs:
|
|||||||
|
|
||||||
- name: Create release
|
- name: Create release
|
||||||
id: release
|
id: release
|
||||||
|
if: steps.eligibility.outputs.proceed == 'true'
|
||||||
run: |
|
run: |
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
php ${MOKO_CLI}/release_create.php \
|
php ${MOKO_CLI}/release_create.php \
|
||||||
--path . --version "$VERSION" --tag "$TAG" \
|
--path . --version "$VERSION" --tag "$TAG" \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
--token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
|
||||||
--repo "${GITEA_REPO}" --branch dev --prerelease
|
--repo "${GIT_REPO}" --branch "${{ github.ref_name }}" --prerelease
|
||||||
|
|
||||||
- name: Update release notes from CHANGELOG.md
|
- name: Update release notes from CHANGELOG.md
|
||||||
|
if: steps.eligibility.outputs.proceed == 'true'
|
||||||
run: |
|
run: |
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
|
|
||||||
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
|
# Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading)
|
||||||
if [ -f "CHANGELOG.md" ]; then
|
if [ -f "CHANGELOG.md" ]; then
|
||||||
@@ -179,7 +209,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Update release body via API
|
# Update release body via API
|
||||||
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGIT_TOKEN }}" \
|
||||||
"${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
"${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
|
||||||
|
|
||||||
if [ -n "$RELEASE_ID" ]; then
|
if [ -n "$RELEASE_ID" ]; then
|
||||||
@@ -191,7 +221,7 @@ jobs:
|
|||||||
'${API_BASE}/releases/${RELEASE_ID}',
|
'${API_BASE}/releases/${RELEASE_ID}',
|
||||||
data=payload, method='PATCH',
|
data=payload, method='PATCH',
|
||||||
headers={
|
headers={
|
||||||
'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}',
|
'Authorization': 'token ${{ secrets.MOKOGIT_TOKEN }}',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
})
|
})
|
||||||
urllib.request.urlopen(req)
|
urllib.request.urlopen(req)
|
||||||
@@ -201,23 +231,25 @@ jobs:
|
|||||||
|
|
||||||
- name: Build package and upload
|
- name: Build package and upload
|
||||||
id: package
|
id: package
|
||||||
|
if: steps.eligibility.outputs.proceed == 'true'
|
||||||
run: |
|
run: |
|
||||||
VERSION="${{ steps.meta.outputs.version }}"
|
VERSION="${{ steps.meta.outputs.version }}"
|
||||||
TAG="${{ steps.meta.outputs.tag }}"
|
TAG="${{ steps.meta.outputs.tag }}"
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
php ${MOKO_CLI}/release_package.php \
|
php ${MOKO_CLI}/release_package.php \
|
||||||
--path . --version "$VERSION" --tag "$TAG" \
|
--path . --version "$VERSION" --tag "$TAG" \
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
--token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
|
||||||
--repo "${GITEA_REPO}" --output /tmp || true
|
--repo "${GIT_REPO}" --output /tmp || true
|
||||||
|
|
||||||
# updates.xml is generated dynamically by MokoGitea license server
|
# updates.xml is generated dynamically by MokoGIT license server
|
||||||
# No need to build, commit, or sync updates.xml from workflows
|
# No need to build, commit, or sync updates.xml from workflows
|
||||||
|
|
||||||
- name: "Delete lesser pre-release channels (cascade)"
|
- name: "Delete lesser pre-release channels (cascade)"
|
||||||
|
if: steps.eligibility.outputs.proceed == 'true'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
|
||||||
|
|
||||||
php ${MOKO_CLI}/release_cascade.php \
|
php ${MOKO_CLI}/release_cascade.php \
|
||||||
--stability "${{ steps.meta.outputs.stability }}" \
|
--stability "${{ steps.meta.outputs.stability }}" \
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
|
# INGROUP: MokoCLI.Universal
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
|
# PATH: /.mokogit/workflows/rc-revert.yml
|
||||||
|
# VERSION: 09.23.00
|
||||||
|
# BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge
|
||||||
|
|
||||||
|
name: "RC Revert"
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [closed]
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
revert:
|
||||||
|
name: Rename rc/ back to dev/
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: >-
|
||||||
|
github.event.pull_request.merged == false &&
|
||||||
|
startsWith(github.event.pull_request.head.ref, 'rc/')
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Rename branch
|
||||||
|
env:
|
||||||
|
BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||||
|
REPO: ${{ github.repository }}
|
||||||
|
GIT_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||||
|
TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
# BRANCH is attacker-controlled (PR head ref). Strict allowlist before ANY use.
|
||||||
|
if ! printf '%s' "$BRANCH" | grep -Eq '^rc/[A-Za-z0-9._/-]+$'; then
|
||||||
|
echo "::error::Refusing unsafe branch name: $BRANCH"; exit 1
|
||||||
|
fi
|
||||||
|
SUFFIX="${BRANCH#rc/}"
|
||||||
|
DEV_BRANCH="dev/${SUFFIX}"
|
||||||
|
API="${GIT_URL}/api/v1/repos/${REPO}/branches"
|
||||||
|
|
||||||
|
# Create dev/ branch from rc/ branch
|
||||||
|
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
|
||||||
|
-H "Authorization: token ${TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \
|
||||||
|
"${API}" 2>/dev/null || true)
|
||||||
|
if [ "$STATUS" = "201" ]; then
|
||||||
|
echo "Created branch: ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
else
|
||||||
|
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Read BRANCH from the environment inside PHP (getenv, no string interpolation -> no PHP injection)
|
||||||
|
ENCODED=$(php -r 'echo rawurlencode(getenv("BRANCH"));')
|
||||||
|
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
||||||
|
-H "Authorization: token ${TOKEN}" \
|
||||||
|
"${API}/${ENCODED}" 2>/dev/null || true)
|
||||||
|
if [ "$STATUS" = "204" ]; then
|
||||||
|
echo "Deleted branch: ${BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
else
|
||||||
|
echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "### RC Reverted" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
echo "${BRANCH} → ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||||
@@ -6,9 +6,9 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
#
|
#
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: Gitea.Workflow
|
# DEFGROUP: MokoGIT.Workflow
|
||||||
# INGROUP: moko-platform.Validation
|
# INGROUP: MokoCLI.Validation
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||||
# PATH: /templates/workflows/joomla/repo_health.yml.template
|
# PATH: /templates/workflows/joomla/repo_health.yml.template
|
||||||
# VERSION: 09.23.00
|
# VERSION: 09.23.00
|
||||||
# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts.
|
# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts.
|
||||||
@@ -33,7 +33,8 @@ on:
|
|||||||
- scripts
|
- scripts
|
||||||
- repo
|
- repo
|
||||||
pull_request:
|
pull_request:
|
||||||
push:
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -44,7 +45,7 @@ env:
|
|||||||
SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate
|
SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate
|
||||||
|
|
||||||
# Repo health policy
|
# Repo health policy
|
||||||
REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.mokogitea/workflows/
|
REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.mokogit/workflows/
|
||||||
REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/
|
REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/
|
||||||
REPO_DISALLOWED_DIRS:
|
REPO_DISALLOWED_DIRS:
|
||||||
REPO_DISALLOWED_FILES: TODO.md,todo.md
|
REPO_DISALLOWED_FILES: TODO.md,todo.md
|
||||||
@@ -55,7 +56,7 @@ env:
|
|||||||
# File / directory variables
|
# File / directory variables
|
||||||
DOCS_INDEX: docs/docs-index.md
|
DOCS_INDEX: docs/docs-index.md
|
||||||
SCRIPT_DIR: scripts
|
SCRIPT_DIR: scripts
|
||||||
WORKFLOWS_DIR: .mokogitea/workflows
|
WORKFLOWS_DIR: .mokogit/workflows
|
||||||
SHELLCHECK_PATTERN: '*.sh'
|
SHELLCHECK_PATTERN: '*.sh'
|
||||||
SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml'
|
SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml'
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
@@ -76,7 +77,7 @@ jobs:
|
|||||||
- name: Check actor permission (admin only)
|
- name: Check actor permission (admin only)
|
||||||
id: perm
|
id: perm
|
||||||
env:
|
env:
|
||||||
TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }}
|
TOKEN: ${{ secrets.MOKOGIT_TOKEN || github.token }}
|
||||||
REPO: ${{ github.repository }}
|
REPO: ${{ github.repository }}
|
||||||
ACTOR: ${{ github.actor }}
|
ACTOR: ${{ github.actor }}
|
||||||
run: |
|
run: |
|
||||||
@@ -87,14 +88,14 @@ jobs:
|
|||||||
|
|
||||||
# Hardcoded authorized users — always allowed
|
# Hardcoded authorized users — always allowed
|
||||||
case "$ACTOR" in
|
case "$ACTOR" in
|
||||||
jmiller|gitea-actions[bot])
|
jmiller|mokogit-actions[bot])
|
||||||
ALLOWED=true
|
ALLOWED=true
|
||||||
PERMISSION=admin
|
PERMISSION=admin
|
||||||
METHOD="hardcoded allowlist"
|
METHOD="hardcoded allowlist"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
# Detect platform and check permissions via API
|
# Detect platform and check permissions via API
|
||||||
API_BASE="${GITHUB_API_URL:-${GITEA_API_URL:-https://api.github.com}}"
|
API_BASE="${GITHUB_API_URL:-${GIT_API_URL:-https://api.github.com}}"
|
||||||
RESP=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
RESP=$(curl -sf -H "Authorization: token ${TOKEN}" \
|
||||||
"${API_BASE}/repos/${REPO}/collaborators/${ACTOR}/permission" 2>/dev/null || echo '{}')
|
"${API_BASE}/repos/${REPO}/collaborators/${ACTOR}/permission" 2>/dev/null || echo '{}')
|
||||||
PERMISSION=$(echo "$RESP" | grep -oP '"permission"\s*:\s*"\K[^"]+' || echo "unknown")
|
PERMISSION=$(echo "$RESP" | grep -oP '"permission"\s*:\s*"\K[^"]+' || echo "unknown")
|
||||||
@@ -604,7 +605,7 @@ jobs:
|
|||||||
printf '%s\n' '| Domain | Status | Notes |'
|
printf '%s\n' '| Domain | Status | Notes |'
|
||||||
printf '%s\n' '|---|---|---|'
|
printf '%s\n' '|---|---|---|'
|
||||||
printf '%s\n' '| Access control | OK | Admin-only execution gate |'
|
printf '%s\n' '| Access control | OK | Admin-only execution gate |'
|
||||||
printf '%s\n' '| Release policy | N/A | Releases handled by MokoGitea |'
|
printf '%s\n' '| Release policy | N/A | Releases handled by MokoGIT |'
|
||||||
printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |'
|
printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |'
|
||||||
printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |'
|
printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |'
|
||||||
printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |'
|
printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |'
|
||||||
@@ -670,42 +671,30 @@ jobs:
|
|||||||
# ═══════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════
|
||||||
# Issue Reporter — file issues for failed gates
|
# Issue Reporter — file issues for failed gates
|
||||||
# ═══════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════
|
||||||
report-issues:
|
report-scripts:
|
||||||
name: "Report Issues"
|
name: "Report: Scripts Governance"
|
||||||
runs-on: ubuntu-latest
|
needs: [access_check, scripts_governance]
|
||||||
needs: [access_check, scripts_governance, repo_health]
|
|
||||||
if: >-
|
if: >-
|
||||||
always() &&
|
always() &&
|
||||||
(needs.scripts_governance.result == 'failure' ||
|
needs.scripts_governance.result == 'failure'
|
||||||
needs.repo_health.result == 'failure')
|
uses: ./.mokogit/workflows/ci-issue-reporter.yml
|
||||||
|
with:
|
||||||
|
gate: "Scripts Governance"
|
||||||
|
workflow: "Repo Health"
|
||||||
|
severity: error
|
||||||
|
details: "Scripts directory policy violations detected. Review required and allowed directories."
|
||||||
|
secrets: inherit
|
||||||
|
|
||||||
steps:
|
report-health:
|
||||||
- name: Checkout
|
name: "Report: Repository Health"
|
||||||
uses: actions/checkout@v4
|
needs: [access_check, repo_health]
|
||||||
with:
|
if: >-
|
||||||
sparse-checkout: automation/ci-issue-reporter.sh
|
always() &&
|
||||||
sparse-checkout-cone-mode: false
|
needs.repo_health.result == 'failure'
|
||||||
|
uses: ./.mokogit/workflows/ci-issue-reporter.yml
|
||||||
- name: "File issues for failed gates"
|
with:
|
||||||
env:
|
gate: "Repository Health"
|
||||||
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
workflow: "Repo Health"
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
severity: error
|
||||||
run: |
|
details: "Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary."
|
||||||
chmod +x automation/ci-issue-reporter.sh
|
secrets: inherit
|
||||||
REPORTER="./automation/ci-issue-reporter.sh"
|
|
||||||
WF="Repo Health"
|
|
||||||
|
|
||||||
report_gate() {
|
|
||||||
local gate="$1" result="$2" details="$3"
|
|
||||||
if [ "$result" = "failure" ]; then
|
|
||||||
"$REPORTER" --gate "$gate" --details "$details" --workflow "$WF" --severity error
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
report_gate "Scripts Governance" \
|
|
||||||
"${{ needs.scripts_governance.result }}" \
|
|
||||||
"Scripts directory policy violations detected. Review required and allowed directories."
|
|
||||||
|
|
||||||
report_gate "Repository Health" \
|
|
||||||
"${{ needs.repo_health.result }}" \
|
|
||||||
"Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary."
|
|
||||||
+1
-1
@@ -11,7 +11,7 @@
|
|||||||
# PATH: /templates/workflows/shared/repository-cleanup.yml.template
|
# PATH: /templates/workflows/shared/repository-cleanup.yml.template
|
||||||
# VERSION: 04.06.00
|
# VERSION: 04.06.00
|
||||||
# BRIEF: Recurring repository maintenance — labels, branches, workflows, logs, doc indexes
|
# BRIEF: Recurring repository maintenance — labels, branches, workflows, logs, doc indexes
|
||||||
# NOTE: Synced via bulk-repo-sync to .mokogitea/workflows/repository-cleanup.yml in all governed repos.
|
# NOTE: Synced via bulk-repo-sync to .mokogit/workflows/repository-cleanup.yml in all governed repos.
|
||||||
# Runs on the 1st and 15th of each month at 6:00 AM UTC, and on manual dispatch.
|
# Runs on the 1st and 15th of each month at 6:00 AM UTC, and on manual dispatch.
|
||||||
|
|
||||||
name: "Universal: Repository Cleanup"
|
name: "Universal: Repository Cleanup"
|
||||||
+59
-60
@@ -2,14 +2,14 @@
|
|||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
# DEFGROUP: GitHub.Workflow
|
# DEFGROUP: GitHub.Workflow
|
||||||
# INGROUP: MokoStandards.Compliance
|
# INGROUP: MokoCLI.Compliance
|
||||||
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
# REPO: https://github.com/mokoconsulting-tech/mokocli
|
||||||
# PATH: /.mokogitea/workflows/standards-compliance.yml
|
# PATH: /.mokogit/workflows/standards-compliance.yml
|
||||||
# VERSION: 04.06.00
|
# VERSION: 04.06.00
|
||||||
# BRIEF: MokoStandards compliance validation workflow
|
# BRIEF: MokoCLI compliance validation workflow
|
||||||
# NOTE: Validates repository structure, documentation, and coding standards
|
# NOTE: Validates repository structure, documentation, and coding standards
|
||||||
|
|
||||||
name: "MCP: Standards Compliance"
|
name: "Generic: Standards Compliance"
|
||||||
|
|
||||||
# ╔════════════════════════════════════════════════════════════════════════╗
|
# ╔════════════════════════════════════════════════════════════════════════╗
|
||||||
# ║ MOKOSTANDARDS COMPLIANCE WORKFLOW ║
|
# ║ MOKOSTANDARDS COMPLIANCE WORKFLOW ║
|
||||||
@@ -42,7 +42,7 @@ env:
|
|||||||
WORKFLOW_VERSION: "04.04.01"
|
WORKFLOW_VERSION: "04.04.01"
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
# MokoStandards Policy Compliance:
|
# MokoCLI Policy Compliance:
|
||||||
# - File formatting: Enforces organizational coding standards
|
# - File formatting: Enforces organizational coding standards
|
||||||
# - Reference: docs/policy/file-formatting.md
|
# - Reference: docs/policy/file-formatting.md
|
||||||
|
|
||||||
@@ -252,7 +252,7 @@ jobs:
|
|||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "### ❌ Validation Failed: LICENSE File Missing" >> $GITHUB_STEP_SUMMARY
|
echo "### ❌ Validation Failed: LICENSE File Missing" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Error:** LICENSE file is required for all MokoStandards-compliant repositories" >> $GITHUB_STEP_SUMMARY
|
echo "**Error:** LICENSE file is required for all MokoCLI-compliant repositories" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Action Required:** Add LICENSE file with appropriate open-source license (GPL-3.0-or-later recommended)" >> $GITHUB_STEP_SUMMARY
|
echo "**Action Required:** Add LICENSE file with appropriate open-source license (GPL-3.0-or-later recommended)" >> $GITHUB_STEP_SUMMARY
|
||||||
echo ""
|
echo ""
|
||||||
echo "❌ ERROR: LICENSE file not found - This is a critical requirement"
|
echo "❌ ERROR: LICENSE file not found - This is a critical requirement"
|
||||||
@@ -291,7 +291,7 @@ jobs:
|
|||||||
|
|
||||||
MISSING=0
|
MISSING=0
|
||||||
PRESENT=0
|
PRESENT=0
|
||||||
TOTAL=2
|
TOTAL=1
|
||||||
|
|
||||||
echo "### Required Directories" >> $GITHUB_STEP_SUMMARY
|
echo "### Required Directories" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -299,7 +299,7 @@ jobs:
|
|||||||
echo "|-----------|--------|-------|------|-------|" >> $GITHUB_STEP_SUMMARY
|
echo "|-----------|--------|-------|------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# Check required directories
|
# Check required directories
|
||||||
for dir in docs .github; do
|
for dir in .mokogit; do
|
||||||
if [ -d "$dir" ]; then
|
if [ -d "$dir" ]; then
|
||||||
FILE_COUNT=$(find "$dir" -type f 2>/dev/null | wc -l)
|
FILE_COUNT=$(find "$dir" -type f 2>/dev/null | wc -l)
|
||||||
DIR_SIZE=$(du -sh "$dir" 2>/dev/null | cut -f1)
|
DIR_SIZE=$(du -sh "$dir" 2>/dev/null | cut -f1)
|
||||||
@@ -320,14 +320,13 @@ jobs:
|
|||||||
echo "### 🔴 Critical Issues: $MISSING" >> $GITHUB_STEP_SUMMARY
|
echo "### 🔴 Critical Issues: $MISSING" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Remediation Steps:**" >> $GITHUB_STEP_SUMMARY
|
echo "**Remediation Steps:**" >> $GITHUB_STEP_SUMMARY
|
||||||
[ ! -d "docs" ] && echo "- Create docs directory: \`mkdir docs && echo '# Documentation' > docs/README.md\`" >> $GITHUB_STEP_SUMMARY
|
[ ! -d ".mokogit" ] && echo "- Create .mokogit directory: \`mkdir -p .mokogit/workflows\`" >> $GITHUB_STEP_SUMMARY
|
||||||
[ ! -d ".github" ] && echo "- Create .github directory: \`mkdir -p .github/workflows\`" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "📚 Reference: [MokoStandards Repository Structure](https://github.com/mokoconsulting-tech/MokoStandards/tree/main/docs/policy/core-structure.md)" >> $GITHUB_STEP_SUMMARY
|
echo "📚 Reference: [MokoCLI Repository Structure](https://github.com/mokoconsulting-tech/mokocli/tree/main/docs/policy/core-structure.md)" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "### ❌ Validation Failed: Required Directories Missing" >> $GITHUB_STEP_SUMMARY
|
echo "### ❌ Validation Failed: Required Directories Missing" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Status:** Repository structure does not meet MokoStandards requirements" >> $GITHUB_STEP_SUMMARY
|
echo "**Status:** Repository structure does not meet MokoCLI requirements" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Missing:** $MISSING required director(y|ies)" >> $GITHUB_STEP_SUMMARY
|
echo "**Missing:** $MISSING required director(y|ies)" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Compliance:** $PERCENT% ($PRESENT/$TOTAL directories present)" >> $GITHUB_STEP_SUMMARY
|
echo "**Compliance:** $PERCENT% ($PRESENT/$TOTAL directories present)" >> $GITHUB_STEP_SUMMARY
|
||||||
echo ""
|
echo ""
|
||||||
@@ -384,17 +383,17 @@ jobs:
|
|||||||
echo "### 🔴 Critical Issues: $MISSING" >> $GITHUB_STEP_SUMMARY
|
echo "### 🔴 Critical Issues: $MISSING" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Remediation Steps:**" >> $GITHUB_STEP_SUMMARY
|
echo "**Remediation Steps:**" >> $GITHUB_STEP_SUMMARY
|
||||||
[ ! -f "README.md" ] && echo "- Create README.md: Use [template](https://github.com/mokoconsulting-tech/MokoStandards/tree/main/templates/docs/required/README.md)" >> $GITHUB_STEP_SUMMARY
|
[ ! -f "README.md" ] && echo "- Create README.md: Use [template](https://github.com/mokoconsulting-tech/mokocli/tree/main/templates/docs/required/README.md)" >> $GITHUB_STEP_SUMMARY
|
||||||
[ ! -f "LICENSE" ] && echo "- Add LICENSE file: Choose from [OSI-approved licenses](https://opensource.org/licenses)" >> $GITHUB_STEP_SUMMARY
|
[ ! -f "LICENSE" ] && echo "- Add LICENSE file: Choose from [OSI-approved licenses](https://opensource.org/licenses)" >> $GITHUB_STEP_SUMMARY
|
||||||
[ ! -f "CONTRIBUTING.md" ] && echo "- Create CONTRIBUTING.md: Use [template](https://github.com/mokoconsulting-tech/MokoStandards/tree/main/templates/docs/required/CONTRIBUTING.md)" >> $GITHUB_STEP_SUMMARY
|
[ ! -f "CONTRIBUTING.md" ] && echo "- Create CONTRIBUTING.md: Use [template](https://github.com/mokoconsulting-tech/mokocli/tree/main/templates/docs/required/CONTRIBUTING.md)" >> $GITHUB_STEP_SUMMARY
|
||||||
[ ! -f "SECURITY.md" ] && echo "- Create SECURITY.md: Use [template](https://github.com/mokoconsulting-tech/MokoStandards/tree/main/templates/docs/required/SECURITY.md)" >> $GITHUB_STEP_SUMMARY
|
[ ! -f "SECURITY.md" ] && echo "- Create SECURITY.md: Use [template](https://github.com/mokoconsulting-tech/mokocli/tree/main/templates/docs/required/SECURITY.md)" >> $GITHUB_STEP_SUMMARY
|
||||||
[ ! -f ".editorconfig" ] && echo "- Add .editorconfig: Use [template](https://github.com/mokoconsulting-tech/MokoStandards/tree/main/templates/.editorconfig)" >> $GITHUB_STEP_SUMMARY
|
[ ! -f ".editorconfig" ] && echo "- Add .editorconfig: Use [template](https://github.com/mokoconsulting-tech/mokocli/tree/main/templates/.editorconfig)" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "📚 Reference: [MokoStandards File Requirements](https://github.com/mokoconsulting-tech/MokoStandards/tree/main/docs/policy/file-header-standards.md)" >> $GITHUB_STEP_SUMMARY
|
echo "📚 Reference: [MokoCLI File Requirements](https://github.com/mokoconsulting-tech/mokocli/tree/main/docs/policy/file-header-standards.md)" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "### ❌ Validation Failed: Required Files Missing" >> $GITHUB_STEP_SUMMARY
|
echo "### ❌ Validation Failed: Required Files Missing" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Status:** Repository files do not meet MokoStandards requirements" >> $GITHUB_STEP_SUMMARY
|
echo "**Status:** Repository files do not meet MokoCLI requirements" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Missing:** $MISSING required file(s)" >> $GITHUB_STEP_SUMMARY
|
echo "**Missing:** $MISSING required file(s)" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Compliance:** $PERCENT% ($PRESENT/$TOTAL files present)" >> $GITHUB_STEP_SUMMARY
|
echo "**Compliance:** $PERCENT% ($PRESENT/$TOTAL files present)" >> $GITHUB_STEP_SUMMARY
|
||||||
echo ""
|
echo ""
|
||||||
@@ -437,7 +436,7 @@ jobs:
|
|||||||
echo "$TABS_IN_SPACES_FILES" >> $GITHUB_STEP_SUMMARY
|
echo "$TABS_IN_SPACES_FILES" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "These languages require spaces (tabs will break): YAML, Python, Haskell, F#, CoffeeScript, Nim, JSON, RST" >> $GITHUB_STEP_SUMMARY
|
echo "These languages require spaces (tabs will break): YAML, Python, Haskell, F#, CoffeeScript, Nim, JSON, RST" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "All other files (including .md, .ps1, LICENSE, etc.) may use tabs per MokoStandards policy" >> $GITHUB_STEP_SUMMARY
|
echo "All other files (including .md, .ps1, LICENSE, etc.) may use tabs per MokoCLI policy" >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
else
|
||||||
echo "✅ No tabs found in files requiring spaces" >> $GITHUB_STEP_SUMMARY
|
echo "✅ No tabs found in files requiring spaces" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "Note: Tabs are allowed in most files (policy default). Only checked files requiring spaces." >> $GITHUB_STEP_SUMMARY
|
echo "Note: Tabs are allowed in most files (policy default). Only checked files requiring spaces." >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -454,7 +453,7 @@ jobs:
|
|||||||
! -path "./vendor/*" \
|
! -path "./vendor/*" \
|
||||||
! -path "./node_modules/*" \
|
! -path "./node_modules/*" \
|
||||||
! -path "./.git/*" \
|
! -path "./.git/*" \
|
||||||
-exec file {} \; | grep -v "UTF-8" | grep -v "ASCII" | head -5)
|
-exec file {} \; | grep -v "UTF-8" | grep -v "ASCII" | head -5 || true)
|
||||||
|
|
||||||
if [ -n "$NON_UTF8" ]; then
|
if [ -n "$NON_UTF8" ]; then
|
||||||
echo "⚠️ Non-UTF-8 files detected:" >> $GITHUB_STEP_SUMMARY
|
echo "⚠️ Non-UTF-8 files detected:" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -476,14 +475,14 @@ jobs:
|
|||||||
! -path "./vendor/*" \
|
! -path "./vendor/*" \
|
||||||
! -path "./node_modules/*" \
|
! -path "./node_modules/*" \
|
||||||
! -path "./.git/*" \
|
! -path "./.git/*" \
|
||||||
-exec file {} \; | grep "CRLF" | head -5)
|
-exec file {} \; | grep "CRLF" | head -5 || true)
|
||||||
|
|
||||||
if [ -n "$CRLF_FILES" ]; then
|
if [ -n "$CRLF_FILES" ]; then
|
||||||
echo "⚠️ Files with CRLF line endings found:" >> $GITHUB_STEP_SUMMARY
|
echo "⚠️ Files with CRLF line endings found:" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "$CRLF_FILES" >> $GITHUB_STEP_SUMMARY
|
echo "$CRLF_FILES" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "MokoStandards requires LF line endings" >> $GITHUB_STEP_SUMMARY
|
echo "MokoCLI requires LF line endings" >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
else
|
||||||
echo "✅ Line endings are consistent (LF)" >> $GITHUB_STEP_SUMMARY
|
echo "✅ Line endings are consistent (LF)" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
@@ -504,13 +503,13 @@ jobs:
|
|||||||
tools: composer
|
tools: composer
|
||||||
coverage: none
|
coverage: none
|
||||||
|
|
||||||
- name: Setup MokoStandards tools
|
- name: Setup MokoCLI tools
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
|
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
|
||||||
run: |
|
run: |
|
||||||
git clone --depth 1 --branch version/04 --quiet \
|
git clone --depth 1 --branch version/04 --quiet \
|
||||||
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
|
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/mokocli.git" \
|
||||||
/tmp/mokostandards 2>/dev/null || true
|
/tmp/mokostandards 2>/dev/null || true
|
||||||
if [ -d "/tmp/mokostandards" ] && [ -f "/tmp/mokostandards/composer.json" ]; then
|
if [ -d "/tmp/mokostandards" ] && [ -f "/tmp/mokostandards/composer.json" ]; then
|
||||||
cd /tmp/mokostandards
|
cd /tmp/mokostandards
|
||||||
@@ -524,7 +523,7 @@ jobs:
|
|||||||
echo "## 🔢 Version Consistency Validation" >> $GITHUB_STEP_SUMMARY
|
echo "## 🔢 Version Consistency Validation" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# Use MokoStandards tools (no Composer needed on the governed repo)
|
# Use MokoCLI tools (no Composer needed on the governed repo)
|
||||||
if [ -f "/tmp/mokostandards/api/validate/check_version_consistency.php" ]; then
|
if [ -f "/tmp/mokostandards/api/validate/check_version_consistency.php" ]; then
|
||||||
php /tmp/mokostandards/api/validate/check_version_consistency.php --path . --verbose 2>&1 | tee /tmp/version-check.log
|
php /tmp/mokostandards/api/validate/check_version_consistency.php --path . --verbose 2>&1 | tee /tmp/version-check.log
|
||||||
EXIT_CODE=${PIPESTATUS[0]}
|
EXIT_CODE=${PIPESTATUS[0]}
|
||||||
@@ -532,7 +531,7 @@ jobs:
|
|||||||
php api/validate/check_version_consistency.php --path . --verbose 2>&1 | tee /tmp/version-check.log
|
php api/validate/check_version_consistency.php --path . --verbose 2>&1 | tee /tmp/version-check.log
|
||||||
EXIT_CODE=${PIPESTATUS[0]}
|
EXIT_CODE=${PIPESTATUS[0]}
|
||||||
else
|
else
|
||||||
echo "⏭️ MokoStandards tools not available — skipping version check" >> $GITHUB_STEP_SUMMARY
|
echo "⏭️ MokoCLI tools not available — skipping version check" >> $GITHUB_STEP_SUMMARY
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -564,17 +563,17 @@ jobs:
|
|||||||
set -x
|
set -x
|
||||||
echo "### GitHub Actions Workflows" >> $GITHUB_STEP_SUMMARY
|
echo "### GitHub Actions Workflows" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
WORKFLOWS_DIR=".github/workflows"
|
WORKFLOWS_DIR=".mokogit/workflows"
|
||||||
|
|
||||||
if [ ! -d "$WORKFLOWS_DIR" ]; then
|
if [ ! -d "$WORKFLOWS_DIR" ]; then
|
||||||
echo "❌ No workflows directory found" >> $GITHUB_STEP_SUMMARY
|
echo "❌ No workflows directory found" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "### ❌ Validation Failed: Workflows Directory Missing" >> $GITHUB_STEP_SUMMARY
|
echo "### ❌ Validation Failed: Workflows Directory Missing" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Error:** .github/workflows directory is required for CI/CD automation" >> $GITHUB_STEP_SUMMARY
|
echo "**Error:** .mokogit/workflows directory is required for CI/CD automation" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Action Required:** Create .github/workflows directory and add GitHub Actions workflows" >> $GITHUB_STEP_SUMMARY
|
echo "**Action Required:** Create .mokogit/workflows directory and add GitHub Actions workflows" >> $GITHUB_STEP_SUMMARY
|
||||||
echo ""
|
echo ""
|
||||||
echo "❌ ERROR: .github/workflows directory not found"
|
echo "❌ ERROR: .mokogit/workflows directory not found"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -597,12 +596,12 @@ jobs:
|
|||||||
echo "⚠️ CodeQL workflow not found" >> $GITHUB_STEP_SUMMARY
|
echo "⚠️ CodeQL workflow not found" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check for MokoStandards-synced workflows
|
# Check for MokoCLI-synced workflows
|
||||||
for wf in deploy-dev.yml deploy-demo.yml deploy-rs.yml sync-version-on-merge.yml auto-release.yml standards-compliance.yml enterprise-firewall-setup.yml; do
|
for wf in deploy-dev.yml deploy-demo.yml deploy-rs.yml sync-version-on-merge.yml auto-release.yml standards-compliance.yml enterprise-firewall-setup.yml; do
|
||||||
if [ -f "$WORKFLOWS_DIR/$wf" ]; then
|
if [ -f "$WORKFLOWS_DIR/$wf" ]; then
|
||||||
echo "✅ ${wf}" >> $GITHUB_STEP_SUMMARY
|
echo "✅ ${wf}" >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
else
|
||||||
echo "⚠️ ${wf} not found (synced from MokoStandards)" >> $GITHUB_STEP_SUMMARY
|
echo "⚠️ ${wf} not found (synced from MokoCLI)" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -613,7 +612,7 @@ jobs:
|
|||||||
echo "### Workflow YAML Syntax" >> $GITHUB_STEP_SUMMARY
|
echo "### Workflow YAML Syntax" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
INVALID=0
|
INVALID=0
|
||||||
for workflow in $(find .github/workflows -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" \) 2>/dev/null); do
|
for workflow in $(find .mokogit/workflows -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" \) 2>/dev/null); do
|
||||||
if [ -f "$workflow" ]; then
|
if [ -f "$workflow" ]; then
|
||||||
if python3 -c "import yaml, sys; yaml.safe_load(open(sys.argv[1]))" "$workflow" 2>/dev/null; then
|
if python3 -c "import yaml, sys; yaml.safe_load(open(sys.argv[1]))" "$workflow" 2>/dev/null; then
|
||||||
echo "✅ $(basename $workflow)" >> $GITHUB_STEP_SUMMARY
|
echo "✅ $(basename $workflow)" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -630,7 +629,7 @@ jobs:
|
|||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Error:** $INVALID workflow file(s) have invalid YAML syntax" >> $GITHUB_STEP_SUMMARY
|
echo "**Error:** $INVALID workflow file(s) have invalid YAML syntax" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Action Required:** Fix YAML syntax errors in the marked workflow files" >> $GITHUB_STEP_SUMMARY
|
echo "**Action Required:** Fix YAML syntax errors in the marked workflow files" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Tool:** Run \`python3 -c \"import yaml; yaml.safe_load(open('.github/workflows/FILE.yml'))\"\` locally" >> $GITHUB_STEP_SUMMARY
|
echo "**Tool:** Run \`python3 -c \"import yaml; yaml.safe_load(open('.mokogit/workflows/FILE.yml'))\"\` locally" >> $GITHUB_STEP_SUMMARY
|
||||||
echo ""
|
echo ""
|
||||||
echo "❌ ERROR: $INVALID workflow file(s) with invalid YAML syntax"
|
echo "❌ ERROR: $INVALID workflow file(s) with invalid YAML syntax"
|
||||||
exit 1
|
exit 1
|
||||||
@@ -642,7 +641,7 @@ jobs:
|
|||||||
echo "✅ SUCCESS: All workflow files passed YAML validation"
|
echo "✅ SUCCESS: All workflow files passed YAML validation"
|
||||||
|
|
||||||
- name: Validate CodeQL Configuration
|
- name: Validate CodeQL Configuration
|
||||||
if: hashFiles('.github/workflows/codeql-analysis.yml') != ''
|
if: hashFiles('.mokogit/workflows/codeql-analysis.yml') != ''
|
||||||
run: |
|
run: |
|
||||||
set -e
|
set -e
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -650,7 +649,7 @@ jobs:
|
|||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# Inline validation (rewritten from Python to bash for PHP-only architecture)
|
# Inline validation (rewritten from Python to bash for PHP-only architecture)
|
||||||
CODEQL_FILE=".github/workflows/codeql-analysis.yml"
|
CODEQL_FILE=".mokogit/workflows/codeql-analysis.yml"
|
||||||
|
|
||||||
if [ ! -f "$CODEQL_FILE" ]; then
|
if [ ! -f "$CODEQL_FILE" ]; then
|
||||||
echo "⚠️ CodeQL workflow file not found" >> $GITHUB_STEP_SUMMARY
|
echo "⚠️ CodeQL workflow file not found" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -809,7 +808,7 @@ jobs:
|
|||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "### ❌ Validation Failed: README.md Missing" >> $GITHUB_STEP_SUMMARY
|
echo "### ❌ Validation Failed: README.md Missing" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Error:** README.md is required for all MokoStandards-compliant repositories" >> $GITHUB_STEP_SUMMARY
|
echo "**Error:** README.md is required for all MokoCLI-compliant repositories" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Action Required:** Create README.md with project description, setup instructions, and usage examples" >> $GITHUB_STEP_SUMMARY
|
echo "**Action Required:** Create README.md with project description, setup instructions, and usage examples" >> $GITHUB_STEP_SUMMARY
|
||||||
echo ""
|
echo ""
|
||||||
echo "❌ ERROR: README.md not found - This is a critical requirement"
|
echo "❌ ERROR: README.md not found - This is a critical requirement"
|
||||||
@@ -915,7 +914,7 @@ jobs:
|
|||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "### ❌ Validation Failed: CHANGELOG.md Missing" >> $GITHUB_STEP_SUMMARY
|
echo "### ❌ Validation Failed: CHANGELOG.md Missing" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Error:** CHANGELOG.md is required for all MokoStandards-compliant repositories" >> $GITHUB_STEP_SUMMARY
|
echo "**Error:** CHANGELOG.md is required for all MokoCLI-compliant repositories" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Action Required:** Create CHANGELOG.md following [Keep a Changelog](https://keepachangelog.com/) format" >> $GITHUB_STEP_SUMMARY
|
echo "**Action Required:** Create CHANGELOG.md following [Keep a Changelog](https://keepachangelog.com/) format" >> $GITHUB_STEP_SUMMARY
|
||||||
echo ""
|
echo ""
|
||||||
echo "❌ ERROR: CHANGELOG.md not found - This is a critical requirement"
|
echo "❌ ERROR: CHANGELOG.md not found - This is a critical requirement"
|
||||||
@@ -1183,11 +1182,11 @@ jobs:
|
|||||||
# Install yamllint if not present
|
# Install yamllint if not present
|
||||||
:
|
:
|
||||||
else
|
else
|
||||||
pip install yamllint >/dev/null 2>&1
|
pip install yamllint >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Run yamllint and count line-length warnings
|
# Run yamllint and count line-length warnings
|
||||||
YAML_OUTPUT=$(yamllint .github/workflows/*.yml 2>&1 | grep "line too long" || true)
|
YAML_OUTPUT=$(yamllint .mokogit/workflows/*.yml 2>&1 | grep "line too long" || true)
|
||||||
if [ -n "$YAML_OUTPUT" ]; then
|
if [ -n "$YAML_OUTPUT" ]; then
|
||||||
YAML_VIOLATIONS=$(echo "$YAML_OUTPUT" | wc -l)
|
YAML_VIOLATIONS=$(echo "$YAML_OUTPUT" | wc -l)
|
||||||
echo "⚠️ Found $YAML_VIOLATIONS lines exceeding 180 characters in YAML files" >> $GITHUB_STEP_SUMMARY
|
echo "⚠️ Found $YAML_VIOLATIONS lines exceeding 180 characters in YAML files" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -1214,7 +1213,7 @@ jobs:
|
|||||||
! -path "./.git/*" \
|
! -path "./.git/*" \
|
||||||
! -path "./build/*" \
|
! -path "./build/*" \
|
||||||
! -path "./dist/*" \
|
! -path "./dist/*" \
|
||||||
-exec awk 'length > 120 { print FILENAME ":" NR ": " length " chars" }' {} \; 2>/dev/null | head -20)
|
-exec awk 'length > 120 { print FILENAME ":" NR ": " length " chars" }' {} \; 2>/dev/null | head -20 || true)
|
||||||
|
|
||||||
if [ -n "$LONG_LINES" ]; then
|
if [ -n "$LONG_LINES" ]; then
|
||||||
LINE_COUNT=$(echo "$LONG_LINES" | wc -l)
|
LINE_COUNT=$(echo "$LONG_LINES" | wc -l)
|
||||||
@@ -1976,10 +1975,10 @@ jobs:
|
|||||||
if [ -f "composer.json" ]; then
|
if [ -f "composer.json" ]; then
|
||||||
composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
|
composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
|
||||||
else
|
else
|
||||||
echo "No composer.json — pulling MokoStandards tools"
|
echo "No composer.json — pulling MokoCLI tools"
|
||||||
if [ ! -d "/tmp/mokostandards" ]; then
|
if [ ! -d "/tmp/mokostandards" ]; then
|
||||||
git clone --depth 1 --branch version/04 --quiet \
|
git clone --depth 1 --branch version/04 --quiet \
|
||||||
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
|
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/mokocli.git" \
|
||||||
/tmp/mokostandards 2>/dev/null || true
|
/tmp/mokostandards 2>/dev/null || true
|
||||||
if [ -f "/tmp/mokostandards/composer.json" ]; then
|
if [ -f "/tmp/mokostandards/composer.json" ]; then
|
||||||
cd /tmp/mokostandards && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
cd /tmp/mokostandards && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||||
@@ -2048,10 +2047,10 @@ jobs:
|
|||||||
if [ -f "composer.json" ]; then
|
if [ -f "composer.json" ]; then
|
||||||
composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
|
composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
|
||||||
else
|
else
|
||||||
echo "No composer.json — pulling MokoStandards tools"
|
echo "No composer.json — pulling MokoCLI tools"
|
||||||
if [ ! -d "/tmp/mokostandards" ]; then
|
if [ ! -d "/tmp/mokostandards" ]; then
|
||||||
git clone --depth 1 --branch version/04 --quiet \
|
git clone --depth 1 --branch version/04 --quiet \
|
||||||
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
|
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/mokocli.git" \
|
||||||
/tmp/mokostandards 2>/dev/null || true
|
/tmp/mokostandards 2>/dev/null || true
|
||||||
if [ -f "/tmp/mokostandards/composer.json" ]; then
|
if [ -f "/tmp/mokostandards/composer.json" ]; then
|
||||||
cd /tmp/mokostandards && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
cd /tmp/mokostandards && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||||
@@ -2131,17 +2130,17 @@ jobs:
|
|||||||
WARNINGS=0
|
WARNINGS=0
|
||||||
ERRORS=0
|
ERRORS=0
|
||||||
|
|
||||||
# 1. Check .github/config.tf location (not root override files)
|
# 1. Check .mokogit/config.tf location (not root override files)
|
||||||
echo "### Override Configuration Check" >> $GITHUB_STEP_SUMMARY
|
echo "### Override Configuration Check" >> $GITHUB_STEP_SUMMARY
|
||||||
LEGACY_OVERRIDES=$(find . -maxdepth 1 -name "*override*.tf" -o -name "MokoStandards.override.tf" 2>/dev/null | wc -l || echo 0)
|
LEGACY_OVERRIDES=$(find . -maxdepth 1 -name "*override*.tf" -o -name "MokoCLI.override.tf" 2>/dev/null | wc -l || echo 0)
|
||||||
if [ "$LEGACY_OVERRIDES" -gt 0 ]; then
|
if [ "$LEGACY_OVERRIDES" -gt 0 ]; then
|
||||||
echo "⚠️ Found legacy override files in root directory" >> $GITHUB_STEP_SUMMARY
|
echo "⚠️ Found legacy override files in root directory" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Expected Location**: .github/config.tf" >> $GITHUB_STEP_SUMMARY
|
echo "**Expected Location**: .mokogit/config.tf" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Legacy files found**: $LEGACY_OVERRIDES" >> $GITHUB_STEP_SUMMARY
|
echo "**Legacy files found**: $LEGACY_OVERRIDES" >> $GITHUB_STEP_SUMMARY
|
||||||
WARNINGS=$((WARNINGS + 1))
|
WARNINGS=$((WARNINGS + 1))
|
||||||
else
|
else
|
||||||
if [ -f ".github/config.tf" ]; then
|
if [ -f ".mokogit/config.tf" ]; then
|
||||||
echo "✅ Override configuration in correct location (.github/config.tf)" >> $GITHUB_STEP_SUMMARY
|
echo "✅ Override configuration in correct location (.mokogit/config.tf)" >> $GITHUB_STEP_SUMMARY
|
||||||
else
|
else
|
||||||
echo "ℹ️ No override configuration found" >> $GITHUB_STEP_SUMMARY
|
echo "ℹ️ No override configuration found" >> $GITHUB_STEP_SUMMARY
|
||||||
fi
|
fi
|
||||||
@@ -2299,7 +2298,7 @@ jobs:
|
|||||||
- name: Generate Compliance Report
|
- name: Generate Compliance Report
|
||||||
run: |
|
run: |
|
||||||
set -x
|
set -x
|
||||||
echo "# 📊 MokoStandards Compliance Report" >> $GITHUB_STEP_SUMMARY
|
echo "# 📊 MokoCLI Compliance Report" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# Calculate overall status
|
# Calculate overall status
|
||||||
@@ -2486,7 +2485,7 @@ jobs:
|
|||||||
else
|
else
|
||||||
echo "## 🎉 Excellent!" >> $GITHUB_STEP_SUMMARY
|
echo "## 🎉 Excellent!" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "Your repository is **fully compliant** with MokoStandards!" >> $GITHUB_STEP_SUMMARY
|
echo "Your repository is **fully compliant** with MokoCLI!" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Achievements:**" >> $GITHUB_STEP_SUMMARY
|
echo "**Achievements:**" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- ✅ All required directories and files present" >> $GITHUB_STEP_SUMMARY
|
echo "- ✅ All required directories and files present" >> $GITHUB_STEP_SUMMARY
|
||||||
@@ -2501,12 +2500,12 @@ jobs:
|
|||||||
echo "---" >> $GITHUB_STEP_SUMMARY
|
echo "---" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "📚 **Resources:**" >> $GITHUB_STEP_SUMMARY
|
echo "📚 **Resources:**" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- [MokoStandards Documentation](https://github.com/mokoconsulting-tech/MokoStandards)" >> $GITHUB_STEP_SUMMARY
|
echo "- [MokoCLI Documentation](https://github.com/mokoconsulting-tech/mokocli)" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- [Repository Structure Guide](https://github.com/mokoconsulting-tech/MokoStandards/tree/main/docs/policy/core-structure.md)" >> $GITHUB_STEP_SUMMARY
|
echo "- [Repository Structure Guide](https://github.com/mokoconsulting-tech/mokocli/tree/main/docs/policy/core-structure.md)" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- [Documentation Standards](https://github.com/mokoconsulting-tech/MokoStandards/tree/main/docs/policy/document-formatting.md)" >> $GITHUB_STEP_SUMMARY
|
echo "- [Documentation Standards](https://github.com/mokoconsulting-tech/mokocli/tree/main/docs/policy/document-formatting.md)" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "- [Coding Standards](https://github.com/mokoconsulting-tech/MokoStandards/tree/main/docs/policy/coding-style-guide.md)" >> $GITHUB_STEP_SUMMARY
|
echo "- [Coding Standards](https://github.com/mokoconsulting-tech/mokocli/tree/main/docs/policy/coding-style-guide.md)" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "_Generated by MokoStandards Compliance Workflow v${WORKFLOW_VERSION}_" >> $GITHUB_STEP_SUMMARY
|
echo "_Generated by MokoCLI Compliance Workflow v${WORKFLOW_VERSION}_" >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
# Create tracking issue for non-compliance if on push
|
# Create tracking issue for non-compliance if on push
|
||||||
if [ "$COMPLIANCE_PERCENT" -lt 100 ] && [ "${{ github.event_name }}" = "push" ]; then
|
if [ "$COMPLIANCE_PERCENT" -lt 100 ] && [ "${{ github.event_name }}" = "push" ]; then
|
||||||
@@ -2530,9 +2529,9 @@ jobs:
|
|||||||
echo "### ✅ Full Standards Compliance Achieved" >> $GITHUB_STEP_SUMMARY
|
echo "### ✅ Full Standards Compliance Achieved" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
echo "" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Overall Compliance:** 100%" >> $GITHUB_STEP_SUMMARY
|
echo "**Overall Compliance:** 100%" >> $GITHUB_STEP_SUMMARY
|
||||||
echo "**Status:** Repository meets all MokoStandards requirements" >> $GITHUB_STEP_SUMMARY
|
echo "**Status:** Repository meets all MokoCLI requirements" >> $GITHUB_STEP_SUMMARY
|
||||||
echo ""
|
echo ""
|
||||||
echo "✅ SUCCESS: Repository is fully MokoStandards compliant"
|
echo "✅ SUCCESS: Repository is fully MokoCLI compliant"
|
||||||
|
|
||||||
- name: Create or reopen tracking issue for standards violations
|
- name: Create or reopen tracking issue for standards violations
|
||||||
if: failure()
|
if: failure()
|
||||||
+12
-15
@@ -11,7 +11,7 @@
|
|||||||
# PATH: /templates/workflows/shared/sync-version-on-merge.yml.template
|
# PATH: /templates/workflows/shared/sync-version-on-merge.yml.template
|
||||||
# VERSION: 04.06.00
|
# VERSION: 04.06.00
|
||||||
# BRIEF: Auto-bump patch version on every push to main and propagate to all file headers
|
# BRIEF: Auto-bump patch version on every push to main and propagate to all file headers
|
||||||
# NOTE: Synced via bulk-repo-sync to .mokogitea/workflows/sync-version-on-merge.yml in all governed repos.
|
# NOTE: Synced via bulk-repo-sync to .mokogit/workflows/sync-version-on-merge.yml in all governed repos.
|
||||||
# README.md is the single source of truth for the repository version.
|
# README.md is the single source of truth for the repository version.
|
||||||
|
|
||||||
name: "Universal: Sync Version on Merge"
|
name: "Universal: Sync Version on Merge"
|
||||||
@@ -47,22 +47,19 @@ jobs:
|
|||||||
token: ${{ secrets.GH_TOKEN || github.token }}
|
token: ${{ secrets.GH_TOKEN || github.token }}
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up PHP
|
|
||||||
uses: shivammathur/setup-php@fcafdd6392932010c2bd5094439b8e33be2a8a09 # v2.37.0
|
|
||||||
with:
|
|
||||||
php-version: '8.1'
|
|
||||||
tools: composer
|
|
||||||
|
|
||||||
- name: Setup MokoStandards tools
|
- name: Setup MokoStandards tools
|
||||||
env:
|
|
||||||
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
|
||||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
|
|
||||||
run: |
|
run: |
|
||||||
git clone --depth 1 --branch version/04 --quiet \
|
# Use pre-installed mokoplatform on runner host (symlink /opt/mokoplatform -> /opt/moko-platform)
|
||||||
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
|
if [ -d /opt/mokoplatform/api/cli ] && [ -f /opt/mokoplatform/vendor/autoload.php ]; then
|
||||||
/tmp/mokostandards
|
ln -sf /opt/mokoplatform /tmp/mokostandards
|
||||||
cd /tmp/mokostandards
|
echo "Using pre-installed /opt/mokoplatform"
|
||||||
composer install --no-dev --no-interaction --quiet
|
elif [ -d /opt/moko-platform/api/cli ]; then
|
||||||
|
ln -sf /opt/moko-platform /tmp/mokostandards
|
||||||
|
echo "Using pre-installed /opt/moko-platform"
|
||||||
|
else
|
||||||
|
echo "::warning::MokoStandards tools not found on runner - skipping"
|
||||||
|
echo "MOKO_SKIP=true" >> "$GITHUB_ENV"
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Auto-bump patch version
|
- name: Auto-bump patch version
|
||||||
if: ${{ github.event_name == 'push' && github.actor != 'github-actions[bot]' }}
|
if: ${{ github.event_name == 'push' && github.actor != 'github-actions[bot]' }}
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
#
|
||||||
|
# FILE INFORMATION
|
||||||
|
# DEFGROUP: MokoGIT.Workflow.Template
|
||||||
|
# INGROUP: MokoCLI.CI
|
||||||
|
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
|
||||||
|
# PATH: /.mokogit/workflows/version-set.yml
|
||||||
|
# VERSION: 01.00.00
|
||||||
|
# BRIEF: Set or reset the extension version across all version-bearing files
|
||||||
|
|
||||||
|
name: "Joomla: Set Version"
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "Version number (e.g. 01.00.00)"
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
branch:
|
||||||
|
description: "Branch to update (default: current)"
|
||||||
|
required: false
|
||||||
|
type: string
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
set-version:
|
||||||
|
name: Set Version to ${{ inputs.version }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Validate version format
|
||||||
|
run: |
|
||||||
|
VERSION="${{ inputs.version }}"
|
||||||
|
if ! echo "$VERSION" | grep -qP '^\d{2}\.\d{2}\.\d{2}$'; then
|
||||||
|
echo "::error::Invalid version format '${VERSION}' — expected XX.YY.ZZ (e.g. 01.00.00)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.MOKOGIT_TOKEN || github.token }}
|
||||||
|
ref: ${{ inputs.branch || github.ref }}
|
||||||
|
fetch-depth: 1
|
||||||
|
|
||||||
|
- name: Update manifest version
|
||||||
|
run: |
|
||||||
|
MANIFEST=""
|
||||||
|
for XML_FILE in $(find . -maxdepth 3 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
||||||
|
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
||||||
|
MANIFEST="$XML_FILE"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ -z "$MANIFEST" ]; then
|
||||||
|
echo "::warning::No Joomla extension manifest found — skipping manifest update"
|
||||||
|
else
|
||||||
|
OLD_VER=$(grep -oP '<version>\K[^<]+' "$MANIFEST" | head -1)
|
||||||
|
sed -i "s|<version>${OLD_VER}</version>|<version>${VERSION}</version>|" "$MANIFEST"
|
||||||
|
echo "Manifest: ${OLD_VER} → ${VERSION} (${MANIFEST})"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Update README.md version
|
||||||
|
run: |
|
||||||
|
if [ -f "README.md" ]; then
|
||||||
|
if grep -qP '^\s*VERSION:\s*\d' README.md; then
|
||||||
|
sed -i -E "s/(VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" README.md
|
||||||
|
echo "README.md version updated to ${VERSION}"
|
||||||
|
else
|
||||||
|
echo "::warning::No VERSION line found in README.md — skipping"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Update CHANGELOG.md
|
||||||
|
run: |
|
||||||
|
if [ -f "CHANGELOG.md" ]; then
|
||||||
|
DATE=$(date +%Y-%m-%d)
|
||||||
|
# Check if this version already has an entry
|
||||||
|
if grep -q "^\#\# \[${VERSION}\]" CHANGELOG.md; then
|
||||||
|
echo "CHANGELOG.md already has entry for ${VERSION} — skipping"
|
||||||
|
else
|
||||||
|
# Insert new version entry after [Unreleased] or at the top after header
|
||||||
|
if grep -q '^\#\# \[Unreleased\]' CHANGELOG.md; then
|
||||||
|
sed -i "/^\#\# \[Unreleased\]/a\\\\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
|
||||||
|
else
|
||||||
|
sed -i "/^\# Changelog/a\\\\n## [Unreleased]\n\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
|
||||||
|
fi
|
||||||
|
echo "CHANGELOG.md: added entry for ${VERSION}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "::warning::No CHANGELOG.md found — skipping"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Update FILE INFORMATION blocks
|
||||||
|
run: |
|
||||||
|
# Update VERSION in file header blocks (# VERSION: XX.YY.ZZ)
|
||||||
|
find . -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" -o -name "*.php" -o -name "*.md" \) \
|
||||||
|
-not -path "./.git/*" -not -path "./vendor/*" -print0 2>/dev/null | \
|
||||||
|
while IFS= read -r -d '' FILE; do
|
||||||
|
if head -20 "$FILE" | grep -qP '^\s*#?\s*VERSION:\s*\d{2}\.\d{2}\.\d{2}'; then
|
||||||
|
sed -i -E "s/(#?\s*VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" "$FILE"
|
||||||
|
echo "Updated FILE INFORMATION VERSION in ${FILE}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
- name: Commit and push
|
||||||
|
run: |
|
||||||
|
git config user.name "Moko Consulting [bot]"
|
||||||
|
git config user.email "hello@mokoconsulting.tech"
|
||||||
|
git add -A
|
||||||
|
if git diff --cached --quiet; then
|
||||||
|
echo "No version changes detected — nothing to commit"
|
||||||
|
else
|
||||||
|
git commit -m "chore: set version to ${VERSION} [skip bump]
|
||||||
|
|
||||||
|
Authored-by: Moko Consulting"
|
||||||
|
git push
|
||||||
|
echo "### Version Set" >> $GITHUB_STEP_SUMMARY
|
||||||
|
echo "Version updated to \`${VERSION}\` on branch \`${GITHUB_REF_NAME}\`" >> $GITHUB_STEP_SUMMARY
|
||||||
|
fi
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!--
|
|
||||||
MokoStandards Repository Manifest
|
|
||||||
Template: MCP Server
|
|
||||||
See: https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home
|
|
||||||
-->
|
|
||||||
<moko-platform xmlns="https://standards.mokoconsulting.tech/moko-platform/1.0" schema-version="1.0">
|
|
||||||
<identity>
|
|
||||||
<name>Template-MCP</name>
|
|
||||||
<org>MokoConsulting</org>
|
|
||||||
<description>Template repository for Model Context Protocol (MCP) servers</description>
|
|
||||||
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
|
||||||
</identity>
|
|
||||||
<governance>
|
|
||||||
<platform>mcp</platform>
|
|
||||||
<standards-version>05.00.00</standards-version>
|
|
||||||
<standards-source>https://git.mokoconsulting.tech/MokoConsulting/moko-platform</standards-source>
|
|
||||||
</governance>
|
|
||||||
<build>
|
|
||||||
<language>TypeScript</language>
|
|
||||||
<package-type>mcp-server</package-type>
|
|
||||||
<entry-point>src/</entry-point>
|
|
||||||
</build>
|
|
||||||
</moko-platform>
|
|
||||||
@@ -1,285 +0,0 @@
|
|||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
# FILE INFORMATION
|
|
||||||
# DEFGROUP: Gitea.Workflow
|
|
||||||
# INGROUP: moko-platform.Release
|
|
||||||
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/moko-platform
|
|
||||||
# PATH: /templates/workflows/universal/auto-release.yml.template
|
|
||||||
# VERSION: 05.00.00
|
|
||||||
# BRIEF: Universal build & release � detects platform from manifest.xml
|
|
||||||
#
|
|
||||||
# +========================================================================+
|
|
||||||
# | UNIVERSAL BUILD & RELEASE PIPELINE |
|
|
||||||
# +========================================================================+
|
|
||||||
# | |
|
|
||||||
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
|
|
||||||
# | |
|
|
||||||
# | Platform-specific: |
|
|
||||||
# | joomla: XML manifest, updates.xml, type-prefixed packages |
|
|
||||||
# | dolibarr: mod*.class.php, update.txt, dev version reset |
|
|
||||||
# | generic: README-only, no update stream |
|
|
||||||
# | |
|
|
||||||
# +========================================================================+
|
|
||||||
|
|
||||||
name: "Universal: Build & Release"
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [opened, closed]
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
action:
|
|
||||||
description: 'Action to perform'
|
|
||||||
required: false
|
|
||||||
type: choice
|
|
||||||
default: release
|
|
||||||
options:
|
|
||||||
- release
|
|
||||||
- promote-rc
|
|
||||||
|
|
||||||
env:
|
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
||||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
|
||||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
|
||||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
# ── PR Opened → Rename branch to RC and build RC release ─────────────────────
|
|
||||||
promote-rc:
|
|
||||||
name: Promote to RC
|
|
||||||
runs-on: release
|
|
||||||
if: >-
|
|
||||||
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
|
|
||||||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
|
||||||
fetch-depth: 1
|
|
||||||
|
|
||||||
- name: Setup moko-platform tools
|
|
||||||
env:
|
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
|
||||||
run: |
|
|
||||||
if ! command -v composer &> /dev/null; then
|
|
||||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
|
||||||
fi
|
|
||||||
# Always fetch latest CLI tools — never use stale cache from previous runs
|
|
||||||
rm -rf /tmp/moko-platform-api
|
|
||||||
git clone --depth 1 --branch main --quiet \
|
|
||||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
|
||||||
/tmp/moko-platform-api
|
|
||||||
cd /tmp/moko-platform-api
|
|
||||||
composer install --no-dev --no-interaction --quiet
|
|
||||||
|
|
||||||
- name: Rename branch to rc
|
|
||||||
run: |
|
|
||||||
php /tmp/moko-platform-api/cli/branch_rename.php \
|
|
||||||
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
|
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
|
||||||
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
|
|
||||||
--pr "${{ github.event.pull_request.number }}"
|
|
||||||
|
|
||||||
- name: Checkout rc and configure git
|
|
||||||
run: |
|
|
||||||
git fetch origin rc
|
|
||||||
git checkout rc
|
|
||||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
|
||||||
git config --local user.name "gitea-actions[bot]"
|
|
||||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
|
||||||
|
|
||||||
- name: Publish RC release
|
|
||||||
run: |
|
|
||||||
php /tmp/moko-platform-api/cli/release_publish.php \
|
|
||||||
--path . --stability rc --bump minor --branch rc \
|
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
|
||||||
--skip-update-stream
|
|
||||||
|
|
||||||
- name: Summary
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "Branch renamed to rc, minor bump, RC release built (updates.xml managed by Gitea Pages)" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
|
|
||||||
release:
|
|
||||||
name: Build & Release Pipeline
|
|
||||||
runs-on: release
|
|
||||||
if: >-
|
|
||||||
github.event.pull_request.merged == true ||
|
|
||||||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Configure git for bot pushes
|
|
||||||
run: |
|
|
||||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
|
||||||
git config --local user.name "gitea-actions[bot]"
|
|
||||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
|
||||||
|
|
||||||
- name: Check for merge conflict markers
|
|
||||||
run: |
|
|
||||||
CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true)
|
|
||||||
if [ -n "$CONFLICTS" ]; then
|
|
||||||
echo "::error::Merge conflict markers found — aborting release"
|
|
||||||
echo "## Release Blocked: Conflict Markers" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
echo "No conflict markers found"
|
|
||||||
|
|
||||||
- name: Setup moko-platform tools
|
|
||||||
env:
|
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
|
||||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
|
||||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}'
|
|
||||||
run: |
|
|
||||||
# Ensure PHP + Composer are available
|
|
||||||
if ! command -v composer &> /dev/null; then
|
|
||||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
|
||||||
fi
|
|
||||||
# Always fetch latest CLI tools — never use stale cache from previous runs
|
|
||||||
rm -rf /tmp/moko-platform-api
|
|
||||||
git clone --depth 1 --branch main --quiet \
|
|
||||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
|
||||||
/tmp/moko-platform-api
|
|
||||||
cd /tmp/moko-platform-api
|
|
||||||
composer install --no-dev --no-interaction --quiet
|
|
||||||
|
|
||||||
|
|
||||||
- name: "Publish stable release"
|
|
||||||
run: |
|
|
||||||
php /tmp/moko-platform-api/cli/release_publish.php \
|
|
||||||
--path . --stability stable --bump minor --branch main \
|
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
|
||||||
--skip-update-stream
|
|
||||||
|
|
||||||
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
|
|
||||||
- name: "Step 9: Mirror release to GitHub"
|
|
||||||
if: >-
|
|
||||||
steps.version.outputs.skip != 'true' &&
|
|
||||||
secrets.GH_MIRROR_TOKEN != ''
|
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
|
||||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
|
||||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
|
||||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
php /tmp/moko-platform-api/cli/release_mirror.php \
|
|
||||||
--version "$VERSION" --tag "$RELEASE_TAG" \
|
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
|
||||||
--gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \
|
|
||||||
--branch main 2>&1 || true
|
|
||||||
echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
# -- STEP 10: Sync main branch to GitHub mirror ----------------------------
|
|
||||||
- name: "Step 10: Push main to GitHub mirror"
|
|
||||||
if: >-
|
|
||||||
steps.version.outputs.skip != 'true' &&
|
|
||||||
secrets.GH_MIRROR_TOKEN != ''
|
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
|
||||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
|
||||||
GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1)
|
|
||||||
GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2)
|
|
||||||
git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \
|
|
||||||
git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git"
|
|
||||||
git fetch origin main --depth=1
|
|
||||||
git push github origin/main:refs/heads/main --force 2>/dev/null \
|
|
||||||
&& echo "main branch pushed to GitHub mirror" \
|
|
||||||
|| echo "WARNING: GitHub mirror push failed"
|
|
||||||
|
|
||||||
- name: "Step 11: Delete rc branch and recreate dev from main"
|
|
||||||
if: steps.version.outputs.skip != 'true'
|
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
|
||||||
|
|
||||||
# Delete rc branch (ephemeral — created by promote-rc)
|
|
||||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
|
||||||
"${API_BASE}/branches/rc" 2>/dev/null \
|
|
||||||
&& echo "Deleted rc branch" || echo "rc branch not found"
|
|
||||||
|
|
||||||
# Delete dev branch
|
|
||||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \
|
|
||||||
"${API_BASE}/branches/dev" 2>/dev/null && echo "Deleted dev branch"
|
|
||||||
|
|
||||||
# Recreate dev from main (now includes version bump + changelog promotion)
|
|
||||||
curl -sf -X POST -H "Authorization: token ${TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${API_BASE}/branches" \
|
|
||||||
-d '{"new_branch_name":"dev","old_branch_name":"main"}' 2>/dev/null && echo "Recreated dev from main"
|
|
||||||
|
|
||||||
echo "Pre-release branches cleaned, dev reset from main" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
- name: "Step 12: Create version branch from main"
|
|
||||||
if: steps.version.outputs.skip != 'true'
|
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
|
||||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
|
||||||
BRANCH_NAME="version/${VERSION}"
|
|
||||||
MAIN_SHA=$(git rev-parse HEAD)
|
|
||||||
|
|
||||||
# Delete old version branch if it exists (same version re-release)
|
|
||||||
curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}"
|
|
||||||
|
|
||||||
# Create version/XX.YY.ZZ from main
|
|
||||||
curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed"
|
|
||||||
|
|
||||||
echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# -- Dolibarr post-release: Reset dev version -----------------------------
|
|
||||||
- name: "Post-release: Reset dev version"
|
|
||||||
if: steps.version.outputs.skip != 'true'
|
|
||||||
continue-on-error: true
|
|
||||||
run: |
|
|
||||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
|
||||||
php /tmp/moko-platform-api/cli/version_reset_dev.php \
|
|
||||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
|
|
||||||
--branch dev --path . 2>&1 || true
|
|
||||||
|
|
||||||
# -- Summary --------------------------------------------------------------
|
|
||||||
- name: Pipeline Summary
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
|
||||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
|
||||||
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
|
|
||||||
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
|
|
||||||
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
|
|
||||||
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
else
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Platform | \`${PLATFORM}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
# FILE INFORMATION
|
|
||||||
# DEFGROUP: Gitea.Workflow
|
|
||||||
# INGROUP: MokoStandards.Universal
|
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
|
||||||
# PATH: /.mokogitea/workflows/branch-cleanup.yml
|
|
||||||
# VERSION: 01.00.00
|
|
||||||
# BRIEF: Delete feature branches after PR merge
|
|
||||||
|
|
||||||
name: "Branch Cleanup"
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [closed]
|
|
||||||
|
|
||||||
env:
|
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
cleanup:
|
|
||||||
name: Delete merged branch
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: >-
|
|
||||||
github.event.pull_request.merged == true &&
|
|
||||||
github.event.pull_request.head.ref != 'dev' &&
|
|
||||||
github.event.pull_request.head.ref != 'main'
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Delete source branch
|
|
||||||
run: |
|
|
||||||
BRANCH="${{ github.event.pull_request.head.ref }}"
|
|
||||||
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
|
|
||||||
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');")
|
|
||||||
|
|
||||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
|
||||||
-H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
|
||||||
"${API}/${ENCODED}" 2>/dev/null || true)
|
|
||||||
|
|
||||||
if [ "$STATUS" = "204" ]; then
|
|
||||||
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
elif [ "$STATUS" = "404" ]; then
|
|
||||||
echo "Branch already deleted: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
else
|
|
||||||
echo "::warning::Failed to delete branch ${BRANCH} (HTTP ${STATUS})"
|
|
||||||
fi
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
#
|
|
||||||
# This file is part of a Moko Consulting project.
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
# FILE INFORMATION
|
|
||||||
# DEFGROUP: GitHub.Workflow.Template
|
|
||||||
# INGROUP: MokoStandards.Security
|
|
||||||
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
|
||||||
# PATH: /templates/workflows/generic/codeql-analysis.yml.template
|
|
||||||
# VERSION: 04.05.00
|
|
||||||
# BRIEF: CodeQL security scanning workflow (generic — all repo types)
|
|
||||||
# NOTE: Deployed to .mokogitea/workflows/codeql-analysis.yml in governed repos.
|
|
||||||
# CodeQL does not support PHP directly; JavaScript scans JSON/YAML/shell.
|
|
||||||
# For PHP-specific security scanning see standards-compliance.yml.
|
|
||||||
|
|
||||||
name: "Universal: CodeQL Analysis"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- dev/**
|
|
||||||
- rc/**
|
|
||||||
- version/**
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
- dev/**
|
|
||||||
- rc/**
|
|
||||||
schedule:
|
|
||||||
# Weekly on Monday at 06:00 UTC
|
|
||||||
- cron: '0 6 * * 1'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
pull-requests: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: Analyze (${{ matrix.language }})
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 360
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
# CodeQL does not support PHP. Use 'javascript' to scan JSON, YAML,
|
|
||||||
# and shell scripts. Add 'actions' to scan GitHub Actions workflows.
|
|
||||||
language: ['javascript', 'actions']
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
||||||
|
|
||||||
- name: Initialize CodeQL
|
|
||||||
uses: github/codeql-action/init@v3
|
|
||||||
with:
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
queries: security-extended,security-and-quality
|
|
||||||
|
|
||||||
- name: Autobuild
|
|
||||||
uses: github/codeql-action/autobuild@v3
|
|
||||||
|
|
||||||
- name: Perform CodeQL Analysis
|
|
||||||
uses: github/codeql-action/analyze@v3
|
|
||||||
with:
|
|
||||||
category: "/language:${{ matrix.language }}"
|
|
||||||
upload: true
|
|
||||||
output: sarif-results
|
|
||||||
wait-for-processing: true
|
|
||||||
|
|
||||||
- name: Upload SARIF results
|
|
||||||
if: always()
|
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.5.0
|
|
||||||
with:
|
|
||||||
name: codeql-results-${{ matrix.language }}
|
|
||||||
path: sarif-results
|
|
||||||
retention-days: 30
|
|
||||||
|
|
||||||
- name: Step summary
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
echo "### 🔍 CodeQL — ${{ matrix.language }}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
URL="https://github.com/${{ github.repository }}/security/code-scanning"
|
|
||||||
echo "See the [Security tab]($URL) for findings." >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Severity | SLA |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "|----------|-----|" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Critical | 7 days |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| High | 14 days |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Medium | 30 days |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Low | 60 days / next release |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
summary:
|
|
||||||
name: Security Scan Summary
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: analyze
|
|
||||||
if: always()
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Summary
|
|
||||||
run: |
|
|
||||||
echo "### 🛡️ CodeQL Complete" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "**Trigger:** ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "**Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
|
|
||||||
SECURITY_URL="https://github.com/${{ github.repository }}/security"
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "📊 [View all security alerts]($SECURITY_URL)" >> $GITHUB_STEP_SUMMARY
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
# Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
# SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
# GitHub Actions workflow for Copilot coding agent
|
|
||||||
# This workflow demonstrates how to use the firewall configuration
|
|
||||||
|
|
||||||
name: "MCP: Copilot Agent"
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [opened, synchronize, reopened]
|
|
||||||
issue_comment:
|
|
||||||
types: [created]
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
pull-requests: write
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
copilot-agent:
|
|
||||||
name: Run Copilot Coding Agent
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Configure Copilot Firewall
|
|
||||||
run: |
|
|
||||||
echo "Configuring firewall allowlist for enterprise-ready sites..."
|
|
||||||
bash .github/copilot/setup-firewall.sh
|
|
||||||
echo "Firewall configuration completed"
|
|
||||||
|
|
||||||
- name: Run Copilot Agent
|
|
||||||
uses: github/copilot-swe-agent@v1
|
|
||||||
with:
|
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
issue_number: ${{ github.event.issue.number || github.event.pull_request.number }}
|
|
||||||
env:
|
|
||||||
# Environment variables are set by setup-firewall.sh
|
|
||||||
COPILOT_FIREWALL_ALLOWLIST: ${{ env.COPILOT_FIREWALL_ALLOWLIST }}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
# FILE INFORMATION
|
|
||||||
# DEFGROUP: Gitea.Workflow
|
|
||||||
# INGROUP: MokoStandards.Deploy
|
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
|
|
||||||
# PATH: /templates/workflows/joomla/deploy-manual.yml.template
|
|
||||||
# VERSION: 04.07.00
|
|
||||||
# BRIEF: Manual SFTP deploy to dev server for Joomla repos
|
|
||||||
|
|
||||||
name: "Universal: Deploy to Dev (Manual)"
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
clear_remote:
|
|
||||||
description: 'Delete all remote files before uploading'
|
|
||||||
required: false
|
|
||||||
default: 'false'
|
|
||||||
type: boolean
|
|
||||||
|
|
||||||
env:
|
|
||||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
name: SFTP Deploy to Dev
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
||||||
|
|
||||||
- name: Setup PHP
|
|
||||||
run: |
|
|
||||||
php -v && composer --version
|
|
||||||
|
|
||||||
- name: Setup MokoStandards tools
|
|
||||||
env:
|
|
||||||
GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
|
|
||||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
|
|
||||||
MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
|
|
||||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
|
||||||
run: |
|
|
||||||
git clone --depth 1 --branch main --quiet \
|
|
||||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
|
|
||||||
/tmp/mokostandards-api 2>/dev/null || true
|
|
||||||
if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
|
|
||||||
cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Check FTP configuration
|
|
||||||
id: check
|
|
||||||
env:
|
|
||||||
HOST: ${{ vars.DEV_FTP_HOST }}
|
|
||||||
PATH_VAR: ${{ vars.DEV_FTP_PATH }}
|
|
||||||
PORT: ${{ vars.DEV_FTP_PORT }}
|
|
||||||
run: |
|
|
||||||
if [ -z "$HOST" ] || [ -z "$PATH_VAR" ]; then
|
|
||||||
echo "DEV_FTP_HOST or DEV_FTP_PATH not configured -- cannot deploy"
|
|
||||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "host=$HOST" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
REMOTE="${PATH_VAR%/}"
|
|
||||||
echo "remote=$REMOTE" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
[ -z "$PORT" ] && PORT="22"
|
|
||||||
echo "port=$PORT" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Deploy via SFTP
|
|
||||||
if: steps.check.outputs.skip != 'true'
|
|
||||||
env:
|
|
||||||
SFTP_KEY: ${{ secrets.DEV_FTP_KEY }}
|
|
||||||
SFTP_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
|
||||||
SFTP_USER: ${{ vars.DEV_FTP_USERNAME }}
|
|
||||||
run: |
|
|
||||||
SOURCE_DIR="src"
|
|
||||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
|
||||||
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ -- nothing to deploy"; exit 0; }
|
|
||||||
|
|
||||||
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
|
|
||||||
"${{ steps.check.outputs.host }}" "${{ steps.check.outputs.port }}" "$SFTP_USER" "${{ steps.check.outputs.remote }}" \
|
|
||||||
> /tmp/sftp-config.json
|
|
||||||
|
|
||||||
if [ -n "$SFTP_KEY" ]; then
|
|
||||||
echo "$SFTP_KEY" > /tmp/deploy_key
|
|
||||||
chmod 600 /tmp/deploy_key
|
|
||||||
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
|
|
||||||
else
|
|
||||||
printf ',"password":"%s"}' "$SFTP_PASS" >> /tmp/sftp-config.json
|
|
||||||
fi
|
|
||||||
|
|
||||||
DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json)
|
|
||||||
[ "${{ inputs.clear_remote }}" = "true" ] && DEPLOY_ARGS+=(--clear-remote)
|
|
||||||
|
|
||||||
PLATFORM=$(php /tmp/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true)
|
|
||||||
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards-api/deploy/deploy-joomla.php" ]; then
|
|
||||||
php /tmp/mokostandards-api/deploy/deploy-joomla.php "${DEPLOY_ARGS[@]}"
|
|
||||||
else
|
|
||||||
php /tmp/mokostandards-api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
|
||||||
|
|
||||||
- name: Summary
|
|
||||||
if: always()
|
|
||||||
run: |
|
|
||||||
if [ "${{ steps.check.outputs.skip }}" = "true" ]; then
|
|
||||||
echo "### Deploy Skipped -- FTP not configured" >> $GITHUB_STEP_SUMMARY
|
|
||||||
else
|
|
||||||
echo "### Manual Dev Deploy Complete" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Host | \`${{ steps.check.outputs.host }}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Remote | \`${{ steps.check.outputs.remote }}\` |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "| Clear | ${{ inputs.clear_remote }} |" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
@@ -1,758 +0,0 @@
|
|||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
#
|
|
||||||
# This file is part of a Moko Consulting project.
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
# FILE INFORMATION
|
|
||||||
# DEFGROUP: GitHub.Workflow
|
|
||||||
# INGROUP: MokoStandards.Firewall
|
|
||||||
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
|
||||||
# PATH: /templates/workflows/shared/enterprise-firewall-setup.yml.template
|
|
||||||
# VERSION: 04.06.00
|
|
||||||
# BRIEF: Enterprise firewall configuration — generates outbound allow-rules including SFTP deployment server
|
|
||||||
# NOTE: Reads DEV_FTP_HOST / DEV_FTP_PORT variables to include SFTP egress rules alongside HTTPS rules.
|
|
||||||
|
|
||||||
name: "MCP: Enterprise Firewall"
|
|
||||||
|
|
||||||
# This workflow provides firewall configuration guidance for enterprise-ready sites
|
|
||||||
# It generates firewall rules for allowing outbound access to trusted domains
|
|
||||||
# including license providers, documentation sources, package registries,
|
|
||||||
# and the SFTP deployment server (DEV_FTP_HOST / DEV_FTP_PORT).
|
|
||||||
#
|
|
||||||
# Runs automatically when:
|
|
||||||
# - Coding agent workflows are triggered (pull requests with copilot/ prefix)
|
|
||||||
# - Manual workflow dispatch for custom configurations
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
firewall_type:
|
|
||||||
description: 'Target firewall type'
|
|
||||||
required: true
|
|
||||||
type: choice
|
|
||||||
options:
|
|
||||||
- 'iptables'
|
|
||||||
- 'ufw'
|
|
||||||
- 'firewalld'
|
|
||||||
- 'aws-security-group'
|
|
||||||
- 'azure-nsg'
|
|
||||||
- 'gcp-firewall'
|
|
||||||
- 'cloudflare'
|
|
||||||
- 'all'
|
|
||||||
default: 'all'
|
|
||||||
output_format:
|
|
||||||
description: 'Output format'
|
|
||||||
required: true
|
|
||||||
type: choice
|
|
||||||
options:
|
|
||||||
- 'shell-script'
|
|
||||||
- 'json'
|
|
||||||
- 'yaml'
|
|
||||||
- 'markdown'
|
|
||||||
- 'all'
|
|
||||||
default: 'markdown'
|
|
||||||
|
|
||||||
# Auto-run when coding agent creates or updates PRs
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- 'copilot/**'
|
|
||||||
- 'agent/**'
|
|
||||||
types: [opened, synchronize, reopened]
|
|
||||||
|
|
||||||
# Auto-run on push to coding agent branches
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'copilot/**'
|
|
||||||
- 'agent/**'
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
actions: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
generate-firewall-rules:
|
|
||||||
name: Generate Firewall Rules
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v6
|
|
||||||
with:
|
|
||||||
python-version: '3.11'
|
|
||||||
|
|
||||||
- name: Apply Firewall Rules to Runner (Auto-run only)
|
|
||||||
if: github.event_name != 'workflow_dispatch'
|
|
||||||
env:
|
|
||||||
DEV_FTP_HOST: ${{ vars.DEV_FTP_HOST }}
|
|
||||||
DEV_FTP_PORT: ${{ vars.DEV_FTP_PORT }}
|
|
||||||
run: |
|
|
||||||
echo "🔥 Applying firewall rules for coding agent environment..."
|
|
||||||
echo ""
|
|
||||||
echo "This step ensures the GitHub Actions runner can access trusted domains"
|
|
||||||
echo "including license providers, package registries, and documentation sources."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Note: GitHub Actions runners are ephemeral and run in controlled environments
|
|
||||||
# This step documents what domains are being accessed during the workflow
|
|
||||||
# Actual firewall configuration is managed by GitHub
|
|
||||||
|
|
||||||
cat > /tmp/trusted-domains.txt << 'EOF'
|
|
||||||
# Trusted domains for coding agent environment
|
|
||||||
# License Providers
|
|
||||||
www.gnu.org
|
|
||||||
opensource.org
|
|
||||||
choosealicense.com
|
|
||||||
spdx.org
|
|
||||||
creativecommons.org
|
|
||||||
apache.org
|
|
||||||
fsf.org
|
|
||||||
|
|
||||||
# Documentation & Standards
|
|
||||||
semver.org
|
|
||||||
keepachangelog.com
|
|
||||||
conventionalcommits.org
|
|
||||||
|
|
||||||
# GitHub & Related
|
|
||||||
github.com
|
|
||||||
api.github.com
|
|
||||||
docs.github.com
|
|
||||||
raw.githubusercontent.com
|
|
||||||
ghcr.io
|
|
||||||
|
|
||||||
# Package Registries
|
|
||||||
npmjs.com
|
|
||||||
registry.npmjs.org
|
|
||||||
pypi.org
|
|
||||||
files.pythonhosted.org
|
|
||||||
packagist.org
|
|
||||||
repo.packagist.org
|
|
||||||
rubygems.org
|
|
||||||
|
|
||||||
# Platform-Specific
|
|
||||||
joomla.org
|
|
||||||
downloads.joomla.org
|
|
||||||
docs.joomla.org
|
|
||||||
php.net
|
|
||||||
getcomposer.org
|
|
||||||
dolibarr.org
|
|
||||||
wiki.dolibarr.org
|
|
||||||
docs.dolibarr.org
|
|
||||||
|
|
||||||
# Moko Consulting
|
|
||||||
mokoconsulting.tech
|
|
||||||
|
|
||||||
# SFTP Deployment Server (DEV_FTP_HOST)
|
|
||||||
${DEV_FTP_HOST:-<not configured>}
|
|
||||||
|
|
||||||
# Google Services
|
|
||||||
drive.google.com
|
|
||||||
docs.google.com
|
|
||||||
sheets.google.com
|
|
||||||
accounts.google.com
|
|
||||||
storage.googleapis.com
|
|
||||||
fonts.googleapis.com
|
|
||||||
fonts.gstatic.com
|
|
||||||
|
|
||||||
# GitHub Extended
|
|
||||||
upload.github.com
|
|
||||||
objects.githubusercontent.com
|
|
||||||
user-images.githubusercontent.com
|
|
||||||
codeload.github.com
|
|
||||||
pkg.github.com
|
|
||||||
|
|
||||||
# Developer Reference
|
|
||||||
developer.mozilla.org
|
|
||||||
stackoverflow.com
|
|
||||||
git-scm.com
|
|
||||||
|
|
||||||
# CDN & Infrastructure
|
|
||||||
cdn.jsdelivr.net
|
|
||||||
unpkg.com
|
|
||||||
cdnjs.cloudflare.com
|
|
||||||
img.shields.io
|
|
||||||
|
|
||||||
# Container Registries
|
|
||||||
hub.docker.com
|
|
||||||
registry-1.docker.io
|
|
||||||
|
|
||||||
# CI & Code Quality
|
|
||||||
codecov.io
|
|
||||||
sonarcloud.io
|
|
||||||
|
|
||||||
# Terraform & Infrastructure
|
|
||||||
registry.terraform.io
|
|
||||||
releases.hashicorp.com
|
|
||||||
checkpoint-api.hashicorp.com
|
|
||||||
EOF
|
|
||||||
|
|
||||||
echo "✓ Trusted domains documented for this runner"
|
|
||||||
echo "✓ GitHub Actions runners have network access to these domains"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Test connectivity to key domains
|
|
||||||
echo "Testing connectivity to key domains..."
|
|
||||||
for domain in "github.com" "www.gnu.org" "npmjs.com" "pypi.org"; do
|
|
||||||
if curl -s --max-time 3 -o /dev/null -w "%{http_code}" "https://$domain" | grep -q "200\|301\|302"; then
|
|
||||||
echo " ✓ $domain is accessible"
|
|
||||||
else
|
|
||||||
echo " ⚠️ $domain connectivity check failed (may be expected)"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Test SFTP server connectivity (TCP port check)
|
|
||||||
SFTP_HOST="${DEV_FTP_HOST:-}"
|
|
||||||
SFTP_PORT="${DEV_FTP_PORT:-22}"
|
|
||||||
if [ -n "$SFTP_HOST" ]; then
|
|
||||||
# Strip any embedded :port suffix
|
|
||||||
SFTP_HOST="${SFTP_HOST%%:*}"
|
|
||||||
echo ""
|
|
||||||
echo "Testing SFTP deployment server connectivity..."
|
|
||||||
if timeout 5 bash -c "echo >/dev/tcp/${SFTP_HOST}/${SFTP_PORT}" 2>/dev/null; then
|
|
||||||
echo " ✓ SFTP server ${SFTP_HOST}:${SFTP_PORT} is reachable"
|
|
||||||
else
|
|
||||||
echo " ⚠️ SFTP server ${SFTP_HOST}:${SFTP_PORT} is not reachable from runner (firewall rule needed)"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo ""
|
|
||||||
echo " ℹ️ DEV_FTP_HOST not configured — skipping SFTP connectivity check"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Generate Firewall Configuration
|
|
||||||
id: generate
|
|
||||||
env:
|
|
||||||
DEV_FTP_HOST: ${{ vars.DEV_FTP_HOST }}
|
|
||||||
DEV_FTP_PORT: ${{ vars.DEV_FTP_PORT }}
|
|
||||||
run: |
|
|
||||||
cat > generate_firewall_config.py << 'PYTHON_EOF'
|
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Enterprise Firewall Configuration Generator
|
|
||||||
|
|
||||||
Generates firewall rules for enterprise-ready deployments allowing
|
|
||||||
access to trusted domains including license providers, documentation
|
|
||||||
sources, package registries, and platform-specific sites.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import yaml
|
|
||||||
import sys
|
|
||||||
from typing import List, Dict
|
|
||||||
|
|
||||||
# SFTP deployment server from org variables
|
|
||||||
_sftp_host_raw = os.environ.get("DEV_FTP_HOST", "").strip()
|
|
||||||
_sftp_port = os.environ.get("DEV_FTP_PORT", "").strip() or "22"
|
|
||||||
# Strip embedded :port suffix if present
|
|
||||||
_sftp_host = _sftp_host_raw.split(":")[0] if _sftp_host_raw else ""
|
|
||||||
if ":" in _sftp_host_raw and not _sftp_port:
|
|
||||||
_sftp_port = _sftp_host_raw.split(":")[1]
|
|
||||||
|
|
||||||
SFTP_HOST = _sftp_host
|
|
||||||
SFTP_PORT = int(_sftp_port) if _sftp_port.isdigit() else 22
|
|
||||||
|
|
||||||
# Trusted domains from .github/copilot.yml
|
|
||||||
TRUSTED_DOMAINS = {
|
|
||||||
"license_providers": [
|
|
||||||
"www.gnu.org",
|
|
||||||
"opensource.org",
|
|
||||||
"choosealicense.com",
|
|
||||||
"spdx.org",
|
|
||||||
"creativecommons.org",
|
|
||||||
"apache.org",
|
|
||||||
"fsf.org",
|
|
||||||
],
|
|
||||||
"documentation_standards": [
|
|
||||||
"semver.org",
|
|
||||||
"keepachangelog.com",
|
|
||||||
"conventionalcommits.org",
|
|
||||||
],
|
|
||||||
"github_related": [
|
|
||||||
"github.com",
|
|
||||||
"api.github.com",
|
|
||||||
"docs.github.com",
|
|
||||||
"raw.githubusercontent.com",
|
|
||||||
"ghcr.io",
|
|
||||||
],
|
|
||||||
"package_registries": [
|
|
||||||
"npmjs.com",
|
|
||||||
"registry.npmjs.org",
|
|
||||||
"pypi.org",
|
|
||||||
"files.pythonhosted.org",
|
|
||||||
"packagist.org",
|
|
||||||
"repo.packagist.org",
|
|
||||||
"rubygems.org",
|
|
||||||
],
|
|
||||||
"standards_organizations": [
|
|
||||||
"json-schema.org",
|
|
||||||
"w3.org",
|
|
||||||
"ietf.org",
|
|
||||||
],
|
|
||||||
"platform_specific": [
|
|
||||||
"joomla.org",
|
|
||||||
"downloads.joomla.org",
|
|
||||||
"docs.joomla.org",
|
|
||||||
"php.net",
|
|
||||||
"getcomposer.org",
|
|
||||||
"dolibarr.org",
|
|
||||||
"wiki.dolibarr.org",
|
|
||||||
"docs.dolibarr.org",
|
|
||||||
],
|
|
||||||
"moko_consulting": [
|
|
||||||
"mokoconsulting.tech",
|
|
||||||
],
|
|
||||||
"google_services": [
|
|
||||||
"drive.google.com",
|
|
||||||
"docs.google.com",
|
|
||||||
"sheets.google.com",
|
|
||||||
"accounts.google.com",
|
|
||||||
"storage.googleapis.com",
|
|
||||||
"fonts.googleapis.com",
|
|
||||||
"fonts.gstatic.com",
|
|
||||||
],
|
|
||||||
"github_extended": [
|
|
||||||
"upload.github.com",
|
|
||||||
"objects.githubusercontent.com",
|
|
||||||
"user-images.githubusercontent.com",
|
|
||||||
"codeload.github.com",
|
|
||||||
"pkg.github.com",
|
|
||||||
],
|
|
||||||
"developer_reference": [
|
|
||||||
"developer.mozilla.org",
|
|
||||||
"stackoverflow.com",
|
|
||||||
"git-scm.com",
|
|
||||||
],
|
|
||||||
"cdn_and_infrastructure": [
|
|
||||||
"cdn.jsdelivr.net",
|
|
||||||
"unpkg.com",
|
|
||||||
"cdnjs.cloudflare.com",
|
|
||||||
"img.shields.io",
|
|
||||||
],
|
|
||||||
"container_registries": [
|
|
||||||
"hub.docker.com",
|
|
||||||
"registry-1.docker.io",
|
|
||||||
],
|
|
||||||
"ci_code_quality": [
|
|
||||||
"codecov.io",
|
|
||||||
"sonarcloud.io",
|
|
||||||
],
|
|
||||||
"terraform_infrastructure": [
|
|
||||||
"registry.terraform.io",
|
|
||||||
"releases.hashicorp.com",
|
|
||||||
"checkpoint-api.hashicorp.com",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
|
|
||||||
# Inject SFTP deployment server as a separate category (port 22, not 443)
|
|
||||||
if SFTP_HOST:
|
|
||||||
TRUSTED_DOMAINS["sftp_deployment_server"] = [SFTP_HOST]
|
|
||||||
print(f"ℹ️ SFTP deployment server: {SFTP_HOST}:{SFTP_PORT}")
|
|
||||||
|
|
||||||
def generate_sftp_iptables_rules(host: str, port: int) -> str:
|
|
||||||
"""Generate iptables rules specifically for SFTP egress"""
|
|
||||||
return (
|
|
||||||
f"# Allow SFTP to deployment server {host}:{port}\n"
|
|
||||||
f"iptables -A OUTPUT -p tcp -d $(dig +short {host} | head -1)"
|
|
||||||
f" --dport {port} -j ACCEPT # SFTP deploy\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
def generate_sftp_ufw_rules(host: str, port: int) -> str:
|
|
||||||
"""Generate UFW rules for SFTP egress"""
|
|
||||||
return (
|
|
||||||
f"# Allow SFTP to deployment server\n"
|
|
||||||
f"ufw allow out to $(dig +short {host} | head -1)"
|
|
||||||
f" port {port} proto tcp comment 'SFTP deploy to {host}'\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
def generate_sftp_firewalld_rules(host: str, port: int) -> str:
|
|
||||||
"""Generate firewalld rules for SFTP egress"""
|
|
||||||
return (
|
|
||||||
f"# Allow SFTP to deployment server\n"
|
|
||||||
f"firewall-cmd --permanent --add-rich-rule='"
|
|
||||||
f"rule family=ipv4 destination address=$(dig +short {host} | head -1)"
|
|
||||||
f" port port={port} protocol=tcp accept' # SFTP deploy\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
def generate_iptables_rules(domains: List[str]) -> str:
|
|
||||||
"""Generate iptables firewall rules"""
|
|
||||||
rules = ["#!/bin/bash", "", "# Enterprise Firewall Rules - iptables", ""]
|
|
||||||
rules.append("# Allow outbound HTTPS to trusted domains")
|
|
||||||
rules.append("")
|
|
||||||
|
|
||||||
for domain in domains:
|
|
||||||
rules.append(f"# Allow {domain}")
|
|
||||||
rules.append(f"iptables -A OUTPUT -p tcp -d $(dig +short {domain} | head -1) --dport 443 -j ACCEPT")
|
|
||||||
|
|
||||||
rules.append("")
|
|
||||||
rules.append("# Allow DNS lookups")
|
|
||||||
rules.append("iptables -A OUTPUT -p udp --dport 53 -j ACCEPT")
|
|
||||||
rules.append("iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT")
|
|
||||||
|
|
||||||
return "\n".join(rules)
|
|
||||||
|
|
||||||
def generate_ufw_rules(domains: List[str]) -> str:
|
|
||||||
"""Generate UFW firewall rules"""
|
|
||||||
rules = ["#!/bin/bash", "", "# Enterprise Firewall Rules - UFW", ""]
|
|
||||||
rules.append("# Allow outbound HTTPS to trusted domains")
|
|
||||||
rules.append("")
|
|
||||||
|
|
||||||
for domain in domains:
|
|
||||||
rules.append(f"# Allow {domain}")
|
|
||||||
rules.append(f"ufw allow out to $(dig +short {domain} | head -1) port 443 proto tcp comment 'Allow {domain}'")
|
|
||||||
|
|
||||||
rules.append("")
|
|
||||||
rules.append("# Allow DNS")
|
|
||||||
rules.append("ufw allow out 53/udp comment 'Allow DNS UDP'")
|
|
||||||
rules.append("ufw allow out 53/tcp comment 'Allow DNS TCP'")
|
|
||||||
|
|
||||||
return "\n".join(rules)
|
|
||||||
|
|
||||||
def generate_firewalld_rules(domains: List[str]) -> str:
|
|
||||||
"""Generate firewalld rules"""
|
|
||||||
rules = ["#!/bin/bash", "", "# Enterprise Firewall Rules - firewalld", ""]
|
|
||||||
rules.append("# Add trusted domains to firewall")
|
|
||||||
rules.append("")
|
|
||||||
|
|
||||||
for domain in domains:
|
|
||||||
rules.append(f"# Allow {domain}")
|
|
||||||
rules.append(f"firewall-cmd --permanent --add-rich-rule='rule family=ipv4 destination address=$(dig +short {domain} | head -1) port port=443 protocol=tcp accept'")
|
|
||||||
|
|
||||||
rules.append("")
|
|
||||||
rules.append("# Reload firewall")
|
|
||||||
rules.append("firewall-cmd --reload")
|
|
||||||
|
|
||||||
return "\n".join(rules)
|
|
||||||
|
|
||||||
def generate_aws_security_group(domains: List[str]) -> Dict:
|
|
||||||
"""Generate AWS Security Group rules (JSON format)"""
|
|
||||||
rules = {
|
|
||||||
"SecurityGroupRules": {
|
|
||||||
"Egress": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for domain in domains:
|
|
||||||
rules["SecurityGroupRules"]["Egress"].append({
|
|
||||||
"Description": f"Allow HTTPS to {domain}",
|
|
||||||
"IpProtocol": "tcp",
|
|
||||||
"FromPort": 443,
|
|
||||||
"ToPort": 443,
|
|
||||||
"CidrIp": "0.0.0.0/0", # In practice, resolve to specific IPs
|
|
||||||
"Tags": [{
|
|
||||||
"Key": "Domain",
|
|
||||||
"Value": domain
|
|
||||||
}]
|
|
||||||
})
|
|
||||||
|
|
||||||
# Add DNS
|
|
||||||
rules["SecurityGroupRules"]["Egress"].append({
|
|
||||||
"Description": "Allow DNS",
|
|
||||||
"IpProtocol": "udp",
|
|
||||||
"FromPort": 53,
|
|
||||||
"ToPort": 53,
|
|
||||||
"CidrIp": "0.0.0.0/0"
|
|
||||||
})
|
|
||||||
|
|
||||||
return rules
|
|
||||||
|
|
||||||
def generate_markdown_documentation(domains_by_category: Dict[str, List[str]]) -> str:
|
|
||||||
"""Generate markdown documentation"""
|
|
||||||
md = ["# Enterprise Firewall Configuration Guide", ""]
|
|
||||||
md.append("## Overview")
|
|
||||||
md.append("")
|
|
||||||
md.append("This document provides firewall configuration guidance for enterprise-ready deployments.")
|
|
||||||
md.append("It lists trusted domains that should be whitelisted for outbound access to ensure")
|
|
||||||
md.append("proper functionality of license validation, package management, and documentation access.")
|
|
||||||
md.append("")
|
|
||||||
|
|
||||||
md.append("## Trusted Domains by Category")
|
|
||||||
md.append("")
|
|
||||||
|
|
||||||
all_domains = []
|
|
||||||
for category, domains in domains_by_category.items():
|
|
||||||
category_name = category.replace("_", " ").title()
|
|
||||||
md.append(f"### {category_name}")
|
|
||||||
md.append("")
|
|
||||||
md.append("| Domain | Purpose |")
|
|
||||||
md.append("|--------|---------|")
|
|
||||||
|
|
||||||
for domain in domains:
|
|
||||||
all_domains.append(domain)
|
|
||||||
purpose = get_domain_purpose(domain)
|
|
||||||
md.append(f"| `{domain}` | {purpose} |")
|
|
||||||
|
|
||||||
md.append("")
|
|
||||||
|
|
||||||
md.append("## Implementation Examples")
|
|
||||||
md.append("")
|
|
||||||
|
|
||||||
md.append("### iptables Example")
|
|
||||||
md.append("")
|
|
||||||
md.append("```bash")
|
|
||||||
md.append("# Allow HTTPS to trusted domain")
|
|
||||||
md.append(f"iptables -A OUTPUT -p tcp -d $(dig +short {all_domains[0]}) --dport 443 -j ACCEPT")
|
|
||||||
md.append("```")
|
|
||||||
md.append("")
|
|
||||||
|
|
||||||
md.append("### UFW Example")
|
|
||||||
md.append("")
|
|
||||||
md.append("```bash")
|
|
||||||
md.append("# Allow HTTPS to trusted domain")
|
|
||||||
md.append(f"ufw allow out to {all_domains[0]} port 443 proto tcp")
|
|
||||||
md.append("```")
|
|
||||||
md.append("")
|
|
||||||
|
|
||||||
md.append("### AWS Security Group Example")
|
|
||||||
md.append("")
|
|
||||||
md.append("```json")
|
|
||||||
md.append("{")
|
|
||||||
md.append(' "IpPermissions": [{')
|
|
||||||
md.append(' "IpProtocol": "tcp",')
|
|
||||||
md.append(' "FromPort": 443,')
|
|
||||||
md.append(' "ToPort": 443,')
|
|
||||||
md.append(' "IpRanges": [{"CidrIp": "0.0.0.0/0", "Description": "HTTPS to trusted domains"}]')
|
|
||||||
md.append(" }]")
|
|
||||||
md.append("}")
|
|
||||||
md.append("```")
|
|
||||||
md.append("")
|
|
||||||
|
|
||||||
md.append("## Ports Required")
|
|
||||||
md.append("")
|
|
||||||
md.append("| Port | Protocol | Purpose |")
|
|
||||||
md.append("|------|----------|---------|")
|
|
||||||
md.append("| 443 | TCP | HTTPS (secure web access) |")
|
|
||||||
md.append("| 80 | TCP | HTTP (redirects to HTTPS) |")
|
|
||||||
md.append("| 53 | UDP/TCP | DNS resolution |")
|
|
||||||
md.append("")
|
|
||||||
|
|
||||||
md.append("## Security Considerations")
|
|
||||||
md.append("")
|
|
||||||
md.append("1. **DNS Resolution**: Ensure DNS queries are allowed (port 53 UDP/TCP)")
|
|
||||||
md.append("2. **Certificate Validation**: HTTPS requires ability to reach certificate authorities")
|
|
||||||
md.append("3. **Dynamic IPs**: Some domains use CDNs with dynamic IPs - consider using FQDNs in rules")
|
|
||||||
md.append("4. **Regular Updates**: Review and update whitelist as services change")
|
|
||||||
md.append("5. **Logging**: Enable logging for blocked connections to identify missing rules")
|
|
||||||
md.append("")
|
|
||||||
|
|
||||||
md.append("## Compliance Notes")
|
|
||||||
md.append("")
|
|
||||||
md.append("- All listed domains provide read-only access to public information")
|
|
||||||
md.append("- License providers enable GPL compliance verification")
|
|
||||||
md.append("- Package registries support dependency security scanning")
|
|
||||||
md.append("- No authentication credentials are transmitted to these domains")
|
|
||||||
md.append("")
|
|
||||||
|
|
||||||
return "\n".join(md)
|
|
||||||
|
|
||||||
def get_domain_purpose(domain: str) -> str:
|
|
||||||
"""Get human-readable purpose for a domain"""
|
|
||||||
purposes = {
|
|
||||||
"www.gnu.org": "GNU licenses and documentation",
|
|
||||||
"opensource.org": "Open Source Initiative resources",
|
|
||||||
"choosealicense.com": "GitHub license selection tool",
|
|
||||||
"spdx.org": "Software Package Data Exchange identifiers",
|
|
||||||
"creativecommons.org": "Creative Commons licenses",
|
|
||||||
"apache.org": "Apache Software Foundation licenses",
|
|
||||||
"fsf.org": "Free Software Foundation resources",
|
|
||||||
"semver.org": "Semantic versioning specification",
|
|
||||||
"keepachangelog.com": "Changelog format standards",
|
|
||||||
"conventionalcommits.org": "Commit message conventions",
|
|
||||||
"github.com": "GitHub platform access",
|
|
||||||
"api.github.com": "GitHub API access",
|
|
||||||
"docs.github.com": "GitHub documentation",
|
|
||||||
"raw.githubusercontent.com": "GitHub raw content access",
|
|
||||||
"npmjs.com": "npm package registry",
|
|
||||||
"pypi.org": "Python Package Index",
|
|
||||||
"packagist.org": "PHP Composer package registry",
|
|
||||||
"rubygems.org": "Ruby gems registry",
|
|
||||||
"joomla.org": "Joomla CMS platform",
|
|
||||||
"php.net": "PHP documentation and downloads",
|
|
||||||
"dolibarr.org": "Dolibarr ERP/CRM platform",
|
|
||||||
}
|
|
||||||
return purposes.get(domain, "Trusted resource")
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# Use inputs if provided (manual dispatch), otherwise use defaults (auto-run)
|
|
||||||
firewall_type = "${{ github.event.inputs.firewall_type }}" or "all"
|
|
||||||
output_format = "${{ github.event.inputs.output_format }}" or "markdown"
|
|
||||||
|
|
||||||
print(f"Running in {'manual' if '${{ github.event.inputs.firewall_type }}' else 'automatic'} mode")
|
|
||||||
print(f"Firewall type: {firewall_type}")
|
|
||||||
print(f"Output format: {output_format}")
|
|
||||||
print("")
|
|
||||||
|
|
||||||
# Collect all domains
|
|
||||||
all_domains = []
|
|
||||||
for domains in TRUSTED_DOMAINS.values():
|
|
||||||
all_domains.extend(domains)
|
|
||||||
|
|
||||||
# Remove duplicates and sort
|
|
||||||
all_domains = sorted(set(all_domains))
|
|
||||||
|
|
||||||
print(f"Generating firewall rules for {len(all_domains)} trusted domains...")
|
|
||||||
print("")
|
|
||||||
|
|
||||||
# Exclude SFTP server from HTTPS rule generation (different port)
|
|
||||||
https_domains = [d for d in all_domains if d != SFTP_HOST]
|
|
||||||
|
|
||||||
# Generate based on firewall type
|
|
||||||
if firewall_type in ["iptables", "all"]:
|
|
||||||
rules = generate_iptables_rules(https_domains)
|
|
||||||
if SFTP_HOST:
|
|
||||||
rules += "\n# ── SFTP Deployment Server ──────────────────────────────\n"
|
|
||||||
rules += generate_sftp_iptables_rules(SFTP_HOST, SFTP_PORT)
|
|
||||||
with open("firewall-rules-iptables.sh", "w") as f:
|
|
||||||
f.write(rules)
|
|
||||||
print("✓ Generated iptables rules: firewall-rules-iptables.sh")
|
|
||||||
|
|
||||||
if firewall_type in ["ufw", "all"]:
|
|
||||||
rules = generate_ufw_rules(https_domains)
|
|
||||||
if SFTP_HOST:
|
|
||||||
rules += "\n# ── SFTP Deployment Server ──────────────────────────────\n"
|
|
||||||
rules += generate_sftp_ufw_rules(SFTP_HOST, SFTP_PORT)
|
|
||||||
with open("firewall-rules-ufw.sh", "w") as f:
|
|
||||||
f.write(rules)
|
|
||||||
print("✓ Generated UFW rules: firewall-rules-ufw.sh")
|
|
||||||
|
|
||||||
if firewall_type in ["firewalld", "all"]:
|
|
||||||
rules = generate_firewalld_rules(https_domains)
|
|
||||||
if SFTP_HOST:
|
|
||||||
rules += "\n# ── SFTP Deployment Server ──────────────────────────────\n"
|
|
||||||
rules += generate_sftp_firewalld_rules(SFTP_HOST, SFTP_PORT)
|
|
||||||
with open("firewall-rules-firewalld.sh", "w") as f:
|
|
||||||
f.write(rules)
|
|
||||||
print("✓ Generated firewalld rules: firewall-rules-firewalld.sh")
|
|
||||||
|
|
||||||
if firewall_type in ["aws-security-group", "all"]:
|
|
||||||
rules = generate_aws_security_group(all_domains)
|
|
||||||
with open("firewall-rules-aws-sg.json", "w") as f:
|
|
||||||
json.dump(rules, f, indent=2)
|
|
||||||
print("✓ Generated AWS Security Group rules: firewall-rules-aws-sg.json")
|
|
||||||
|
|
||||||
if output_format in ["yaml", "all"]:
|
|
||||||
with open("trusted-domains.yml", "w") as f:
|
|
||||||
yaml.dump(TRUSTED_DOMAINS, f, default_flow_style=False)
|
|
||||||
print("✓ Generated YAML domain list: trusted-domains.yml")
|
|
||||||
|
|
||||||
if output_format in ["json", "all"]:
|
|
||||||
with open("trusted-domains.json", "w") as f:
|
|
||||||
json.dump(TRUSTED_DOMAINS, f, indent=2)
|
|
||||||
print("✓ Generated JSON domain list: trusted-domains.json")
|
|
||||||
|
|
||||||
if output_format in ["markdown", "all"]:
|
|
||||||
md = generate_markdown_documentation(TRUSTED_DOMAINS)
|
|
||||||
with open("FIREWALL_CONFIGURATION.md", "w") as f:
|
|
||||||
f.write(md)
|
|
||||||
print("✓ Generated documentation: FIREWALL_CONFIGURATION.md")
|
|
||||||
|
|
||||||
print("")
|
|
||||||
print("Domain Categories:")
|
|
||||||
for category, domains in TRUSTED_DOMAINS.items():
|
|
||||||
print(f" - {category}: {len(domains)} domains")
|
|
||||||
|
|
||||||
print("")
|
|
||||||
print("Total unique domains: ", len(all_domains))
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
PYTHON_EOF
|
|
||||||
|
|
||||||
chmod +x generate_firewall_config.py
|
|
||||||
pip install PyYAML
|
|
||||||
python3 generate_firewall_config.py
|
|
||||||
|
|
||||||
- name: Upload Firewall Configuration Artifacts
|
|
||||||
uses: actions/upload-artifact@v6
|
|
||||||
with:
|
|
||||||
name: firewall-configurations
|
|
||||||
path: |
|
|
||||||
firewall-rules-*.sh
|
|
||||||
firewall-rules-*.json
|
|
||||||
trusted-domains.*
|
|
||||||
FIREWALL_CONFIGURATION.md
|
|
||||||
retention-days: 90
|
|
||||||
|
|
||||||
- name: Display Summary
|
|
||||||
run: |
|
|
||||||
echo "## Firewall Configuration" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
||||||
echo "**Mode**: Manual Execution" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "Firewall rules have been generated for enterprise-ready deployments." >> $GITHUB_STEP_SUMMARY
|
|
||||||
else
|
|
||||||
echo "**Mode**: Automatic Execution (Coding Agent Active)" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "This workflow ran automatically because a coding agent (GitHub Copilot) is active." >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "Firewall configuration has been validated for the coding agent environment." >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "### Files Generated" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
if ls firewall-rules-* trusted-domains.* FIREWALL_CONFIGURATION.md 2>/dev/null; then
|
|
||||||
ls -lh firewall-rules-* trusted-domains.* FIREWALL_CONFIGURATION.md 2>/dev/null | awk '{print "- " $9 " (" $5 ")"}' >> $GITHUB_STEP_SUMMARY
|
|
||||||
else
|
|
||||||
echo "- Documentation generated" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
|
||||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
|
||||||
echo "### Download Artifacts" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "Download the generated firewall configurations from the workflow artifacts." >> $GITHUB_STEP_SUMMARY
|
|
||||||
else
|
|
||||||
echo "### Trusted Domains Active" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "The coding agent has access to:" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "- License providers (GPL, OSI, SPDX, Apache, etc.)" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "- Package registries (npm, PyPI, Packagist, RubyGems)" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "- Documentation sources (GitHub, Joomla, Dolibarr, PHP)" >> $GITHUB_STEP_SUMMARY
|
|
||||||
echo "- Standards organizations (W3C, IETF, JSON Schema)" >> $GITHUB_STEP_SUMMARY
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Usage Instructions:
|
|
||||||
#
|
|
||||||
# This workflow runs in two modes:
|
|
||||||
#
|
|
||||||
# 1. AUTOMATIC MODE (Coding Agent):
|
|
||||||
# - Triggers when coding agent branches (copilot/**, agent/**) are pushed or PR'd
|
|
||||||
# - Validates firewall configuration for the coding agent environment
|
|
||||||
# - Documents accessible domains for compliance
|
|
||||||
# - Ensures license sources and package registries are available
|
|
||||||
#
|
|
||||||
# 2. MANUAL MODE (Enterprise Configuration):
|
|
||||||
# - Manually trigger from the Actions tab
|
|
||||||
# - Select desired firewall type and output format
|
|
||||||
# - Download generated artifacts
|
|
||||||
# - Apply firewall rules to your enterprise environment
|
|
||||||
#
|
|
||||||
# Configuration:
|
|
||||||
# - Trusted domains are sourced from .github/copilot.yml
|
|
||||||
# - Modify copilot.yml to add/remove trusted domains
|
|
||||||
# - Changes automatically propagate to firewall rules
|
|
||||||
#
|
|
||||||
# Important Notes:
|
|
||||||
# - Review generated rules before applying to production
|
|
||||||
# - Some domains may use CDNs with dynamic IPs
|
|
||||||
# - Consider using FQDN-based rules where supported
|
|
||||||
# - Test thoroughly in staging environment first
|
|
||||||
# - Monitor logs for blocked connections
|
|
||||||
# - Update rules as domains/services change
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
# FILE INFORMATION
|
|
||||||
# DEFGROUP: Gitea.Workflow
|
|
||||||
# INGROUP: MokoStandards.Security
|
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
|
||||||
# PATH: /.mokogitea/workflows/security-audit.yml
|
|
||||||
# VERSION: 01.00.00
|
|
||||||
# BRIEF: Dependency vulnerability scanning for composer and npm packages
|
|
||||||
|
|
||||||
name: "Universal: Security Audit"
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '0 6 * * 1' # Weekly on Monday at 06:00 UTC
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- 'composer.json'
|
|
||||||
- 'composer.lock'
|
|
||||||
- 'package.json'
|
|
||||||
- 'package-lock.json'
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
env:
|
|
||||||
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
|
||||||
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }}
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
audit:
|
|
||||||
name: Dependency Audit
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Composer audit
|
|
||||||
if: hashFiles('composer.lock') != ''
|
|
||||||
run: |
|
|
||||||
echo "=== Composer Security Audit ==="
|
|
||||||
if ! command -v composer &> /dev/null; then
|
|
||||||
sudo apt-get update -qq
|
|
||||||
sudo apt-get install -y -qq php-cli composer >/dev/null 2>&1
|
|
||||||
fi
|
|
||||||
composer audit --format=plain 2>&1 | tee /tmp/composer-audit.txt
|
|
||||||
RESULT=$?
|
|
||||||
if [ $RESULT -ne 0 ]; then
|
|
||||||
echo "::warning::Composer vulnerabilities found"
|
|
||||||
echo "composer_vulnerable=true" >> "$GITHUB_ENV"
|
|
||||||
else
|
|
||||||
echo "No known vulnerabilities in composer dependencies"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: NPM audit
|
|
||||||
if: hashFiles('package-lock.json') != ''
|
|
||||||
run: |
|
|
||||||
echo "=== NPM Security Audit ==="
|
|
||||||
npm audit --production 2>&1 | tee /tmp/npm-audit.txt || true
|
|
||||||
if npm audit --production 2>&1 | grep -q "found 0 vulnerabilities"; then
|
|
||||||
echo "No known vulnerabilities in npm dependencies"
|
|
||||||
else
|
|
||||||
echo "::warning::NPM vulnerabilities found"
|
|
||||||
echo "npm_vulnerable=true" >> "$GITHUB_ENV"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Notify on vulnerabilities
|
|
||||||
if: env.composer_vulnerable == 'true' || env.npm_vulnerable == 'true'
|
|
||||||
run: |
|
|
||||||
REPO="${{ github.event.repository.name }}"
|
|
||||||
curl -sS \
|
|
||||||
-H "Title: ${REPO} has vulnerable dependencies" \
|
|
||||||
-H "Tags: lock,warning" \
|
|
||||||
-H "Priority: high" \
|
|
||||||
-d "Security audit found vulnerabilities. Review dependency updates." \
|
|
||||||
"${NTFY_URL}/${NTFY_TOPIC}" || true
|
|
||||||
+9
-2
@@ -3,9 +3,16 @@
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Migrated all workflow and template paths from `.github/` to `.mokogitea/`
|
- Branding: Git -> MokoGIT in workflow DEFGROUP tags, committer identity (git-actions[bot] -> mokogit-actions[bot]), and platform prose. Functional tokens (GIT_TOKEN/GITHUB_TOKEN, GIT_ACTIONS_*, vars.GITEA_*, git.mokoconsulting.tech) preserved.
|
||||||
- Template source paths updated: `templates/gitea/` to `templates/mokogitea/`
|
|
||||||
|
### Removed
|
||||||
|
- CI: stripped GitHub-only workflows (DEFGROUP GitHub.Workflow), which do not run on MokoGIT Actions: auto-dev-issue.yml changelog-validation.yml standards-compliance.yml sync-version-on-merge.yml.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Migrated all workflow and template paths from `.github/` to `.mokogit/`
|
||||||
|
- Template source paths updated: `templates/git/` to `templates/mokogit/`
|
||||||
- HCL definition files removed -- Template repos are now the canonical source
|
- HCL definition files removed -- Template repos are now the canonical source
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- CI: gate build/CI/release/version workflows so they skip template repos; smart-gate the sync workflows to run only on Template-* repos; normalize mokocli/MokoCLI refs and drop the stale mokoconsulting-tech org slug.
|
||||||
- `branch-cleanup.yml`: auto-delete merged feature branches after PR merge
|
- `branch-cleanup.yml`: auto-delete merged feature branches after PR merge
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code when working with this repository.
|
|||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
**Template-MCP** -- Template repository for creating MokoStandards-compliant MCP API servers
|
**Template-NPM** -- Template repository for creating MokoCLI-compliant MCP API servers
|
||||||
|
|
||||||
| Field | Value |
|
| Field | Value |
|
||||||
|---|---|
|
|---|---|
|
||||||
@@ -12,8 +12,8 @@ This file provides guidance to Claude Code when working with this repository.
|
|||||||
| **Language** | TypeScript |
|
| **Language** | TypeScript |
|
||||||
| **Default branch** | main |
|
| **Default branch** | main |
|
||||||
| **License** | GPL-3.0-or-later |
|
| **License** | GPL-3.0-or-later |
|
||||||
| **Wiki** | [Template-MCP Wiki](https://git.mokoconsulting.tech/MokoConsulting/Template-MCP/wiki) |
|
| **Wiki** | [Template-NPM Wiki](https://git.mokoconsulting.tech/MokoConsulting/Template-NPM/wiki) |
|
||||||
| **Standards** | [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home) |
|
| **Standards** | [MokoCLI](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home) |
|
||||||
|
|
||||||
## Common Commands
|
## Common Commands
|
||||||
|
|
||||||
@@ -42,11 +42,11 @@ This is an MCP (Model Context Protocol) server. Key files:
|
|||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
|
|
||||||
- **Workflow directory**: `.mokogitea/` (not `.gitea/` or `.github/`)
|
- **Workflow directory**: `.mokogit/` (not `.gitea/` or `.github/`)
|
||||||
|
|
||||||
- **Never commit** `.claude/`, `.mcp.json`, `TODO.md`, or `*.min.css`/`*.min.js`
|
- **Never commit** `.claude/`, `.mcp.json`, `TODO.md`, or `*.min.css`/`*.min.js`
|
||||||
- **Attribution**: use `Authored-by: Moko Consulting` in commits
|
- **Attribution**: use `Authored-by: Moko Consulting` in commits
|
||||||
- **Branch strategy**: develop on `dev`, merge to `main` for release
|
- **Branch strategy**: develop on `dev`, merge to `main` for release
|
||||||
- **Minification**: handled at build time (CI) and runtime (MokoMinifyHelper for Joomla templates)
|
- **Minification**: handled at build time (CI) and runtime (MokoMinifyHelper for Joomla templates)
|
||||||
- **Wiki**: documentation lives in the Gitea wiki, not in `docs/` files
|
- **Wiki**: documentation lives in the MokoGIT wiki, not in `docs/` files
|
||||||
- **Standards**: this repo follows [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)
|
- **Standards**: this repo follows [MokoCLI](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
DEFGROUP:
|
DEFGROUP:
|
||||||
INGROUP: Project.Documentation
|
INGROUP: Project.Documentation
|
||||||
REPO: mokoconsulting-tech/MokoStandards-Template-Generic
|
REPO: MokoConsulting/Template-NPM
|
||||||
VERSION: 00.00.01
|
VERSION: 00.00.01
|
||||||
PATH: ./CODE_OF_CONDUCT.md
|
PATH: ./CODE_OF_CONDUCT.md
|
||||||
BRIEF: Contributor Covenant Code of Conduct version 1.3.0
|
BRIEF: Contributor Covenant Code of Conduct version 1.3.0
|
||||||
|
|||||||
+5
-5
@@ -13,13 +13,13 @@
|
|||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
DEFGROUP:
|
DEFGROUP:
|
||||||
INGROUP: Project.Documentation
|
INGROUP: Project.Documentation
|
||||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-Template-Generic
|
REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-NPM
|
||||||
VERSION: 00.00.01
|
VERSION: 00.00.01
|
||||||
PATH: ./CONTRIBUTING.md
|
PATH: ./CONTRIBUTING.md
|
||||||
BRIEF: Contribution guidelines for the project
|
BRIEF: Contribution guidelines for the project
|
||||||
-->
|
-->
|
||||||
|
|
||||||
# Contributing to MokoStandards-Template-Generic
|
# Contributing to Template-NPM
|
||||||
|
|
||||||
We appreciate your interest in contributing to this project! This document provides guidelines for contributing.
|
We appreciate your interest in contributing to this project! This document provides guidelines for contributing.
|
||||||
|
|
||||||
@@ -148,13 +148,13 @@ Every repo maintains 5 standard release channel tags:
|
|||||||
|
|
||||||
### CI/CD
|
### CI/CD
|
||||||
|
|
||||||
- Gitea Actions runs all CI workflows
|
- MokoGIT Actions runs all CI workflows
|
||||||
- GitHub Actions are disabled on mirrored repos
|
- GitHub Actions are disabled on mirrored repos
|
||||||
- Workflows live in both `.github/workflows/` and `.gitea/workflows/`
|
- Workflows live in both `.github/workflows/` and `.mokogit/workflows/`
|
||||||
|
|
||||||
### Update Servers (Joomla)
|
### Update Servers (Joomla)
|
||||||
|
|
||||||
In manifest `<updateservers>`, Gitea must be priority 1, GitHub priority 2.
|
In manifest `<updateservers>`, MokoGIT must be priority 1, GitHub priority 2.
|
||||||
|
|
||||||
### Secrets
|
### Secrets
|
||||||
|
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
You should have received a copy of the GNU General Public License (./LICENSE).
|
You should have received a copy of the GNU General Public License (./LICENSE).
|
||||||
|
|
||||||
# FILE INFORMATION
|
# FILE INFORMATION
|
||||||
DEFGROUP: MokoStandards
|
DEFGROUP: MokoCLI
|
||||||
INGROUP: MokoStandards.Documentation
|
INGROUP: MokoCLI.Documentation
|
||||||
REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards/
|
REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli/
|
||||||
VERSION: 05.00.00
|
VERSION: 05.00.00
|
||||||
PATH: ./LICENSE
|
PATH: ./LICENSE
|
||||||
BRIEF: Project license (GPL-3.0-or-later)
|
BRIEF: Project license (GPL-3.0-or-later)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ install-deps: ## Install dependencies
|
|||||||
@$(NPM) install
|
@$(NPM) install
|
||||||
@echo "$(COLOR_GREEN)✓ Dependencies installed$(COLOR_RESET)"
|
@echo "$(COLOR_GREEN)✓ Dependencies installed$(COLOR_RESET)"
|
||||||
|
|
||||||
MOKO_PLATFORM ?= $(or $(wildcard ../moko-platform),$(wildcard $(HOME)/moko-platform),$(wildcard /opt/moko-platform))
|
MOKO_PLATFORM ?= $(or $(wildcard ../mokocli),$(wildcard $(HOME)/mokocli),$(wildcard /opt/mokocli))
|
||||||
MINIFY_SCRIPT := $(MOKO_PLATFORM)/build/minify.js
|
MINIFY_SCRIPT := $(MOKO_PLATFORM)/build/minify.js
|
||||||
|
|
||||||
.PHONY: minify
|
.PHONY: minify
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# Template-MCP
|
# Template-NPM
|
||||||
|
|
||||||
   
|
   
|
||||||
|
|
||||||
Template repository for creating MokoStandards-compliant Model Context Protocol (MCP) API servers. Provides the scaffolding, configuration patterns, and architecture conventions used by all Moko Consulting MCP servers, enabling AI assistants like Claude to interact with REST APIs through a standardized tool interface.
|
Template repository for creating MokoCLI-compliant Model Context Protocol (MCP) API servers. Provides the scaffolding, configuration patterns, and architecture conventions used by all Moko Consulting MCP servers, enabling AI assistants like Claude to interact with REST APIs through a standardized tool interface.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ Template repository for creating MokoStandards-compliant Model Context Protocol
|
|||||||
- **REST API bridge architecture** -- standardized pattern for bridging any REST API as MCP tools, with typed request/response handling
|
- **REST API bridge architecture** -- standardized pattern for bridging any REST API as MCP tools, with typed request/response handling
|
||||||
- **TypeScript-first** -- full TypeScript setup with `tsconfig.json`, strict mode, and ES module output
|
- **TypeScript-first** -- full TypeScript setup with `tsconfig.json`, strict mode, and ES module output
|
||||||
- **Build tooling** -- Makefile with `build`, `dev`, `clean`, and `lint` targets; npm scripts for compilation and development
|
- **Build tooling** -- Makefile with `build`, `dev`, `clean`, and `lint` targets; npm scripts for compilation and development
|
||||||
- **MokoStandards compliant** -- follows all Moko Consulting governance conventions: file headers, commit messages, `.gitattributes`, `.gitmessage`, PR/issue templates
|
- **MokoCLI compliant** -- follows all Moko Consulting governance conventions: file headers, commit messages, `.gitattributes`, `.gitmessage`, PR/issue templates
|
||||||
- **Example configuration** -- `config.example.json` demonstrates the connection configuration schema
|
- **Example configuration** -- `config.example.json` demonstrates the connection configuration schema
|
||||||
- **Documentation templates** -- wiki pages for API reference, architecture overview, and installation pre-generated
|
- **Documentation templates** -- wiki pages for API reference, architecture overview, and installation pre-generated
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@ Template repository for creating MokoStandards-compliant Model Context Protocol
|
|||||||
|
|
||||||
1. Create a new repository from this template (or clone directly):
|
1. Create a new repository from this template (or clone directly):
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.mokoconsulting.tech/MokoConsulting/Template-MCP.git my-mcp-server
|
git clone https://git.mokoconsulting.tech/MokoConsulting/Template-NPM.git my-mcp-server
|
||||||
cd my-mcp-server
|
cd my-mcp-server
|
||||||
```
|
```
|
||||||
2. Install dependencies:
|
2. Install dependencies:
|
||||||
@@ -61,11 +61,11 @@ Template repository for creating MokoStandards-compliant Model Context Protocol
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
Full documentation is available on the [Wiki](https://git.mokoconsulting.tech/MokoConsulting/Template-MCP/wiki), including:
|
Full documentation is available on the [Wiki](https://git.mokoconsulting.tech/MokoConsulting/Template-NPM/wiki), including:
|
||||||
|
|
||||||
- [API](https://git.mokoconsulting.tech/MokoConsulting/Template-MCP/wiki/API) -- tool reference and connection parameter documentation
|
- [API](https://git.mokoconsulting.tech/MokoConsulting/Template-NPM/wiki/API) -- tool reference and connection parameter documentation
|
||||||
- [ARCHITECTURE](https://git.mokoconsulting.tech/MokoConsulting/Template-MCP/wiki/ARCHITECTURE) -- MCP server design and REST API bridge pattern
|
- [ARCHITECTURE](https://git.mokoconsulting.tech/MokoConsulting/Template-NPM/wiki/ARCHITECTURE) -- MCP server design and REST API bridge pattern
|
||||||
- [INSTALLATION](https://git.mokoconsulting.tech/MokoConsulting/Template-MCP/wiki/INSTALLATION) -- setup prerequisites and configuration guide
|
- [INSTALLATION](https://git.mokoconsulting.tech/MokoConsulting/Template-NPM/wiki/INSTALLATION) -- setup prerequisites and configuration guide
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
@@ -77,4 +77,8 @@ This project is licensed under the GNU General Public License v3.0 or later -- s
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*[Moko Consulting](https://mokoconsulting.tech) -- [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home)*
|
*[Moko Consulting](https://mokoconsulting.tech) -- [MokoCLI](https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home)*
|
||||||
|
|
||||||
|
## Template CI Notes
|
||||||
|
|
||||||
|
Build/CI/release/version workflows are guarded to skip template repos (Template-*) and only run in repos created from them.
|
||||||
|
|||||||
@@ -1,237 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# ============================================================================
|
|
||||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
||||||
#
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
#
|
|
||||||
# FILE INFORMATION
|
|
||||||
# DEFGROUP: Automation.CI
|
|
||||||
# INGROUP: moko-platform.Automation
|
|
||||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
|
||||||
# PATH: /automation/ci-issue-reporter.sh
|
|
||||||
# VERSION: 09.23.00
|
|
||||||
# BRIEF: Creates or updates a Gitea issue when a CI gate fails.
|
|
||||||
# Deduplicates by searching open issues with the "ci-auto" label
|
|
||||||
# whose title matches the gate. If a matching issue exists, a comment
|
|
||||||
# is appended instead of opening a duplicate.
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# ── Defaults ────────────────────────────────────────────────────────────────
|
|
||||||
GITEA_URL="${GITEA_URL:-https://git.mokoconsulting.tech}"
|
|
||||||
GITEA_TOKEN="${GITEA_TOKEN:-}"
|
|
||||||
REPO="${GITHUB_REPOSITORY:-}"
|
|
||||||
RUN_URL="${GITHUB_SERVER_URL:-${GITEA_URL}}/${REPO}/actions/runs/${GITHUB_RUN_ID:-0}"
|
|
||||||
LABEL_NAME="ci-auto"
|
|
||||||
LABEL_COLOR="#e11d48"
|
|
||||||
|
|
||||||
GATE=""
|
|
||||||
DETAILS=""
|
|
||||||
SEVERITY="error"
|
|
||||||
WORKFLOW=""
|
|
||||||
|
|
||||||
# ── Parse arguments ─────────────────────────────────────────────────────────
|
|
||||||
usage() {
|
|
||||||
cat <<EOF
|
|
||||||
Usage: ci-issue-reporter.sh --gate NAME --details TEXT [OPTIONS]
|
|
||||||
|
|
||||||
Required:
|
|
||||||
--gate CI gate name (e.g. "Code Quality", "Self-Health")
|
|
||||||
--details Human-readable failure description
|
|
||||||
|
|
||||||
Optional:
|
|
||||||
--severity "error" (default) or "warning"
|
|
||||||
--workflow Workflow name for the issue title
|
|
||||||
--repo owner/repo (default: \$GITHUB_REPOSITORY)
|
|
||||||
--run-url URL to the CI run (auto-detected from env)
|
|
||||||
--token Gitea API token (default: \$GITEA_TOKEN)
|
|
||||||
--url Gitea base URL (default: \$GITEA_URL)
|
|
||||||
EOF
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
|
||||||
case "$1" in
|
|
||||||
--gate) GATE="$2"; shift 2 ;;
|
|
||||||
--details) DETAILS="$2"; shift 2 ;;
|
|
||||||
--severity) SEVERITY="$2"; shift 2 ;;
|
|
||||||
--workflow) WORKFLOW="$2"; shift 2 ;;
|
|
||||||
--repo) REPO="$2"; shift 2 ;;
|
|
||||||
--run-url) RUN_URL="$2"; shift 2 ;;
|
|
||||||
--token) GITEA_TOKEN="$2"; shift 2 ;;
|
|
||||||
--url) GITEA_URL="$2"; shift 2 ;;
|
|
||||||
-h|--help) usage ;;
|
|
||||||
*) echo "Unknown option: $1"; usage ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
|
|
||||||
[[ -z "$GATE" ]] && { echo "ERROR: --gate is required"; usage; }
|
|
||||||
[[ -z "$DETAILS" ]] && { echo "ERROR: --details is required"; usage; }
|
|
||||||
[[ -z "$GITEA_TOKEN" ]] && { echo "ERROR: GITEA_TOKEN not set"; exit 1; }
|
|
||||||
[[ -z "$REPO" ]] && { echo "ERROR: GITHUB_REPOSITORY not set"; exit 1; }
|
|
||||||
|
|
||||||
API="${GITEA_URL}/api/v1/repos/${REPO}"
|
|
||||||
|
|
||||||
# ── Build title ─────────────────────────────────────────────────────────────
|
|
||||||
if [[ -n "$WORKFLOW" ]]; then
|
|
||||||
TITLE="[CI] ${WORKFLOW}: ${GATE} failed"
|
|
||||||
else
|
|
||||||
TITLE="[CI] ${GATE} failed"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ── Ensure label exists ─────────────────────────────────────────────────────
|
|
||||||
ensure_label() {
|
|
||||||
local exists
|
|
||||||
exists=$(curl -sf -o /dev/null -w '%{http_code}' \
|
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
||||||
"${API}/labels" 2>/dev/null || echo "000")
|
|
||||||
|
|
||||||
if [[ "$exists" == "200" ]]; then
|
|
||||||
# Check if label already exists
|
|
||||||
local found
|
|
||||||
found=$(curl -sf \
|
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
||||||
"${API}/labels" 2>/dev/null \
|
|
||||||
| grep -o "\"name\":\"${LABEL_NAME}\"" || true)
|
|
||||||
|
|
||||||
if [[ -z "$found" ]]; then
|
|
||||||
curl -sf -X POST \
|
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${API}/labels" \
|
|
||||||
-d "{\"name\":\"${LABEL_NAME}\",\"color\":\"${LABEL_COLOR}\",\"description\":\"Auto-created by CI issue reporter\"}" \
|
|
||||||
> /dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Search for existing open issue ──────────────────────────────────────────
|
|
||||||
find_existing_issue() {
|
|
||||||
# URL-encode the gate name for the query
|
|
||||||
local query
|
|
||||||
query=$(printf '%s' "[CI] ${GATE}" | sed 's/ /%20/g; s/\[/%5B/g; s/\]/%5D/g')
|
|
||||||
|
|
||||||
local response
|
|
||||||
response=$(curl -sf \
|
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
||||||
"${API}/issues?type=issues&state=open&labels=${LABEL_NAME}&q=${query}&limit=5" \
|
|
||||||
2>/dev/null || echo "[]")
|
|
||||||
|
|
||||||
# Extract the first matching issue number
|
|
||||||
echo "$response" \
|
|
||||||
| grep -oP '"number":\s*\K[0-9]+' \
|
|
||||||
| head -1
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Build issue body ────────────────────────────────────────────────────────
|
|
||||||
build_body() {
|
|
||||||
local severity_badge
|
|
||||||
if [[ "$SEVERITY" == "error" ]]; then
|
|
||||||
severity_badge="**Severity:** Error"
|
|
||||||
else
|
|
||||||
severity_badge="**Severity:** Warning"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cat <<BODY
|
|
||||||
## CI Gate Failure: ${GATE}
|
|
||||||
|
|
||||||
${severity_badge}
|
|
||||||
**Workflow:** ${WORKFLOW:-unknown}
|
|
||||||
**Branch:** ${GITHUB_REF_NAME:-unknown}
|
|
||||||
**Commit:** \`${GITHUB_SHA:0:8}\`
|
|
||||||
**Run:** [View CI run](${RUN_URL})
|
|
||||||
|
|
||||||
### Details
|
|
||||||
|
|
||||||
${DETAILS}
|
|
||||||
|
|
||||||
### Resolution
|
|
||||||
|
|
||||||
Fix the issue described above and push a new commit. This issue will be closed automatically when the gate passes, or can be closed manually.
|
|
||||||
|
|
||||||
---
|
|
||||||
*Auto-created by [ci-issue-reporter](${GITEA_URL}/${REPO}/src/branch/main/automation/ci-issue-reporter.sh)*
|
|
||||||
BODY
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Build comment body (for existing issues) ────────────────────────────────
|
|
||||||
build_comment() {
|
|
||||||
cat <<COMMENT
|
|
||||||
### CI failure recurrence
|
|
||||||
|
|
||||||
**Branch:** ${GITHUB_REF_NAME:-unknown}
|
|
||||||
**Commit:** \`${GITHUB_SHA:0:8}\`
|
|
||||||
**Run:** [View CI run](${RUN_URL})
|
|
||||||
|
|
||||||
${DETAILS}
|
|
||||||
COMMENT
|
|
||||||
}
|
|
||||||
|
|
||||||
# ── Main ────────────────────────────────────────────────────────────────────
|
|
||||||
ensure_label
|
|
||||||
|
|
||||||
EXISTING=$(find_existing_issue)
|
|
||||||
|
|
||||||
if [[ -n "$EXISTING" ]]; then
|
|
||||||
# Append comment to existing issue
|
|
||||||
COMMENT_BODY=$(build_comment)
|
|
||||||
COMMENT_JSON=$(printf '%s' "$COMMENT_BODY" | python3 -c "
|
|
||||||
import sys, json
|
|
||||||
print(json.dumps({'body': sys.stdin.read()}))" 2>/dev/null)
|
|
||||||
|
|
||||||
HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \
|
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${API}/issues/${EXISTING}/comments" \
|
|
||||||
-d "${COMMENT_JSON}" 2>/dev/null || echo "000")
|
|
||||||
|
|
||||||
if [[ "$HTTP" == "201" ]]; then
|
|
||||||
echo "Commented on existing issue #${EXISTING}"
|
|
||||||
else
|
|
||||||
echo "WARNING: Failed to comment on issue #${EXISTING} (HTTP ${HTTP})"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# Create new issue
|
|
||||||
ISSUE_BODY=$(build_body)
|
|
||||||
ISSUE_JSON=$(python3 -c "
|
|
||||||
import sys, json
|
|
||||||
body = sys.stdin.read()
|
|
||||||
print(json.dumps({
|
|
||||||
'title': sys.argv[1],
|
|
||||||
'body': body,
|
|
||||||
'labels': []
|
|
||||||
}))" "$TITLE" <<< "$ISSUE_BODY" 2>/dev/null)
|
|
||||||
|
|
||||||
# Create the issue
|
|
||||||
RESPONSE=$(curl -sf -X POST \
|
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${API}/issues" \
|
|
||||||
-d "${ISSUE_JSON}" 2>/dev/null || echo "{}")
|
|
||||||
|
|
||||||
ISSUE_NUM=$(echo "$RESPONSE" | grep -oP '"number":\s*\K[0-9]+' | head -1)
|
|
||||||
|
|
||||||
if [[ -n "$ISSUE_NUM" ]]; then
|
|
||||||
# Apply label (separate call — more reliable across Gitea versions)
|
|
||||||
LABEL_ID=$(curl -sf \
|
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
||||||
"${API}/labels" 2>/dev/null \
|
|
||||||
| grep -oP "\"id\":\s*\K[0-9]+(?=[^}]*\"name\":\s*\"${LABEL_NAME}\")" \
|
|
||||||
| head -1 || true)
|
|
||||||
|
|
||||||
if [[ -n "$LABEL_ID" ]]; then
|
|
||||||
curl -sf -X POST \
|
|
||||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${API}/issues/${ISSUE_NUM}/labels" \
|
|
||||||
-d "{\"labels\":[${LABEL_ID}]}" \
|
|
||||||
> /dev/null 2>&1 || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Created issue #${ISSUE_NUM}: ${TITLE}"
|
|
||||||
else
|
|
||||||
echo "WARNING: Failed to create issue"
|
|
||||||
echo "Response: ${RESPONSE}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
Reference in New Issue
Block a user