Merge branch 'dev' into version/03

# Conflicts:
#	README.md
This commit is contained in:
2026-04-07 23:15:32 -05:00
52 changed files with 481 additions and 218 deletions

26
.github/CLAUDE.md vendored
View File

@@ -122,13 +122,13 @@ BRIEF: One-line description
### Joomla Version Alignment ### 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 ```xml
<!-- In manifest.xml — must match README.md version --> <!-- In manifest.xml — must match README.md version -->
<version>01.02.04</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 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 in the XML attribute value. Joomla's update server treats the value as a
regular expression, so \. matches a literal dot. --> regular expression, so \. matches a literal dot. -->
@@ -154,7 +154,7 @@ The version in `README.md` **must always match** the `<version>` tag in `manifes
``` ```
MokoCassiopeia/ MokoCassiopeia/
├── manifest.xml # Joomla installer manifest (root — required) ├── 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 ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -183,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: The `manifest.xml` must reference it via:
```xml ```xml
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <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> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release must prepend a new `<update>` block at the top of `update.xml` — old entries must be preserved below. - Every release must prepend a new `<update>` block at the top of `updates.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`. - 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). - 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. - `<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.
@@ -207,8 +207,8 @@ The `manifest.xml` must reference it via:
## manifest.xml Rules ## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). - 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`. - `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
- Must include `<updateservers>` block pointing to this repo's `update.xml`. - Must include `<updateservers>` block pointing to this repo's `updates.xml`.
- Must include `<files folder="site">` and `<administration>` sections. - Must include `<files folder="site">` and `<administration>` sections.
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions. - Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
@@ -286,8 +286,8 @@ Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `d
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | 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 or changed manifest.xml | Update `updates.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `update.xml`; update CHANGELOG.md; 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` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | 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 | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -301,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 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 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 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

24
.github/CODEOWNERS vendored
View File

@@ -8,8 +8,26 @@
# Combined with branch protection (require PR reviews), this prevents # Combined with branch protection (require PR reviews), this prevents
# unauthorized modifications to workflows, configs, and governance files. # unauthorized modifications to workflows, configs, and governance files.
# ── Workflows (synced from MokoStandards — must not be manually edited) ── # ── Synced workflows (managed by MokoStandards — do not edit manually) ────
/.github/workflows/ @jmiller-moko /.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 configuration ─────────────────────────────────────────────────
/.github/ISSUE_TEMPLATE/ @jmiller-moko /.github/ISSUE_TEMPLATE/ @jmiller-moko
@@ -23,7 +41,7 @@
/composer.json @jmiller-moko /composer.json @jmiller-moko
/phpstan.neon @jmiller-moko /phpstan.neon @jmiller-moko
/Makefile @jmiller-moko /Makefile @jmiller-moko
/.ftp_ignore @jmiller-moko /.ftpignore @jmiller-moko
/.gitignore @jmiller-moko /.gitignore @jmiller-moko
/.gitattributes @jmiller-moko /.gitattributes @jmiller-moko
/.editorconfig @jmiller-moko /.editorconfig @jmiller-moko

View File

@@ -122,13 +122,13 @@ BRIEF: One-line description
### Joomla Version Alignment ### 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 ```xml
<!-- In manifest.xml — must match README.md version --> <!-- In manifest.xml — must match README.md version -->
<version>01.02.04</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 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 in the XML attribute value. Joomla's update server treats the value as a
regular expression, so \. matches a literal dot. --> regular expression, so \. matches a literal dot. -->
@@ -154,7 +154,7 @@ The version in `README.md` **must always match** the `<version>` tag in `manifes
``` ```
MokoCassiopeia/ MokoCassiopeia/
├── manifest.xml # Joomla installer manifest (root — required) ├── 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 ├── site/ # Frontend (site) code
│ ├── controller.php │ ├── controller.php
│ ├── controllers/ │ ├── controllers/
@@ -183,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: The `manifest.xml` must reference it via:
```xml ```xml
<updateservers> <updateservers>
<server type="extension" priority="1" name="{{EXTENSION_NAME}}"> <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> </server>
</updateservers> </updateservers>
``` ```
**Rules:** **Rules:**
- Every release must prepend a new `<update>` block at the top of `update.xml` — old entries must be preserved below. - Every release must prepend a new `<update>` block at the top of `updates.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`. - 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). - 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. - `<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.
@@ -207,8 +207,8 @@ The `manifest.xml` must reference it via:
## manifest.xml Rules ## manifest.xml Rules
- Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). - 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`. - `<version>` tag must be kept in sync with `README.md` version and `updates.xml`.
- Must include `<updateservers>` block pointing to this repo's `update.xml`. - Must include `<updateservers>` block pointing to this repo's `updates.xml`.
- Must include `<files folder="site">` and `<administration>` sections. - Must include `<files folder="site">` and `<administration>` sections.
- Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions. - Joomla 4.x requires `<namespace path="src">Moko\{{EXTENSION_NAME}}</namespace>` for namespaced extensions.
@@ -286,8 +286,8 @@ Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `d
| Change type | Documentation to update | | Change type | Documentation to update |
|-------------|------------------------| |-------------|------------------------|
| New or renamed PHP class/method | PHPDoc block; `docs/api/` entry | | 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 or changed manifest.xml | Update `updates.xml` version; bump README.md version |
| New release | Prepend `<update>` block to `update.xml`; update CHANGELOG.md; 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` | | New or changed workflow | `docs/workflows/<workflow-name>.md` |
| Any modified file | Update the `VERSION` field in that file's `FILE INFORMATION` block | | 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 | | **Every PR** | **Bump the patch version** — increment `XX.YY.ZZ` in `README.md`; `sync-version-on-merge` propagates it |
@@ -301,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 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 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 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 # INGROUP: MokoStandards.Security
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/dependabot.yml # PATH: /.github/dependabot.yml
# VERSION: 01.00.00 # VERSION: 03.09.03
# BRIEF: Dependabot configuration for automated dependency updates and security patches # BRIEF: Dependabot configuration for automated dependency updates and security patches
# NOTE: Monitors GitHub Actions for vulnerabilities and keeps ecosystem secure # NOTE: Monitors GitHub Actions for vulnerabilities and keeps ecosystem secure

View File

@@ -6,7 +6,7 @@
# INGROUP: MokoStandards.Workflows.Shared # INGROUP: MokoStandards.Workflows.Shared
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/auto-assign.yml # PATH: /.github/workflows/auto-assign.yml
# VERSION: 04.05.11 # VERSION: 04.06.00
# BRIEF: Auto-assign jmiller-moko to unassigned issues and PRs every 15 minutes # BRIEF: Auto-assign jmiller-moko to unassigned issues and PRs every 15 minutes
name: Auto-Assign Issues & PRs name: Auto-Assign Issues & PRs

View File

@@ -9,7 +9,7 @@
# INGROUP: MokoStandards.Automation # INGROUP: MokoStandards.Automation
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/auto-dev-issue.yml.template # PATH: /templates/workflows/shared/auto-dev-issue.yml.template
# VERSION: 04.05.13 # VERSION: 04.06.00
# BRIEF: Auto-create tracking issue with sub-issues for dev/rc branch workflow # BRIEF: Auto-create tracking issue with sub-issues for dev/rc branch workflow
# NOTE: Synced via bulk-repo-sync to .github/workflows/auto-dev-issue.yml in all governed repos. # NOTE: Synced via bulk-repo-sync to .github/workflows/auto-dev-issue.yml in all governed repos.
@@ -39,7 +39,10 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: >- if: >-
(github.event_name == 'workflow_dispatch') || (github.event_name == 'workflow_dispatch') ||
(github.event.ref_type == 'branch' && startsWith(github.event.ref, 'rc/')) (github.event.ref_type == 'branch' &&
(startsWith(github.event.ref, 'rc/') ||
startsWith(github.event.ref, 'alpha/') ||
startsWith(github.event.ref, 'beta/')))
steps: steps:
- name: Create tracking issue and sub-issues - name: Create tracking issue and sub-issues
@@ -62,6 +65,16 @@ jobs:
BRANCH_TYPE="Release Candidate" BRANCH_TYPE="Release Candidate"
LABEL_TYPE="type: release" LABEL_TYPE="type: release"
TITLE_PREFIX="rc" 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 else
VERSION="${BRANCH#dev/}" VERSION="${BRANCH#dev/}"
BRANCH_TYPE="Development" BRANCH_TYPE="Development"
@@ -80,14 +93,20 @@ jobs:
exit 0 exit 0
fi fi
# ── Define sub-issues for the dev workflow ──────────────────────── # ── Define sub-issues for the workflow ────────────────────────
if [[ "$BRANCH" == rc/* ]]; then if [[ "$BRANCH" == rc/* ]]; then
SUB_ISSUES=( SUB_ISSUES=(
"RC Testing|Verify all features work on rc branch|type: test,release-candidate" "RC Testing|Verify all features work on rc branch|type: test,release-candidate"
"Regression Testing|Run full regression suite before merge to main|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" "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" "Changelog Update|Update CHANGELOG.md with release notes|documentation,release-candidate"
"Merge to Main|Create PR from rc branch to main|type: release,needs-review" "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 else
SUB_ISSUES=( SUB_ISSUES=(
@@ -156,22 +175,26 @@ jobs:
done done
fi fi
# ── RC: Create or update release-candidate release ────────────── # ── Create or update prerelease for alpha/beta/rc ────────────────
if [[ "$BRANCH" == rc/* ]]; then if [[ "$BRANCH" == rc/* ]] || [[ "$BRANCH" == alpha/* ]] || [[ "$BRANCH" == beta/* ]]; then
RELEASE_TAG="release-candidate" case "$BRANCH_TYPE" in
EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true) 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 if [ -z "$EXISTING" ]; then
gh release create "$RELEASE_TAG" \ gh release create "$RELEASE_TAG" \
--title "release-candidate (${VERSION})" \ --title "${RELEASE_TAG} (${VERSION})" \
--notes "## Release Candidate ${VERSION}\n\nRC branch: \`${BRANCH}\`\nTracking issue: ${PARENT_URL}" \ --notes "## ${BRANCH_TYPE} ${VERSION}\n\nBranch: \`${BRANCH}\`\nTracking issue: ${PARENT_URL}" \
--prerelease \ --prerelease \
--target main 2>/dev/null || true --target main 2>/dev/null || true
echo "RC release created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY echo "${BRANCH_TYPE} release created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
else else
gh release edit "$RELEASE_TAG" \ gh release edit "$RELEASE_TAG" \
--title "release-candidate (${VERSION})" --prerelease 2>/dev/null || true --title "${RELEASE_TAG} (${VERSION})" --prerelease 2>/dev/null || true
echo "RC release updated: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY echo "${BRANCH_TYPE} release updated: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
fi fi
fi fi

View File

@@ -7,7 +7,7 @@
# INGROUP: MokoStandards.Release # INGROUP: MokoStandards.Release
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/joomla/auto-release.yml.template # PATH: /templates/workflows/joomla/auto-release.yml.template
# VERSION: 04.05.13 # VERSION: 04.06.00
# BRIEF: Joomla build & release — ZIP package, updates.xml, SHA-256 checksum # BRIEF: Joomla build & release — ZIP package, updates.xml, SHA-256 checksum
# #
# +========================================================================+ # +========================================================================+
@@ -316,54 +316,47 @@ jobs:
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/v${VERSION}/${EXT_ELEMENT}-${VERSION}.zip" DOWNLOAD_URL="https://github.com/${REPO}/releases/download/v${VERSION}/${EXT_ELEMENT}-${VERSION}.zip"
INFO_URL="https://github.com/${REPO}/releases/tag/v${VERSION}" INFO_URL="https://github.com/${REPO}/releases/tag/v${VERSION}"
# -- Build stable entry ────────────────────────────────────── # -- Build stable entry to temp file ─────────────────────────
STABLE_ENTRY=$(cat <<XMLEOF {
<update> printf '%s\n' ' <update>'
<name>${EXT_NAME}</name> printf '%s\n' " <name>${EXT_NAME}</name>"
<description>${EXT_NAME} update</description> printf '%s\n' " <description>${EXT_NAME} update</description>"
<element>${EXT_ELEMENT}</element> printf '%s\n' " <element>${EXT_ELEMENT}</element>"
<type>${EXT_TYPE}</type> printf '%s\n' " <type>${EXT_TYPE}</type>"
<version>${VERSION}</version> printf '%s\n' " <version>${VERSION}</version>"
$([ -n "$CLIENT_TAG" ] && echo " ${CLIENT_TAG}") [ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}"
$([ -n "$FOLDER_TAG" ] && echo " ${FOLDER_TAG}") [ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}"
<tags> printf '%s\n' ' <tags>'
<tag>stable</tag> printf '%s\n' ' <tag>stable</tag>'
</tags> printf '%s\n' ' </tags>'
<infourl title="${EXT_NAME}">${INFO_URL}</infourl> printf '%s\n' " <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>"
<downloads> printf '%s\n' ' <downloads>'
<downloadurl type="full" format="zip">${DOWNLOAD_URL}</downloadurl> printf '%s\n' " <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>"
</downloads> printf '%s\n' ' </downloads>'
${TARGET_PLATFORM} printf '%s\n' " ${TARGET_PLATFORM}"
$([ -n "$PHP_TAG" ] && echo " ${PHP_TAG}") [ -n "$PHP_TAG" ] && printf '%s\n' " ${PHP_TAG}"
<maintainer>Moko Consulting</maintainer> printf '%s\n' ' <maintainer>Moko Consulting</maintainer>'
<maintainerurl>https://mokoconsulting.tech</maintainerurl> printf '%s\n' ' <maintainerurl>https://mokoconsulting.tech</maintainerurl>'
</update> printf '%s\n' ' </update>'
XMLEOF } > /tmp/stable_entry.xml
)
# -- Write updates.xml preserving dev/rc entries ────────────── # -- Write updates.xml preserving dev/rc entries ──────────────
# Extract existing dev and rc entries if present
RC_ENTRY="" RC_ENTRY=""
DEV_ENTRY="" DEV_ENTRY=""
if [ -f "updates.xml" ]; then if [ -f "updates.xml" ]; then
RC_ENTRY=$(python3 -c " printf 'import re\n' > /tmp/extract.py
import re printf 'with open("updates.xml") as f: c = f.read()\n' >> /tmp/extract.py
with open('updates.xml') as f: c = f.read() printf 'import sys; tag = sys.argv[1]\n' >> /tmp/extract.py
m = re.search(r'( <update>.*?<tag>rc</tag>.*?</update>)', c, re.DOTALL) printf 'm = re.search(r"( <update>.*?<tag>" + re.escape(tag) + r"</tag>.*?</update>)", c, re.DOTALL)\n' >> /tmp/extract.py
if m: print(m.group(1)) printf 'if m: print(m.group(1))\n' >> /tmp/extract.py
" 2>/dev/null || true) RC_ENTRY=$(python3 /tmp/extract.py rc 2>/dev/null || true)
DEV_ENTRY=$(python3 -c " DEV_ENTRY=$(python3 /tmp/extract.py development 2>/dev/null || true)
import re
with open('updates.xml') as f: c = f.read()
m = re.search(r'( <update>.*?<tag>development</tag>.*?</update>)', c, re.DOTALL)
if m: print(m.group(1))
" 2>/dev/null || true)
fi fi
{ {
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>' printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>'
printf '%s\n' '<updates>' printf '%s\n' '<updates>'
echo "$STABLE_ENTRY" cat /tmp/stable_entry.xml
[ -n "$RC_ENTRY" ] && echo "$RC_ENTRY" [ -n "$RC_ENTRY" ] && echo "$RC_ENTRY"
[ -n "$DEV_ENTRY" ] && echo "$DEV_ENTRY" [ -n "$DEV_ENTRY" ] && echo "$DEV_ENTRY"
printf '%s\n' '</updates>' printf '%s\n' '</updates>'
@@ -484,7 +477,7 @@ if m: print(m.group(1))
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ — skipping package"; exit 0; } [ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ — skipping package"; exit 0; }
cd "$SOURCE_DIR" cd "$SOURCE_DIR"
zip -r "/tmp/${PACKAGE_NAME}" . zip -r "/tmp/${PACKAGE_NAME}" . -x '.ftpignore'
cd .. cd ..
FILESIZE=$(stat -c%s "/tmp/${PACKAGE_NAME}" 2>/dev/null || stat -f%z "/tmp/${PACKAGE_NAME}" 2>/dev/null || echo "unknown") FILESIZE=$(stat -c%s "/tmp/${PACKAGE_NAME}" 2>/dev/null || stat -f%z "/tmp/${PACKAGE_NAME}" 2>/dev/null || echo "unknown")

View File

@@ -5,7 +5,7 @@
# INGROUP: MokoCassiopeia.Automation # INGROUP: MokoCassiopeia.Automation
# REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia # REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
# PATH: /.github/workflows/auto-update-sha.yml # PATH: /.github/workflows/auto-update-sha.yml
# VERSION: 01.00.00 # VERSION: 03.09.03
# BRIEF: Automatically update SHA-256 hash in updates.xml after release # BRIEF: Automatically update SHA-256 hash in updates.xml after release
# NOTE: Ensures updates.xml stays synchronized with release packages # NOTE: Ensures updates.xml stays synchronized with release packages

View File

@@ -9,7 +9,7 @@
# INGROUP: MokoStandards.CI # INGROUP: MokoStandards.CI
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/changelog-validation.yml.template # PATH: /templates/workflows/shared/changelog-validation.yml.template
# VERSION: 04.05.13 # VERSION: 04.06.00
# BRIEF: Validates CHANGELOG.md format and version consistency # BRIEF: Validates CHANGELOG.md format and version consistency
# NOTE: Deployed to .github/workflows/changelog-validation.yml in governed repos. # NOTE: Deployed to .github/workflows/changelog-validation.yml in governed repos.

View File

@@ -9,7 +9,7 @@
# INGROUP: MokoStandards.CI # INGROUP: MokoStandards.CI
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/joomla/ci-joomla.yml.template # PATH: /templates/workflows/joomla/ci-joomla.yml.template
# VERSION: 04.05.13 # VERSION: 04.06.00
# BRIEF: CI workflow for Joomla extensions — lint, validate, test # BRIEF: CI workflow for Joomla extensions — lint, validate, test
# NOTE: Deployed to .github/workflows/ci-joomla.yml in governed Joomla extension repos. # NOTE: Deployed to .github/workflows/ci-joomla.yml in governed Joomla extension repos.

View File

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

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.Firewall # INGROUP: MokoStandards.Firewall
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/enterprise-firewall-setup.yml.template # PATH: /templates/workflows/shared/enterprise-firewall-setup.yml.template
# VERSION: 04.05.13 # VERSION: 04.06.00
# BRIEF: Enterprise firewall configuration — generates outbound allow-rules including SFTP deployment server # 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. # 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 # INGROUP: MokoCassiopeia.Release
# REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia # REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
# PATH: /.github/workflows/release.yml # PATH: /.github/workflows/release.yml
# VERSION: 01.00.00 # VERSION: 03.09.03
# BRIEF: Automated release workflow for MokoCassiopeia Joomla template # BRIEF: Automated release workflow for MokoCassiopeia Joomla template
# NOTE: Creates release packages and publishes to GitHub Releases # NOTE: Creates release packages and publishes to GitHub Releases

View File

@@ -10,7 +10,7 @@
# INGROUP: MokoStandards.Validation # INGROUP: MokoStandards.Validation
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/repo_health.yml # PATH: /.github/workflows/repo_health.yml
# VERSION: 04.05.00 # VERSION: 04.06.00
# BRIEF: Enforces repository guardrails by validating release configuration, scripts governance, tooling availability, and core repository health artifacts. # BRIEF: Enforces repository guardrails by validating release configuration, scripts governance, tooling availability, and core repository health artifacts.
# NOTE: Field is user-managed. # NOTE: Field is user-managed.
# ============================================================================ # ============================================================================

View File

@@ -9,7 +9,7 @@
# INGROUP: MokoStandards.Maintenance # INGROUP: MokoStandards.Maintenance
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/repository-cleanup.yml.template # PATH: /templates/workflows/shared/repository-cleanup.yml.template
# VERSION: 04.05.13 # VERSION: 04.06.00
# BRIEF: Recurring repository maintenance — labels, branches, workflows, logs, doc indexes # BRIEF: Recurring repository maintenance — labels, branches, workflows, logs, doc indexes
# NOTE: Synced via bulk-repo-sync to .github/workflows/repository-cleanup.yml in all governed repos. # 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. # Runs on the 1st and 15th of each month at 6:00 AM UTC, and on manual dispatch.

View File

@@ -5,7 +5,7 @@
# INGROUP: MokoStandards.Compliance # INGROUP: MokoStandards.Compliance
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/standards-compliance.yml # PATH: /.github/workflows/standards-compliance.yml
# VERSION: 04.05.00 # VERSION: 04.06.00
# BRIEF: MokoStandards compliance validation workflow # BRIEF: MokoStandards compliance validation workflow
# NOTE: Validates repository structure, documentation, and coding standards # NOTE: Validates repository structure, documentation, and coding standards

View File

@@ -9,7 +9,7 @@
# INGROUP: MokoStandards.Automation # INGROUP: MokoStandards.Automation
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/shared/sync-version-on-merge.yml.template # PATH: /templates/workflows/shared/sync-version-on-merge.yml.template
# VERSION: 04.05.13 # VERSION: 04.06.00
# BRIEF: Auto-bump patch version on every push to main and propagate to all file headers # BRIEF: Auto-bump patch version on every push to main and propagate to all file headers
# NOTE: Synced via bulk-repo-sync to .github/workflows/sync-version-on-merge.yml in all governed repos. # 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. # README.md is the single source of truth for the repository version.

View File

@@ -7,7 +7,7 @@
# INGROUP: MokoStandards.Joomla # INGROUP: MokoStandards.Joomla
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /templates/workflows/joomla/update-server.yml.template # PATH: /templates/workflows/joomla/update-server.yml.template
# VERSION: 04.05.13 # VERSION: 04.06.00
# BRIEF: Update Joomla update server XML feed with stable/rc/dev entries # BRIEF: Update Joomla update server XML feed with stable/rc/dev entries
# #
# Writes updates.xml with multiple <update> entries: # Writes updates.xml with multiple <update> entries:
@@ -23,6 +23,8 @@ on:
push: push:
branches: branches:
- 'dev/**' - 'dev/**'
- 'alpha/**'
- 'beta/**'
- 'rc/**' - 'rc/**'
paths: paths:
- 'src/**' - 'src/**'
@@ -30,12 +32,14 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
stability: stability:
description: 'Stability tag (development, rc, stable)' description: 'Stability tag'
required: true required: true
default: 'development' default: 'development'
type: choice type: choice
options: options:
- development - development
- alpha
- beta
- rc - rc
- stable - stable
@@ -75,11 +79,29 @@ jobs:
REPO="${{ github.repository }}" REPO="${{ github.repository }}"
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null || echo "0.0.0") 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 # Determine stability from branch or input
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
STABILITY="${{ inputs.stability }}" STABILITY="${{ inputs.stability }}"
elif [[ "$BRANCH" == rc/* ]]; then elif [[ "$BRANCH" == rc/* ]]; then
STABILITY="rc" STABILITY="rc"
elif [[ "$BRANCH" == beta/* ]]; then
STABILITY="beta"
elif [[ "$BRANCH" == alpha/* ]]; then
STABILITY="alpha"
elif [[ "$BRANCH" == dev/* ]]; then elif [[ "$BRANCH" == dev/* ]]; then
STABILITY="development" STABILITY="development"
else else
@@ -116,19 +138,23 @@ jobs:
# Version suffix for non-stable # Version suffix for non-stable
DISPLAY_VERSION="$VERSION" DISPLAY_VERSION="$VERSION"
[ "$STABILITY" = "rc" ] && DISPLAY_VERSION="${VERSION}-rc" case "$STABILITY" in
[ "$STABILITY" = "development" ] && DISPLAY_VERSION="${VERSION}-dev" 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}') MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
# Each stability level has its own release tag # Each stability level has its own release tag
if [ "$STABILITY" = "rc" ]; then case "$STABILITY" in
RELEASE_TAG="release-candidate" development) RELEASE_TAG="development" ;;
elif [ "$STABILITY" = "development" ]; then alpha) RELEASE_TAG="alpha" ;;
RELEASE_TAG="development" beta) RELEASE_TAG="beta" ;;
else rc) RELEASE_TAG="release-candidate" ;;
RELEASE_TAG="v${MAJOR}" *) RELEASE_TAG="v${MAJOR}" ;;
fi esac
PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip" PACKAGE_NAME="${EXT_ELEMENT}-${DISPLAY_VERSION}.zip"
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}" DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
@@ -139,7 +165,7 @@ jobs:
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
if [ -d "$SOURCE_DIR" ]; then if [ -d "$SOURCE_DIR" ]; then
cd "$SOURCE_DIR" cd "$SOURCE_DIR"
zip -r "/tmp/${PACKAGE_NAME}" . zip -r "/tmp/${PACKAGE_NAME}" . -x '.ftpignore'
cd .. cd ..
SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1) SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1)
@@ -157,95 +183,63 @@ jobs:
fi fi
# ── Build the new entry ─────────────────────────────────────── # ── Build the new entry ───────────────────────────────────────
NEW_ENTRY=$(cat <<XMLEOF NEW_ENTRY=""
<update> NEW_ENTRY="${NEW_ENTRY} <update>\n"
<name>${EXT_NAME}</name> NEW_ENTRY="${NEW_ENTRY} <name>${EXT_NAME}</name>\n"
<description>${EXT_NAME} (${STABILITY})</description> NEW_ENTRY="${NEW_ENTRY} <description>${EXT_NAME} (${STABILITY})</description>\n"
<element>${EXT_ELEMENT}</element> NEW_ENTRY="${NEW_ENTRY} <element>${EXT_ELEMENT}</element>\n"
<type>${EXT_TYPE}</type> NEW_ENTRY="${NEW_ENTRY} <type>${EXT_TYPE}</type>\n"
<version>${DISPLAY_VERSION}</version> NEW_ENTRY="${NEW_ENTRY} <version>${DISPLAY_VERSION}</version>\n"
$([ -n "$CLIENT_TAG" ] && echo " ${CLIENT_TAG}") [ -n "$CLIENT_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${CLIENT_TAG}\n"
$([ -n "$FOLDER_TAG" ] && echo " ${FOLDER_TAG}") [ -n "$FOLDER_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${FOLDER_TAG}\n"
<tags> NEW_ENTRY="${NEW_ENTRY} <tags>\n"
<tag>${STABILITY}</tag> NEW_ENTRY="${NEW_ENTRY} <tag>${STABILITY}</tag>\n"
</tags> NEW_ENTRY="${NEW_ENTRY} </tags>\n"
<infourl title="${EXT_NAME}">${INFO_URL}</infourl> NEW_ENTRY="${NEW_ENTRY} <infourl title=\"${EXT_NAME}\">${INFO_URL}</infourl>\n"
<downloads> NEW_ENTRY="${NEW_ENTRY} <downloads>\n"
<downloadurl type="full" format="zip">${DOWNLOAD_URL}</downloadurl> NEW_ENTRY="${NEW_ENTRY} <downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>\n"
</downloads> NEW_ENTRY="${NEW_ENTRY} </downloads>\n"
$([ -n "$SHA256" ] && echo " <sha256>sha256:${SHA256}</sha256>") [ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} <sha256>sha256:${SHA256}</sha256>\n"
${TARGET_PLATFORM} NEW_ENTRY="${NEW_ENTRY} ${TARGET_PLATFORM}\n"
$([ -n "$PHP_TAG" ] && echo " ${PHP_TAG}") [ -n "$PHP_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_TAG}\n"
<maintainer>Moko Consulting</maintainer> NEW_ENTRY="${NEW_ENTRY} <maintainer>Moko Consulting</maintainer>\n"
<maintainerurl>https://mokoconsulting.tech</maintainerurl> NEW_ENTRY="${NEW_ENTRY} <maintainerurl>https://mokoconsulting.tech</maintainerurl>\n"
</update> NEW_ENTRY="${NEW_ENTRY} </update>"
XMLEOF
) # ── Write new entry to temp file ───────────────────────────────
printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml
# ── Merge into updates.xml ───────────────────────────────────── # ── Merge into updates.xml ─────────────────────────────────────
if [ ! -f "updates.xml" ]; then if [ ! -f "updates.xml" ]; then
# Create fresh
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>' > updates.xml printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>' > updates.xml
printf '%s\n' '<updates>' >> updates.xml printf '%s\n' '<updates>' >> updates.xml
echo "$NEW_ENTRY" >> updates.xml cat /tmp/new_entry.xml >> updates.xml
printf '%s\n' '</updates>' >> updates.xml printf '\n%s\n' '</updates>' >> updates.xml
else else
# Remove existing entry for this stability, add new one # Remove existing entry for this stability, insert new one
# Use python for reliable XML manipulation printf 'import re\nstability = "%s"\n' "${STABILITY}" > /tmp/merge_xml.py
python3 -c " printf 'with open("updates.xml") as f: content = f.read()\n' >> /tmp/merge_xml.py
import re, sys 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
with open('updates.xml', 'r') as f: printf 'content = re.sub(pattern, "", content, flags=re.DOTALL)\n' >> /tmp/merge_xml.py
content = f.read() 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
# Remove existing entry with this stability tag printf 'with open("updates.xml", "w") as f: f.write(content)\n' >> /tmp/merge_xml.py
pattern = r' <update>.*?<tag>${STABILITY}</tag>.*?</update>\n?' python3 /tmp/merge_xml.py 2>/dev/null || {
content = re.sub(pattern, '', content, flags=re.DOTALL) # Fallback: rebuild keeping other stability entries
# Insert new entry before </updates>
new_entry = '''${NEW_ENTRY}'''
content = content.replace('</updates>', new_entry + '\n</updates>')
# Clean up empty lines
content = re.sub(r'\n{3,}', '\n\n', content)
with open('updates.xml', 'w') as f:
f.write(content)
" 2>/dev/null || {
# Fallback: just rewrite the whole file if python fails
# Keep existing stable entry if present
STABLE_ENTRY=""
if [ "$STABILITY" != "stable" ] && grep -q '<tag>stable</tag>' updates.xml; then
STABLE_ENTRY=$(sed -n '/<update>/,/<\/update>/{ /<tag>stable<\/tag>/,/<\/update>/p; /<update>/,/<tag>stable<\/tag>/p }' updates.xml | sort -u)
fi
RC_ENTRY=""
if [ "$STABILITY" != "rc" ] && grep -q '<tag>rc</tag>' updates.xml; then
RC_ENTRY=$(python3 -c "
import re
with open('updates.xml') as f: c = f.read()
m = re.search(r'(<update>.*?<tag>rc</tag>.*?</update>)', c, re.DOTALL)
if m: print(m.group(1))
" 2>/dev/null || true)
fi
DEV_ENTRY=""
if [ "$STABILITY" != "development" ] && grep -q '<tag>development</tag>' updates.xml; then
DEV_ENTRY=$(python3 -c "
import re
with open('updates.xml') as f: c = f.read()
m = re.search(r'(<update>.*?<tag>development</tag>.*?</update>)', c, re.DOTALL)
if m: print(m.group(1))
" 2>/dev/null || true)
fi
{ {
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>' printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>'
printf '%s\n' '<updates>' printf '%s\n' '<updates>'
[ -n "$STABLE_ENTRY" ] && echo "$STABLE_ENTRY" for TAG in stable rc development; do
[ -n "$RC_ENTRY" ] && echo "$RC_ENTRY" [ "$TAG" = "${STABILITY}" ] && continue
[ -n "$DEV_ENTRY" ] && echo "$DEV_ENTRY" if grep -q "<tag>${TAG}</tag>" updates.xml 2>/dev/null; then
echo "$NEW_ENTRY" sed -n "/<update>/,/<\/update>/{ /<tag>${TAG}<\/tag>/p; }" updates.xml
printf '%s\n' '</updates>' fi
} > updates.xml done
cat /tmp/new_entry.xml
printf '\n%s\n' '</updates>'
} > /tmp/updates_new.xml
mv /tmp/updates_new.xml updates.xml
} }
fi fi
@@ -259,6 +253,64 @@ if m: print(m.group(1))
git push 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 "## Joomla Update Server" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY

View File

@@ -13,20 +13,14 @@
BRIEF: Documentation for MokoCassiopeia template BRIEF: Documentation for MokoCassiopeia template
--> -->
# MokoCassiopeia Template
[![Version](https://img.shields.io/badge/version-03.09.03-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)
# MokoCassiopeia
**A Modern, Lightweight Joomla Template Based on Cassiopeia** **A Modern, Lightweight Joomla Template Based on Cassiopeia**
](https://img.shields.io/badge/version-03.09.04-green.svg)](https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/tag/v03) [![Version](https://img.shields.io/badge/version-03.09.07-blue.svg?logo=v&logoColor=white)](https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/tag/v03)
: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) [![License](https://img.shields.io/badge/license-GPL--3.0--or--later-green.svg?logo=gnu&logoColor=white)](LICENSE)
](https://img.shields.io/badge/Joomla-5.x%20%7C%206.x-blue.svg)](https://www.joomla.org) [![Joomla](https://img.shields.io/badge/Joomla-5.x%20%7C%206.x-red.svg?logo=joomla&logoColor=white)](https://www.joomla.org)
](https://img.shields.io/badge/PHP-8.1%2B-blue.svg)](https://www.php.net) [![PHP](https://img.shields.io/badge/PHP-8.1%2B-777BB4.svg?logo=php&logoColor=white)](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. 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

@@ -10,31 +10,62 @@
/** /**
* Default layout override for mod_breadcrumbs. * Default layout override for mod_breadcrumbs.
* Bootstrap 5 breadcrumb with schema.org BreadcrumbList markup. * Bootstrap 5 breadcrumb with schema.org BreadcrumbList markup.
* Respects showHome, showLast, homeText module settings.
*/ */
defined('_JEXEC') or die; defined('_JEXEC') or die;
use Joomla\CMS\Factory; use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text; use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Uri\Uri;
Factory::getApplication()->getLanguage()->load('mod_breadcrumbs', JPATH_SITE); Factory::getApplication()->getLanguage()->load('mod_breadcrumbs', JPATH_SITE);
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8'); $suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8'); $headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8'); $headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
$showHome = $params->get('showHome', 1);
$showLast = $params->get('showLast', 1);
$homeText = $params->get('homeText', '') ?: Text::_('MOD_BREADCRUMBS_HOME');
// Build filtered list respecting module settings
$items = [];
$count = count($list);
foreach ($list as $key => $item) {
// Skip Home item if showHome is off
if ($key === 0 && !$showHome) {
continue;
}
// Replace Home text if custom homeText is set
if ($key === 0 && $showHome) {
$item->name = $homeText;
}
// Skip last item if showLast is off
if ($key === $count - 1 && !$showLast) {
continue;
}
$items[] = $item;
}
if (empty($items)) {
return;
}
?> ?>
<nav class="mod-breadcrumbs<?php echo $suffix ? ' ' . $suffix : ''; ?>" aria-label="<?php echo Text::_('MOD_BREADCRUMBS_HERE'); ?>"> <nav class="mod-breadcrumbs<?php echo $suffix ? ' ' . $suffix : ''; ?>" aria-label="<?php echo Text::_('MOD_BREADCRUMBS_HERE'); ?>">
<?php if ($module->showtitle) : ?> <?php if ($module->showtitle) : ?>
<<?php echo $headerTag; ?> class="mod-breadcrumbs__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>> <<?php echo $headerTag; ?> class="mod-breadcrumbs__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
<?php endif; ?> <?php endif; ?>
<ol class="breadcrumb" itemscope itemtype="https://schema.org/BreadcrumbList"> <ol class="breadcrumb" itemscope itemtype="https://schema.org/BreadcrumbList">
<?php foreach ($list as $key => $item) : ?> <?php foreach ($items as $key => $item) : ?>
<?php <?php $isLast = ($key === array_key_last($items)); ?>
$isLast = ($key === array_key_last($list));
?>
<li class="breadcrumb-item<?php echo $isLast ? ' active' : ''; ?>" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem" <li class="breadcrumb-item<?php echo $isLast ? ' active' : ''; ?>" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem"
<?php echo $isLast ? ' aria-current="page"' : ''; ?>> <?php echo $isLast ? ' aria-current="page"' : ''; ?>>
<?php if (!$isLast && $item->link) : ?> <?php if (!$isLast && !empty($item->link)) : ?>
<a href="<?php echo $item->link; ?>" itemprop="item"> <a href="<?php echo $item->link; ?>" itemprop="item">
<span itemprop="name"><?php echo $item->name; ?></span> <span itemprop="name"><?php echo $item->name; ?></span>
</a> </a>

View File

@@ -451,7 +451,7 @@ $wa->useScript('user.js'); // js/user.js
<button class="search-toggler d-lg-none" type="button" data-bs-toggle="collapse" data-bs-target="#headerSearchCollapse" aria-controls="headerSearchCollapse" aria-expanded="false" aria-label="<?php echo Text::_('JSEARCH_FILTER_SUBMIT'); ?>"> <button class="search-toggler d-lg-none" type="button" data-bs-toggle="collapse" data-bs-target="#headerSearchCollapse" aria-controls="headerSearchCollapse" aria-expanded="false" aria-label="<?php echo Text::_('JSEARCH_FILTER_SUBMIT'); ?>">
<span class="fa-solid fa-magnifying-glass" aria-hidden="true"></span> <span class="fa-solid fa-magnifying-glass" aria-hidden="true"></span>
</button> </button>
<div class="container-search collapse d-lg-block" id="headerSearchCollapse"> <div class="container-search" id="headerSearchCollapse">
<jdoc:include type="modules" name="search" style="none" /> <jdoc:include type="modules" name="search" style="none" />
</div> </div>
<?php endif; ?> <?php endif; ?>

View File

@@ -18672,10 +18672,14 @@ nav[data-toggle=toc] .nav-link.active+ul{
margin-top: 0.5rem; margin-top: 0.5rem;
} }
.container-header .container-search.collapse:not(.show) { .container-header .container-search {
display: none; display: none;
} }
.container-header .container-search.show {
display: block;
}
.mod-finder__search.input-group { .mod-finder__search.input-group {
max-width: 100%; max-width: 100%;
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -539,6 +539,20 @@
}); });
} }
/**
* Toggle search on mobile via .show class
*/
function initSearchToggle() {
var btn = doc.querySelector(".search-toggler");
var target = doc.getElementById("headerSearchCollapse");
if (!btn || !target) return;
btn.addEventListener("click", function () {
var isOpen = target.classList.toggle("show");
btn.setAttribute("aria-expanded", isOpen ? "true" : "false");
});
}
/** /**
* Run all template JS initializations * Run all template JS initializations
*/ */
@@ -563,6 +577,7 @@
// Init features // Init features
initDrawers(); initDrawers();
initBackTop(); initBackTop();
initSearchToggle();
initSidebarAccordion(); initSidebarAccordion();
} }

View File

@@ -36,7 +36,7 @@
</server> </server>
</updateservers> </updateservers>
<name>MokoCassiopeia</name> <name>MokoCassiopeia</name>
<version>03.09.07</version> <version>03.09.09</version>
<scriptfile>script.php</scriptfile> <scriptfile>script.php</scriptfile>
<creationDate>2026-03-26</creationDate> <creationDate>2026-03-26</creationDate>
<author>Jonathan Miller || Moko Consulting</author> <author>Jonathan Miller || Moko Consulting</author>
@@ -66,6 +66,7 @@
<folder>css</folder> <folder>css</folder>
<folder>images</folder> <folder>images</folder>
<folder>fonts</folder> <folder>fonts</folder>
<folder>vendor</folder>
</media> </media>
<positions> <positions>
<position>topbar</position> <position>topbar</position>

View File

@@ -26,7 +26,7 @@
<type>template</type> <type>template</type>
<client>site</client> <client>site</client>
<version>03.09.04</version> <version>03.09.07</version>
<creationDate>2026-04-07</creationDate> <creationDate>2026-04-07</creationDate>
<author>Jonathan Miller || Moko Consulting</author> <author>Jonathan Miller || Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -35,9 +35,9 @@
<infourl title='MokoCassiopeia'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/tag/v03</infourl> <infourl title='MokoCassiopeia'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/tag/v03</infourl>
<downloads> <downloads>
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.09.04.zip</downloadurl> <downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/v03/mokocassiopeia-03.09.07.zip</downloadurl>
</downloads> </downloads>
<sha256>da85bf5a34cafadbae26df199ed36c04438e7dab440d046e0e4117d25510feaf</sha256> <sha256>16a7ac98dd6e26144618a4ba534ea8fae0d115ec8373712743ab6daff0960916</sha256>
<tags> <tags>
<tag>stable</tag> <tag>stable</tag>
@@ -59,7 +59,7 @@
<type>template</type> <type>template</type>
<client>site</client> <client>site</client>
<version>03.09.06</version> <version>03.09.07</version>
<creationDate>2026-04-07</creationDate> <creationDate>2026-04-07</creationDate>
<author>Jonathan Miller || Moko Consulting</author> <author>Jonathan Miller || Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -70,7 +70,7 @@
<downloads> <downloads>
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/release-candidate/mokocassiopeia-rc.zip</downloadurl> <downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/release-candidate/mokocassiopeia-rc.zip</downloadurl>
</downloads> </downloads>
<sha256>99b868a54466138d22a544d3e489559e7303960922c8eb3a142c504225b0e647</sha256> <sha256>31e660078e728e8c9177b5a2d75efd89fea1fd4e9320d77444ab8fe28d3b354d</sha256>
<tags> <tags>
<tag>rc</tag> <tag>rc</tag>
@@ -92,7 +92,7 @@
<type>template</type> <type>template</type>
<client>site</client> <client>site</client>
<version>03.09.07</version> <version>03.09.08</version>
<creationDate>2026-04-07</creationDate> <creationDate>2026-04-07</creationDate>
<author>Jonathan Miller || Moko Consulting</author> <author>Jonathan Miller || Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -101,9 +101,9 @@
<infourl title='MokoCassiopeia Dev'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/tag/development</infourl> <infourl title='MokoCassiopeia Dev'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/tag/development</infourl>
<downloads> <downloads>
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/development/mokocassiopeia-dev.zip</downloadurl> <downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/development/mokocassiopeia-03.09.08-dev.zip</downloadurl>
</downloads> </downloads>
<sha256>b6403918c5f65f4f52b889fbe4bd217c903f0d9c2fcf90f8eb59df1e7b4b7333</sha256> <sha256>ecff187531e65a40ae958ae91fff74da0c8856d1cc13e17a6e3d6905806b189e</sha256>
<tags> <tags>
<tag>development</tag> <tag>development</tag>
@@ -134,9 +134,9 @@
<infourl title='MokoCassiopeia Alpha'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/tag/alpha</infourl> <infourl title='MokoCassiopeia Alpha'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/tag/alpha</infourl>
<downloads> <downloads>
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/alpha/mokocassiopeia-alpha.zip</downloadurl> <downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/alpha/mokocassiopeia-03.09.07-alpha.zip</downloadurl>
</downloads> </downloads>
<sha256>b5f6d073d126f0041ddd475e61bc4eaa050e060e9b51f4fe0e8a348f188c40b2</sha256> <sha256>1a32180f8b26749bf5daf0602262e33464bcb3a042a8ff51ec2844cdeef2f9e5</sha256>
<tags> <tags>
<tag>alpha</tag> <tag>alpha</tag>
@@ -167,9 +167,9 @@
<infourl title='MokoCassiopeia Beta'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/tag/beta</infourl> <infourl title='MokoCassiopeia Beta'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/tag/beta</infourl>
<downloads> <downloads>
<downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/beta/mokocassiopeia-beta.zip</downloadurl> <downloadurl type='full' format='zip'>https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/beta/mokocassiopeia-03.09.07-beta.zip</downloadurl>
</downloads> </downloads>
<sha256>b5f6d073d126f0041ddd475e61bc4eaa050e060e9b51f4fe0e8a348f188c40b2</sha256> <sha256>1a32180f8b26749bf5daf0602262e33464bcb3a042a8ff51ec2844cdeef2f9e5</sha256>
<tags> <tags>
<tag>beta</tag> <tag>beta</tag>