chore: Sync MokoStandards v04.04 #110

Closed
jmiller-moko wants to merge 54 commits from chore/sync-mokostandards-v04.04 into main
31 changed files with 2607 additions and 1795 deletions

1
.github/.mokostandards vendored Normal file
View File

@@ -0,0 +1 @@
platform: waas-component

378
.github/CLAUDE.md vendored
View File

@@ -1,3 +1,22 @@
<!--
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: MokoStandards.Templates.GitHub
INGROUP: MokoStandards.Templates
REPO: https://github.com/mokoconsulting-tech/MokoStandards
PATH: /templates/github/copilot-instructions.joomla.md.template
VERSION: XX.YY.ZZ
BRIEF: GitHub Copilot custom instructions template for Joomla/MokoWaaS governed repositories
NOTE: Synced to .github/copilot-instructions.md in all Joomla/WaaS repos via bulk sync.
Tokens replaced at sync time: MokoCassiopeia, https://github.com/mokoconsulting-tech/MokoCassiopeia, {{EXTENSION_NAME}},
{{EXTENSION_TYPE}}, {{EXTENSION_ELEMENT}}
-->
> [!IMPORTANT]
> **🔧 AI Self-Update Required on First Use**
>
@@ -19,135 +38,34 @@
> |---|---|
> | `MokoCassiopeia` | The GitHub repository name (visible in the URL, `README.md` heading, or `git remote -v`) |
> | `https://github.com/mokoconsulting-tech/MokoCassiopeia` | Full GitHub URL, e.g. `https://github.com/mokoconsulting-tech/<repo-name>` |
> | `A modern enhancement layer for Joomlas Cassiopeia template. Moko-Cassiopeia adds Font Awesome 7, Bootstrap 5 helpers, an automatic Table of Contents (TOC) utility, and optional expansions including Google Tag Manager and Google Analytics (GA4) hooks.` | First paragraph of `README.md` body, or the GitHub repo description |
> | `{{EXTENSION_NAME}}` | The `<name>` element in `manifest.xml` at the repository root |
> | `{{EXTENSION_TYPE}}` | The `type` attribute of the `<extension>` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) |
> | `{{EXTENSION_ELEMENT}}` | The `<element>` tag in `manifest.xml`, or the filename prefix (e.g. `com_myextension`, `mod_mymodule`) |
>
> ---
# What This Repo Is
# MokoCassiopeia — GitHub Copilot Custom Instructions
**MokoCassiopeia** is a Moko Consulting **MokoWaaS** (Joomla) extension repository.
## What This Repo Is
A modern enhancement layer for Joomlas Cassiopeia template. Moko-Cassiopeia adds Font Awesome 7, Bootstrap 5 helpers, an automatic Table of Contents (TOC) utility, and optional expansions including Google Tag Manager and Google Analytics (GA4) hooks.
This is a **Moko Consulting MokoWaaS** (Joomla) repository governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). All coding standards, workflows, and policies are defined there and enforced here via bulk sync.
Repository URL: https://github.com/mokoconsulting-tech/MokoCassiopeia
Extension name: **{{EXTENSION_NAME}}**
Extension type: **{{EXTENSION_TYPE}}** (`{{EXTENSION_ELEMENT}}`)
Repository URL: https://github.com/mokoconsulting-tech/MokoCassiopeia
This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) — the single source of truth for coding standards, file-header policies, GitHub Actions workflows, and Terraform configuration templates across all Moko Consulting repositories.
Platform: **Joomla 4.x / MokoWaaS**
---
# Repo Structure
## Primary Language
```
MokoCassiopeia/
├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required)
├── site/ # Frontend (site) code
│ ├── controller.php
│ ├── controllers/
│ ├── models/
│ └── views/
├── admin/ # Backend (admin) code
│ ├── controller.php
│ ├── controllers/
│ ├── models/
│ ├── views/
│ └── sql/
├── language/ # Language INI files
├── media/ # CSS, JS, images
├── docs/ # Technical documentation
├── tests/ # Test suite
├── .github/
│ ├── workflows/ # CI/CD workflows (synced from MokoStandards)
│ ├── copilot-instructions.md
│ └── CLAUDE.md # This file
├── README.md # Version source of truth
├── CHANGELOG.md
├── CONTRIBUTING.md
└── LICENSE # GPL-3.0-or-later
```
**PHP** (≥ 7.4) is the primary language for this Joomla extension. JavaScript may be used for frontend enhancements. YAML uses 2-space indentation. All other text files use tabs per `.editorconfig`.
---
# Primary Language
## File Header — Always Required on New Files
**PHP** (≥ 7.4) is the primary language for this Joomla extension. YAML uses 2-space indentation. All other text files use tabs per `.editorconfig`.
---
# Version Management
**`README.md` is the single source of truth for the repository version.**
- **Bump the patch version on every PR** — increment `XX.YY.ZZ` (e.g. `01.02.03``01.02.04`) in `README.md` before opening the PR; the `sync-version-on-merge` workflow propagates it to all `FILE INFORMATION` headers automatically on merge.
- Version format is zero-padded semver: `XX.YY.ZZ` (e.g. `01.02.03`).
- Never hardcode a version number in body text — use the badge or FILE INFORMATION header only.
### Joomla Version Alignment
Three files must **always have the same version**:
| File | Where the version lives |
|------|------------------------|
| `README.md` | `FILE INFORMATION` block + badge |
| `manifest.xml` | `<version>` tag |
| `update.xml` | `<version>` in the most recent `<update>` block |
The `make release` command / release workflow syncs all three automatically.
---
# update.xml — Required in Repo Root
`update.xml` is the Joomla update server manifest. It allows Joomla installations to check for new versions of this extension via:
```xml
<!-- In manifest.xml -->
<updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}">
https://github.com/mokoconsulting-tech/MokoCassiopeia/raw/main/update.xml
</server>
</updateservers>
```
**Rules:**
- Every release prepends a new `<update>` block at the top — older entries are preserved.
- `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and `README.md`.
- `<downloadurl>` must be a publicly accessible GitHub Releases asset URL.
- `<targetplatform version="4\.[0-9]+">` — backslash is literal (Joomla regex syntax).
Example `update.xml` entry for a new release:
```xml
<updates>
<update>
<name>{{EXTENSION_NAME}}</name>
<description>MokoCassiopeia</description>
<element>{{EXTENSION_ELEMENT}}</element>
<type>{{EXTENSION_TYPE}}</type>
<version>01.02.04</version>
<infourl title="Release Information">https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/tag/01.02.04</infourl>
<downloads>
<downloadurl type="full" format="zip">
https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/01.02.04/{{EXTENSION_ELEMENT}}-01.02.04.zip
</downloadurl>
</downloads>
<targetplatform name="joomla" version="4\.[0-9]+" />
<php_minimum>7.4</php_minimum>
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>
</updates>
```
---
# File Header Requirements
Every new file **must** have a copyright header as its first content. JSON files, binary files, generated files, and third-party files are exempt.
Every new file needs a copyright header as its first content.
**PHP:**
```php
@@ -162,47 +80,141 @@ Every new file **must** have a copyright header as its first content. JSON files
* DEFGROUP: MokoCassiopeia.{{EXTENSION_TYPE}}
* INGROUP: MokoCassiopeia
* REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
* PATH: /site/controllers/item.php
* PATH: /path/to/file.php
* VERSION: XX.YY.ZZ
* BRIEF: One-line description of file purpose
* BRIEF: One-line description of purpose
*/
defined('_JEXEC') or die;
```
**Markdown / YAML / Shell / XML:** Use the appropriate comment syntax with the same fields.
**Markdown:**
```markdown
<!--
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: MokoCassiopeia.Documentation
INGROUP: MokoCassiopeia
REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
PATH: /docs/file.md
VERSION: XX.YY.ZZ
BRIEF: One-line description
-->
```
**YAML / Shell / XML:** Use the appropriate comment syntax with the same fields. JSON files are exempt.
---
# Coding Standards
## Version Management
## Naming Conventions
**`README.md` is the single source of truth for the repository version.**
| Context | Convention | Example |
|---------|-----------|---------|
| PHP class | `PascalCase` | `ItemModel` |
| PHP method / function | `camelCase` | `getItems()` |
| PHP variable | `$snake_case` | `$item_id` |
| PHP constant | `UPPER_SNAKE_CASE` | `MAX_ITEMS` |
| PHP class file | `PascalCase.php` | `ItemModel.php` |
| YAML workflow | `kebab-case.yml` | `ci-joomla.yml` |
| Markdown doc | `kebab-case.md` | `installation-guide.md` |
- **Bump the patch version on every PR** — increment `XX.YY.ZZ` (e.g. `01.02.03``01.02.04`) in `README.md` before opening the PR; the `sync-version-on-merge` workflow propagates it automatically to all badges and `FILE INFORMATION` headers on merge to `main`.
- The `VERSION: XX.YY.ZZ` field in `README.md` governs all other version references.
- Version format is zero-padded semver: `XX.YY.ZZ` (e.g. `01.02.03`).
- Never hardcode a specific version in document body text — use the badge or FILE INFORMATION header only.
## Commit Messages
### Joomla Version Alignment
Format: `<type>(<scope>): <subject>` — imperative, lower-case subject, no trailing period.
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
Valid types: `feat` · `fix` · `docs` · `chore` · `ci` · `refactor` · `style` · `test` · `perf` · `revert` · `build`
```xml
<!-- In manifest.xml — must match README.md version -->
<version>01.02.04</version>
## Branch Naming
Format: `<prefix>/<MAJOR.MINOR.PATCH>[/description]`
Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `dependabot/`
<!-- In updates.xml — prepend a new <update> block for every release.
Note: the backslash in version="4\.[0-9]+" is a literal backslash character
in the XML attribute value. Joomla's update server treats the value as a
regular expression, so \. matches a literal dot. -->
<updates>
<update>
<name>{{EXTENSION_NAME}}</name>
<version>01.02.04</version>
<downloads>
<downloadurl type="full" format="zip">
https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/01.02.04/{{EXTENSION_ELEMENT}}-01.02.04.zip
</downloadurl>
</downloads>
<targetplatform name="joomla" version="4\.[0-9]+" />
</update>
<!-- … older entries preserved below … -->
</updates>
```
---
# GitHub Actions — Token Usage
## Joomla Extension Structure
```
MokoCassiopeia/
├── manifest.xml # Joomla installer manifest (root — required)
├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code
│ ├── controller.php
│ ├── controllers/
│ ├── models/
│ └── views/
├── admin/ # Backend (admin) code
│ ├── controller.php
│ ├── controllers/
│ ├── models/
│ ├── views/
│ └── sql/
├── language/ # Language INI files
├── media/ # CSS, JS, images (deployed to /media/{{EXTENSION_ELEMENT}}/)
├── docs/ # Technical documentation
├── tests/ # Test suite
├── .github/
│ ├── workflows/
│ ├── copilot-instructions.md # This file
│ └── CLAUDE.md
├── README.md # Version source of truth
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE # GPL-3.0-or-later
└── Makefile # Build automation
```
---
## updates.xml — Required in Repo Root
`updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via:
```xml
<updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}">
https://github.com/mokoconsulting-tech/MokoCassiopeia/raw/main/updates.xml
</server>
</updateservers>
```
**Rules:**
- Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
- The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
---
## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
- Must include `<updateservers>` block pointing to this repo's `updates.xml`.
- Must include `<files folder="site">` and `<administration>` sections.
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
---
## GitHub Actions — Token Usage
Every workflow must use **`secrets.GH_TOKEN`** (the org-level Personal Access Token).
@@ -217,58 +229,76 @@ env:
```
```yaml
# ❌ Wrong — never use these
# ❌ Wrong — never use these in workflows
token: ${{ github.token }}
token: ${{ secrets.GITHUB_TOKEN }}
```
---
# Keeping Documentation Current
## MokoStandards Reference
| Change type | Documentation to update |
|-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed `manifest.xml` | Sync version to `update.xml` and `README.md` |
| New release | Prepend `<update>` to `update.xml`; update `CHANGELOG.md`; bump `README.md` |
| New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
---
# What NOT to Do
- **Never commit directly to `main`** — all changes go through a PR.
- **Never hardcode version numbers** in body text — update `README.md` and let automation propagate.
- **Never let `manifest.xml`, `update.xml`, and `README.md` versions diverge.**
- **Never skip the FILE INFORMATION block** on a new source file.
- **Never use bare `catch (\Throwable $e) {}`** — always log or re-throw.
- **Never mix tabs and spaces** within a file — follow `.editorconfig`.
- **Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows** — always use `secrets.GH_TOKEN`.
- **Never remove `defined('_JEXEC') or die;`** from web-accessible PHP files.
---
# PR Checklist
Before opening a PR, verify:
- [ ] Patch version bumped in `README.md` (e.g. `01.02.03``01.02.04`)
- [ ] If this is a release: `manifest.xml` version updated; `update.xml` updated with new entry
- [ ] FILE INFORMATION headers updated in modified files
- [ ] CHANGELOG.md updated
- [ ] Tests pass
---
# Key Policy Documents (MokoStandards)
This repository is governed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). Authoritative policies:
| Document | Purpose |
|----------|---------|
| [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) | Copyright-header rules for every file type |
| [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) | Naming and formatting conventions |
| [branching-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branching-strategy.md) | Branch naming, hierarchy, and release workflow |
| [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR conventions |
| [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) | Squash-merge policy and PR title/body conventions |
| [changelog-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/changelog-standards.md) | How and when to update CHANGELOG.md |
| [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide |
| [joomla-development-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/guide/waas/joomla-development-guide.md) | MokoWaaS Joomla extension development guide |
---
## Naming Conventions
| Context | Convention | Example |
|---------|-----------|---------|
| PHP class | `PascalCase` | `MyController` |
| PHP method / function | `camelCase` | `getItems()` |
| PHP variable | `$snake_case` | `$item_id` |
| PHP constant | `UPPER_SNAKE_CASE` | `MAX_ITEMS` |
| PHP class file | `PascalCase.php` | `ItemModel.php` |
| YAML workflow | `kebab-case.yml` | `ci-joomla.yml` |
| Markdown doc | `kebab-case.md` | `installation-guide.md` |
---
## Commit Messages
Format: `<type>(<scope>): <subject>` — imperative, lower-case subject, no trailing period.
Valid types: `feat` · `fix` · `docs` · `chore` · `ci` · `refactor` · `style` · `test` · `perf` · `revert` · `build`
---
## Branch Naming
Format: `<prefix>/<MAJOR.MINOR.PATCH>[/description]`
Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `dependabot/`
---
## Keeping Documentation Current
| Change type | Documentation to update |
|-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
---
## Key Constraints
- Never commit directly to `main` — all changes go via PR, squash-merged
- Never skip the FILE INFORMATION block on a new file
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync

24
.github/CODEOWNERS vendored
View File

@@ -8,8 +8,26 @@
# Combined with branch protection (require PR reviews), this prevents
# unauthorized modifications to workflows, configs, and governance files.
# ── Workflows (synced from MokoStandards — must not be manually edited) ──
/.github/workflows/ @jmiller-moko
# ── Synced workflows (managed by MokoStandards — do not edit manually) ────
/.github/workflows/deploy-dev.yml @jmiller-moko
/.github/workflows/deploy-demo.yml @jmiller-moko
/.github/workflows/deploy-rs.yml @jmiller-moko
/.github/workflows/auto-release.yml @jmiller-moko
/.github/workflows/auto-dev-issue.yml @jmiller-moko
/.github/workflows/auto-assign.yml @jmiller-moko
/.github/workflows/sync-version-on-merge.yml @jmiller-moko
/.github/workflows/enterprise-firewall-setup.yml @jmiller-moko
/.github/workflows/repository-cleanup.yml @jmiller-moko
/.github/workflows/standards-compliance.yml @jmiller-moko
/.github/workflows/codeql-analysis.yml @jmiller-moko
/.github/workflows/repo_health.yml @jmiller-moko
/.github/workflows/ci-joomla.yml @jmiller-moko
/.github/workflows/update-server.yml @jmiller-moko
/.github/workflows/deploy-manual.yml @jmiller-moko
/.github/workflows/ci-dolibarr.yml @jmiller-moko
/.github/workflows/publish-to-mokodolimods.yml @jmiller-moko
/.github/workflows/changelog-validation.yml @jmiller-moko
# Custom workflows in .github/workflows/ not listed above are repo-owned.
# ── GitHub configuration ─────────────────────────────────────────────────
/.github/ISSUE_TEMPLATE/ @jmiller-moko
@@ -23,7 +41,7 @@
/composer.json @jmiller-moko
/phpstan.neon @jmiller-moko
/Makefile @jmiller-moko
/.ftp_ignore @jmiller-moko
/.ftpignore @jmiller-moko
/.gitignore @jmiller-moko
/.gitattributes @jmiller-moko
/.editorconfig @jmiller-moko

View File

@@ -1,3 +1,22 @@
<!--
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: MokoStandards.Templates.GitHub
INGROUP: MokoStandards.Templates
REPO: https://github.com/mokoconsulting-tech/MokoStandards
PATH: /templates/github/copilot-instructions.joomla.md.template
VERSION: XX.YY.ZZ
BRIEF: GitHub Copilot custom instructions template for Joomla/MokoWaaS governed repositories
NOTE: Synced to .github/copilot-instructions.md in all Joomla/WaaS repos via bulk sync.
Tokens replaced at sync time: MokoCassiopeia, https://github.com/mokoconsulting-tech/MokoCassiopeia, {{EXTENSION_NAME}},
{{EXTENSION_TYPE}}, {{EXTENSION_ELEMENT}}
-->
> [!IMPORTANT]
> **🔧 AI Self-Update Required on First Use**
>
@@ -103,13 +122,13 @@ BRIEF: One-line description
### Joomla Version Alignment
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `update.xml`. The `make release` command / release workflow updates all three automatically.
The version in `README.md` **must always match** the `<version>` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically.
```xml
<!-- In manifest.xml — must match README.md version -->
<version>01.02.04</version>
<!-- In update.xml — prepend a new <update> block for every release.
<!-- In updates.xml — prepend a new <update> block for every release.
Note: the backslash in version="4\.[0-9]+" is a literal backslash character
in the XML attribute value. Joomla's update server treats the value as a
regular expression, so \. matches a literal dot. -->
@@ -135,7 +154,7 @@ The version in `README.md` **must always match** the `<version>` tag in `manifes
```
MokoCassiopeia/
├── manifest.xml # Joomla installer manifest (root — required)
├── update.xml # Update server manifest (root — required, see below)
├── updates.xml # Update server manifest (root — required, see below)
├── site/ # Frontend (site) code
│ ├── controller.php
│ ├── controllers/
@@ -164,22 +183,22 @@ MokoCassiopeia/
---
## update.xml — Required in Repo Root
## updates.xml — Required in Repo Root
`update.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
`updates.xml` **must exist at the repository root**. It is the Joomla update server manifest that allows Joomla installations to check for new versions of this extension.
The `manifest.xml` must reference it via:
```xml
<updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}">
https://github.com/mokoconsulting-tech/MokoCassiopeia/raw/main/update.xml
https://github.com/mokoconsulting-tech/MokoCassiopeia/raw/main/updates.xml
</server>
</updateservers>
```
**Rules:**
- Every release must prepend a new `<update>` block at the top of `update.xml` — old entries must be preserved below.
- The `<version>` in `update.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
- Every release must prepend a new `<update>` block at the top of `updates.xml` — old entries must be preserved below.
- The `<version>` in `updates.xml` must exactly match `<version>` in `manifest.xml` and the version in `README.md`.
- The `<downloadurl>` must be a publicly accessible direct download link (GitHub Releases asset URL).
- `<targetplatform name="joomla" version="4\.[0-9]+">` — the backslash is a **literal backslash character** in the XML attribute value; Joomla's update-server parser treats the value as a regular expression, so `\.` matches a literal dot and `[0-9]+` matches one or more digits. Do not double-escape it.
@@ -188,8 +207,8 @@ The `manifest.xml` must reference it via:
## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`).
- `<version>` tag must be kept in sync with `README.md` version and `update.xml`.
- Must include `<updateservers>` block pointing to this repo's `update.xml`.
- `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
- Must include `<updateservers>` block pointing to this repo's `updates.xml`.
- Must include `<files folder="site">` and `<administration>` sections.
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
@@ -267,8 +286,8 @@ Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `d
| Change type | Documentation to update |
|-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry |
| New or changed manifest.xml | Update `update.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `update.xml`; update CHANGELOG.md; bump README.md version |
| New or changed manifest.xml | Update `updates.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `updates.xml`; update CHANGELOG.md; bump README.md version |
| New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block |
| **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -282,4 +301,4 @@ Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `d
- Never add `defined('_JEXEC') or die;` to CLI scripts or model tests — only to web-accessible PHP files
- Never hardcode version numbers in body text — update `README.md` and let automation propagate
- Never use `github.token` or `secrets.GITHUB_TOKEN` in workflows — always use `secrets.GH_TOKEN`
- Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync
- Never let `manifest.xml` version, `updates.xml` version, and `README.md` version go out of sync

View File

@@ -5,7 +5,7 @@
# INGROUP: MokoStandards.Security
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/dependabot.yml
# VERSION: 01.00.00
# VERSION: 03.09.03
# BRIEF: Dependabot configuration for automated dependency updates and security patches
# NOTE: Monitors GitHub Actions for vulnerabilities and keeps ecosystem secure

76
.github/workflows/auto-assign.yml vendored Normal file
View File

@@ -0,0 +1,76 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoStandards.Workflows.Shared
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/auto-assign.yml
# VERSION: 04.06.00
# BRIEF: Auto-assign jmiller-moko to unassigned issues and PRs every 15 minutes
name: Auto-Assign Issues & PRs
on:
issues:
types: [opened]
pull_request_target:
types: [opened]
schedule:
- cron: '0 */12 * * *'
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
auto-assign:
name: Assign unassigned issues and PRs
runs-on: ubuntu-latest
steps:
- name: Assign unassigned issues
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
REPO="${{ github.repository }}"
ASSIGNEE="jmiller-moko"
echo "## 🏷️ Auto-Assign Report" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
ASSIGNED_ISSUES=0
ASSIGNED_PRS=0
# Assign unassigned open issues
ISSUES=$(gh api "repos/$REPO/issues?state=open&per_page=100&assignee=none" --jq '.[].number' 2>/dev/null || true)
for NUM in $ISSUES; do
# Skip PRs (the issues endpoint returns PRs too)
IS_PR=$(gh api "repos/$REPO/issues/$NUM" --jq '.pull_request // empty' 2>/dev/null || true)
if [ -z "$IS_PR" ]; then
gh api "repos/$REPO/issues/$NUM/assignees" -X POST -f "assignees[]=$ASSIGNEE" --silent 2>/dev/null && {
ASSIGNED_ISSUES=$((ASSIGNED_ISSUES + 1))
echo " Assigned issue #$NUM"
} || true
fi
done
# Assign unassigned open PRs
PRS=$(gh api "repos/$REPO/pulls?state=open&per_page=100" --jq '.[] | select(.assignees | length == 0) | .number' 2>/dev/null || true)
for NUM in $PRS; do
gh api "repos/$REPO/issues/$NUM/assignees" -X POST -f "assignees[]=$ASSIGNEE" --silent 2>/dev/null && {
ASSIGNED_PRS=$((ASSIGNED_PRS + 1))
echo " Assigned PR #$NUM"
} || true
done
echo "| Type | Assigned |" >> $GITHUB_STEP_SUMMARY
echo "|------|----------|" >> $GITHUB_STEP_SUMMARY
echo "| Issues | $ASSIGNED_ISSUES |" >> $GITHUB_STEP_SUMMARY
echo "| Pull Requests | $ASSIGNED_PRS |" >> $GITHUB_STEP_SUMMARY
if [ "$ASSIGNED_ISSUES" -eq 0 ] && [ "$ASSIGNED_PRS" -eq 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ All issues and PRs already have assignees" >> $GITHUB_STEP_SUMMARY
fi

View File

@@ -9,14 +9,22 @@
# INGROUP: MokoStandards.Automation
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/auto-dev-issue.yml.template
# VERSION: 04.04.01
# BRIEF: Auto-create tracking issue when a dev/** or rc/** branch is pushed
# VERSION: 04.06.00
# BRIEF: Auto-create tracking issue with sub-issues for dev/rc branch workflow
# NOTE: Synced via bulk-repo-sync to .github/workflows/auto-dev-issue.yml in all governed repos.
name: Auto Dev Branch Issue
name: Dev/RC Branch Issue
on:
# Auto-create on RC branch creation
create:
# Manual trigger for dev branches
workflow_dispatch:
inputs:
branch:
description: 'Branch name (e.g., dev/my-feature or dev/04.06)'
required: true
type: string
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
@@ -30,15 +38,23 @@ jobs:
name: Create version tracking issue
runs-on: ubuntu-latest
if: >-
github.event.ref_type == 'branch' &&
(startsWith(github.event.ref, 'dev/') || startsWith(github.event.ref, 'rc/'))
(github.event_name == 'workflow_dispatch') ||
(github.event.ref_type == 'branch' &&
(startsWith(github.event.ref, 'rc/') ||
startsWith(github.event.ref, 'alpha/') ||
startsWith(github.event.ref, 'beta/')))
steps:
- name: Create tracking issue
- name: Create tracking issue and sub-issues
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
BRANCH="${{ github.event.ref }}"
# For manual dispatch, use input; for auto, use event ref
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
BRANCH="${{ inputs.branch }}"
else
BRANCH="${{ github.event.ref }}"
fi
REPO="${{ github.repository }}"
ACTOR="${{ github.actor }}"
NOW=$(date -u '+%Y-%m-%d %H:%M UTC')
@@ -49,6 +65,16 @@ jobs:
BRANCH_TYPE="Release Candidate"
LABEL_TYPE="type: release"
TITLE_PREFIX="rc"
elif [[ "$BRANCH" == beta/* ]]; then
VERSION="${BRANCH#beta/}"
BRANCH_TYPE="Beta"
LABEL_TYPE="type: release"
TITLE_PREFIX="beta"
elif [[ "$BRANCH" == alpha/* ]]; then
VERSION="${BRANCH#alpha/}"
BRANCH_TYPE="Alpha"
LABEL_TYPE="type: release"
TITLE_PREFIX="alpha"
else
VERSION="${BRANCH#dev/}"
BRANCH_TYPE="Development"
@@ -58,45 +84,124 @@ jobs:
TITLE="${TITLE_PREFIX}(${VERSION}): ${BRANCH_TYPE} tracking for ${BRANCH}"
BODY="## ${BRANCH_TYPE} Branch Created
| Field | Value |
|-------|-------|
| **Branch** | \`${BRANCH}\` |
| **Version** | \`${VERSION}\` |
| **Type** | ${BRANCH_TYPE} |
| **Created by** | @${ACTOR} |
| **Created at** | ${NOW} |
| **Repository** | \`${REPO}\` |
## Checklist
- [ ] Feature development complete
- [ ] Tests passing
- [ ] README.md version bumped to \`${VERSION}\`
- [ ] CHANGELOG.md updated
- [ ] PR created targeting \`main\`
- [ ] Code reviewed and approved
- [ ] Merged to \`main\`
---
*Auto-created by [auto-dev-issue.yml](.github/workflows/auto-dev-issue.yml) on branch creation.*"
# Dedent heredoc
BODY=$(echo "$BODY" | sed 's/^ //')
# Check for existing issue with same title prefix
EXISTING=$(gh api "repos/${REPO}/issues?state=open&per_page=5" \
EXISTING=$(gh api "repos/${REPO}/issues?state=open&per_page=10" \
--jq ".[] | select(.title | startswith(\"${TITLE_PREFIX}(${VERSION})\")) | .number" 2>/dev/null | head -1)
if [ -n "$EXISTING" ]; then
echo " Issue #${EXISTING} already exists for ${VERSION}" >> $GITHUB_STEP_SUMMARY
else
ISSUE_URL=$(gh issue create \
--repo "$REPO" \
--title "$TITLE" \
--body "$BODY" \
--label "${LABEL_TYPE},version" \
--assignee "jmiller-moko" 2>&1)
echo "✅ Created tracking issue: ${ISSUE_URL}" >> $GITHUB_STEP_SUMMARY
exit 0
fi
# ── Define sub-issues for the workflow ─────────────────────────
if [[ "$BRANCH" == rc/* ]]; then
SUB_ISSUES=(
"RC Testing|Verify all features work on rc branch|type: test,release-candidate"
"Regression Testing|Run full regression suite before merge|type: test,release-candidate"
"Version Bump|Bump version in README.md and all headers|type: version,release-candidate"
"Changelog Update|Update CHANGELOG.md with release notes|documentation,release-candidate"
"Merge to Version Branch|Create PR to version/XX|type: release,needs-review"
)
elif [[ "$BRANCH" == alpha/* ]] || [[ "$BRANCH" == beta/* ]]; then
SUB_ISSUES=(
"Testing|Verify features on ${BRANCH_TYPE} branch|type: test,status: in-progress"
"Bug Fixes|Fix issues found during ${BRANCH_TYPE} testing|type: bug,status: pending"
"Promote to Next Stage|Create PR to promote to next release stage|type: release,needs-review"
)
else
SUB_ISSUES=(
"Development|Implement feature/fix on dev branch|type: feature,status: in-progress"
"Unit Testing|Write and pass unit tests|type: test,status: pending"
"Code Review|Request and complete code review|needs-review,status: pending"
"Version Bump|Bump version in README.md and all headers|type: version,status: pending"
"Changelog Update|Update CHANGELOG.md with release notes|documentation,status: pending"
"Create RC Branch|Promote dev to rc branch for final testing|type: release,status: pending"
"Merge to Main|Create PR from rc/dev to main|type: release,needs-review,status: pending"
)
fi
# ── Create sub-issues first ───────────────────────────────────────
SUB_LIST=""
SUB_NUMBERS=""
for SUB in "${SUB_ISSUES[@]}"; do
IFS='|' read -r SUB_TITLE SUB_DESC SUB_LABELS <<< "$SUB"
SUB_FULL_TITLE="${TITLE_PREFIX}(${VERSION}): ${SUB_TITLE}"
SUB_BODY=$(printf '### %s\n\n%s\n\n| Field | Value |\n|-------|-------|\n| **Parent Branch** | `%s` |\n| **Version** | `%s` |\n\n---\n*Sub-issue of the %s tracking issue for `%s`.*' \
"$SUB_TITLE" "$SUB_DESC" "$BRANCH" "$VERSION" "$BRANCH_TYPE" "$BRANCH")
SUB_URL=$(gh issue create \
--repo "$REPO" \
--title "$SUB_FULL_TITLE" \
--body "$SUB_BODY" \
--label "${SUB_LABELS}" \
--assignee "jmiller-moko" 2>&1)
SUB_NUM=$(echo "$SUB_URL" | grep -oE '[0-9]+$')
if [ -n "$SUB_NUM" ]; then
SUB_LIST="${SUB_LIST}\n- [ ] ${SUB_TITLE} (#${SUB_NUM})"
SUB_NUMBERS="${SUB_NUMBERS} #${SUB_NUM}"
fi
sleep 0.3
done
# ── Create parent tracking issue ──────────────────────────────────
PARENT_BODY=$(printf '## %s Branch Created\n\n| Field | Value |\n|-------|-------|\n| **Branch** | `%s` |\n| **Version** | `%s` |\n| **Type** | %s |\n| **Created by** | @%s |\n| **Created at** | %s |\n| **Repository** | `%s` |\n\n## Workflow Sub-Issues\n\n%b\n\n---\n*Auto-created by [auto-dev-issue.yml](.github/workflows/auto-dev-issue.yml) on branch creation.*' \
"$BRANCH_TYPE" "$BRANCH" "$VERSION" "$BRANCH_TYPE" "$ACTOR" "$NOW" "$REPO" "$SUB_LIST")
PARENT_URL=$(gh issue create \
--repo "$REPO" \
--title "$TITLE" \
--body "$PARENT_BODY" \
--label "${LABEL_TYPE},version" \
--assignee "jmiller-moko" 2>&1)
PARENT_NUM=$(echo "$PARENT_URL" | grep -oE '[0-9]+$')
# ── Link sub-issues back to parent ────────────────────────────────
if [ -n "$PARENT_NUM" ]; then
for SUB in "${SUB_ISSUES[@]}"; do
IFS='|' read -r SUB_TITLE _ _ <<< "$SUB"
SUB_FULL_TITLE="${TITLE_PREFIX}(${VERSION}): ${SUB_TITLE}"
SUB_NUM=$(gh api "repos/${REPO}/issues?state=open&per_page=20" \
--jq ".[] | select(.title == \"${SUB_FULL_TITLE}\") | .number" 2>/dev/null | head -1)
if [ -n "$SUB_NUM" ]; then
gh api "repos/${REPO}/issues/${SUB_NUM}" -X PATCH \
-f body="$(gh api "repos/${REPO}/issues/${SUB_NUM}" --jq '.body' 2>/dev/null)
> **Parent Issue:** #${PARENT_NUM}" --silent 2>/dev/null || true
fi
sleep 0.2
done
fi
# ── Create or update prerelease for alpha/beta/rc ────────────────
if [[ "$BRANCH" == rc/* ]] || [[ "$BRANCH" == alpha/* ]] || [[ "$BRANCH" == beta/* ]]; then
case "$BRANCH_TYPE" in
Alpha) RELEASE_TAG="alpha" ;;
Beta) RELEASE_TAG="beta" ;;
"Release Candidate") RELEASE_TAG="release-candidate" ;;
esac
EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true)
if [ -z "$EXISTING" ]; then
gh release create "$RELEASE_TAG" \
--title "${RELEASE_TAG} (${VERSION})" \
--notes "## ${BRANCH_TYPE} ${VERSION}\n\nBranch: \`${BRANCH}\`\nTracking issue: ${PARENT_URL}" \
--prerelease \
--target main 2>/dev/null || true
echo "${BRANCH_TYPE} release created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
else
gh release edit "$RELEASE_TAG" \
--title "${RELEASE_TAG} (${VERSION})" --prerelease 2>/dev/null || true
echo "${BRANCH_TYPE} release updated: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
fi
fi
# ── Summary ───────────────────────────────────────────────────────
echo "## Dev Workflow Issues Created" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Item | Issue |" >> $GITHUB_STEP_SUMMARY
echo "|------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| **Parent** | ${PARENT_URL} |" >> $GITHUB_STEP_SUMMARY
echo "| **Sub-issues** |${SUB_NUMBERS} |" >> $GITHUB_STEP_SUMMARY

View File

@@ -6,29 +6,31 @@
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoStandards.Release
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/auto-release.yml.template
# VERSION: 04.04.01
# BRIEF: Unified build & release pipeline — version branch, platform version, badges, tag, release
# PATH: /templates/workflows/joomla/auto-release.yml.template
# VERSION: 04.06.00
# BRIEF: Joomla build & release — ZIP package, updates.xml, SHA-256 checksum
#
# ╔════════════════════════════════════════════════════════════════════════╗
# BUILD & RELEASE PIPELINE
# ╠════════════════════════════════════════════════════════════════════════╣
#
# Triggers on push to main (skips bot commits + [skip ci]):
#
# Every push:
# 1. Read version from README.md
# 3. Set platform version (Dolibarr $this->version, Joomla <version>)║
# 4. Update [VERSION: XX.YY.ZZ] badges in markdown files
# 5. Write update.txt / update.xml
# 6. Create git tag vXX.YY.ZZ
# 7a. Patch: update existing GitHub Release for this minor
#
# Minor releases only (patch == 00):
# 2. Create/update version/XX.YY branch (patches update in-place)
# 7b. Create new GitHub Release ║
#
# ╚════════════════════════════════════════════════════════════════════════╝
# +========================================================================+
# | BUILD & RELEASE PIPELINE (JOOMLA) |
# +========================================================================+
# | |
# | Triggers on push to main (skips bot commits + [skip ci]): |
# | |
# | Every push: |
# | 1. Read version from README.md |
# | 3. Set platform version (Joomla <version>) |
# | 4. Update [VERSION: XX.YY.ZZ] badges in markdown files |
# | 5. Write updates.xml (Joomla update server XML) |
# | 6. Create git tag vXX.YY.ZZ |
# | 7a. Patch: update existing GitHub Release for this minor |
# | 8. Build ZIP, upload asset, write SHA-256 to updates.xml |
# | |
# | Every version change: archives main -> version/XX.YY branch |
# | Patch 00 = development (no release). First release = patch 01. |
# | First release only (patch == 01): |
# | 7b. Create new GitHub Release |
# | |
# +========================================================================+
name: Build & Release
@@ -37,6 +39,9 @@ on:
branches:
- main
- master
paths:
- 'src/**'
- 'htdocs/**'
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
@@ -64,19 +69,19 @@ jobs:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
run: |
git clone --depth 1 --branch version/04.04 --quiet \
git clone --depth 1 --branch version/04 --quiet \
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
/tmp/mokostandards
cd /tmp/mokostandards
composer install --no-dev --no-interaction --quiet
# ── STEP 1: Read version ───────────────────────────────────────────
# -- STEP 1: Read version -----------------------------------------------
- name: "Step 1: Read version from README.md"
id: version
run: |
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null)
if [ -z "$VERSION" ]; then
echo "⏭️ No VERSION in README.md — skipping release"
echo "No VERSION in README.md — skipping release"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
@@ -84,24 +89,34 @@ jobs:
MINOR=$(echo "$VERSION" | awk -F. '{printf "%s.%s", $1, $2}')
PATCH=$(echo "$VERSION" | awk -F. '{print $3}')
MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
MINOR_NUM=$(echo "$VERSION" | awk -F. '{print $2}')
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
echo "branch=version/${MINOR}" >> "$GITHUB_OUTPUT"
echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT"
echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
echo "release_tag=v${MAJOR}" >> "$GITHUB_OUTPUT"
if [ "$PATCH" = "00" ]; then
echo "is_minor=true" >> "$GITHUB_OUTPUT"
echo "✅ Version: $VERSION (minor release — full pipeline)"
else
echo "skip=true" >> "$GITHUB_OUTPUT"
echo "is_minor=false" >> "$GITHUB_OUTPUT"
echo "Version: $VERSION (patch — platform version + badges only)"
echo "Version: $VERSION (patch 00 = development — skipping release)"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
if [ "$PATCH" = "01" ]; then
echo "is_minor=true" >> "$GITHUB_OUTPUT"
echo "Version: $VERSION (first release — full pipeline)"
else
echo "is_minor=false" >> "$GITHUB_OUTPUT"
echo "Version: $VERSION (patch — platform version + badges only)"
fi
fi
- name: Check if already released
if: steps.version.outputs.skip != 'true'
id: check
run: |
TAG="${{ steps.version.outputs.tag }}"
TAG="${{ steps.version.outputs.release_tag }}"
BRANCH="${{ steps.version.outputs.branch }}"
TAG_EXISTS=false
@@ -119,102 +134,109 @@ jobs:
echo "already_released=false" >> "$GITHUB_OUTPUT"
fi
# ── SANITY CHECKS ────────────────────────────────────────────────────
- name: "Sanity: Platform-specific validation"
# -- SANITY CHECKS -------------------------------------------------------
- name: "Sanity: Pre-release validation"
if: >-
steps.version.outputs.skip != 'true' &&
steps.check.outputs.already_released != 'true'
run: |
VERSION="${{ steps.version.outputs.version }}"
PLATFORM=$(php /tmp/mokostandards/api/cli/platform_detect.php --path . 2>/dev/null)
ERRORS=0
echo "## 🔍 Pre-Release Sanity Checks" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Platform: \`${PLATFORM}\`" >> $GITHUB_STEP_SUMMARY
echo "## Pre-Release Sanity Checks (Joomla)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# -- Version drift check (must pass before release) --------
README_VER=$(grep -oP 'VERSION:\s*\K[\d.]+' README.md 2>/dev/null | head -1)
if [ "$README_VER" != "$VERSION" ]; then
echo "- Version drift: README says \`${README_VER}\` but releasing \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
else
echo "- Version consistent: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
fi
# Check CHANGELOG version matches
CL_VER=$(grep -oP 'VERSION:\s*\K[\d.]+' CHANGELOG.md 2>/dev/null | head -1)
if [ -n "$CL_VER" ] && [ "$CL_VER" != "$VERSION" ]; then
echo "- CHANGELOG drift: \`${CL_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
fi
# Check composer.json version if present
if [ -f "composer.json" ]; then
COMP_VER=$(grep -oP '"version"\s*:\s*"\K[^"]+' composer.json 2>/dev/null | head -1)
if [ -n "$COMP_VER" ] && [ "$COMP_VER" != "$VERSION" ]; then
echo "- composer.json drift: \`${COMP_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
fi
fi
# Common checks
if [ ! -f "LICENSE" ]; then
echo " Missing LICENSE file" >> $GITHUB_STEP_SUMMARY
echo "- Missing LICENSE file" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
else
echo " LICENSE" >> $GITHUB_STEP_SUMMARY
echo "- LICENSE present" >> $GITHUB_STEP_SUMMARY
fi
if [ ! -d "src" ]; then
echo "⚠️ No src/ directory" >> $GITHUB_STEP_SUMMARY
if [ ! -d "src" ] && [ ! -d "htdocs" ]; then
echo "- Warning: No src/ or htdocs/ directory" >> $GITHUB_STEP_SUMMARY
else
echo "✅ src/ directory" >> $GITHUB_STEP_SUMMARY
echo "- Source directory present" >> $GITHUB_STEP_SUMMARY
fi
# Dolibarr-specific checks
if [ "$PLATFORM" = "crm-module" ]; then
MOD_FILE=$(find src htdocs -path "*/core/modules/mod*.class.php" -print -quit 2>/dev/null)
if [ -z "$MOD_FILE" ]; then
echo "❌ No module descriptor (src/core/modules/mod*.class.php)" >> $GITHUB_STEP_SUMMARY
# -- Joomla: manifest version drift --------
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
if [ -n "$MANIFEST" ]; then
XML_VER=$(grep -oP '<version>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1)
if [ -n "$XML_VER" ] && [ "$XML_VER" != "$VERSION" ]; then
echo "- Manifest drift: \`${XML_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
else
echo " Module descriptor: \`${MOD_FILE}\`" >> $GITHUB_STEP_SUMMARY
# Check module number
NUMERO=$(grep -oP '\$this->numero\s*=\s*\K\d+' "$MOD_FILE" 2>/dev/null || echo "0")
if [ "$NUMERO" = "0" ] || [ -z "$NUMERO" ]; then
echo "❌ Module number (\$this->numero) is 0 or not set" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
else
echo "✅ Module number: ${NUMERO}" >> $GITHUB_STEP_SUMMARY
fi
# Check url_last_version exists
if grep -q 'url_last_version' "$MOD_FILE" 2>/dev/null; then
echo "✅ url_last_version is set" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ url_last_version not set — update checks won't work" >> $GITHUB_STEP_SUMMARY
fi
echo "- Manifest version: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
fi
fi
# Joomla-specific checks
if [ "$PLATFORM" = "waas-component" ]; then
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
if [ -z "$MANIFEST" ]; then
echo "❌ No Joomla XML manifest found" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
else
echo "✅ Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
# -- Joomla: XML manifest existence --------
if [ -z "$MANIFEST" ]; then
echo "- No Joomla XML manifest found" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
else
echo "- Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
# Check extension type
TYPE=$(grep -oP '<extension[^>]+type="\K[^"]+' "$MANIFEST" 2>/dev/null)
echo " Extension type: ${TYPE:-unknown}" >> $GITHUB_STEP_SUMMARY
fi
# -- Joomla: extension type check --------
TYPE=$(grep -oP '<extension[^>]+type="\K[^"]+' "$MANIFEST" 2>/dev/null)
echo "- Extension type: ${TYPE:-unknown}" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$ERRORS" -gt 0 ]; then
echo "**${ERRORS} error(s) — release may be incomplete**" >> $GITHUB_STEP_SUMMARY
echo "**${ERRORS} error(s) — release may be incomplete**" >> $GITHUB_STEP_SUMMARY
else
echo "**All sanity checks passed**" >> $GITHUB_STEP_SUMMARY
echo "**All sanity checks passed**" >> $GITHUB_STEP_SUMMARY
fi
# ── STEP 2: Create or update version/XX.YY branch ──────────────────
- name: "Step 2: Version branch"
if: >-
steps.version.outputs.skip != 'true' &&
steps.check.outputs.already_released != 'true'
# -- STEP 2: Create or update version/XX.YY archive branch ---------------
# Always runs — every version change on main archives to version/XX.YY
- name: "Step 2: Version archive branch"
if: steps.check.outputs.already_released != 'true'
run: |
BRANCH="${{ steps.version.outputs.branch }}"
IS_MINOR="${{ steps.version.outputs.is_minor }}"
if [ "$IS_MINOR" = "true" ]; then
PATCH="${{ steps.version.outputs.version }}"
PATCH_NUM=$(echo "$PATCH" | awk -F. '{print $3}')
# Check if branch exists
if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then
git push origin HEAD:"$BRANCH" --force
echo "Updated archive branch: ${BRANCH} (patch ${PATCH_NUM})" >> $GITHUB_STEP_SUMMARY
else
git checkout -b "$BRANCH" 2>/dev/null || git checkout "$BRANCH"
git push origin "$BRANCH" --force
echo "🌿 Created branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
else
git push origin HEAD:"$BRANCH" --force
echo "📝 Updated branch: ${BRANCH} (patch)" >> $GITHUB_STEP_SUMMARY
echo "Created archive branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
fi
# ── STEP 3: Set platform version ───────────────────────────────────
# -- STEP 3: Set platform version ----------------------------------------
- name: "Step 3: Set platform version"
if: >-
steps.version.outputs.skip != 'true' &&
@@ -224,7 +246,7 @@ jobs:
php /tmp/mokostandards/api/cli/version_set_platform.php \
--path . --version "$VERSION" --branch main
# ── STEP 4: Update version badges ──────────────────────────────────
# -- STEP 4: Update version badges ----------------------------------------
- name: "Step 4: Update version badges"
if: >-
steps.version.outputs.skip != 'true' &&
@@ -237,107 +259,119 @@ jobs:
fi
done
# ── STEP 5: Write update files (Dolibarr: update.txt / Joomla: update.xml)
- name: "Step 5: Write update files"
# -- STEP 5: Write updates.xml (Joomla update server) ---------------------
- name: "Step 5: Write updates.xml"
if: >-
steps.version.outputs.skip != 'true' &&
steps.check.outputs.already_released != 'true'
run: |
PLATFORM=$(php /tmp/mokostandards/api/cli/platform_detect.php --path . 2>/dev/null)
VERSION="${{ steps.version.outputs.version }}"
REPO="${{ github.repository }}"
if [ "$PLATFORM" = "crm-module" ]; then
printf '%s' "$VERSION" > update.txt
echo "📦 update.txt: ${VERSION}" >> $GITHUB_STEP_SUMMARY
# -- Parse extension metadata from XML manifest ----------------
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
if [ -z "$MANIFEST" ]; then
echo "Warning: No Joomla XML manifest found — skipping updates.xml" >> $GITHUB_STEP_SUMMARY
exit 0
fi
if [ "$PLATFORM" = "waas-component" ]; then
# ── Parse extension metadata from XML manifest ──────────────
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
if [ -z "$MANIFEST" ]; then
echo "⚠️ No Joomla XML manifest found — skipping update.xml" >> $GITHUB_STEP_SUMMARY
else
EXT_NAME=$(grep -oP '<name>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "${{ github.event.repository.name }}")
EXT_TYPE=$(grep -oP '<extension[^>]+type="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "component")
EXT_ELEMENT=$(grep -oP '<element>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "")
EXT_CLIENT=$(grep -oP '<extension[^>]+client="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
EXT_FOLDER=$(grep -oP '<extension[^>]+group="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
TARGET_PLATFORM=$(grep -oP '<targetplatform[^/]*/>' "$MANIFEST" 2>/dev/null | head -1 || echo "")
PHP_MINIMUM=$(grep -oP '<php_minimum>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "")
EXT_NAME=$(grep -oP '<name>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "${{ github.event.repository.name }}")
EXT_TYPE=$(grep -oP '<extension[^>]+type="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "component")
EXT_ELEMENT=$(grep -oP '<element>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "")
EXT_CLIENT=$(grep -oP '<extension[^>]+client="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
EXT_FOLDER=$(grep -oP '<extension[^>]+group="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
TARGET_PLATFORM=$(grep -oP '<targetplatform[^/]*/>' "$MANIFEST" 2>/dev/null | head -1 || echo "")
PHP_MINIMUM=$(grep -oP '<php_minimum>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "")
# Derive element from manifest filename if not in XML
if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(basename "$MANIFEST" .xml)
fi
# Build client tag: plugins and frontend modules need <client>site</client>
CLIENT_TAG=""
if [ -n "$EXT_CLIENT" ]; then
CLIENT_TAG="<client>${EXT_CLIENT}</client>"
elif [ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]; then
CLIENT_TAG="<client>site</client>"
fi
# Build folder tag for plugins (required for Joomla to match the update)
FOLDER_TAG=""
if [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ]; then
FOLDER_TAG="<folder>${EXT_FOLDER}</folder>"
fi
# Build targetplatform (fallback to Joomla 5+6 if not in manifest)
if [ -z "$TARGET_PLATFORM" ]; then
TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="5.*" %s>' "/")
fi
# Build php_minimum tag
PHP_TAG=""
if [ -n "$PHP_MINIMUM" ]; then
PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
fi
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/v${VERSION}/${EXT_ELEMENT}-${VERSION}.zip"
INFO_URL="https://github.com/${REPO}/releases/tag/v${VERSION}"
# ── Write update.xml (stable release) ───────────────────────
{
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>'
printf '%s\n' '<updates>'
printf '%s\n' ' <update>'
printf '%s\n' " <name>${EXT_NAME}</name>"
printf '%s\n' " <description>${EXT_NAME} update</description>"
printf '%s\n' " <element>${EXT_ELEMENT}</element>"
printf '%s\n' " <type>${EXT_TYPE}</type>"
printf '%s\n' " <version>${VERSION}</version>"
[ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}"
[ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}"
printf '%s\n' ' <tags>'
printf '%s\n' ' <tag>stable</tag>'
printf '%s\n' ' </tags>'
printf '%s\n' " <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>"
printf '%s\n' ' <downloads>'
printf '%s\n' " <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>"
printf '%s\n' ' </downloads>'
printf '%s\n' " ${TARGET_PLATFORM}"
[ -n "$PHP_TAG" ] && printf '%s\n' " ${PHP_TAG}"
printf '%s\n' ' <maintainer>Moko Consulting</maintainer>'
printf '%s\n' ' <maintainerurl>https://mokoconsulting.tech</maintainerurl>'
printf '%s\n' ' </update>'
printf '%s\n' '</updates>'
} > update.xml
echo "📦 update.xml: ${VERSION} (stable) — ${EXT_TYPE}/${EXT_ELEMENT}" >> $GITHUB_STEP_SUMMARY
fi
# Derive element from manifest filename if not in XML
if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(basename "$MANIFEST" .xml)
fi
# ── Commit all changes ─────────────────────────────────────────────
# Build client tag: plugins and frontend modules need <client>site</client>
CLIENT_TAG=""
if [ -n "$EXT_CLIENT" ]; then
CLIENT_TAG="<client>${EXT_CLIENT}</client>"
elif [ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]; then
CLIENT_TAG="<client>site</client>"
fi
# Build folder tag for plugins (required for Joomla to match the update)
FOLDER_TAG=""
if [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ]; then
FOLDER_TAG="<folder>${EXT_FOLDER}</folder>"
fi
# Build targetplatform (fallback to Joomla 5 if not in manifest)
if [ -z "$TARGET_PLATFORM" ]; then
TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="5.*" %s>' "/")
fi
# Build php_minimum tag
PHP_TAG=""
if [ -n "$PHP_MINIMUM" ]; then
PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
fi
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/v${VERSION}/${EXT_ELEMENT}-${VERSION}.zip"
INFO_URL="https://github.com/${REPO}/releases/tag/v${VERSION}"
# -- Build stable entry to temp file ─────────────────────────
{
printf '%s\n' ' <update>'
printf '%s\n' " <name>${EXT_NAME}</name>"
printf '%s\n' " <description>${EXT_NAME} update</description>"
printf '%s\n' " <element>${EXT_ELEMENT}</element>"
printf '%s\n' " <type>${EXT_TYPE}</type>"
printf '%s\n' " <version>${VERSION}</version>"
[ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}"
[ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}"
printf '%s\n' ' <tags>'
printf '%s\n' ' <tag>stable</tag>'
printf '%s\n' ' </tags>'
printf '%s\n' " <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>"
printf '%s\n' ' <downloads>'
printf '%s\n' " <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>"
printf '%s\n' ' </downloads>'
printf '%s\n' " ${TARGET_PLATFORM}"
[ -n "$PHP_TAG" ] && printf '%s\n' " ${PHP_TAG}"
printf '%s\n' ' <maintainer>Moko Consulting</maintainer>'
printf '%s\n' ' <maintainerurl>https://mokoconsulting.tech</maintainerurl>'
printf '%s\n' ' </update>'
} > /tmp/stable_entry.xml
# -- Write updates.xml preserving dev/rc entries ──────────────
RC_ENTRY=""
DEV_ENTRY=""
if [ -f "updates.xml" ]; then
printf 'import re\n' > /tmp/extract.py
printf 'with open("updates.xml") as f: c = f.read()\n' >> /tmp/extract.py
printf 'import sys; tag = sys.argv[1]\n' >> /tmp/extract.py
printf 'm = re.search(r"( <update>.*?<tag>" + re.escape(tag) + r"</tag>.*?</update>)", c, re.DOTALL)\n' >> /tmp/extract.py
printf 'if m: print(m.group(1))\n' >> /tmp/extract.py
RC_ENTRY=$(python3 /tmp/extract.py rc 2>/dev/null || true)
DEV_ENTRY=$(python3 /tmp/extract.py development 2>/dev/null || true)
fi
{
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>'
printf '%s\n' '<updates>'
cat /tmp/stable_entry.xml
[ -n "$RC_ENTRY" ] && echo "$RC_ENTRY"
[ -n "$DEV_ENTRY" ] && echo "$DEV_ENTRY"
printf '%s\n' '</updates>'
} > updates.xml
echo "updates.xml: ${VERSION} (stable + rc/dev preserved)" >> $GITHUB_STEP_SUMMARY
# -- Commit all changes ---------------------------------------------------
- name: Commit release changes
if: >-
steps.version.outputs.skip != 'true' &&
steps.check.outputs.already_released != 'true'
run: |
if git diff --quiet && git diff --cached --quiet; then
echo " No changes to commit"
echo "No changes to commit"
exit 0
fi
VERSION="${{ steps.version.outputs.version }}"
@@ -348,18 +382,25 @@ jobs:
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
git push
# ── STEP 6: Create tag ─────────────────────────────────────────────
# -- STEP 6: Create tag ---------------------------------------------------
- name: "Step 6: Create git tag"
if: >-
steps.version.outputs.skip != 'true' &&
steps.check.outputs.tag_exists != 'true'
steps.check.outputs.tag_exists != 'true' &&
steps.version.outputs.is_minor == 'true'
run: |
TAG="${{ steps.version.outputs.tag }}"
git tag "$TAG"
git push origin "$TAG"
echo "🏷️ Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
# Only create the major release tag if it doesn't exist yet
if ! git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then
git tag "$RELEASE_TAG"
git push origin "$RELEASE_TAG"
echo "Tag created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
else
echo "Tag ${RELEASE_TAG} already exists" >> $GITHUB_STEP_SUMMARY
fi
echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
# ── STEP 7: Create or update GitHub Release ──────────────────────────
# -- STEP 7: Create or update GitHub Release ------------------------------
- name: "Step 7: GitHub Release"
if: >-
steps.version.outputs.skip != 'true' &&
@@ -368,67 +409,129 @@ jobs:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
VERSION="${{ steps.version.outputs.version }}"
TAG="${{ steps.version.outputs.tag }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
BRANCH="${{ steps.version.outputs.branch }}"
IS_MINOR="${{ steps.version.outputs.is_minor }}"
# Derive the minor version base (XX.YY.00)
MINOR_BASE=$(echo "$VERSION" | sed 's/\.[0-9]*$/.00/')
MINOR_TAG="v${MINOR_BASE}"
MAJOR="${{ steps.version.outputs.major }}"
NOTES=$(php /tmp/mokostandards/api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
echo "$NOTES" > /tmp/release_notes.md
if [ "$IS_MINOR" = "true" ]; then
# Minor release: create new GitHub Release
gh release create "$TAG" \
--title "${VERSION}" \
# Check if the major release already exists
EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true)
if [ -z "$EXISTING" ]; then
# First release for this major
gh release create "$RELEASE_TAG" \
--title "v${MAJOR} (latest: ${VERSION})" \
--notes-file /tmp/release_notes.md \
--target "$BRANCH"
echo "🚀 Release created: ${VERSION}" >> $GITHUB_STEP_SUMMARY
echo "Release created: ${RELEASE_TAG} (${VERSION})" >> $GITHUB_STEP_SUMMARY
else
# Patch release: update the existing minor release with new tag
# Find the latest release for this minor version
EXISTING=$(gh release view "$MINOR_TAG" --json tagName -q .tagName 2>/dev/null || true)
if [ -n "$EXISTING" ]; then
# Update existing release body with patch info
CURRENT_NOTES=$(gh release view "$MINOR_TAG" --json body -q .body 2>/dev/null || true)
{
echo "$CURRENT_NOTES"
echo ""
echo "---"
echo "### Patch ${VERSION}"
echo ""
cat /tmp/release_notes.md
} > /tmp/updated_notes.md
# Append version notes to existing major release
CURRENT_NOTES=$(gh release view "$RELEASE_TAG" --json body -q .body 2>/dev/null || true)
{
echo "$CURRENT_NOTES"
echo ""
echo "---"
echo "### ${VERSION}"
echo ""
cat /tmp/release_notes.md
} > /tmp/updated_notes.md
gh release edit "$MINOR_TAG" \
--title "${MINOR_BASE} (latest: ${VERSION})" \
--notes-file /tmp/updated_notes.md
echo "📝 Release updated: ${MINOR_BASE} → patch ${VERSION}" >> $GITHUB_STEP_SUMMARY
else
# No existing minor release found — create one for this patch
gh release create "$TAG" \
--title "${VERSION}" \
--notes-file /tmp/release_notes.md
echo "🚀 Release created: ${VERSION} (no minor release found)" >> $GITHUB_STEP_SUMMARY
fi
gh release edit "$RELEASE_TAG" \
--title "v${MAJOR} (latest: ${VERSION})" \
--notes-file /tmp/updated_notes.md
echo "Release updated: ${RELEASE_TAG} -> ${VERSION}" >> $GITHUB_STEP_SUMMARY
fi
# ── Summary ────────────────────────────────────────────────────────
# -- STEP 8: Build Joomla install ZIP + SHA-256 checksum ------------------
# Every patch builds an install-ready ZIP and uploads it to the minor release.
# Result: one Release per minor version with a ZIP for each patch.
- name: "Step 8: Build Joomla package and update checksum"
if: >-
steps.version.outputs.skip != 'true'
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
VERSION="${{ steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
REPO="${{ github.repository }}"
# All ZIPs upload to the major release tag (vXX)
gh release view "$RELEASE_TAG" --json tagName > /dev/null 2>&1 || {
echo "No release ${RELEASE_TAG} found — skipping ZIP upload"
exit 0
}
# Find extension element name from manifest
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)
[ -z "$MANIFEST" ] && exit 0
EXT_ELEMENT=$(grep -oP '<element>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || basename "$MANIFEST" .xml)
PACKAGE_NAME="${EXT_ELEMENT}-${VERSION}.zip"
# -- Build install-ready ZIP from src/ ----------------------------
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ — skipping package"; exit 0; }
cd "$SOURCE_DIR"
zip -r "/tmp/${PACKAGE_NAME}" . -x '.ftpignore'
cd ..
FILESIZE=$(stat -c%s "/tmp/${PACKAGE_NAME}" 2>/dev/null || stat -f%z "/tmp/${PACKAGE_NAME}" 2>/dev/null || echo "unknown")
# -- Calculate SHA-256 -------------------------------------------
SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1)
# -- Upload ZIP to the minor release tag -------------------------
gh release upload "$RELEASE_TAG" "/tmp/${PACKAGE_NAME}" --clobber 2>/dev/null || {
echo "Could not upload with --clobber, retrying..."
gh release upload "$RELEASE_TAG" "/tmp/${PACKAGE_NAME}" 2>/dev/null || true
}
# -- Update updates.xml with SHA-256 for latest patch -------------
if [ -f "updates.xml" ]; then
if grep -q '<sha256>' updates.xml; then
sed -i "s|<sha256>.*</sha256>|<sha256>sha256:${SHA256}</sha256>|" updates.xml
else
sed -i "s|</downloads>|</downloads>\n <sha256>sha256:${SHA256}</sha256>|" updates.xml
fi
# Also update the download URL to point to this patch's ZIP
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
sed -i "s|<downloadurl[^>]*>[^<]*</downloadurl>|<downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>|" updates.xml
git add updates.xml
git commit -m "chore(release): SHA-256 + download URL for ${VERSION} [skip ci]" \
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>" || true
git push || true
fi
echo "### Joomla Package" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Package | \`${PACKAGE_NAME}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Size | ${FILESIZE} bytes |" >> $GITHUB_STEP_SUMMARY
echo "| SHA-256 | \`${SHA256}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Release | \`${RELEASE_TAG}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Download | [${PACKAGE_NAME}](https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}) |" >> $GITHUB_STEP_SUMMARY
# -- Summary --------------------------------------------------------------
- name: Pipeline Summary
if: always()
run: |
VERSION="${{ steps.version.outputs.version }}"
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
echo "## ⏭️ Release Skipped" >> $GITHUB_STEP_SUMMARY
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
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "## Build & Release Complete" >> $GITHUB_STEP_SUMMARY
echo "## Build & Release Complete (Joomla)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY

View File

@@ -1,147 +1,144 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoCassiopeia.Automation
# REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
# PATH: /.github/workflows/auto-update-sha.yml
# VERSION: 01.00.00
# BRIEF: Automatically update SHA-256 hash in updates.xml after release
# NOTE: Ensures updates.xml stays synchronized with release packages
name: Auto-Update SHA Hash
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: 'Release tag to update SHA for (e.g., 03.08.03)'
required: true
type: string
permissions:
contents: write
jobs:
update-sha:
name: Update SHA-256 Hash in updates.xml
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: main
- name: Get release tag
id: tag
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
TAG="${{ inputs.tag }}"
else
TAG="${{ github.event.release.tag_name }}"
fi
echo "tag=${TAG}" >> $GITHUB_OUTPUT
echo "Processing release: ${TAG}"
- name: Download release package
run: |
TAG="${{ steps.tag.outputs.tag }}"
PACKAGE_NAME="mokocassiopeia-src-${TAG}.zip"
DOWNLOAD_URL="https://github.com/${{ github.repository }}/releases/download/${TAG}/${PACKAGE_NAME}"
echo "Downloading: ${DOWNLOAD_URL}"
curl -L -o "${PACKAGE_NAME}" "${DOWNLOAD_URL}"
if [ ! -f "${PACKAGE_NAME}" ]; then
echo "Error: Failed to download package"
exit 1
fi
echo "PACKAGE_NAME=${PACKAGE_NAME}" >> $GITHUB_ENV
- name: Calculate SHA-256 hash
id: sha
run: |
SHA256_HASH=$(sha256sum "${PACKAGE_NAME}" | cut -d' ' -f1)
echo "sha256=${SHA256_HASH}" >> $GITHUB_OUTPUT
echo "SHA-256 Hash: ${SHA256_HASH}"
- name: Update updates.xml
run: |
TAG="${{ steps.tag.outputs.tag }}"
SHA256="${{ steps.sha.outputs.sha256 }}"
DATE=$(date +%Y-%m-%d)
# Update version
sed -i "s|<version>.*</version>|<version>${TAG}</version>|" updates.xml
# Update creation date
sed -i "s|<creationDate>.*</creationDate>|<creationDate>${DATE}</creationDate>|" updates.xml
# Update download URL
sed -i "s|<downloadurl type='full' format='zip'>.*</downloadurl>|<downloadurl type='full' format='zip'>https://github.com/${{ github.repository }}/releases/download/${TAG}/mokocassiopeia-src-${TAG}.zip</downloadurl>|" updates.xml
# Update or add SHA-256 hash
if grep -q "<sha256>" updates.xml; then
sed -i "s|<sha256>.*</sha256>|<sha256>sha256:${SHA256}</sha256>|" updates.xml
else
# Add SHA-256 after downloadurl
sed -i "/<\/downloadurl>/a\ <sha256>sha256:${SHA256}<\/sha256>" updates.xml
fi
echo "Updated updates.xml with:"
echo " Version: ${TAG}"
echo " Date: ${DATE}"
echo " SHA-256: ${SHA256}"
- name: Check for changes
id: changes
run: |
if git diff --quiet updates.xml; then
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "No changes to updates.xml"
else
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "Changes detected in updates.xml"
git diff updates.xml
fi
- name: Commit and push changes
if: steps.changes.outputs.has_changes == 'true'
run: |
TAG="${{ steps.tag.outputs.tag }}"
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add updates.xml
git commit -m "chore: Update SHA-256 hash for release ${TAG}
Auto-generated by auto-update-sha workflow
SHA-256: ${{ steps.sha.outputs.sha256 }}"
git push origin main
echo "Successfully updated updates.xml with SHA-256 hash for release ${TAG}"
- name: Summary
if: steps.changes.outputs.has_changes == 'true'
run: |
echo "### SHA-256 Hash Updated Successfully" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- Release: ${{ steps.tag.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
echo "- SHA-256: \`${{ steps.sha.outputs.sha256 }}\`" >> $GITHUB_STEP_SUMMARY
echo "- File: updates.xml" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "The Joomla update server will now provide the correct package hash." >> $GITHUB_STEP_SUMMARY
- name: Summary (no changes)
if: steps.changes.outputs.has_changes == 'false'
run: |
echo "### No Updates Needed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "updates.xml already contains the correct SHA-256 hash for release ${{ steps.tag.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoCassiopeia.Automation
# REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
# PATH: /.github/workflows/auto-update-sha.yml
# VERSION: 03.09.03
# BRIEF: Automatically update SHA-256 hash in updates.xml after release
# NOTE: Ensures updates.xml stays synchronized with release packages
name: Auto-Update SHA Hash
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: 'Release tag to update SHA for (e.g., 03.08.03)'
required: true
type: string
permissions:
contents: write
jobs:
update-sha:
name: Update SHA-256 Hash in updates.xml
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: main
- name: Get release tag
id: tag
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
TAG="${{ inputs.tag }}"
else
TAG="${{ github.event.release.tag_name }}"
fi
echo "tag=${TAG}" >> $GITHUB_OUTPUT
echo "Processing release: ${TAG}"
- name: Download release package
run: |
TAG="${{ steps.tag.outputs.tag }}"
PACKAGE_NAME="mokocassiopeia-src-${TAG}.zip"
DOWNLOAD_URL="https://github.com/${{ github.repository }}/releases/download/${TAG}/${PACKAGE_NAME}"
echo "Downloading: ${DOWNLOAD_URL}"
curl -L -o "${PACKAGE_NAME}" "${DOWNLOAD_URL}"
if [ ! -f "${PACKAGE_NAME}" ]; then
echo "Error: Failed to download package"
exit 1
fi
echo "PACKAGE_NAME=${PACKAGE_NAME}" >> $GITHUB_ENV
- name: Calculate SHA-256 hash
id: sha
run: |
SHA256_HASH=$(sha256sum "${PACKAGE_NAME}" | cut -d' ' -f1)
echo "sha256=${SHA256_HASH}" >> $GITHUB_OUTPUT
echo "SHA-256 Hash: ${SHA256_HASH}"
- name: Update updates.xml
run: |
TAG="${{ steps.tag.outputs.tag }}"
SHA256="${{ steps.sha.outputs.sha256 }}"
DATE=$(date +%Y-%m-%d)
# Update version
sed -i "s|<version>.*</version>|<version>${TAG}</version>|" updates.xml
# Update creation date
sed -i "s|<creationDate>.*</creationDate>|<creationDate>${DATE}</creationDate>|" updates.xml
# Update download URL
sed -i "s|<downloadurl type='full' format='zip'>.*</downloadurl>|<downloadurl type='full' format='zip'>https://github.com/${{ github.repository }}/releases/download/${TAG}/mokocassiopeia-src-${TAG}.zip</downloadurl>|" updates.xml
# Update or add SHA-256 hash
if grep -q "<sha256>" updates.xml; then
sed -i "s|<sha256>.*</sha256>|<sha256>sha256:${SHA256}</sha256>|" updates.xml
else
# Add SHA-256 after downloadurl
sed -i "/<\/downloadurl>/a\ <sha256>sha256:${SHA256}<\/sha256>" updates.xml
fi
echo "Updated updates.xml with:"
echo " Version: ${TAG}"
echo " Date: ${DATE}"
echo " SHA-256: ${SHA256}"
- name: Check for changes
id: changes
run: |
if git diff --quiet updates.xml; then
echo "has_changes=false" >> $GITHUB_OUTPUT
echo "No changes to updates.xml"
else
echo "has_changes=true" >> $GITHUB_OUTPUT
echo "Changes detected in updates.xml"
git diff updates.xml
fi
- name: Commit and push changes
if: steps.changes.outputs.has_changes == 'true'
run: |
TAG="${{ steps.tag.outputs.tag }}"
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add updates.xml
git commit -m "chore: Update SHA-256 hash for release ${TAG} - SHA: ${{ steps.sha.outputs.sha256 }}"
git push origin main
echo "Successfully updated updates.xml with SHA-256 hash for release ${TAG}"
- name: Summary
if: steps.changes.outputs.has_changes == 'true'
run: |
echo "### SHA-256 Hash Updated Successfully" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- Release: ${{ steps.tag.outputs.tag }}" >> $GITHUB_STEP_SUMMARY
echo "- SHA-256: \`${{ steps.sha.outputs.sha256 }}\`" >> $GITHUB_STEP_SUMMARY
echo "- File: updates.xml" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "The Joomla update server will now provide the correct package hash." >> $GITHUB_STEP_SUMMARY
- name: Summary (no changes)
if: steps.changes.outputs.has_changes == 'false'
run: |
echo "### No Updates Needed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "updates.xml already contains the correct SHA-256 hash for release ${{ steps.tag.outputs.tag }}" >> $GITHUB_STEP_SUMMARY

View File

@@ -0,0 +1,101 @@
# 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.CI
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/changelog-validation.yml.template
# VERSION: 04.06.00
# BRIEF: Validates CHANGELOG.md format and version consistency
# NOTE: Deployed to .github/workflows/changelog-validation.yml in governed repos.
name: Changelog Validation
on:
push:
branches:
- main
pull_request:
branches:
- main
workflow_dispatch:
permissions:
contents: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
validate-changelog:
name: Validate CHANGELOG.md
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Check CHANGELOG.md exists
run: |
echo "### Changelog Validation" >> $GITHUB_STEP_SUMMARY
if [ ! -f "CHANGELOG.md" ]; then
echo "CHANGELOG.md not found in repository root." >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "CHANGELOG.md exists." >> $GITHUB_STEP_SUMMARY
- name: Check VERSION header matches README.md
run: |
# Extract version from README.md FILE INFORMATION block
README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1)
if [ -z "$README_VERSION" ]; then
echo "No VERSION found in README.md FILE INFORMATION block." >> $GITHUB_STEP_SUMMARY
exit 1
fi
# Check that CHANGELOG.md has a matching version header
CHANGELOG_VERSION=$(grep -oP '^\#\#\s*\[\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' CHANGELOG.md | head -1)
if [ -z "$CHANGELOG_VERSION" ]; then
echo "No version header found in CHANGELOG.md (expected \`## [XX.YY.ZZ] - YYYY-MM-DD\`)." >> $GITHUB_STEP_SUMMARY
exit 1
fi
if [ "$CHANGELOG_VERSION" != "$README_VERSION" ]; then
echo "CHANGELOG latest version \`${CHANGELOG_VERSION}\` does not match README VERSION \`${README_VERSION}\`." >> $GITHUB_STEP_SUMMARY
exit 1
fi
echo "CHANGELOG version \`${CHANGELOG_VERSION}\` matches README VERSION." >> $GITHUB_STEP_SUMMARY
- name: Validate conventional changelog format
run: |
ERRORS=0
# Check that version entries follow ## [XX.YY.ZZ] - YYYY-MM-DD format
while IFS= read -r LINE; do
if ! echo "$LINE" | grep -qP '^\#\#\s*\[[0-9]{2}\.[0-9]{2}\.[0-9]{2}\]\s*-\s*[0-9]{4}-[0-9]{2}-[0-9]{2}'; then
echo "Malformed version header: \`${LINE}\`" >> $GITHUB_STEP_SUMMARY
echo " Expected format: \`## [XX.YY.ZZ] - YYYY-MM-DD\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done < <(grep -P '^\#\#\s*\[' CHANGELOG.md)
ENTRY_COUNT=$(grep -cP '^\#\#\s*\[' CHANGELOG.md || echo "0")
if [ "$ENTRY_COUNT" -eq 0 ]; then
echo "No version entries found in CHANGELOG.md." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Found ${ENTRY_COUNT} version entr(ies) in CHANGELOG.md." >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} format issue(s) found.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Changelog format validation passed.**" >> $GITHUB_STEP_SUMMARY
fi

391
.github/workflows/ci-joomla.yml vendored Normal file
View File

@@ -0,0 +1,391 @@
# 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.CI
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/joomla/ci-joomla.yml.template
# VERSION: 04.06.00
# BRIEF: CI workflow for Joomla extensions — lint, validate, test
# NOTE: Deployed to .github/workflows/ci-joomla.yml in governed Joomla extension repos.
name: Joomla Extension CI
on:
push:
branches:
- main
- dev/**
- rc/**
- version/**
pull_request:
branches:
- main
- dev/**
- rc/**
workflow_dispatch:
permissions:
contents: read
pull-requests: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
lint-and-validate:
name: Lint & Validate
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup PHP
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.31.0
with:
php-version: '8.2'
extensions: mbstring, xml, zip, gd, curl, json, simplexml
tools: composer:v2
coverage: none
- name: Clone MokoStandards
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
git clone --depth 1 --branch version/04 --quiet \
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
/tmp/mokostandards
- name: Install dependencies
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
run: |
if [ -f "composer.json" ]; then
composer install \
--no-interaction \
--prefer-dist \
--optimize-autoloader
else
echo "No composer.json found — skipping dependency install"
fi
- name: PHP syntax check
run: |
ERRORS=0
for DIR in src/ htdocs/; do
if [ -d "$DIR" ]; then
FOUND=1
while IFS= read -r -d '' FILE; do
OUTPUT=$(php -l "$FILE" 2>&1)
if echo "$OUTPUT" | grep -q "Parse error"; then
echo "::error file=${FILE}::${OUTPUT}"
ERRORS=$((ERRORS + 1))
fi
done < <(find "$DIR" -name "*.php" -print0)
fi
done
echo "### PHP Syntax Check" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} syntax error(s) found.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY
fi
- name: XML manifest validation
run: |
echo "### XML Manifest Validation" >> $GITHUB_STEP_SUMMARY
ERRORS=0
# Find the extension manifest (XML with <extension tag)
MANIFEST=""
for XML_FILE in $(find . -maxdepth 2 -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 "No Joomla extension manifest found (XML file with \`<extension\` tag)." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Manifest found: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
# Validate well-formed XML
php -r "
\$xml = @simplexml_load_file('$MANIFEST');
if (\$xml === false) {
echo 'INVALID';
exit(1);
}
echo 'VALID';
" > /tmp/xml_result 2>&1
XML_RESULT=$(cat /tmp/xml_result)
if [ "$XML_RESULT" != "VALID" ]; then
echo "Manifest is not well-formed XML." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Manifest is well-formed XML." >> $GITHUB_STEP_SUMMARY
fi
# Check required tags: name, version, author, namespace (Joomla 5+)
for TAG in name version author namespace; do
if ! grep -q "<${TAG}>" "$MANIFEST" 2>/dev/null; then
echo "Missing required tag: \`<${TAG}>\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Found required tag: \`<${TAG}>\`" >> $GITHUB_STEP_SUMMARY
fi
done
fi
if [ "${ERRORS}" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "**${ERRORS} manifest issue(s) found.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Manifest validation passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: Check language files referenced in manifest
run: |
echo "### Language File Check" >> $GITHUB_STEP_SUMMARY
ERRORS=0
MANIFEST=""
for XML_FILE in $(find . -maxdepth 2 -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 [ -n "$MANIFEST" ]; then
# Extract language file references from manifest
LANG_FILES=$(grep -oP 'language\s+tag="[^"]*"[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null || true)
if [ -z "$LANG_FILES" ]; then
echo "No language file references found in manifest — skipping." >> $GITHUB_STEP_SUMMARY
else
while IFS= read -r LANG_FILE; do
LANG_FILE=$(echo "$LANG_FILE" | xargs)
if [ -z "$LANG_FILE" ]; then
continue
fi
# Check in common locations
FOUND=0
for BASE in "." "src" "htdocs"; do
if [ -f "${BASE}/${LANG_FILE}" ]; then
FOUND=1
break
fi
done
if [ "$FOUND" -eq 0 ]; then
echo "Missing language file: \`${LANG_FILE}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Language file present: \`${LANG_FILE}\`" >> $GITHUB_STEP_SUMMARY
fi
done <<< "$LANG_FILES"
fi
else
echo "No manifest found — skipping language check." >> $GITHUB_STEP_SUMMARY
fi
if [ "${ERRORS}" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "**${ERRORS} missing language file(s).**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Language file check passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: Check index.html files in directories
run: |
echo "### Index.html Check" >> $GITHUB_STEP_SUMMARY
MISSING=0
CHECKED=0
for DIR in src/ htdocs/; do
if [ -d "$DIR" ]; then
while IFS= read -r -d '' SUBDIR; do
CHECKED=$((CHECKED + 1))
if [ ! -f "${SUBDIR}/index.html" ]; then
echo "Missing index.html in: \`${SUBDIR}\`" >> $GITHUB_STEP_SUMMARY
MISSING=$((MISSING + 1))
fi
done < <(find "$DIR" -type d -print0)
fi
done
if [ "${CHECKED}" -eq 0 ]; then
echo "No src/ or htdocs/ directories found — skipping." >> $GITHUB_STEP_SUMMARY
elif [ "${MISSING}" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "**${MISSING} director(ies) missing index.html out of ${CHECKED} checked.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "All ${CHECKED} directories contain index.html." >> $GITHUB_STEP_SUMMARY
fi
release-readiness:
name: Release Readiness Check
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && github.base_ref == 'main'
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Validate release readiness
run: |
echo "## Release Readiness" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
ERRORS=0
# Extract version from README.md
README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1)
if [ -z "$README_VERSION" ]; then
echo "No VERSION found in README.md FILE INFORMATION block." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "README version: \`${README_VERSION}\`" >> $GITHUB_STEP_SUMMARY
fi
# Find the extension manifest
MANIFEST=""
for XML_FILE in $(find . -maxdepth 2 -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 "No Joomla extension manifest found." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
# Check <version> matches README VERSION
MANIFEST_VERSION=$(grep -oP '<version>\K[^<]+' "$MANIFEST" | head -1)
if [ -z "$MANIFEST_VERSION" ]; then
echo "No \`<version>\` tag in manifest." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
elif [ -n "$README_VERSION" ] && [ "$MANIFEST_VERSION" != "$README_VERSION" ]; then
echo "Manifest version \`${MANIFEST_VERSION}\` does not match README \`${README_VERSION}\`." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Manifest version: \`${MANIFEST_VERSION}\`" >> $GITHUB_STEP_SUMMARY
fi
# Check extension type, element, client attributes
EXT_TYPE=$(grep -oP '<extension[^>]*\btype="\K[^"]+' "$MANIFEST" | head -1)
if [ -z "$EXT_TYPE" ]; then
echo "Missing \`type\` attribute on \`<extension>\` tag." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Extension type: \`${EXT_TYPE}\`" >> $GITHUB_STEP_SUMMARY
fi
# Element check (component/module/plugin name)
HAS_ELEMENT=$(grep -cP '<(element|name)>' "$MANIFEST" 2>/dev/null || echo "0")
if [ "$HAS_ELEMENT" -eq 0 ]; then
echo "Missing \`<element>\` or \`<name>\` in manifest." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
# Client attribute for site/admin modules and plugins
if echo "$EXT_TYPE" | grep -qP "^(module|plugin)$"; then
HAS_CLIENT=$(grep -cP '<extension[^>]*\bclient=' "$MANIFEST" 2>/dev/null || echo "0")
if [ "$HAS_CLIENT" -eq 0 ]; then
echo "Missing \`client\` attribute for ${EXT_TYPE} extension." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
fi
fi
# Check updates.xml exists
if [ -f "updates.xml" ] || [ -f "updates.xml" ]; then
echo "Update XML present." >> $GITHUB_STEP_SUMMARY
else
echo "No updates.xml found." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
# Check CHANGELOG.md exists
if [ -f "CHANGELOG.md" ]; then
echo "CHANGELOG.md present." >> $GITHUB_STEP_SUMMARY
else
echo "No CHANGELOG.md found." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ $ERRORS -gt 0 ]; then
echo "**${ERRORS} issue(s) must be resolved before release.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Extension is ready for release.**" >> $GITHUB_STEP_SUMMARY
fi
test:
name: Tests (PHP ${{ matrix.php }})
runs-on: ubuntu-latest
needs: lint-and-validate
strategy:
fail-fast: false
matrix:
php: ['8.2', '8.3']
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup PHP ${{ matrix.php }}
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.31.0
with:
php-version: ${{ matrix.php }}
extensions: mbstring, xml, zip, gd, curl, json, simplexml
tools: composer:v2
coverage: none
- name: Install dependencies
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
run: |
if [ -f "composer.json" ]; then
composer install \
--no-interaction \
--prefer-dist \
--optimize-autoloader
else
echo "No composer.json found — skipping dependency install"
fi
- name: Run tests
run: |
echo "### Test Results (PHP ${{ matrix.php }})" >> $GITHUB_STEP_SUMMARY
if [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then
vendor/bin/phpunit --testdox 2>&1 | tee /tmp/test-output.log
EXIT=${PIPESTATUS[0]}
if [ $EXIT -eq 0 ]; then
echo "All tests passed." >> $GITHUB_STEP_SUMMARY
else
echo "Test failures detected — see log." >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat /tmp/test-output.log >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
fi
exit $EXIT
else
echo "No phpunit.xml found — skipping tests." >> $GITHUB_STEP_SUMMARY
fi

View File

@@ -9,7 +9,7 @@
# INGROUP: MokoStandards.Security
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/generic/codeql-analysis.yml.template
# VERSION: 04.04.01
# VERSION: 03.09.03
# BRIEF: CodeQL security scanning workflow (generic — all repo types)
# NOTE: Deployed to .github/workflows/codeql-analysis.yml in governed repos.
# CodeQL does not support PHP directly; JavaScript scans JSON/YAML/shell.

View File

@@ -22,7 +22,7 @@
# INGROUP: MokoStandards.Deploy
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/deploy-demo.yml.template
# VERSION: 04.04.01
# VERSION: 04.05.00
# BRIEF: SFTP deployment workflow for demo server — synced to all governed repos
# NOTE: Synced via bulk-repo-sync to .github/workflows/deploy-demo.yml in all governed repos.
# Port is resolved in order: DEMO_FTP_PORT variable → :port suffix in DEMO_FTP_HOST → 22.
@@ -36,10 +36,9 @@ name: Deploy to Demo Server (SFTP)
# Optional org-level variable: DEMO_FTP_PORT (auto-detected from host or defaults to 22)
# Optional org/repo variable: DEMO_FTP_SUFFIX — when set, appended to DEMO_FTP_PATH to form the
# full remote destination: DEMO_FTP_PATH/DEMO_FTP_SUFFIX
# Ignore rules: Place a .ftp_ignore file in the repository root. Each non-empty,
# non-comment line is a regex pattern tested against the relative path
# of each file (e.g. "subdir/file.txt"). The .gitignore is also
# respected automatically.
# Ignore rules: Place a .ftpignore file in the repository root. Each non-empty,
# non-comment line is a glob pattern tested against the relative path
# of each file (e.g. "subdir/file.txt"). The .gitignore is NOT used.
# Required org-level secret: DEMO_FTP_KEY (preferred) or DEMO_FTP_PASSWORD
#
# Access control: only users with admin or maintain role on the repository may deploy.
@@ -195,8 +194,8 @@ jobs:
env:
SOURCE_DIR: ${{ steps.source.outputs.dir }}
run: |
# ── Convert a gitignore-style glob line to an ERE pattern ──────────────
ftp_ignore_to_regex() {
# ── Convert a ftpignore-style glob line to an ERE pattern ──────────────
ftpignore_to_regex() {
local line="$1"
local anchored=false
# Strip inline comments and whitespace
@@ -226,15 +225,15 @@ jobs:
fi
}
# ── Read .ftp_ignore (gitignore-style globs) ─────────────────────────
# ── Read .ftpignore (ftpignore-style globs) ─────────────────────────
IGNORE_PATTERNS=()
IGNORE_SOURCES=()
if [ -f ".ftp_ignore" ]; then
if [ -f ".ftpignore" ]; then
while IFS= read -r line; do
[[ "$line" =~ ^[[:space:]]*$ || "$line" =~ ^[[:space:]]*# ]] && continue
regex=$(ftp_ignore_to_regex "$line")
regex=$(ftpignore_to_regex "$line")
[ -n "$regex" ] && IGNORE_PATTERNS+=("$regex") && IGNORE_SOURCES+=("$line")
done < ".ftp_ignore"
done < ".ftpignore"
fi
# ── Walk src/ and classify every file ────────────────────────────────
@@ -245,17 +244,11 @@ jobs:
SKIP=false
for i in "${!IGNORE_PATTERNS[@]}"; do
if echo "$rel" | grep -qE "${IGNORE_PATTERNS[$i]}" 2>/dev/null; then
IGNORED_FILES+=("$rel | .ftp_ignore \`${IGNORE_SOURCES[$i]}\`")
IGNORED_FILES+=("$rel | .ftpignore \`${IGNORE_SOURCES[$i]}\`")
SKIP=true; break
fi
done
$SKIP && continue
if [ -f ".gitignore" ]; then
git check-ignore -q "$rel" 2>/dev/null && {
IGNORED_FILES+=("$rel | .gitignore")
continue
} || true
fi
WILL_UPLOAD+=("$rel")
done < <(find "$SOURCE_DIR" -type f -print0 | sort -z)
@@ -426,7 +419,7 @@ jobs:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
run: |
git clone --depth 1 --branch version/04.04 --quiet \
git clone --depth 1 --branch version/04.05 --quiet \
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
/tmp/mokostandards
cd /tmp/mokostandards
@@ -637,8 +630,12 @@ jobs:
DEPLOY_ARGS+=(--key-passphrase "$SFTP_PASSWORD")
fi
php /tmp/mokostandards/api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
# (deploy-sftp.php handles dotfile skipping and .ftp_ignore natively)
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
# Remove temp files that should never be left behind
rm -f /tmp/deploy_key /tmp/sftp-config.json

View File

@@ -22,7 +22,7 @@
# INGROUP: MokoStandards.Deploy
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/deploy-dev.yml.template
# VERSION: 04.04.01
# VERSION: 04.05.00
# BRIEF: SFTP deployment workflow for development server — synced to all governed repos
# NOTE: Synced via bulk-repo-sync to .github/workflows/deploy-dev.yml in all governed repos.
# Port is resolved in order: DEV_FTP_PORT variable → :port suffix in DEV_FTP_HOST → 22.
@@ -37,10 +37,9 @@ name: Deploy to Dev Server (SFTP)
# Optional org-level variable: DEV_FTP_PORT (auto-detected from host or defaults to 22)
# Optional org/repo variable: DEV_FTP_SUFFIX — when set, appended to DEV_FTP_PATH to form the
# full remote destination: DEV_FTP_PATH/DEV_FTP_SUFFIX
# Ignore rules: Place a .ftp_ignore file in the repository root. Each non-empty,
# non-comment line is a regex pattern tested against the relative path
# of each file (e.g. "subdir/file.txt"). The .gitignore is also
# respected automatically.
# Ignore rules: Place a .ftpignore file in the repository root. Each non-empty,
# non-comment line is a glob pattern tested against the relative path
# of each file (e.g. "subdir/file.txt"). The .gitignore is NOT used.
# Required org-level secret: DEV_FTP_KEY (preferred) or DEV_FTP_PASSWORD
#
# Access control: only users with admin or maintain role on the repository may deploy.
@@ -200,8 +199,8 @@ jobs:
env:
SOURCE_DIR: ${{ steps.source.outputs.dir }}
run: |
# ── Convert a gitignore-style glob line to an ERE pattern ──────────────
ftp_ignore_to_regex() {
# ── Convert a ftpignore-style glob line to an ERE pattern ──────────────
ftpignore_to_regex() {
local line="$1"
local anchored=false
# Strip inline comments and whitespace
@@ -231,15 +230,15 @@ jobs:
fi
}
# ── Read .ftp_ignore (gitignore-style globs) ─────────────────────────
# ── Read .ftpignore (ftpignore-style globs) ─────────────────────────
IGNORE_PATTERNS=()
IGNORE_SOURCES=()
if [ -f ".ftp_ignore" ]; then
if [ -f ".ftpignore" ]; then
while IFS= read -r line; do
[[ "$line" =~ ^[[:space:]]*$ || "$line" =~ ^[[:space:]]*# ]] && continue
regex=$(ftp_ignore_to_regex "$line")
regex=$(ftpignore_to_regex "$line")
[ -n "$regex" ] && IGNORE_PATTERNS+=("$regex") && IGNORE_SOURCES+=("$line")
done < ".ftp_ignore"
done < ".ftpignore"
fi
# ── Walk src/ and classify every file ────────────────────────────────
@@ -250,17 +249,11 @@ jobs:
SKIP=false
for i in "${!IGNORE_PATTERNS[@]}"; do
if echo "$rel" | grep -qE "${IGNORE_PATTERNS[$i]}" 2>/dev/null; then
IGNORED_FILES+=("$rel | .ftp_ignore \`${IGNORE_SOURCES[$i]}\`")
IGNORED_FILES+=("$rel | .ftpignore \`${IGNORE_SOURCES[$i]}\`")
SKIP=true; break
fi
done
$SKIP && continue
if [ -f ".gitignore" ]; then
git check-ignore -q "$rel" 2>/dev/null && {
IGNORED_FILES+=("$rel | .gitignore")
continue
} || true
fi
WILL_UPLOAD+=("$rel")
done < <(find "$SOURCE_DIR" -type f -print0 | sort -z)
@@ -431,7 +424,7 @@ jobs:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
run: |
git clone --depth 1 --branch version/04.04 --quiet \
git clone --depth 1 --branch version/04.05 --quiet \
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
/tmp/mokostandards
cd /tmp/mokostandards
@@ -582,6 +575,10 @@ jobs:
> /tmp/sftp-config.json
fi
# Dev deploys skip minified files — use unminified sources for debugging
echo "*.min.js" >> .ftpignore
echo "*.min.css" >> .ftpignore
# ── Run deploy-sftp.php from MokoStandards ────────────────────────────
DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json)
if [ "$USE_PASSPHRASE" = "true" ]; then
@@ -662,74 +659,20 @@ jobs:
fi
fi
php /tmp/mokostandards/api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
# (deploy-sftp.php handles dotfile skipping and .ftp_ignore natively)
# Use Joomla-aware deploy for waas-component (routes files to correct Joomla dirs)
# Use standard SFTP deploy for everything else
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
# (both scripts handle dotfile skipping and .ftpignore natively)
# Remove temp files that should never be left behind
rm -f /tmp/deploy_key /tmp/sftp-config.json
- name: Create or update failure issue
if: failure()
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
REPO="${{ github.repository }}"
RUN_URL="${{ github.server_url }}/${REPO}/actions/runs/${{ github.run_id }}"
ACTOR="${{ github.actor }}"
BRANCH="${{ github.ref_name }}"
EVENT="${{ github.event_name }}"
NOW=$(date -u '+%Y-%m-%d %H:%M:%S UTC')
LABEL="deploy-failure"
TITLE="fix: Dev deployment failed — ${REPO}"
BODY="## Dev Deployment Failed
A deployment to the dev server failed and requires attention.
| Field | Value |
|-------|-------|
| **Repository** | \`${REPO}\` |
| **Branch** | \`${BRANCH}\` |
| **Trigger** | ${EVENT} |
| **Actor** | @${ACTOR} |
| **Failed at** | ${NOW} |
| **Run** | [View workflow run](${RUN_URL}) |
### Next steps
1. Review the [workflow run log](${RUN_URL}) for the specific error.
2. Fix the underlying issue (credentials, SFTP connectivity, permissions).
3. Re-trigger the deployment via **Actions → Deploy to Dev Server → Run workflow**.
---
*Auto-created by deploy-dev.yml — close this issue once the deployment is resolved.*"
# Ensure the label exists (idempotent — no-op if already present)
gh label create "$LABEL" \
--repo "$REPO" \
--color "CC0000" \
--description "Automated deploy failure tracking" \
--force 2>/dev/null || true
# Look for an existing open deploy-failure issue
EXISTING=$(gh api "repos/${REPO}/issues?labels=${LABEL}&state=all&per_page=1&sort=created&direction=desc" \
--jq '.[0].number' 2>/dev/null)
if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then
gh api "repos/${REPO}/issues/${EXISTING}" \
-X PATCH \
-f title="$TITLE" \
-f body="$BODY" \
-f state="open" \
--silent
echo "📋 Failure issue #${EXISTING} updated/reopened: ${REPO}" >> "$GITHUB_STEP_SUMMARY"
else
gh issue create \
--repo "$REPO" \
--title "$TITLE" \
--body "$BODY" \
--label "$LABEL" \
--assignee "jmiller-moko" \
| tee -a "$GITHUB_STEP_SUMMARY"
fi
# Dev deploys fail silently — no issue creation.
# Demo and RS deploys create failure issues (production-facing).
- name: Deployment summary
if: always()

132
.github/workflows/deploy-manual.yml vendored Normal file
View File

@@ -0,0 +1,132 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoStandards.Deploy
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/joomla/deploy-manual.yml.template
# VERSION: 04.06.00
# BRIEF: Manual SFTP deploy to dev server for Joomla repos
# NOTE: Joomla repos use update.xml for distribution. This is for manual
# dev server testing only — triggered via workflow_dispatch.
name: 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
uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.31.0
with:
php-version: '8.2'
extensions: json, ssh2
tools: composer
coverage: none
- name: Setup MokoStandards tools
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
run: |
git clone --depth 1 --branch version/04 --quiet \
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
/tmp/mokostandards 2>/dev/null || true
if [ -d "/tmp/mokostandards" ] && [ -f "/tmp/mokostandards/composer.json" ]; then
cd /tmp/mokostandards && 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 }}
SUFFIX: ${{ vars.DEV_FTP_SUFFIX }}
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%/}"
[ -n "$SUFFIX" ] && REMOTE="${REMOTE}/${SUFFIX#/}"
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

View File

@@ -22,7 +22,7 @@
# INGROUP: MokoStandards.Deploy
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/deploy-rs.yml.template
# VERSION: 04.04.01
# VERSION: 04.05.00
# BRIEF: SFTP deployment workflow for release staging server — synced to all governed repos
# NOTE: Synced via bulk-repo-sync to .github/workflows/deploy-rs.yml in all governed repos.
# Port is resolved in order: RS_FTP_PORT variable → :port suffix in RS_FTP_HOST → 22.
@@ -36,10 +36,9 @@ name: Deploy to RS Server (SFTP)
# Optional org-level variable: RS_FTP_PORT (auto-detected from host or defaults to 22)
# Optional org/repo variable: RS_FTP_SUFFIX — when set, appended to RS_FTP_PATH to form the
# full remote destination: RS_FTP_PATH/RS_FTP_SUFFIX
# Ignore rules: Place a .ftp_ignore file in the repository root. Each non-empty,
# non-comment line is a regex pattern tested against the relative path
# of each file (e.g. "subdir/file.txt"). The .gitignore is also
# respected automatically.
# Ignore rules: Place a .ftpignore file in the repository root. Each non-empty,
# non-comment line is a glob pattern tested against the relative path
# of each file (e.g. "subdir/file.txt"). The .gitignore is NOT used.
# Required org-level secret: RS_FTP_KEY (preferred) or RS_FTP_PASSWORD
#
# Access control: only users with admin or maintain role on the repository may deploy.
@@ -195,8 +194,8 @@ jobs:
env:
SOURCE_DIR: ${{ steps.source.outputs.dir }}
run: |
# ── Convert a gitignore-style glob line to an ERE pattern ──────────────
ftp_ignore_to_regex() {
# ── Convert a ftpignore-style glob line to an ERE pattern ──────────────
ftpignore_to_regex() {
local line="$1"
local anchored=false
# Strip inline comments and whitespace
@@ -226,15 +225,15 @@ jobs:
fi
}
# ── Read .ftp_ignore (gitignore-style globs) ─────────────────────────
# ── Read .ftpignore (ftpignore-style globs) ─────────────────────────
IGNORE_PATTERNS=()
IGNORE_SOURCES=()
if [ -f ".ftp_ignore" ]; then
if [ -f ".ftpignore" ]; then
while IFS= read -r line; do
[[ "$line" =~ ^[[:space:]]*$ || "$line" =~ ^[[:space:]]*# ]] && continue
regex=$(ftp_ignore_to_regex "$line")
regex=$(ftpignore_to_regex "$line")
[ -n "$regex" ] && IGNORE_PATTERNS+=("$regex") && IGNORE_SOURCES+=("$line")
done < ".ftp_ignore"
done < ".ftpignore"
fi
# ── Walk src/ and classify every file ────────────────────────────────
@@ -245,17 +244,11 @@ jobs:
SKIP=false
for i in "${!IGNORE_PATTERNS[@]}"; do
if echo "$rel" | grep -qE "${IGNORE_PATTERNS[$i]}" 2>/dev/null; then
IGNORED_FILES+=("$rel | .ftp_ignore \`${IGNORE_SOURCES[$i]}\`")
IGNORED_FILES+=("$rel | .ftpignore \`${IGNORE_SOURCES[$i]}\`")
SKIP=true; break
fi
done
$SKIP && continue
if [ -f ".gitignore" ]; then
git check-ignore -q "$rel" 2>/dev/null && {
IGNORED_FILES+=("$rel | .gitignore")
continue
} || true
fi
WILL_UPLOAD+=("$rel")
done < <(find "$SOURCE_DIR" -type f -print0 | sort -z)
@@ -407,7 +400,7 @@ jobs:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
run: |
git clone --depth 1 --branch version/04.04 --quiet \
git clone --depth 1 --branch version/04.05 --quiet \
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
/tmp/mokostandards
cd /tmp/mokostandards
@@ -564,8 +557,12 @@ jobs:
DEPLOY_ARGS+=(--key-passphrase "$SFTP_PASSWORD")
fi
php /tmp/mokostandards/api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
# (deploy-sftp.php handles dotfile skipping and .ftp_ignore natively)
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
# Remove temp files that should never be left behind
rm -f /tmp/deploy_key /tmp/sftp-config.json

View File

@@ -22,7 +22,7 @@
# INGROUP: MokoStandards.Firewall
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/enterprise-firewall-setup.yml.template
# VERSION: 04.04.01
# 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.

View File

@@ -22,7 +22,7 @@
# INGROUP: MokoCassiopeia.Release
# REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
# PATH: /.github/workflows/release.yml
# VERSION: 01.00.00
# VERSION: 03.09.03
# BRIEF: Automated release workflow for MokoCassiopeia Joomla template
# NOTE: Creates release packages and publishes to GitHub Releases

View File

@@ -10,7 +10,7 @@
# INGROUP: MokoStandards.Validation
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/repo_health.yml
# VERSION: 04.01.00
# VERSION: 04.06.00
# BRIEF: Enforces repository guardrails by validating release configuration, scripts governance, tooling availability, and core repository health artifacts.
# NOTE: Field is user-managed.
# ============================================================================
@@ -29,7 +29,7 @@ on:
workflow_dispatch:
inputs:
profile:
description: Which configuration profile to validate. release checks SFTP variables used by release pipeline. scripts checks baseline script prerequisites. repo runs repository health only. al[...]
description: 'Validation profile: all, release, scripts, or repo'
required: true
default: all
type: choice
@@ -39,19 +39,7 @@ on:
- scripts
- repo
pull_request:
paths:
- .github/workflows/**
- scripts/**
- docs/**
- dev/**
push:
branches:
- main
paths:
- .github/workflows/**
- scripts/**
- docs/**
- dev/**
permissions:
contents: read
@@ -68,7 +56,7 @@ env:
# Repo health policy
# Files are listed as-is; directories must end with a trailing slash.
REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.github/workflows/,src/
REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.github/workflows/
REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/
REPO_DISALLOWED_DIRS:
REPO_DISALLOWED_FILES: TODO.md,todo.md
@@ -82,6 +70,7 @@ env:
WORKFLOWS_DIR: .github/workflows
SHELLCHECK_PATTERN: '*.sh'
SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml'
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
access_check:
@@ -173,7 +162,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
@@ -268,7 +257,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
@@ -382,7 +371,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
@@ -412,6 +401,15 @@ jobs:
exit 0
fi
# Source directory: src/ or htdocs/ (either is valid)
if [ -d "src" ]; then
SOURCE_DIR="src"
elif [ -d "htdocs" ]; then
SOURCE_DIR="htdocs"
else
missing_required+=("src/ or htdocs/ (source directory required)")
fi
IFS=',' read -r -a required_artifacts <<< "${REPO_REQUIRED_ARTIFACTS}"
IFS=',' read -r -a optional_files <<< "${REPO_OPTIONAL_FILES}"
IFS=',' read -r -a disallowed_dirs <<< "${REPO_DISALLOWED_DIRS}"
@@ -561,6 +559,73 @@ jobs:
} >> "${GITHUB_STEP_SUMMARY}"
fi
# ── Joomla-specific checks ───────────────────────────────────────
joomla_findings=()
# XML manifest: find any XML file containing <extension
MANIFEST="$(find . -maxdepth 2 -name '*.xml' -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)"
if [ -z "${MANIFEST}" ]; then
joomla_findings+=("Joomla XML manifest not found (no *.xml with <extension> tag)")
else
# Check <version> tag exists
if ! grep -qP '<version>' "${MANIFEST}"; then
joomla_findings+=("XML manifest: <version> tag missing")
fi
# Check extension type attribute
if ! grep -qP 'type="(component|module|plugin|library|package|template|language)"' "${MANIFEST}"; then
joomla_findings+=("XML manifest: type attribute missing or invalid")
fi
# Check <name> tag
if ! grep -qP '<name>' "${MANIFEST}"; then
joomla_findings+=("XML manifest: <name> tag missing")
fi
# Check <author> tag
if ! grep -qP '<author>' "${MANIFEST}"; then
joomla_findings+=("XML manifest: <author> tag missing")
fi
# Check <namespace> for Joomla 5+
if ! grep -qP '<namespace' "${MANIFEST}"; then
joomla_findings+=("XML manifest: <namespace> missing (required for Joomla 5+)")
fi
fi
# Language files: check for at least one .ini file
INI_COUNT="$(find . -name '*.ini' -type f 2>/dev/null | wc -l)"
if [ "${INI_COUNT}" -eq 0 ]; then
joomla_findings+=("No .ini language files found")
fi
# updates.xml must exist in root (Joomla update server)
if [ ! -f 'updates.xml' ]; then
joomla_findings+=("updates.xml missing in root (required for Joomla update server)")
fi
# index.html files for directory listing protection
INDEX_DIRS=("${SOURCE_DIR}" "${SOURCE_DIR}/admin" "${SOURCE_DIR}/site")
for dir in "${INDEX_DIRS[@]}"; do
if [ -d "${dir}" ] && [ ! -f "${dir}/index.html" ]; then
joomla_findings+=("${dir}/index.html missing (directory listing protection)")
fi
done
if [ "${#joomla_findings[@]}" -gt 0 ]; then
{
printf '%s\n' '### Joomla extension checks'
printf '%s\n' '| Check | Status |'
printf '%s\n' '|---|---|'
for f in "${joomla_findings[@]}"; do
printf '%s\n' "| ${f} | Warning |"
done
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
else
{
printf '%s\n' '### Joomla extension checks'
printf '%s\n' 'All Joomla-specific checks passed.'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
fi
extended_enabled="${EXTENDED_CHECKS:-true}"
extended_findings=()

View File

@@ -9,7 +9,7 @@
# INGROUP: MokoStandards.Maintenance
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/repository-cleanup.yml.template
# VERSION: 04.04.01
# VERSION: 04.06.00
# BRIEF: Recurring repository maintenance — labels, branches, workflows, logs, doc indexes
# NOTE: Synced via bulk-repo-sync to .github/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.
@@ -266,7 +266,7 @@ jobs:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
REPO="${{ github.repository }}"
CURRENT="chore/sync-mokostandards-v04.04"
CURRENT="chore/sync-mokostandards-v04.05"
echo "## 🌿 Branch Cleanup" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY

View File

@@ -5,7 +5,7 @@
# INGROUP: MokoStandards.Compliance
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/standards-compliance.yml
# VERSION: 04.04.01
# VERSION: 04.06.00
# BRIEF: MokoStandards compliance validation workflow
# NOTE: Validates repository structure, documentation, and coding standards
@@ -163,7 +163,11 @@ jobs:
--include="*.php" --include="*.py" --include="*.js" --include="*.ts" \
--exclude-dir=".git" --exclude-dir="vendor" --exclude-dir="node_modules" 2>/dev/null | \
grep -v -E '(test|example|sample|getenv|getString|getArgument|config\[|/\.\*/|^\s*//|^\s*\*|CREDENTIAL_PATTERNS|SecurityValidator|SECRET_PATTERN|===|!==|ApiClient|str_contains|gen_wrappers)' | \
grep -v "= ''" | grep -v '= ""' | grep -v '\$this->config' > /tmp/secrets1.txt 2>/dev/null || true
grep -v "= ''" | grep -v '= ""' | grep -v '\$this->config' | \
grep -v 'type="password"' | grep -v 'type="text"' | grep -v 'name="password"' | grep -v 'name="secretkey"' | \
grep -v '<input ' | grep -v '<label ' | grep -v 'for="' | \
grep -v 'index\.php?option=' | grep -v 'Route::_' | grep -v 'lostpassword' | \
grep -v 'resetpassword' | grep -v 'JRoute' | grep -v 'href=' > /tmp/secrets1.txt 2>/dev/null || true
scan_pattern "Secret assignments" "⚠️" /tmp/secrets1.txt
# Pattern 2: Private keys
@@ -500,10 +504,18 @@ jobs:
tools: composer
coverage: none
- name: Install API Package
run: composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
- name: Setup MokoStandards tools
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN }}"}}'
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
run: |
git clone --depth 1 --branch version/04 --quiet \
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
/tmp/mokostandards 2>/dev/null || true
if [ -d "/tmp/mokostandards" ] && [ -f "/tmp/mokostandards/composer.json" ]; then
cd /tmp/mokostandards
composer install --no-dev --no-interaction --quiet 2>/dev/null || true
fi
- name: Run Version Consistency Check
id: version_check
@@ -512,18 +524,15 @@ jobs:
echo "## 🔢 Version Consistency Validation" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Use PHP enterprise library for version consistency check
if [ -f "vendor/bin/moko" ]; then
php vendor/bin/moko check:version -- --path . --verbose 2>&1 | tee /tmp/version-check.log
EXIT_CODE=${PIPESTATUS[0]}
elif [ -f "/tmp/mokostandards/api/validate/check_version_consistency.php" ]; then
# Use MokoStandards tools (no Composer needed on the governed repo)
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
EXIT_CODE=${PIPESTATUS[0]}
elif [ -f "api/validate/check_version_consistency.php" ]; then
php api/validate/check_version_consistency.php --path . --verbose 2>&1 | tee /tmp/version-check.log
EXIT_CODE=${PIPESTATUS[0]}
else
echo "⏭️ Install mokoconsulting-tech/enterprise via Composer for version checks" >> $GITHUB_STEP_SUMMARY
echo "⏭️ MokoStandards tools not available — skipping version check" >> $GITHUB_STEP_SUMMARY
exit 0
fi
@@ -1960,17 +1969,39 @@ jobs:
coverage: none
- name: Install API Package
run: composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN }}"}}'
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
run: |
if [ -f "composer.json" ]; then
composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
else
echo "No composer.json — pulling MokoStandards tools"
if [ ! -d "/tmp/mokostandards" ]; then
git clone --depth 1 --branch version/04 --quiet \
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
/tmp/mokostandards 2>/dev/null || true
if [ -f "/tmp/mokostandards/composer.json" ]; then
cd /tmp/mokostandards && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
cd -
fi
fi
fi
- name: Check Enterprise Readiness
id: enterprise_check
run: |
echo "" >> $GITHUB_STEP_SUMMARY
SCRIPT=""
if [ -f "api/validate/check_enterprise_readiness.php" ]; then
php api/validate/check_enterprise_readiness.php --verbose | tee /tmp/enterprise-check.log
SCRIPT="api/validate/check_enterprise_readiness.php"
elif [ -f "/tmp/mokostandards/api/validate/check_enterprise_readiness.php" ]; then
SCRIPT="/tmp/mokostandards/api/validate/check_enterprise_readiness.php"
fi
if [ -n "$SCRIPT" ]; then
php "$SCRIPT" --verbose | tee /tmp/enterprise-check.log
EXIT_CODE=$?
echo "" >> $GITHUB_STEP_SUMMARY
@@ -2010,17 +2041,39 @@ jobs:
coverage: none
- name: Install API Package
run: composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN }}"}}'
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
run: |
if [ -f "composer.json" ]; then
composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader
else
echo "No composer.json — pulling MokoStandards tools"
if [ ! -d "/tmp/mokostandards" ]; then
git clone --depth 1 --branch version/04 --quiet \
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
/tmp/mokostandards 2>/dev/null || true
if [ -f "/tmp/mokostandards/composer.json" ]; then
cd /tmp/mokostandards && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
cd -
fi
fi
fi
- name: Check Repository Health
id: health_check
run: |
echo "" >> $GITHUB_STEP_SUMMARY
SCRIPT=""
if [ -f "api/validate/check_repo_health.php" ]; then
php api/validate/check_repo_health.php --verbose | tee /tmp/health-check.log
SCRIPT="api/validate/check_repo_health.php"
elif [ -f "/tmp/mokostandards/api/validate/check_repo_health.php" ]; then
SCRIPT="/tmp/mokostandards/api/validate/check_repo_health.php"
fi
if [ -n "$SCRIPT" ]; then
php "$SCRIPT" --verbose | tee /tmp/health-check.log
EXIT_CODE=$?
echo "" >> $GITHUB_STEP_SUMMARY
@@ -2481,8 +2534,8 @@ jobs:
echo ""
echo "✅ SUCCESS: Repository is fully MokoStandards compliant"
- name: Create tracking issue for standards violations
if: failure() && github.event_name == 'push'
- name: Create or reopen tracking issue for standards violations
if: failure()
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |

View File

@@ -9,7 +9,7 @@
# INGROUP: MokoStandards.Automation
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/sync-version-on-merge.yml.template
# VERSION: 04.04.01
# VERSION: 04.06.00
# BRIEF: Auto-bump patch version on every push to main and propagate to all file headers
# NOTE: Synced via bulk-repo-sync to .github/workflows/sync-version-on-merge.yml in all governed repos.
# README.md is the single source of truth for the repository version.
@@ -58,7 +58,7 @@ jobs:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
run: |
git clone --depth 1 --branch version/04.04 --quiet \
git clone --depth 1 --branch version/04 --quiet \
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
/tmp/mokostandards
cd /tmp/mokostandards

321
.github/workflows/update-server.yml vendored Normal file
View File

@@ -0,0 +1,321 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoStandards.Joomla
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/joomla/update-server.yml.template
# VERSION: 04.06.00
# BRIEF: Update Joomla update server XML feed with stable/rc/dev entries
#
# Writes updates.xml with multiple <update> entries:
# - <tag>stable</tag> on push to main (from auto-release)
# - <tag>rc</tag> on push to rc/**
# - <tag>development</tag> on push to dev/**
#
# Joomla filters by user's "Minimum Stability" setting.
name: Update Joomla Update Server XML Feed
on:
push:
branches:
- 'dev/**'
- 'alpha/**'
- 'beta/**'
- 'rc/**'
paths:
- 'src/**'
- 'htdocs/**'
workflow_dispatch:
inputs:
stability:
description: 'Stability tag'
required: true
default: 'development'
type: choice
options:
- development
- alpha
- beta
- rc
- stable
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
permissions:
contents: write
jobs:
update-xml:
name: Update updates.xml
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.GH_TOKEN || github.token }}
fetch-depth: 0
- name: Setup MokoStandards tools
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
run: |
git clone --depth 1 --branch version/04 --quiet \
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
/tmp/mokostandards 2>/dev/null || true
if [ -d "/tmp/mokostandards" ] && [ -f "/tmp/mokostandards/composer.json" ]; then
cd /tmp/mokostandards && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
fi
- name: Generate updates.xml entry
run: |
BRANCH="${{ github.ref_name }}"
REPO="${{ github.repository }}"
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null || echo "0.0.0")
# Auto-bump patch on alpha/beta/rc branches (not dev — dev bumps manually)
if [[ "$BRANCH" != dev/* ]]; then
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
BUMPED=$(php /tmp/mokostandards/api/cli/version_bump.php --path . 2>/dev/null || true)
if [ -n "$BUMPED" ]; then
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null || echo "$VERSION")
git add -A
git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>" 2>/dev/null || true
git push 2>/dev/null || true
fi
fi
# Determine stability from branch or input
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
STABILITY="${{ inputs.stability }}"
elif [[ "$BRANCH" == rc/* ]]; then
STABILITY="rc"
elif [[ "$BRANCH" == beta/* ]]; then
STABILITY="beta"
elif [[ "$BRANCH" == alpha/* ]]; then
STABILITY="alpha"
elif [[ "$BRANCH" == dev/* ]]; then
STABILITY="development"
else
STABILITY="stable"
fi
# Parse manifest
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
if [ -z "$MANIFEST" ]; then
echo "No Joomla manifest found — skipping"
exit 0
fi
EXT_NAME=$(grep -oP '<name>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "${{ github.event.repository.name }}")
EXT_TYPE=$(grep -oP '<extension[^>]+type="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "component")
EXT_ELEMENT=$(grep -oP '<element>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || basename "$MANIFEST" .xml)
EXT_CLIENT=$(grep -oP '<extension[^>]+client="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
EXT_FOLDER=$(grep -oP '<extension[^>]+group="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "")
TARGET_PLATFORM=$(grep -oP '<targetplatform[^/]*/>' "$MANIFEST" 2>/dev/null | head -1 || echo "")
PHP_MINIMUM=$(grep -oP '<php_minimum>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "")
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(basename "$MANIFEST" .xml)
[ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="5.*" %s>' "/")
CLIENT_TAG=""
[ -n "$EXT_CLIENT" ] && CLIENT_TAG="<client>${EXT_CLIENT}</client>"
[ -z "$CLIENT_TAG" ] && ([ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]) && CLIENT_TAG="<client>site</client>"
FOLDER_TAG=""
[ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ] && FOLDER_TAG="<folder>${EXT_FOLDER}</folder>"
PHP_TAG=""
[ -n "$PHP_MINIMUM" ] && PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
# Version suffix for non-stable
DISPLAY_VERSION="$VERSION"
case "$STABILITY" in
development) DISPLAY_VERSION="${VERSION}-dev" ;;
alpha) DISPLAY_VERSION="${VERSION}-alpha" ;;
beta) DISPLAY_VERSION="${VERSION}-beta" ;;
rc) DISPLAY_VERSION="${VERSION}-rc" ;;
esac
MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
# Each stability level has its own release tag
case "$STABILITY" in
development) RELEASE_TAG="development" ;;
alpha) RELEASE_TAG="alpha" ;;
beta) RELEASE_TAG="beta" ;;
rc) RELEASE_TAG="release-candidate" ;;
*) RELEASE_TAG="v${MAJOR}" ;;
esac
PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip"
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
INFO_URL="https://github.com/${REPO}"
# ── Build install-ready ZIP ─────────────────────────────────
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
if [ -d "$SOURCE_DIR" ]; then
cd "$SOURCE_DIR"
zip -r "/tmp/${PACKAGE_NAME}" . -x '.ftpignore'
cd ..
SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1)
# Ensure draft release exists for this major
gh release view "$RELEASE_TAG" --json tagName > /dev/null 2>&1 || \
gh release create "$RELEASE_TAG" --title "${RELEASE_TAG} (${DISPLAY_VERSION})" --notes "${STABILITY} release" --prerelease --target main 2>/dev/null || true
# Upload ZIP to the major release
gh release upload "$RELEASE_TAG" "/tmp/${PACKAGE_NAME}" --clobber 2>/dev/null || true
echo "Package: ${PACKAGE_NAME} (SHA: ${SHA256})" >> $GITHUB_STEP_SUMMARY
else
SHA256=""
fi
# ── Build the new entry ───────────────────────────────────────
NEW_ENTRY=""
NEW_ENTRY="${NEW_ENTRY} <update>\n"
NEW_ENTRY="${NEW_ENTRY} <name>${EXT_NAME}</name>\n"
NEW_ENTRY="${NEW_ENTRY} <description>${EXT_NAME} (${STABILITY})</description>\n"
NEW_ENTRY="${NEW_ENTRY} <element>${EXT_ELEMENT}</element>\n"
NEW_ENTRY="${NEW_ENTRY} <type>${EXT_TYPE}</type>\n"
NEW_ENTRY="${NEW_ENTRY} <version>${DISPLAY_VERSION}</version>\n"
[ -n "$CLIENT_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${CLIENT_TAG}\n"
[ -n "$FOLDER_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${FOLDER_TAG}\n"
NEW_ENTRY="${NEW_ENTRY} <tags>\n"
NEW_ENTRY="${NEW_ENTRY} <tag>${STABILITY}</tag>\n"
NEW_ENTRY="${NEW_ENTRY} </tags>\n"
NEW_ENTRY="${NEW_ENTRY} <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>\n"
NEW_ENTRY="${NEW_ENTRY} <downloads>\n"
NEW_ENTRY="${NEW_ENTRY} <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>\n"
NEW_ENTRY="${NEW_ENTRY} </downloads>\n"
[ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} <sha256>sha256:${SHA256}</sha256>\n"
NEW_ENTRY="${NEW_ENTRY} ${TARGET_PLATFORM}\n"
[ -n "$PHP_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_TAG}\n"
NEW_ENTRY="${NEW_ENTRY} <maintainer>Moko Consulting</maintainer>\n"
NEW_ENTRY="${NEW_ENTRY} <maintainerurl>https://mokoconsulting.tech</maintainerurl>\n"
NEW_ENTRY="${NEW_ENTRY} </update>"
# ── Write new entry to temp file ───────────────────────────────
printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml
# ── Merge into updates.xml ─────────────────────────────────────
if [ ! -f "updates.xml" ]; then
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>' > updates.xml
printf '%s\n' '<updates>' >> updates.xml
cat /tmp/new_entry.xml >> updates.xml
printf '\n%s\n' '</updates>' >> updates.xml
else
# Remove existing entry for this stability, insert new one
printf 'import re\nstability = "%s"\n' "${STABILITY}" > /tmp/merge_xml.py
printf 'with open("updates.xml") as f: content = f.read()\n' >> /tmp/merge_xml.py
printf 'with open("/tmp/new_entry.xml") as f: new_entry = f.read()\n' >> /tmp/merge_xml.py
printf 'pattern = r" <update>.*?<tag>" + re.escape(stability) + r"</tag>.*?</update>\\n?"\n' >> /tmp/merge_xml.py
printf 'content = re.sub(pattern, "", content, flags=re.DOTALL)\n' >> /tmp/merge_xml.py
printf 'content = content.replace("</updates>", new_entry + "\\n</updates>")\n' >> /tmp/merge_xml.py
printf 'content = re.sub(r"\\n{3,}", "\\n\\n", content)\n' >> /tmp/merge_xml.py
printf 'with open("updates.xml", "w") as f: f.write(content)\n' >> /tmp/merge_xml.py
python3 /tmp/merge_xml.py 2>/dev/null || {
# Fallback: rebuild keeping other stability entries
{
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>'
printf '%s\n' '<updates>'
for TAG in stable rc development; do
[ "$TAG" = "${STABILITY}" ] && continue
if grep -q "<tag>${TAG}</tag>" updates.xml 2>/dev/null; then
sed -n "/<update>/,/<\/update>/{ /<tag>${TAG}<\/tag>/p; }" updates.xml
fi
done
cat /tmp/new_entry.xml
printf '\n%s\n' '</updates>'
} > /tmp/updates_new.xml
mv /tmp/updates_new.xml updates.xml
}
fi
# Commit
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add updates.xml
git diff --cached --quiet || {
git commit -m "chore: update updates.xml (${STABILITY}: ${DISPLAY_VERSION}) [skip ci]" \
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
git push
}
- name: SFTP deploy to dev server
if: contains(github.ref, 'dev/')
env:
DEV_HOST: ${{ vars.DEV_FTP_HOST }}
DEV_PATH: ${{ vars.DEV_FTP_PATH }}
DEV_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }}
DEV_USER: ${{ vars.DEV_FTP_USERNAME }}
DEV_PORT: ${{ vars.DEV_FTP_PORT }}
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
# ── Permission check: admin or maintain role required ──────
ACTOR="${{ github.actor }}"
REPO="${{ github.repository }}"
PERMISSION=$(gh api "repos/${REPO}/collaborators/${ACTOR}/permission" \
--jq '.permission' 2>/dev/null || \
gh api "repos/${REPO}/collaborators/${ACTOR}" \
--jq '.role' 2>/dev/null || echo "read")
case "$PERMISSION" in
admin|maintain|write) ;;
*)
echo "Deploy denied: ${ACTOR} has '${PERMISSION}' — requires admin, maintain, or write"
exit 0
;;
esac
[ -z "$DEV_HOST" ] || [ -z "$DEV_PATH" ] && { echo "DEV FTP not configured — skipping SFTP"; exit 0; }
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
[ ! -d "$SOURCE_DIR" ] && exit 0
PORT="${DEV_PORT:-22}"
REMOTE="${DEV_PATH%/}"
[ -n "$DEV_SUFFIX" ] && REMOTE="${REMOTE}/${DEV_SUFFIX#/}"
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
"$DEV_HOST" "$PORT" "$DEV_USER" "$REMOTE" > /tmp/sftp-config.json
if [ -n "$DEV_KEY" ]; then
echo "$DEV_KEY" > /tmp/deploy_key && chmod 600 /tmp/deploy_key
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
else
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
fi
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 --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
elif [ -f "/tmp/mokostandards/api/deploy/deploy-sftp.php" ]; then
php /tmp/mokostandards/api/deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
fi
rm -f /tmp/deploy_key /tmp/sftp-config.json
echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY
- name: Summary
if: always()
run: |
echo "## Joomla Update Server" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${DISPLAY_VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Element | \`${EXT_ELEMENT}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Download | [ZIP](${DOWNLOAD_URL}) |" >> $GITHUB_STEP_SUMMARY

747
.gitignore vendored
View File

@@ -1,3 +1,8 @@
# ============================================================
# Local task tracking (not version controlled)
# ============================================================
TODO.md
# ============================================================
# Environment and secrets
# ============================================================
@@ -23,10 +28,7 @@ secrets/
*.log
*.pid
*.seed
*.sql
*.sql.gz
*.sqlite
*.sqlite3
# ============================================================
# OS / Editor / IDE cruft
@@ -43,6 +45,7 @@ System Volume Information/
Icon?
.idea/
.settings/
.claude/
.vscode/*
!.vscode/tasks.json
!.vscode/settings.json.example
@@ -70,6 +73,17 @@ todo*
# SFTP / sync tools
# ============================================================
sftp-config*.json
sftp-config.json.template
sftp-settings.json
# ============================================================
# Sublime SFTP / FTP sync
# ============================================================
*.sublime-project
*.sublime-workspace
*.sublime-settings
.libsass.json
*.ffs*
# ============================================================
# Replit / cloud IDE
@@ -138,6 +152,7 @@ package-lock.json
# PHP / Composer tooling
# ============================================================
vendor/
!src/media/vendor/
composer.lock
*.phar
codeception.phar
@@ -185,727 +200,3 @@ venv/
*.coverage
hypothesis/
# ============================================================
# Joomla Development
# ============================================================
# Custom user overrides for Joomla development
user.css
user.js
colors_custom.css
# Joomla Module Builder auto-generated files
modulebuilder.txt
# ============================================================
# Joomla Core
# ============================================================
/.htaccess
/administrator/cache/*
/administrator/components/com_actionlogs/*
/administrator/components/com_admin/*
/administrator/components/com_ajax/*
/administrator/components/com_associations/*
/administrator/components/com_banners/*
/administrator/components/com_cache/*
/administrator/components/com_categories/*
/administrator/components/com_checkin/*
/administrator/components/com_config/*
/administrator/components/com_contact/*
/administrator/components/com_content/*
/administrator/components/com_contenthistory/*
/administrator/components/com_cpanel/*
/administrator/components/com_fields/*
/administrator/components/com_finder/*
/administrator/components/com_installer/*
/administrator/components/com_joomlaupdate/*
/administrator/components/com_languages/*
/administrator/components/com_login/*
/administrator/components/com_media/*
/administrator/components/com_menus/*
/administrator/components/com_messages/*
/administrator/components/com_modules/*
/administrator/components/com_newsfeeds/*
/administrator/components/com_plugins/*
/administrator/components/com_postinstall/*
/administrator/components/com_privacy/*
/administrator/components/com_redirect/*
/administrator/components/com_search/*
/administrator/components/com_tags/*
/administrator/components/com_templates/*
/administrator/components/com_users/*
/administrator/help/*
/administrator/includes/*
/administrator/index.php
/administrator/language/en-GB/en-GB.com_actionlogs.ini
/administrator/language/en-GB/en-GB.com_actionlogs.sys.ini
/administrator/language/en-GB/en-GB.com_admin.ini
/administrator/language/en-GB/en-GB.com_admin.sys.ini
/administrator/language/en-GB/en-GB.com_ajax.ini
/administrator/language/en-GB/en-GB.com_ajax.sys.ini
/administrator/language/en-GB/en-GB.com_associations.ini
/administrator/language/en-GB/en-GB.com_associations.sys.ini
/administrator/language/en-GB/en-GB.com_banners.ini
/administrator/language/en-GB/en-GB.com_banners.sys.ini
/administrator/language/en-GB/en-GB.com_cache.ini
/administrator/language/en-GB/en-GB.com_cache.sys.ini
/administrator/language/en-GB/en-GB.com_categories.ini
/administrator/language/en-GB/en-GB.com_categories.sys.ini
/administrator/language/en-GB/en-GB.com_checkin.ini
/administrator/language/en-GB/en-GB.com_checkin.sys.ini
/administrator/language/en-GB/en-GB.com_config.ini
/administrator/language/en-GB/en-GB.com_config.sys.ini
/administrator/language/en-GB/en-GB.com_contact.ini
/administrator/language/en-GB/en-GB.com_contact.sys.ini
/administrator/language/en-GB/en-GB.com_content.ini
/administrator/language/en-GB/en-GB.com_content.sys.ini
/administrator/language/en-GB/en-GB.com_contenthistory.ini
/administrator/language/en-GB/en-GB.com_contenthistory.sys.ini
/administrator/language/en-GB/en-GB.com_cpanel.ini
/administrator/language/en-GB/en-GB.com_cpanel.sys.ini
/administrator/language/en-GB/en-GB.com_fields.ini
/administrator/language/en-GB/en-GB.com_fields.sys.ini
/administrator/language/en-GB/en-GB.com_finder.ini
/administrator/language/en-GB/en-GB.com_finder.sys.ini
/administrator/language/en-GB/en-GB.com_installer.ini
/administrator/language/en-GB/en-GB.com_installer.sys.ini
/administrator/language/en-GB/en-GB.com_joomlaupdate.ini
/administrator/language/en-GB/en-GB.com_joomlaupdate.sys.ini
/administrator/language/en-GB/en-GB.com_languages.ini
/administrator/language/en-GB/en-GB.com_languages.sys.ini
/administrator/language/en-GB/en-GB.com_login.ini
/administrator/language/en-GB/en-GB.com_login.sys.ini
/administrator/language/en-GB/en-GB.com_mailto.sys.ini
/administrator/language/en-GB/en-GB.com_media.ini
/administrator/language/en-GB/en-GB.com_media.sys.ini
/administrator/language/en-GB/en-GB.com_menus.ini
/administrator/language/en-GB/en-GB.com_menus.sys.ini
/administrator/language/en-GB/en-GB.com_messages.ini
/administrator/language/en-GB/en-GB.com_messages.sys.ini
/administrator/language/en-GB/en-GB.com_modules.ini
/administrator/language/en-GB/en-GB.com_modules.sys.ini
/administrator/language/en-GB/en-GB.com_newsfeeds.ini
/administrator/language/en-GB/en-GB.com_newsfeeds.sys.ini
/administrator/language/en-GB/en-GB.com_plugins.ini
/administrator/language/en-GB/en-GB.com_plugins.sys.ini
/administrator/language/en-GB/en-GB.com_postinstall.ini
/administrator/language/en-GB/en-GB.com_postinstall.sys.ini
/administrator/language/en-GB/en-GB.com_privacy.ini
/administrator/language/en-GB/en-GB.com_privacy.sys.ini
/administrator/language/en-GB/en-GB.com_redirect.ini
/administrator/language/en-GB/en-GB.com_redirect.sys.ini
/administrator/language/en-GB/en-GB.com_search.ini
/administrator/language/en-GB/en-GB.com_search.sys.ini
/administrator/language/en-GB/en-GB.com_tags.ini
/administrator/language/en-GB/en-GB.com_tags.sys.ini
/administrator/language/en-GB/en-GB.com_templates.ini
/administrator/language/en-GB/en-GB.com_templates.sys.ini
/administrator/language/en-GB/en-GB.com_users.ini
/administrator/language/en-GB/en-GB.com_users.sys.ini
/administrator/language/en-GB/en-GB.com_weblinks.ini
/administrator/language/en-GB/en-GB.com_weblinks.sys.ini
/administrator/language/en-GB/en-GB.com_wrapper.ini
/administrator/language/en-GB/en-GB.com_wrapper.sys.ini
/administrator/language/en-GB/en-GB.ini
/administrator/language/en-GB/en-GB.lib_fof.ini
/administrator/language/en-GB/en-GB.lib_fof.sys.ini
/administrator/language/en-GB/en-GB.lib_joomla.ini
/administrator/language/en-GB/en-GB.lib_phpass.ini
/administrator/language/en-GB/en-GB.lib_phpmailer.ini
/administrator/language/en-GB/en-GB.lib_simplepie.ini
/administrator/language/en-GB/en-GB.localise.php
/administrator/language/en-GB/en-GB.mod_custom.ini
/administrator/language/en-GB/en-GB.mod_custom.sys.ini
/administrator/language/en-GB/en-GB.mod_feed.ini
/administrator/language/en-GB/en-GB.mod_feed.sys.ini
/administrator/language/en-GB/en-GB.mod_latest.ini
/administrator/language/en-GB/en-GB.mod_latest.sys.ini
/administrator/language/en-GB/en-GB.mod_latestactions.ini
/administrator/language/en-GB/en-GB.mod_latestactions.sys.ini
/administrator/language/en-GB/en-GB.mod_logged.ini
/administrator/language/en-GB/en-GB.mod_logged.sys.ini
/administrator/language/en-GB/en-GB.mod_login.ini
/administrator/language/en-GB/en-GB.mod_login.sys.ini
/administrator/language/en-GB/en-GB.mod_menu.ini
/administrator/language/en-GB/en-GB.mod_menu.sys.ini
/administrator/language/en-GB/en-GB.mod_multilangstatus.ini
/administrator/language/en-GB/en-GB.mod_multilangstatus.sys.ini
/administrator/language/en-GB/en-GB.mod_online.ini
/administrator/language/en-GB/en-GB.mod_online.sys.ini
/administrator/language/en-GB/en-GB.mod_popular.ini
/administrator/language/en-GB/en-GB.mod_popular.sys.ini
/administrator/language/en-GB/en-GB.mod_privacy_dashboard.ini
/administrator/language/en-GB/en-GB.mod_privacy_dashboard.sys.ini
/administrator/language/en-GB/en-GB.mod_quickicon.ini
/administrator/language/en-GB/en-GB.mod_quickicon.sys.ini
/administrator/language/en-GB/en-GB.mod_sampledata.ini
/administrator/language/en-GB/en-GB.mod_sampledata.sys.ini
/administrator/language/en-GB/en-GB.mod_stats_admin.ini
/administrator/language/en-GB/en-GB.mod_stats_admin.sys.ini
/administrator/language/en-GB/en-GB.mod_status.ini
/administrator/language/en-GB/en-GB.mod_status.sys.ini
/administrator/language/en-GB/en-GB.mod_submenu.ini
/administrator/language/en-GB/en-GB.mod_submenu.sys.ini
/administrator/language/en-GB/en-GB.mod_title.ini
/administrator/language/en-GB/en-GB.mod_title.sys.ini
/administrator/language/en-GB/en-GB.mod_toolbar.ini
/administrator/language/en-GB/en-GB.mod_toolbar.sys.ini
/administrator/language/en-GB/en-GB.mod_unread.ini
/administrator/language/en-GB/en-GB.mod_unread.sys.ini
/administrator/language/en-GB/en-GB.mod_version.ini
/administrator/language/en-GB/en-GB.mod_version.sys.ini
/administrator/language/en-GB/en-GB.plg_actionlog_joomla.ini
/administrator/language/en-GB/en-GB.plg_actionlog_joomla.sys.ini
/administrator/language/en-GB/en-GB.plg_authentication_cookie.ini
/administrator/language/en-GB/en-GB.plg_authentication_cookie.sys.ini
/administrator/language/en-GB/en-GB.plg_authentication_example.ini
/administrator/language/en-GB/en-GB.plg_authentication_example.sys.ini
/administrator/language/en-GB/en-GB.plg_authentication_gmail.ini
/administrator/language/en-GB/en-GB.plg_authentication_gmail.sys.ini
/administrator/language/en-GB/en-GB.plg_authentication_joomla.ini
/administrator/language/en-GB/en-GB.plg_authentication_joomla.sys.ini
/administrator/language/en-GB/en-GB.plg_authentication_ldap.ini
/administrator/language/en-GB/en-GB.plg_authentication_ldap.sys.ini
/administrator/language/en-GB/en-GB.plg_captcha_recaptcha.ini
/administrator/language/en-GB/en-GB.plg_captcha_recaptcha.sys.ini
/administrator/language/en-GB/en-GB.plg_captcha_recaptcha_invisible.ini
/administrator/language/en-GB/en-GB.plg_captcha_recaptcha_invisible.sys.ini
/administrator/language/en-GB/en-GB.plg_content_confirmconsent.ini
/administrator/language/en-GB/en-GB.plg_content_confirmconsent.sys.ini
/administrator/language/en-GB/en-GB.plg_content_contact.ini
/administrator/language/en-GB/en-GB.plg_content_contact.sys.ini
/administrator/language/en-GB/en-GB.plg_content_emailcloak.ini
/administrator/language/en-GB/en-GB.plg_content_emailcloak.sys.ini
/administrator/language/en-GB/en-GB.plg_content_example.ini
/administrator/language/en-GB/en-GB.plg_content_example.sys.ini
/administrator/language/en-GB/en-GB.plg_content_fields.ini
/administrator/language/en-GB/en-GB.plg_content_fields.sys.ini
/administrator/language/en-GB/en-GB.plg_content_finder.ini
/administrator/language/en-GB/en-GB.plg_content_finder.sys.ini
/administrator/language/en-GB/en-GB.plg_content_geshi.ini
/administrator/language/en-GB/en-GB.plg_content_geshi.sys.ini
/administrator/language/en-GB/en-GB.plg_content_joomla.ini
/administrator/language/en-GB/en-GB.plg_content_joomla.sys.ini
/administrator/language/en-GB/en-GB.plg_content_loadmodule.ini
/administrator/language/en-GB/en-GB.plg_content_loadmodule.sys.ini
/administrator/language/en-GB/en-GB.plg_content_pagebreak.ini
/administrator/language/en-GB/en-GB.plg_content_pagebreak.sys.ini
/administrator/language/en-GB/en-GB.plg_content_pagenavigation.ini
/administrator/language/en-GB/en-GB.plg_content_pagenavigation.sys.ini
/administrator/language/en-GB/en-GB.plg_content_vote.ini
/administrator/language/en-GB/en-GB.plg_content_vote.sys.ini
/administrator/language/en-GB/en-GB.plg_editors_codemirror.ini
/administrator/language/en-GB/en-GB.plg_editors_codemirror.sys.ini
/administrator/language/en-GB/en-GB.plg_editors_none.ini
/administrator/language/en-GB/en-GB.plg_editors_none.sys.ini
/administrator/language/en-GB/en-GB.plg_editors_tinymce.ini
/administrator/language/en-GB/en-GB.plg_editors_tinymce.sys.ini
/administrator/language/en-GB/en-GB.plg_editors-xtd_article.ini
/administrator/language/en-GB/en-GB.plg_editors-xtd_article.sys.ini
/administrator/language/en-GB/en-GB.plg_editors-xtd_contact.ini
/administrator/language/en-GB/en-GB.plg_editors-xtd_contact.sys.ini
/administrator/language/en-GB/en-GB.plg_editors-xtd_fields.ini
/administrator/language/en-GB/en-GB.plg_editors-xtd_fields.sys.ini
/administrator/language/en-GB/en-GB.plg_editors-xtd_image.ini
/administrator/language/en-GB/en-GB.plg_editors-xtd_image.sys.ini
/administrator/language/en-GB/en-GB.plg_editors-xtd_menu.ini
/administrator/language/en-GB/en-GB.plg_editors-xtd_menu.sys.ini
/administrator/language/en-GB/en-GB.plg_editors-xtd_module.ini
/administrator/language/en-GB/en-GB.plg_editors-xtd_module.sys.ini
/administrator/language/en-GB/en-GB.plg_editors-xtd_pagebreak.ini
/administrator/language/en-GB/en-GB.plg_editors-xtd_pagebreak.sys.ini
/administrator/language/en-GB/en-GB.plg_editors-xtd_readmore.ini
/administrator/language/en-GB/en-GB.plg_editors-xtd_readmore.sys.ini
/administrator/language/en-GB/en-GB.plg_extension_example.ini
/administrator/language/en-GB/en-GB.plg_extension_example.sys.ini
/administrator/language/en-GB/en-GB.plg_extension_joomla.ini
/administrator/language/en-GB/en-GB.plg_extension_joomla.sys.ini
/administrator/language/en-GB/en-GB.plg_fields_calendar.ini
/administrator/language/en-GB/en-GB.plg_fields_calendar.sys.ini
/administrator/language/en-GB/en-GB.plg_fields_checkboxes.ini
/administrator/language/en-GB/en-GB.plg_fields_checkboxes.sys.ini
/administrator/language/en-GB/en-GB.plg_fields_color.ini
/administrator/language/en-GB/en-GB.plg_fields_color.sys.ini
/administrator/language/en-GB/en-GB.plg_fields_editor.ini
/administrator/language/en-GB/en-GB.plg_fields_editor.sys.ini
/administrator/language/en-GB/en-GB.plg_fields_imagelist.ini
/administrator/language/en-GB/en-GB.plg_fields_imagelist.sys.ini
/administrator/language/en-GB/en-GB.plg_fields_integer.ini
/administrator/language/en-GB/en-GB.plg_fields_integer.sys.ini
/administrator/language/en-GB/en-GB.plg_fields_list.ini
/administrator/language/en-GB/en-GB.plg_fields_list.sys.ini
/administrator/language/en-GB/en-GB.plg_fields_media.ini
/administrator/language/en-GB/en-GB.plg_fields_media.sys.ini
/administrator/language/en-GB/en-GB.plg_fields_radio.ini
/administrator/language/en-GB/en-GB.plg_fields_radio.sys.ini
/administrator/language/en-GB/en-GB.plg_fields_repeatable.ini
/administrator/language/en-GB/en-GB.plg_fields_repeatable.sys.ini
/administrator/language/en-GB/en-GB.plg_fields_sql.ini
/administrator/language/en-GB/en-GB.plg_fields_sql.sys.ini
/administrator/language/en-GB/en-GB.plg_fields_text.ini
/administrator/language/en-GB/en-GB.plg_fields_text.sys.ini
/administrator/language/en-GB/en-GB.plg_fields_textarea.ini
/administrator/language/en-GB/en-GB.plg_fields_textarea.sys.ini
/administrator/language/en-GB/en-GB.plg_fields_url.ini
/administrator/language/en-GB/en-GB.plg_fields_url.sys.ini
/administrator/language/en-GB/en-GB.plg_fields_user.ini
/administrator/language/en-GB/en-GB.plg_fields_user.sys.ini
/administrator/language/en-GB/en-GB.plg_fields_usergrouplist.ini
/administrator/language/en-GB/en-GB.plg_fields_usergrouplist.sys.ini
/administrator/language/en-GB/en-GB.plg_finder_categories.ini
/administrator/language/en-GB/en-GB.plg_finder_categories.sys.ini
/administrator/language/en-GB/en-GB.plg_finder_contacts.ini
/administrator/language/en-GB/en-GB.plg_finder_contacts.sys.ini
/administrator/language/en-GB/en-GB.plg_finder_content.ini
/administrator/language/en-GB/en-GB.plg_finder_content.sys.ini
/administrator/language/en-GB/en-GB.plg_finder_newsfeeds.ini
/administrator/language/en-GB/en-GB.plg_finder_newsfeeds.sys.ini
/administrator/language/en-GB/en-GB.plg_finder_tags.ini
/administrator/language/en-GB/en-GB.plg_finder_tags.sys.ini
/administrator/language/en-GB/en-GB.plg_installer_folderinstaller.ini
/administrator/language/en-GB/en-GB.plg_installer_folderinstaller.sys.ini
/administrator/language/en-GB/en-GB.plg_installer_packageinstaller.ini
/administrator/language/en-GB/en-GB.plg_installer_packageinstaller.sys.ini
/administrator/language/en-GB/en-GB.plg_installer_urlinstaller.ini
/administrator/language/en-GB/en-GB.plg_installer_urlinstaller.sys.ini
/administrator/language/en-GB/en-GB.plg_privacy_actionlogs.ini
/administrator/language/en-GB/en-GB.plg_privacy_actionlogs.sys.ini
/administrator/language/en-GB/en-GB.plg_privacy_consents.ini
/administrator/language/en-GB/en-GB.plg_privacy_consents.sys.ini
/administrator/language/en-GB/en-GB.plg_privacy_contact.ini
/administrator/language/en-GB/en-GB.plg_privacy_contact.sys.ini
/administrator/language/en-GB/en-GB.plg_privacy_content.ini
/administrator/language/en-GB/en-GB.plg_privacy_content.sys.ini
/administrator/language/en-GB/en-GB.plg_privacy_message.ini
/administrator/language/en-GB/en-GB.plg_privacy_message.sys.ini
/administrator/language/en-GB/en-GB.plg_privacy_user.ini
/administrator/language/en-GB/en-GB.plg_privacy_user.sys.ini
/administrator/language/en-GB/en-GB.plg_quickicon_extensionupdate.ini
/administrator/language/en-GB/en-GB.plg_quickicon_extensionupdate.sys.ini
/administrator/language/en-GB/en-GB.plg_quickicon_joomlaupdate.ini
/administrator/language/en-GB/en-GB.plg_quickicon_joomlaupdate.sys.ini
/administrator/language/en-GB/en-GB.plg_quickicon_phpversioncheck.ini
/administrator/language/en-GB/en-GB.plg_quickicon_phpversioncheck.sys.ini
/administrator/language/en-GB/en-GB.plg_quickicon_privacycheck.ini
/administrator/language/en-GB/en-GB.plg_quickicon_privacycheck.sys.ini
/administrator/language/en-GB/en-GB.plg_sampledata_blog.ini
/administrator/language/en-GB/en-GB.plg_sampledata_blog.sys.ini
/administrator/language/en-GB/en-GB.plg_search_categories.ini
/administrator/language/en-GB/en-GB.plg_search_categories.sys.ini
/administrator/language/en-GB/en-GB.plg_search_contacts.ini
/administrator/language/en-GB/en-GB.plg_search_contacts.sys.ini
/administrator/language/en-GB/en-GB.plg_search_content.ini
/administrator/language/en-GB/en-GB.plg_search_content.sys.ini
/administrator/language/en-GB/en-GB.plg_search_newsfeeds.ini
/administrator/language/en-GB/en-GB.plg_search_newsfeeds.sys.ini
/administrator/language/en-GB/en-GB.plg_search_tags.ini
/administrator/language/en-GB/en-GB.plg_search_tags.sys.ini
/administrator/language/en-GB/en-GB.plg_search_weblinks.ini
/administrator/language/en-GB/en-GB.plg_search_weblinks.sys.ini
/administrator/language/en-GB/en-GB.plg_system_actionlogs.ini
/administrator/language/en-GB/en-GB.plg_system_actionlogs.sys.ini
/administrator/language/en-GB/en-GB.plg_system_cache.ini
/administrator/language/en-GB/en-GB.plg_system_cache.sys.ini
/administrator/language/en-GB/en-GB.plg_system_debug.ini
/administrator/language/en-GB/en-GB.plg_system_debug.sys.ini
/administrator/language/en-GB/en-GB.plg_system_fields.ini
/administrator/language/en-GB/en-GB.plg_system_fields.sys.ini
/administrator/language/en-GB/en-GB.plg_system_highlight.ini
/administrator/language/en-GB/en-GB.plg_system_highlight.sys.ini
/administrator/language/en-GB/en-GB.plg_system_languagecode.ini
/administrator/language/en-GB/en-GB.plg_system_languagecode.sys.ini
/administrator/language/en-GB/en-GB.plg_system_languagefilter.ini
/administrator/language/en-GB/en-GB.plg_system_languagefilter.sys.ini
/administrator/language/en-GB/en-GB.plg_system_log.ini
/administrator/language/en-GB/en-GB.plg_system_log.sys.ini
/administrator/language/en-GB/en-GB.plg_system_logout.ini
/administrator/language/en-GB/en-GB.plg_system_logout.sys.ini
/administrator/language/en-GB/en-GB.plg_system_logrotation.ini
/administrator/language/en-GB/en-GB.plg_system_logrotation.sys.ini
/administrator/language/en-GB/en-GB.plg_system_p3p.ini
/administrator/language/en-GB/en-GB.plg_system_p3p.sys.ini
/administrator/language/en-GB/en-GB.plg_system_privacyconsent.ini
/administrator/language/en-GB/en-GB.plg_system_privacyconsent.sys.ini
/administrator/language/en-GB/en-GB.plg_system_redirect.ini
/administrator/language/en-GB/en-GB.plg_system_redirect.sys.ini
/administrator/language/en-GB/en-GB.plg_system_remember.ini
/administrator/language/en-GB/en-GB.plg_system_remember.sys.ini
/administrator/language/en-GB/en-GB.plg_system_sef.ini
/administrator/language/en-GB/en-GB.plg_system_sef.sys.ini
/administrator/language/en-GB/en-GB.plg_system_sessiongc.ini
/administrator/language/en-GB/en-GB.plg_system_sessiongc.sys.ini
/administrator/language/en-GB/en-GB.plg_system_stats.ini
/administrator/language/en-GB/en-GB.plg_system_stats.sys.ini
/administrator/language/en-GB/en-GB.plg_system_updatenotification.ini
/administrator/language/en-GB/en-GB.plg_system_updatenotification.sys.ini
/administrator/language/en-GB/en-GB.plg_twofactorauth_totp.ini
/administrator/language/en-GB/en-GB.plg_twofactorauth_totp.sys.ini
/administrator/language/en-GB/en-GB.plg_twofactorauth_yubikey.ini
/administrator/language/en-GB/en-GB.plg_twofactorauth_yubikey.sys.ini
/administrator/language/en-GB/en-GB.plg_user_contactcreator.ini
/administrator/language/en-GB/en-GB.plg_user_contactcreator.sys.ini
/administrator/language/en-GB/en-GB.plg_user_example.ini
/administrator/language/en-GB/en-GB.plg_user_example.sys.ini
/administrator/language/en-GB/en-GB.plg_user_joomla.ini
/administrator/language/en-GB/en-GB.plg_user_joomla.sys.ini
/administrator/language/en-GB/en-GB.plg_user_profile.ini
/administrator/language/en-GB/en-GB.plg_user_profile.sys.ini
/administrator/language/en-GB/en-GB.plg_user_terms.ini
/administrator/language/en-GB/en-GB.plg_user_terms.sys.ini
/administrator/language/en-GB/en-GB.xml
/administrator/language/en-GB/index.html
/administrator/language/index.html
/administrator/language/overrides/*
/administrator/logs/*
/administrator/manifests/files/*
/administrator/manifests/libraries/*
/administrator/manifests/packages/*
/administrator/modules/mod_custom/*
/administrator/modules/mod_feed/*
/administrator/modules/mod_latest/*
/administrator/modules/mod_latestactions/*
/administrator/modules/mod_logged/*
/administrator/modules/mod_login/*
/administrator/modules/mod_menu/*
/administrator/modules/mod_multilangstatus/*
/administrator/modules/mod_online/*
/administrator/modules/mod_popular/*
/administrator/modules/mod_privacy_dashboard/*
/administrator/modules/mod_quickicon/*
/administrator/modules/mod_sampledata/*
/administrator/modules/mod_stats_admin/*
/administrator/modules/mod_status/*
/administrator/modules/mod_submenu/*
/administrator/modules/mod_title/*
/administrator/modules/mod_toolbar/*
/administrator/modules/mod_unread/*
/administrator/modules/mod_version/*
/administrator/templates/hathor/*
/administrator/templates/isis/*
/administrator/templates/system/*
/bin/*
/cache/*
/cli/*
/components/com_ajax/*
/components/com_banners/*
/components/com_config/*
/components/com_contact/*
/components/com_content/*
/components/com_contenthistory/*
/components/com_fields/*
/components/com_finder/*
/components/com_mailto/*
/components/com_media/*
/components/com_newsfeeds/*
/components/com_privacy/*
/components/com_search/*
/components/com_tags/*
/components/com_users/*
/components/com_weblinks/*
/components/com_wrapper/*
/images/banners/*
/images/headers/*
/images/joomla*
/images/sampledata/*
/images/index.html
/includes/*
/installation/*
/language/en-GB/en-GB.com_ajax.ini
/language/en-GB/en-GB.com_config.ini
/language/en-GB/en-GB.com_contact.ini
/language/en-GB/en-GB.com_content.ini
/language/en-GB/en-GB.com_finder.ini
/language/en-GB/en-GB.com_mailto.ini
/language/en-GB/en-GB.com_media.ini
/language/en-GB/en-GB.com_messages.ini
/language/en-GB/en-GB.com_newsfeeds.ini
/language/en-GB/en-GB.com_privacy.ini
/language/en-GB/en-GB.com_search.ini
/language/en-GB/en-GB.com_tags.ini
/language/en-GB/en-GB.com_users.ini
/language/en-GB/en-GB.com_weblinks.ini
/language/en-GB/en-GB.com_wrapper.ini
/language/en-GB/en-GB.files_joomla.sys.ini
/language/en-GB/en-GB.ini
/language/en-GB/en-GB.lib_fof.ini
/language/en-GB/en-GB.lib_fof.sys.ini
/language/en-GB/en-GB.lib_idna_convert.sys.ini
/language/en-GB/en-GB.lib_joomla.ini
/language/en-GB/en-GB.lib_joomla.sys.ini
/language/en-GB/en-GB.lib_phpmailer.sys.ini
/language/en-GB/en-GB.lib_phputf8.sys.ini
/language/en-GB/en-GB.lib_simplepie.sys.ini
/language/en-GB/en-GB.localise.php
/language/en-GB/en-GB.mod_articles_archive.ini
/language/en-GB/en-GB.mod_articles_archive.sys.ini
/language/en-GB/en-GB.mod_articles_categories.ini
/language/en-GB/en-GB.mod_articles_categories.sys.ini
/language/en-GB/en-GB.mod_articles_category.ini
/language/en-GB/en-GB.mod_articles_category.sys.ini
/language/en-GB/en-GB.mod_articles_latest.ini
/language/en-GB/en-GB.mod_articles_latest.sys.ini
/language/en-GB/en-GB.mod_articles_news.ini
/language/en-GB/en-GB.mod_articles_news.sys.ini
/language/en-GB/en-GB.mod_articles_popular.ini
/language/en-GB/en-GB.mod_articles_popular.sys.ini
/language/en-GB/en-GB.mod_banners.ini
/language/en-GB/en-GB.mod_banners.sys.ini
/language/en-GB/en-GB.mod_breadcrumbs.ini
/language/en-GB/en-GB.mod_breadcrumbs.sys.ini
/language/en-GB/en-GB.mod_custom.ini
/language/en-GB/en-GB.mod_custom.sys.ini
/language/en-GB/en-GB.mod_feed.ini
/language/en-GB/en-GB.mod_feed.sys.ini
/language/en-GB/en-GB.mod_finder.ini
/language/en-GB/en-GB.mod_finder.sys.ini
/language/en-GB/en-GB.mod_footer.ini
/language/en-GB/en-GB.mod_footer.sys.ini
/language/en-GB/en-GB.mod_languages.ini
/language/en-GB/en-GB.mod_languages.sys.ini
/language/en-GB/en-GB.mod_login.ini
/language/en-GB/en-GB.mod_login.sys.ini
/language/en-GB/en-GB.mod_menu.ini
/language/en-GB/en-GB.mod_menu.sys.ini
/language/en-GB/en-GB.mod_random_image.ini
/language/en-GB/en-GB.mod_random_image.sys.ini
/language/en-GB/en-GB.mod_related_items.ini
/language/en-GB/en-GB.mod_related_items.sys.ini
/language/en-GB/en-GB.mod_search.ini
/language/en-GB/en-GB.mod_search.sys.ini
/language/en-GB/en-GB.mod_stats.ini
/language/en-GB/en-GB.mod_stats.sys.ini
/language/en-GB/en-GB.mod_syndicate.ini
/language/en-GB/en-GB.mod_syndicate.sys.ini
/language/en-GB/en-GB.mod_tags_popular.ini
/language/en-GB/en-GB.mod_tags_popular.sys.ini
/language/en-GB/en-GB.mod_tags_similar.ini
/language/en-GB/en-GB.mod_tags_similar.sys.ini
/language/en-GB/en-GB.mod_users_latest.ini
/language/en-GB/en-GB.mod_users_latest.sys.ini
/language/en-GB/en-GB.mod_weblinks.ini
/language/en-GB/en-GB.mod_weblinks.sys.ini
/language/en-GB/en-GB.mod_whosonline.ini
/language/en-GB/en-GB.mod_whosonline.sys.ini
/language/en-GB/en-GB.mod_wrapper.ini
/language/en-GB/en-GB.mod_wrapper.sys.ini
/language/en-GB/en-GB.tpl_atomic.ini
/language/en-GB/en-GB.tpl_atomic.sys.ini
/language/en-GB/en-GB.tpl_beez3.ini
/language/en-GB/en-GB.tpl_beez3.sys.ini
/language/en-GB/en-GB.tpl_beez5.ini
/language/en-GB/en-GB.tpl_beez5.sys.ini
/language/en-GB/en-GB.tpl_beez_20.ini
/language/en-GB/en-GB.tpl_beez_20.sys.ini
/language/en-GB/en-GB.tpl_protostar.ini
/language/en-GB/en-GB.tpl_protostar.sys.ini
/language/en-GB/en-GB.xml
/language/en-GB/index.html
/language/index.html
/language/overrides/*
/layouts/joomla/*
/libraries/cms/*
/libraries/fof/*
/libraries/idna_convert/*
/libraries/joomla/*
/libraries/legacy/*
/libraries/php-encryption/*
/libraries/phpass/*
/libraries/phpmailer/*
/libraries/phputf8/*
/libraries/simplepie/*
/libraries/vendor/*
/libraries/classmap.php
/libraries/cms.php
/libraries/import.legacy.php
/libraries/import.php
/libraries/index.html
/libraries/loader.php
/media/cms/*
/media/com_associations/*
/media/com_contact/*
/media/com_content/*
/media/com_contenthistory/*
/media/com_fields/*
/media/com_finder/*
/media/com_joomlaupdate/*
/media/com_mailto/*
/media/com_media/*
/media/com_menus/*
/media/com_modules/*
/media/com_newsfeeds/*
/media/com_privacy/*
/media/com_tags/*
/media/com_weblinks/*
/media/com_wrapper/*
/media/contacts/*
/media/editors/*
/media/jui/*
/media/mailto/*
/media/media/*
/media/mod_languages/*
/media/overrider/*
/media/plg_captcha_recaptcha/*
/media/plg_captcha_recaptcha_invisible/*
/media/plg_editors-xtd_article/*
/media/plg_quickicon_extensionupdate/*
/media/plg_quickicon_joomlaupdate/*
/media/plg_quickicon_privacycheck/*
/media/plg_system_debug/*
/media/plg_system_highlight/*
/media/plg_system_stats/*
/media/plg_twofactorauth_totp/*
/media/system/*
/modules/mod_articles_archive/*
/modules/mod_articles_categories/*
/modules/mod_articles_category/*
/modules/mod_articles_latest/*
/modules/mod_articles_news/*
/modules/mod_articles_popular/*
/modules/mod_banners/*
/modules/mod_breadcrumbs/*
/modules/mod_custom/*
/modules/mod_feed/*
/modules/mod_finder/*
/modules/mod_footer/*
/modules/mod_languages/*
/modules/mod_login/*
/modules/mod_menu/*
/modules/mod_random_image/*
/modules/mod_related_items/*
/modules/mod_search/*
/modules/mod_stats/*
/modules/mod_syndicate/*
/modules/mod_tags_popular/*
/modules/mod_tags_similar/*
/modules/mod_users_latest/*
/modules/mod_weblinks/*
/modules/mod_whosonline/*
/modules/mod_wrapper/*
/plugins/actionlog/joomla/*
/plugins/authentication/cookie/*
/plugins/authentication/example/*
/plugins/authentication/gmail/*
/plugins/authentication/joomla/*
/plugins/authentication/ldap/*
/plugins/captcha/recaptcha/*
/plugins/captcha/recaptcha_invisible/*
/plugins/content/confirmconsent/*
/plugins/content/contact/*
/plugins/content/emailcloak/*
/plugins/content/example/*
/plugins/content/fields/*
/plugins/content/finder/*
/plugins/content/geshi/*
/plugins/content/joomla/*
/plugins/content/loadmodule/*
/plugins/content/pagebreak/*
/plugins/content/pagenavigation/*
/plugins/content/vote/*
/plugins/editors/codemirror/*
/plugins/editors/none/*
/plugins/editors/tinymce/*
/plugins/editors-xtd/article/*
/plugins/editors-xtd/contact/*
/plugins/editors-xtd/fields/*
/plugins/editors-xtd/image/*
/plugins/editors-xtd/menu/*
/plugins/editors-xtd/module/*
/plugins/editors-xtd/pagebreak/*
/plugins/editors-xtd/readmore/*
/plugins/extension/example/*
/plugins/extension/joomla/*
/plugins/fields/calendar/*
/plugins/fields/checkboxes/*
/plugins/fields/color/*
/plugins/fields/editor/*
/plugins/fields/imagelist/*
/plugins/fields/integer/*
/plugins/fields/list/*
/plugins/fields/media/*
/plugins/fields/radio/*
/plugins/fields/repeatable/*
/plugins/fields/sql/*
/plugins/fields/text/*
/plugins/fields/textarea/*
/plugins/fields/url/*
/plugins/fields/user/*
/plugins/fields/usergrouplist/*
/plugins/finder/categories/*
/plugins/finder/contacts/*
/plugins/finder/content/*
/plugins/finder/newsfeeds/*
/plugins/finder/tags/*
/plugins/installer/folderinstaller/*
/plugins/installer/packageinstaller/*
/plugins/installer/urlinstaller/*
/plugins/privacy/actionlogs/*
/plugins/privacy/consents/*
/plugins/privacy/contact/*
/plugins/privacy/content/*
/plugins/privacy/message/*
/plugins/privacy/user/*
/plugins/quickicon/extensionupdate/*
/plugins/quickicon/joomlaupdate/*
/plugins/quickicon/phpversioncheck/*
/plugins/quickicon/privacycheck/*
/plugins/quickicon/index.html
/plugins/sampledata/blog/*
/plugins/search/categories/*
/plugins/search/contacts/*
/plugins/search/content/*
/plugins/search/newsfeeds/*
/plugins/search/tags/*
/plugins/search/weblinks/*
/plugins/search/index.html
/plugins/system/actionlogs/*
/plugins/system/cache/*
/plugins/system/debug/*
/plugins/system/fields/*
/plugins/system/highlight/*
/plugins/system/languagecode/*
/plugins/system/languagefilter/*
/plugins/system/log/*
/plugins/system/logout/*
/plugins/system/logrotation/*
/plugins/system/p3p/*
/plugins/system/privacyconsent/*
/plugins/system/redirect/*
/plugins/system/remember/*
/plugins/system/sef/*
/plugins/system/sessiongc/*
/plugins/system/stats/*
/plugins/system/updatenotification/*
/plugins/system/index.html
/plugins/twofactorauth/totp/*
/plugins/twofactorauth/yubikey/*
/plugins/user/contactcreator/*
/plugins/user/example/*
/plugins/user/joomla/*
/plugins/user/profile/*
/plugins/user/terms/*
/plugins/user/index.html
/plugins/user/index.html
/plugins/index.html
/templates/beez3/*
/templates/protostar/*
/templates/system/*
/templates/index.html
/tmp/*
/configuration.php
/htaccess.txt
/index.php
/joomla.xml
/LICENSE.txt
/README.txt
/robots.txt.dist
/web.config.txt
# ============================================================
# Keep-empty folders helper
# ============================================================
!.gitkeep
# ── MokoStandards sync (auto-appended) ────────────────────────────────
/.claude
# ── MokoStandards sync (auto-appended) ────────────────────────────────
!src/media/vendor/

20
.mokostandards Normal file
View File

@@ -0,0 +1,20 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
# DEFGROUP: MokoStandards.Templates.Config
# INGROUP: MokoStandards.Templates
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/configs/moko-standards.yml
# VERSION: 04.04.01
# BRIEF: Governance attachment template — synced to .mokostandards in every governed repository
# NOTE: Tokens replaced at sync time: mokoconsulting-tech, MokoCassiopeia, waas-component, 04.04.00
#
# This file is managed automatically by MokoStandards bulk sync.
# Do not edit manually — changes will be overwritten on the next sync.
# To update governance settings, open a PR in MokoStandards instead:
# https://github.com/mokoconsulting-tech/MokoStandards
standards_source: "https://github.com/mokoconsulting-tech/MokoStandards"
standards_version: "04.04.00"
platform: "waas-component"
governed_repo: "mokoconsulting-tech/MokoCassiopeia"

View File

@@ -1,98 +1,87 @@
<!--
Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
<!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
This file is part of a Moko Consulting project.
This file is part of a Moko Consulting project.
SPDX-License-Identifier: GPL-3.0-or-later
SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
# FILE INFORMATION
DEFGROUP: Joomla.Template
INGROUP: MokoCassiopeia.Governance
REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
FILE: CODE_OF_CONDUCT.md
VERSION: 03.06.03
BRIEF: Contributor code of conduct for the MokoCassiopeia project.
PATH: /CODE_OF_CONDUCT.md
NOTE: This document defines behavioral expectations and enforcement processes.
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 (./LICENSE.md).
# FILE INFORMATION
DEFGROUP:
INGROUP: Project.Documentation
REPO:
VERSION: 04.04.01
PATH: ./CODE_OF_CONDUCT.md
BRIEF: Reference + packaging repo for Moko Consulting Developer GPT Other Default
-->
# Code of Conduct
## Code of Conduct
## 1. Purpose
This Code of Conduct establishes expectations for behavior within the MokoCassiopeia project community. The objective is to maintain a professional, inclusive, and respectful environment aligned with open source governance best practices.
The purpose of this Code of Conduct is to ensure a safe, inclusive, and respectful environment for all contributors and participants in Moko Consulting projects. This applies to all interactions, whether in repositories, issue trackers, documentation, meetings, or community spaces.
## Scope
## 2. Our Standards
This Code of Conduct applies to all project spaces, including:
Participants are expected to uphold behaviors that strengthen our community, including:
* GitHub repositories, issues, pull requests, discussions, and security advisories.
* Project documentation, workflows, and release processes.
* Any communication channels officially associated with the project.
Demonstrating empathy and respect toward others.
Being inclusive of diverse viewpoints and backgrounds.
Gracefully accepting constructive feedback.
Prioritizing collaboration over conflict.
Showing professionalism in all interactions.
## Our Standards
### Unacceptable behavior includes:
Participants are expected to:
Harassment, discrimination, or derogatory comments.
Threatening or violent language or actions.
Disruptive, aggressive, or intentionally harmful behavior.
Publishing others private information without permission.
Any behavior that violates applicable laws.
* Communicate professionally and respectfully.
* Provide constructive feedback focused on technical merit and project objectives.
* Respect differing viewpoints, experience levels, and backgrounds.
* Follow documented contribution, security, and governance policies.
## 3. Responsibilities of Maintainers
Unacceptable behavior includes:
Maintainers are responsible for:
* Harassment, discrimination, or exclusionary conduct.
* Personal attacks, insults, or inflammatory comments.
* Publishing private information without consent.
* Disruptive behavior that materially interferes with project operations.
Clarifying acceptable behavior.
Taking appropriate corrective action when unacceptable behavior occurs.
Removing, editing, or rejecting contributions that violate this Code.
Temporarily or permanently banning contributors who engage in repeated or severe violations.
## Enforcement Responsibilities
## 4. Scope
Project maintainers are responsible for:
This Code applies to:
* Clarifying standards when questions arise.
* Taking appropriate and proportionate corrective action when violations occur.
* Maintaining confidentiality to the extent practical during investigations.
All Moko Consulting repositories.
All documentation and collaboration platforms.
Public and private communication related to project activities.
Any representation of Moko Consulting in online or offline spaces.
## Reporting
## 5. Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported through:
Instances of misconduct may be reported to:
**[hello@mokoconsulting.tech](mailto:hello@mokoconsulting.tech)**
* Email: `hello@mokoconsulting.tech` with subject `CODE OF CONDUCT: MokoCassiopeia`.
All reports will be reviewed and investigated promptly and fairly. Maintainers are obligated to maintain confidentiality where possible.
Reports should include relevant context, links, screenshots, or other supporting information.
Consequences may include:
## Enforcement Guidelines
A warning.
Required training or mediation.
Temporary or permanent bans.
Escalation to legal authorities when required.
Corrective actions may include, but are not limited to:
## 6. Acknowledgements
* Private warning or request for corrective action.
* Temporary or permanent restriction from project participation.
* Removal of content that violates this Code of Conduct.
This Code of Conduct is inspired by widely adopted community guidelines, including the Contributor Covenant and major open-source collaboration standards.
Decisions are made based on impact, severity, and pattern of behavior.
## 7. Related Documents
## No Retaliation
[Governance Guide](./docs-governance.md)
[Contributor Guide](./docs-contributing.md)
[Documentation Index](./docs-index.md)
Retaliation against individuals who report concerns in good faith is not tolerated. Any retaliatory behavior will be treated as a separate violation.
## Jurisdiction
This project is managed from Tennessee, USA. This statement is informational and does not constitute legal advice.
---
## Metadata
* **Document:** CODE_OF_CONDUCT.md
* **Repository:** [https://github.com/mokoconsulting-tech/MokoCassiopeia](https://github.com/mokoconsulting-tech/MokoCassiopeia)
* **Path:** /CODE_OF_CONDUCT.md
* **Owner:** Moko Consulting
* **Version:** 03.06.00
* **Status:** Active
* **Effective Date:** 2025-12-18
* **Last Reviewed:** 2025-12-18
## Revision History
| Date | Change Summary | Author |
| ---------- | ----------------------------------------------------------------------------- | --------------- |
| 2025-12-18 | Initial publication of contributor conduct standards and enforcement process. | Moko Consulting |
This Code of Conduct is a living document and may be updated following the established Change Management process.

View File

@@ -1,145 +1,128 @@
<!--
Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
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 (./LICENSE).
# FILE INFORMATION
DEFGROUP: Joomla.Template
INGROUP: MokoCassiopeia.Governance
DEFGROUP: {{DEFGROUP}}
INGROUP: Project.Documentation
REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
FILE: CONTRIBUTING.md
VERSION: 03.06.03
BRIEF: Contribution guidelines for the MokoCassiopeia project.
PATH: /CONTRIBUTING.md
NOTE: This document defines contribution workflow, standards, and governance alignment.
-->
VERSION: 04.04.00
PATH: ./CONTRIBUTING.md
BRIEF: How to contribute; branch strategy, commit conventions, PR workflow, and release pipeline
-->
## Contributing
# Contributing
This document defines how to contribute to the MokoCassiopeia project. The goal is to ensure changes are reviewable, auditable, and aligned with project governance and release processes.
Thank you for your interest in contributing to **MokoCassiopeia**!
## Scope
This repository is governed by **[MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards)** — the authoritative source of coding standards, workflows, and policies for all Moko Consulting repositories.
These guidelines apply to all contributions, including:
## Branch Strategy
* Source code changes
* Documentation updates
* Bug reports and enhancement proposals
| Branch | Purpose | Deploys To |
|--------|---------|------------|
| `main` | Bleeding edge — all development merges here | CI only |
| `dev/XX.YY.ZZ` | Feature development | Dev server (version: "development") |
| `version/XX.YY` | Stable frozen snapshot | Demo + RS servers |
## Prerequisites
### Development Workflow
Contributors are expected to:
* Have a working understanding of Joomla template structure.
* Be familiar with Git and GitHub pull request workflows.
* Review repository governance documents prior to submitting changes.
* Set up the development environment using the provided tools.
### Quick Setup
For first-time contributors:
```bash
# Clone the repository
git clone https://github.com/mokoconsulting-tech/MokoCassiopeia.git
cd MokoCassiopeia
```
1. Create branch: git checkout -b dev/XX.YY.ZZ/my-feature
2. Develop + test (dev server auto-deploys on push)
3. Open PR → main (squash merge only)
4. Auto-release (version branch + tag + GitHub Release created automatically)
```
See [docs/QUICK_START.md](./docs/QUICK_START.md) for detailed setup instructions.
### Branch Naming
## Development Tools
| Prefix | Use |
|--------|-----|
| `dev/XX.YY.ZZ` | Feature development (e.g., `dev/02.00.00/add-extrafields`) |
| `version/XX.YY` | Stable release (auto-created, never manually pushed) |
| `chore/` | Automated sync branches (managed by MokoStandards) |
The repository provides several tools to streamline development:
> **Never use** `feature/`, `hotfix/`, or `release/` prefixes — they are not part of the MokoStandards branch strategy.
* **Pre-commit Hooks**: Automatic local validation before commits
## Commit Conventions
## Contribution Workflow
Use [conventional commits](https://www.conventionalcommits.org/):
1. Fork the repository.
2. Create a branch from the active development branch.
3. Make focused, minimal changes that address a single concern.
4. Submit a pull request with a clear description of intent and impact.
```
feat(scope): add new extrafield for invoice tracking
fix(sql): correct column type in llx_mytable
docs(readme): update installation instructions
chore(deps): bump enterprise library to 04.02.30
```
Direct commits to protected branches are not permitted.
**Valid types:** `feat` | `fix` | `docs` | `chore` | `ci` | `refactor` | `style` | `test` | `perf` | `revert` | `build`
## Branching and Versioning
## Pull Request Workflow
* Development work occurs on designated development branches.
* Releases are produced from versioned branches following repository standards.
* Contributors should not bump version numbers unless explicitly requested.
1. **Branch** from `main` using `dev/XX.YY.ZZ/description` format
2. **Bump** the patch version in `README.md` before opening the PR
3. **Title** must be a valid conventional commit subject line
4. **Target** `main` — squash merge only (merge commits are disabled)
5. **CI checks** must pass before merge
## Coding and Formatting Standards
### What Happens on Merge
All contributions must:
When your PR is merged to `main`, these workflows run automatically:
* Follow Joomla coding standards where applicable.
* Conform to Moko Consulting repository standards for headers, metadata, and file structure.
* Avoid introducing tabs, inconsistent path separators, or non portable assumptions.
1. **sync-version-on-merge** — auto-bumps patch version, propagates to all file headers
2. **auto-release** — creates `version/XX.YY` branch, git tag, and GitHub Release
3. **deploy-demo / deploy-rs** — deploys to demo and RS servers (if `src/**` changed)
Automated checks may reject changes that do not meet these requirements.
## Coding Standards
## Documentation Standards
All contributions must follow [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards):
Documentation changes must:
| Standard | Reference |
|----------|-----------|
| Coding Style | [coding-style-guide.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/coding-style-guide.md) |
| File Headers | [file-header-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/file-header-standards.md) |
| Branching | [branch-release-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/branch-release-strategy.md) |
| Merge Strategy | [merge-strategy.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/merge-strategy.md) |
| Scripting | [scripting-standards.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/scripting-standards.md) |
| Build & Release | [build-release.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/workflows/build-release.md) |
* Include required metadata and revision history sections.
* Avoid embedding version numbers in revision history tables.
* Preserve existing structure unless a structural change is explicitly proposed.
## PR Checklist
## Commit Messages
- [ ] Branch named `dev/XX.YY.ZZ/description`
- [ ] Patch version bumped in `README.md`
- [ ] Conventional commit format for PR title
- [ ] All new files have FILE INFORMATION headers
- [ ] `declare(strict_types=1)` in all PHP files
- [ ] PHPDoc on all public methods
- [ ] Tests pass
- [ ] CHANGELOG.md updated
- [ ] No secrets, tokens, or credentials committed
Commit messages should:
## Custom Workflows
* Be concise and descriptive.
* Focus on what changed and why.
* Avoid referencing internal issue trackers unless required.
Place repo-specific workflows in `.github/workflows/custom/` — they are **never overwritten or deleted** by MokoStandards sync:
## Reporting Issues
Bug reports and enhancement requests should be filed as GitHub issues and include:
* Clear reproduction steps or use cases.
* Expected versus actual behavior.
* Relevant environment details.
Security related issues must follow the process defined in SECURITY.md and must not be reported publicly.
## Review Process
All pull requests are subject to review. Review criteria include:
* Technical correctness
* Alignment with project goals
* Maintainability and clarity
* Risk introduced to release and update processes
Maintainers may request changes prior to approval.
```
.github/workflows/
├── deploy-dev.yml ← Synced from MokoStandards
├── auto-release.yml ← Synced from MokoStandards
└── custom/ ← Your custom workflows (safe)
└── my-custom-ci.yml
```
## License
By contributing, you agree that your contributions will be licensed under GPL-3.0-or-later, consistent with the rest of the project.
## Code of Conduct
Participation in this project is governed by the Code of Conduct. Unacceptable behavior may result in contribution restrictions.
By contributing, you agree that your contributions will be licensed under the [GPL-3.0-or-later](LICENSE) license.
---
## Metadata
* **Document:** CONTRIBUTING.md
* **Repository:** [https://github.com/mokoconsulting-tech/MokoCassiopeia](https://github.com/mokoconsulting-tech/MokoCassiopeia)
* **Path:** /CONTRIBUTING.md
* **Owner:** Moko Consulting
* **Version:** 03.06.00
* **Status:** Active
* **Effective Date:** 2025-12-18
* **Last Reviewed:** 2025-12-18
## Revision History
| Date | Change Summary | Author |
| ---------- | ------------------------------------------------------------------------- | --------------- |
| 2025-12-18 | Initial publication of contribution guidelines and workflow expectations. | Moko Consulting |
*This file is synced from [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). Do not edit directly — changes will be overwritten on the next sync.*

View File

@@ -13,13 +13,19 @@
BRIEF: Documentation for MokoCassiopeia template
-->
[![Version](https://img.shields.io/badge/version-03.06.10-blue.svg?logo=v&logoColor=white)](https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/tag/v03)
[![License](https://img.shields.io/badge/license-GPL--3.0--or--later-green.svg?logo=gnu&logoColor=white)](LICENSE)
[![Joomla](https://img.shields.io/badge/Joomla-5.x%20%7C%206.x-red.svg?logo=joomla&logoColor=white)](https://www.joomla.org)
[![PHP](https://img.shields.io/badge/PHP-8.1%2B-777BB4.svg?logo=php&logoColor=white)](https://www.php.net)
# README - MokoCassiopeia (VERSION: 03.06.03)
**A Modern, Lightweight Joomla Template Based on Cassiopeia**
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Joomla](https://img.shields.io/badge/Joomla-4.4.x%20%7C%205.x-blue.svg)](https://www.joomla.org)
[![PHP](https://img.shields.io/badge/PHP-8.0%2B-blue.svg)](https://www.php.net)
: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
](https://img.shields.io/badge/Joomla-4.4.x%20%7C%205.x-blue.svg)](https://www.joomla.org)
](https://img.shields.io/badge/PHP-8.0%2B-blue.svg)](https://www.php.net)
MokoCassiopeia is a modern, lightweight enhancement layer built on top of Joomla's Cassiopeia template. It adds **Font Awesome 7**, **Bootstrap 5** helpers, an automatic **Table of Contents (TOC)** utility, advanced **Dark Mode** theming, and optional integrations for **Google Tag Manager** and **Google Analytics (GA4)**—all while maintaining minimal core template overrides for maximum upgrade compatibility.

View File

@@ -1,185 +1,240 @@
<!--
Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
This file is part of a Moko Consulting project.
This file is part of a Moko Consulting project.
SPDX-License-Identifier: GPL-3.0-or-later
SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
DEFGROUP: Joomla.Template
INGROUP: MokoCassiopeia.Governance
REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
FILE: SECURITY.md
VERSION: 03.06.02
BRIEF: Security policy and vulnerability reporting process for MokoCassiopeia.
PATH: /SECURITY.md
NOTE: This policy is process oriented and does not replace secure engineering practices.
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: [PROJECT_NAME]
INGROUP: [PROJECT_NAME].Documentation
REPO: [REPOSITORY_URL]
PATH: /SECURITY.md
VERSION: 04.04.01
BRIEF: Security vulnerability reporting and handling policy
-->
## Security Policy
# Security Policy
This document defines how MokoCassiopeia handles vulnerability intake, triage, remediation, and disclosure. The objective is to reduce risk, protect downstream users, and preserve operational continuity with a verifiable audit trail.
## Purpose and Scope
## Scope
This policy applies to:
* Repository source code, workflows, scripts, and build artifacts.
* Release packaging (ZIP outputs) generated from the repository.
* Configuration and metadata used for distribution (for example manifests and update metadata).
Out of scope:
* Vulnerabilities in upstream Joomla core, third party extensions, or external infrastructure not controlled by this repository.
* Issues that require physical access to a host, compromised administrator credentials, or a compromised hosting provider, unless the repository materially increases impact.
This document defines the security vulnerability reporting, response, and disclosure policy for [PROJECT_NAME] and all repositories governed by these standards. It establishes the authoritative process for responsible disclosure, assessment, remediation, and communication of security issues.
## Supported Versions
Security fixes are prioritized for:
Security updates are provided for the following versions:
* The latest released version.
* The current development line when it is actively used for release engineering.
| Version | Supported |
| ------- | ------------------ |
| [X.x.x] | :white_check_mark: |
| < [X.0] | :x: |
Backports may be provided based on impact, deployment footprint, and engineering capacity.
Only the current major version receives security updates. Users should upgrade to the latest supported version to receive security patches.
## Reporting a Vulnerability
Use one of the following channels:
### Where to Report
* GitHub Security Advisories (preferred): use the repository security tab to submit a private report.
* Email: send details to `hello@mokoconsulting.tech` with subject `SECURITY: MokoCassiopeia vulnerability report`.
**DO NOT** create public GitHub issues for security vulnerabilities.
Do not file a public GitHub issue for suspected security vulnerabilities.
Report security vulnerabilities privately to:
### What to include
**Email**: `security@[DOMAIN]`
Provide enough detail to reproduce and triage:
**Subject Line**: `[SECURITY] Brief Description`
* A clear description of the vulnerability and expected impact.
* A minimal proof of concept or reproduction steps.
* Affected versions, configuration assumptions, and environment details.
* Any proposed mitigation or patch.
* Your preferred contact details for follow up.
### What to Include
## Triage and Response Targets
A complete vulnerability report should include:
The project operates with response targets aligned to practical delivery realities:
1. **Description**: Clear explanation of the vulnerability
2. **Impact**: Potential security impact and severity assessment
3. **Affected Versions**: Which versions are vulnerable
4. **Reproduction Steps**: Detailed steps to reproduce the issue
5. **Proof of Concept**: Code, configuration, or demonstration (if applicable)
6. **Suggested Fix**: Proposed remediation (if known)
7. **Disclosure Timeline**: Your expectations for public disclosure
* **Acknowledgement:** within 3 business days.
* **Initial triage:** within 10 business days.
* **Fix plan:** communicated once severity is confirmed.
### Response Timeline
These targets are not guarantees. Complex issues, supply chain considerations, and coordination with upstream vendors may extend timelines.
* **Initial Response**: Within 3 business days
* **Assessment Complete**: Within 7 business days
* **Fix Timeline**: Depends on severity (see below)
* **Disclosure**: Coordinated with reporter
## Severity Assessment
## Severity Classification
Issues are triaged based on business impact and technical exploitability, including:
Vulnerabilities are classified using the following severity levels:
* Remote exploitability and required privileges.
* Data confidentiality, integrity, and availability impact.
* Likelihood of exploitation in typical Joomla deployments.
* Exposure surface (public endpoints, administrator area, installation flows, and update mechanisms).
### Critical
* Remote code execution
* Authentication bypass
* Data breach or exposure of sensitive information
* **Fix Timeline**: 7 days
When appropriate, industry standard scoring such as CVSS may be used for internal prioritization.
### High
* Privilege escalation
* SQL injection or command injection
* Cross-site scripting (XSS) with significant impact
* **Fix Timeline**: 14 days
## Coordinated Disclosure
### Medium
* Information disclosure (limited scope)
* Denial of service
* Security misconfigurations with moderate impact
* **Fix Timeline**: 30 days
The project follows coordinated vulnerability disclosure:
### Low
* Security best practice violations
* Minor information leaks
* Issues requiring user interaction or complex preconditions
* **Fix Timeline**: 60 days or next release
* Reports are treated as confidential until remediation is available.
* A public advisory may be published once a fix is released.
* A reasonable embargo period is expected to enable patch distribution.
## Remediation Process
If you believe disclosure is time sensitive due to active exploitation, include that assessment and any supporting indicators.
1. **Acknowledgment**: Security team confirms receipt and begins investigation
2. **Assessment**: Vulnerability is validated, severity assigned, and impact analyzed
3. **Development**: Security patch is developed and tested
4. **Review**: Patch undergoes security review and validation
5. **Release**: Fixed version is released with security advisory
6. **Disclosure**: Public disclosure follows coordinated timeline
## Security Updates and Advisories
## Security Advisories
Security updates are distributed through:
Security advisories are published via:
* GitHub releases for the repository.
* GitHub Security Advisories when applicable.
* GitHub Security Advisories
* Release notes and CHANGELOG.md
* Security mailing list (when established)
Advisories may include:
Advisories include:
* Affected versions and fixed versions.
* Mitigations and workarounds when a fix is not immediately available.
* Upgrade guidance.
* CVE identifier (if applicable)
* Severity rating
* Affected versions
* Fixed versions
* Mitigation steps
* Attribution (with reporter consent)
## Dependencies and Supply Chain Controls
## Security Best Practices
The project aims to manage supply chain risk through:
For repositories adopting MokoStandards:
* Pinning and review of workflow dependencies where feasible.
* Minimizing privileged GitHub token permissions.
* Validating build inputs prior to packaging releases.
### Required Controls
If you identify a supply chain issue (for example compromised action, dependency confusion, or malicious upstream artifact), report it as a vulnerability.
* Enable GitHub security features (Dependabot, code scanning)
* Implement branch protection on `main`
* Require code review for all changes
* Enforce signed commits (recommended)
* Use secrets management (never commit credentials)
* Maintain security documentation
* Follow secure coding standards defined in `/docs/policy/`
## Secure Development and CI Expectations
### CI/CD Security
Security posture is reinforced through operational controls:
* Validate all inputs
* Sanitize outputs
* Use least privilege access
* Pin dependencies with hash verification
* Scan for vulnerabilities in dependencies
* Audit third-party actions and tools
* CI validation for packaging inputs and manifest integrity.
* Consistent path normalization and whitespace hygiene checks where required for release correctness.
* Least privilege for GitHub Actions permissions.
#### Automated Security Scanning
### Template Security Features
All repositories MUST implement:
**Custom Head Content Injection**
**CodeQL Analysis**:
* Enabled for all supported languages (Python, JavaScript, TypeScript, Java, C/C++, C#, Go, Ruby)
* Runs on: push to main, pull requests, weekly schedule
* Query sets: `security-extended` and `security-and-quality`
* Configuration: `.github/workflows/codeql-analysis.yml`
The template provides Custom Head Code fields (`custom_head_start` and `custom_head_end`) that allow administrators to inject custom HTML, CSS, and JavaScript code. This is an intentional feature for:
**Dependabot Security Updates**:
* Weekly scans for vulnerable dependencies
* Automated pull requests for security patches
* Configuration: `.github/dependabot.yml`
* Adding analytics scripts (Google Analytics, Google Tag Manager)
* Custom meta tags
* Third-party integrations
* Custom styling
**Secret Scanning**:
* Enabled by default with push protection
* Prevents accidental credential commits
* Partner patterns enabled
**Security Considerations:**
**Dependency Review**:
* Required for all pull requests
* Blocks introduction of known vulnerable dependencies
* Automatic license compliance checking
* These fields use `filter="raw"` to allow HTML/JS injection
* **Access is restricted to Joomla administrators only** via template configuration
* This is not an XSS vulnerability as it requires administrator privileges
* Administrators should only add trusted code from verified sources
* Regular security audits should review custom head content
See [Security Scanning Policy](docs/policy/security-scanning.md) for detailed requirements.
This policy does not guarantee that all vulnerabilities will be prevented. It defines how risk is managed when issues are discovered.
### Dependency Management
## Safe Harbor
* Keep dependencies up to date
* Monitor security advisories for dependencies
* Remove unused dependencies
* Audit new dependencies before adoption
* Document security-critical dependencies
The project supports good faith security research. When you:
## Compliance and Governance
* Avoid privacy violations, data destruction, and service disruption.
* Limit testing to systems you own or have explicit permission to test.
* Provide a reasonable window for coordinated disclosure.
This security policy is binding for all repositories governed by MokoStandards. Deviations require documented justification and approval from the Security Owner.
Then the project will treat your report as a constructive security contribution.
Security policies are reviewed and updated at least annually or following significant security incidents.
Jurisdiction note: this repository is managed from Tennessee, USA. This note is informational only and does not constitute legal advice.
## Attribution and Recognition
## Public Communications
We acknowledge and appreciate responsible disclosure. With your permission, we will:
Only maintainers will publish security advisories or public statements for confirmed vulnerabilities. Public communication will focus on actionable remediation and operational risk reduction.
* Credit you in security advisories
* List you in CHANGELOG.md for the fix release
* Recognize your contribution publicly (if desired)
## Acknowledgements
## Contact and Escalation
If you want credit, include the name or handle to list in an advisory. If you prefer anonymity, state that explicitly.
* **Security Team**: security@[DOMAIN]
* **Primary Contact**: [CONTACT_EMAIL]
* **Escalation**: For urgent matters requiring immediate attention, contact the maintainer directly via GitHub
## Out of Scope
The following are explicitly out of scope:
* Issues in third-party dependencies (report directly to maintainers)
* Social engineering attacks
* Physical security issues
* Denial of service via resource exhaustion without amplification
* Issues requiring physical access to systems
* Theoretical vulnerabilities without proof of exploitability
---
## Metadata
* **Document:** SECURITY.md
* **Repository:** [https://github.com/mokoconsulting-tech/MokoCassiopeia](https://github.com/mokoconsulting-tech/MokoCassiopeia)
* **Path:** /SECURITY.md
* **Owner:** Moko Consulting
* **Version:** 03.06.00
* **Status:** Active
* **Effective Date:** 2025-12-18
* **Last Reviewed:** 2025-12-18
| Field | Value |
| ------------ | ----------------------------------------------- |
| Document | Security Policy |
| Path | /SECURITY.md |
| Repository | [REPOSITORY_URL] |
| Owner | [OWNER_NAME] |
| Scope | Security vulnerability handling |
| Applies To | All repositories governed by MokoStandards |
| Status | Active |
| Effective | [YYYY-MM-DD] |
## Revision History
| Date | Change Summary | Author |
| ---------- | ------------------------------------------------------------------------------------------------ | --------------- |
| 2026-01-30 | Added Template Security Features section documenting custom head content injection controls. | Copilot Agent |
| 2025-12-18 | Initial publication of security policy, intake channels, triage targets, and disclosure process. | Moko Consulting |
| Date | Change Description | Author |
| ---------- | ------------------------------------------------- | --------------- |
| [YYYY-MM-DD] | Initial creation | [AUTHOR_NAME] |

View File

@@ -14,7 +14,7 @@
"prefer-stable": true,
"require": {
"php": ">=8.1",
"mokoconsulting-tech/enterprise": "^4.0"
"mokoconsulting-tech/enterprise": "dev-version/04.02.00"
},
"require-dev": {
"phpunit/phpunit": "^10.5",

119
docs/update-server.md Normal file
View File

@@ -0,0 +1,119 @@
<!--
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: MokoCassiopeia.Documentation
INGROUP: MokoStandards.Templates
REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
PATH: /docs/update-server.md
VERSION: 04.04.00
BRIEF: How this extension's Joomla update server file (update.xml) is managed
-->
# Joomla Update Server
[![MokoStandards](https://img.shields.io/badge/MokoStandards-04.04.00-blue)](https://github.com/mokoconsulting-tech/MokoStandards)
This document explains how `update.xml` is automatically managed for this Joomla extension following the [Joomla Update Server specification](https://docs.joomla.org/Deploying_an_Update_Server).
## How It Works
Joomla checks for extension updates by fetching an XML file from the URL defined in the `<updateservers>` tag in the extension's XML manifest. MokoStandards generates this file automatically.
### Automatic Generation
| Event | Workflow | `<tag>` | `<version>` |
|-------|----------|---------|-------------|
| Merge to `main` | `auto-release.yml` | `stable` | `XX.YY.ZZ` |
| Push to `dev/**` | `deploy-dev.yml` | `development` | `development` |
| Push to `rc/**` | `deploy-dev.yml` | `rc` | `XX.YY.ZZ-rc` |
### Generated XML Structure
```xml
<?xml version="1.0" encoding="utf-8"?>
<updates>
<update>
<name>Extension Name</name>
<description>Extension Name update</description>
<element>com_extensionname</element>
<type>component</type>
<version>01.02.03</version>
<client>site</client>
<folder>system</folder> <!-- plugins only -->
<tags>
<tag>stable</tag>
</tags>
<infourl title="Extension Name">https://github.com/.../releases/tag/v01.02.03</infourl>
<downloads>
<downloadurl type="full" format="zip">https://github.com/.../releases/download/v01.02.03/com_ext-01.02.03.zip</downloadurl>
</downloads>
<targetplatform name="joomla" version="((5\.[0-9])|(6\.[0-9]))" />
<php_minimum>8.2</php_minimum> <!-- if present in manifest -->
<maintainer>Moko Consulting</maintainer>
<maintainerurl>https://mokoconsulting.tech</maintainerurl>
</update>
</updates>
```
### Metadata Source
All metadata is extracted from the extension's XML manifest (`src/*.xml`) at build time:
| XML Element | Source | Notes |
|-------------|--------|-------|
| `<name>` | `<name>` in manifest | Extension display name |
| `<element>` | `<element>` in manifest | Must match installed extension identifier |
| `<type>` | `type` attribute on `<extension>` | `component`, `module`, `plugin`, `library`, `package`, `template` |
| `<client>` | `client` attribute on `<extension>` | `site` or `administrator`**required for plugins and modules** |
| `<folder>` | `group` attribute on `<extension>` | Plugin group (e.g., `system`, `content`) — **required for plugins** |
| `<targetplatform>` | `<targetplatform>` in manifest | Falls back to Joomla 5.x / 6.x if not specified |
| `<php_minimum>` | `<php_minimum>` in manifest | Included only if present |
### Extension Manifest Setup
Your XML manifest must include an `<updateservers>` tag pointing to the `update.xml` on the `main` branch:
```xml
<extension type="component" client="site" method="upgrade">
<name>My Extension</name>
<element>com_myextension</element>
<!-- ... -->
<updateservers>
<server type="extension" name="My Extension Updates">
https://raw.githubusercontent.com/mokoconsulting-tech/MokoCassiopeia/main/update.xml
</server>
</updateservers>
</extension>
```
### Branch Lifecycle
```
dev/XX.YY.ZZ → rc/XX.YY.ZZ → main → version/XX.YY
(development) (rc) (stable) (frozen snapshot)
```
1. **Development** (`dev/**`): `update.xml` with `<tag>development</tag>`, download points to branch archive
2. **Release Candidate** (`rc/**`): `update.xml` with `<tag>rc</tag>`, version set to `XX.YY.ZZ-rc`
3. **Stable Release** (merge to `main`): `update.xml` with `<tag>stable</tag>`, download points to GitHub Release asset
4. **Frozen Snapshot** (`version/XX.YY`): immutable, never force-pushed
### Health Checks
The `repo_health.yml` workflow verifies on every commit:
- `update.xml` exists in the repository root
- XML manifest exists with `<extension>` tag
- `<version>`, `<name>`, `<author>`, `<namespace>` tags present
- Extension `type` attribute is valid
- Language `.ini` files exist
- `index.html` directory listing protection in `src/`, `src/admin/`, `src/site/`
---
*Managed by [MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards). See [docs/workflows/update-server.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/workflows/update-server.md) for the full specification.*