From 3df143482ea8923e77705227ce414c35d0214e9d Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:08 -0500 Subject: [PATCH 01/54] chore: update LICENSE from MokoStandards -- 2.49.1 From 91381fcd9525e0b4e4b25c507d3ea2acebc348ea Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:09 -0500 Subject: [PATCH 02/54] chore: update SECURITY.md from MokoStandards --- SECURITY.md | 287 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 171 insertions(+), 116 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 9c2b528..5a7fba6 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,185 +1,240 @@ -## 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] | -- 2.49.1 From fb9f18cae62509e91bd27b63609203758a750579 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:10 -0500 Subject: [PATCH 03/54] chore: update CODE_OF_CONDUCT.md from MokoStandards --- CODE_OF_CONDUCT.md | 131 +++++++++++++++++++++------------------------ 1 file changed, 60 insertions(+), 71 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b0b170d..9e68438 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,98 +1,87 @@ - +# 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. -- 2.49.1 From 83d809f6ec8ef55989677a0bd61b85150a4dcb00 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:11 -0500 Subject: [PATCH 04/54] chore: update CONTRIBUTING.md from MokoStandards --- CONTRIBUTING.md | 189 ++++++++++++++++++++++-------------------------- 1 file changed, 86 insertions(+), 103 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1b8f124..9f6464c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,145 +1,128 @@ + 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.* -- 2.49.1 From 9a58238ac80e2a7ed3d825308730ce990e06ad30 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:12 -0500 Subject: [PATCH 05/54] chore: update update.xml from MokoStandards -- 2.49.1 From 9b9f691cd036a420bc367d2d21742a686db56655 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:13 -0500 Subject: [PATCH 06/54] chore: update phpstan.neon from MokoStandards -- 2.49.1 From ddb5727fb925ec9ef3f7230268925724e919aa9c Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:14 -0500 Subject: [PATCH 07/54] chore: update Makefile from MokoStandards -- 2.49.1 From cff9d9817b7483c8c83f8e293aa34cd6d1e2f88a Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:15 -0500 Subject: [PATCH 08/54] chore: update .gitignore from MokoStandards --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 6284ec5..75765e9 100644 --- a/.gitignore +++ b/.gitignore @@ -909,3 +909,6 @@ modulebuilder.txt # ── MokoStandards sync (auto-appended) ──────────────────────────────── !src/media/vendor/ + +# ── MokoStandards sync (auto-appended) ──────────────────────────────── +.claude/ -- 2.49.1 From eb1141dd7a74e81060fc7a227512bc63704377a4 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:15 -0500 Subject: [PATCH 09/54] chore: update composer.json from MokoStandards --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index b60f555..83ea53f 100644 --- a/composer.json +++ b/composer.json @@ -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", -- 2.49.1 From 3fab12dad9c7104f89d116f8c0d3e43ba3affd1e Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:16 -0500 Subject: [PATCH 10/54] chore: add .mokostandards from MokoStandards --- .mokostandards | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .mokostandards diff --git a/.mokostandards b/.mokostandards new file mode 100644 index 0000000..623a1d9 --- /dev/null +++ b/.mokostandards @@ -0,0 +1,20 @@ +# Copyright (C) 2026 Moko Consulting +# 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" -- 2.49.1 From ec10d10a16609b6eba80d019da7115530f312b71 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:17 -0500 Subject: [PATCH 11/54] chore: add docs/update-server.md from MokoStandards --- docs/update-server.md | 119 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 docs/update-server.md diff --git a/docs/update-server.md b/docs/update-server.md new file mode 100644 index 0000000..5e5d719 --- /dev/null +++ b/docs/update-server.md @@ -0,0 +1,119 @@ + + +# 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 `` tag in the extension's XML manifest. MokoStandards generates this file automatically. + +### Automatic Generation + +| Event | Workflow | `` | `` | +|-------|----------|---------|-------------| +| 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 + + + + Extension Name + Extension Name update + com_extensionname + component + 01.02.03 + site + system + + stable + + https://github.com/.../releases/tag/v01.02.03 + + https://github.com/.../releases/download/v01.02.03/com_ext-01.02.03.zip + + + 8.2 + Moko Consulting + https://mokoconsulting.tech + + +``` + +### Metadata Source + +All metadata is extracted from the extension's XML manifest (`src/*.xml`) at build time: + +| XML Element | Source | Notes | +|-------------|--------|-------| +| `` | `` in manifest | Extension display name | +| `` | `` in manifest | Must match installed extension identifier | +| `` | `type` attribute on `` | `component`, `module`, `plugin`, `library`, `package`, `template` | +| `` | `client` attribute on `` | `site` or `administrator` — **required for plugins and modules** | +| `` | `group` attribute on `` | Plugin group (e.g., `system`, `content`) — **required for plugins** | +| `` | `` in manifest | Falls back to Joomla 5.x / 6.x if not specified | +| `` | `` in manifest | Included only if present | + +### Extension Manifest Setup + +Your XML manifest must include an `` tag pointing to the `update.xml` on the `main` branch: + +```xml + + My Extension + com_myextension + + + + https://raw.githubusercontent.com/mokoconsulting-tech/MokoCassiopeia/main/update.xml + + + +``` + +### 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 `development`, download points to branch archive +2. **Release Candidate** (`rc/**`): `update.xml` with `rc`, version set to `XX.YY.ZZ-rc` +3. **Stable Release** (merge to `main`): `update.xml` with `stable`, 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 `` tag +- ``, ``, ``, `` 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.* -- 2.49.1 From 34d5e127847e23628b70c4e8942fb72f3697be22 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:18 -0500 Subject: [PATCH 12/54] chore: update .github/copilot.yml from MokoStandards -- 2.49.1 From 06fc7ec198d4bb7eef88c071945fc4c56d18dd5c Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:19 -0500 Subject: [PATCH 13/54] chore: update .github/copilot-instructions.md from MokoStandards -- 2.49.1 From a5aff5d9d3cc570ba5b4ec3132767b719d2d5d31 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:19 -0500 Subject: [PATCH 14/54] chore: update .github/CLAUDE.md from MokoStandards -- 2.49.1 From 38b957e18085691e77efc5f68bca90e728eb8ffc Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:20 -0500 Subject: [PATCH 15/54] chore: update .github/workflows/codeql-analysis.yml from MokoStandards --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3f50896..7b591b6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -8,7 +8,7 @@ # DEFGROUP: GitHub.Workflow.Template # INGROUP: MokoStandards.Security # REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /templates/workflows/generic/codeql-analysis.yml.template +# PATH: /templates/workflows/generic/codeql-analysis.yml # VERSION: 04.04.01 # BRIEF: CodeQL security scanning workflow (generic — all repo types) # NOTE: Deployed to .github/workflows/codeql-analysis.yml in governed repos. -- 2.49.1 From b195a24c9f4e55db99c25f675ec3bd9cd35bdb53 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:21 -0500 Subject: [PATCH 16/54] chore: update .github/workflows/standards-compliance.yml from MokoStandards -- 2.49.1 From ae0d6f6f0c3e6ef7b389c9b675be976dac244c7a Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:22 -0500 Subject: [PATCH 17/54] chore: update .github/workflows/enterprise-firewall-setup.yml from MokoStandards --- .github/workflows/enterprise-firewall-setup.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/enterprise-firewall-setup.yml b/.github/workflows/enterprise-firewall-setup.yml index 9465c77..dbce33c 100644 --- a/.github/workflows/enterprise-firewall-setup.yml +++ b/.github/workflows/enterprise-firewall-setup.yml @@ -21,7 +21,7 @@ # DEFGROUP: GitHub.Workflow # INGROUP: MokoStandards.Firewall # REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /templates/workflows/shared/enterprise-firewall-setup.yml.template +# PATH: /templates/workflows/shared/enterprise-firewall-setup.yml # VERSION: 04.04.01 # 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. -- 2.49.1 From 37d5cb1aa575c2f1ab73e13fe6dc68fe7db752b1 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:22 -0500 Subject: [PATCH 18/54] chore: update .github/workflows/deploy-dev.yml from MokoStandards --- .github/workflows/deploy-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 644a38b..1c9815d 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -21,7 +21,7 @@ # DEFGROUP: GitHub.Workflow # INGROUP: MokoStandards.Deploy # REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /templates/workflows/shared/deploy-dev.yml.template +# PATH: /templates/workflows/shared/deploy-dev.yml # VERSION: 04.04.01 # 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. -- 2.49.1 From 994d5f56b0c0bab66792651279ec3b30613cd626 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:23 -0500 Subject: [PATCH 19/54] chore: update .github/workflows/deploy-demo.yml from MokoStandards --- .github/workflows/deploy-demo.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-demo.yml b/.github/workflows/deploy-demo.yml index 061e867..4238ec6 100644 --- a/.github/workflows/deploy-demo.yml +++ b/.github/workflows/deploy-demo.yml @@ -21,7 +21,7 @@ # DEFGROUP: GitHub.Workflow # INGROUP: MokoStandards.Deploy # REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /templates/workflows/shared/deploy-demo.yml.template +# PATH: /templates/workflows/shared/deploy-demo.yml # VERSION: 04.04.01 # 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. -- 2.49.1 From 1a560849507bac3b0129b6b00ffa57e619b310f0 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:24 -0500 Subject: [PATCH 20/54] chore: update .github/workflows/deploy-rs.yml from MokoStandards --- .github/workflows/deploy-rs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-rs.yml b/.github/workflows/deploy-rs.yml index c6c45ae..9f3c212 100644 --- a/.github/workflows/deploy-rs.yml +++ b/.github/workflows/deploy-rs.yml @@ -21,7 +21,7 @@ # DEFGROUP: GitHub.Workflow # INGROUP: MokoStandards.Deploy # REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /templates/workflows/shared/deploy-rs.yml.template +# PATH: /templates/workflows/shared/deploy-rs.yml # VERSION: 04.04.01 # 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. -- 2.49.1 From 0f707c84f9796f2ab4dca199fcf42625217342d4 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:25 -0500 Subject: [PATCH 21/54] chore: update .github/workflows/sync-version-on-merge.yml from MokoStandards --- .github/workflows/sync-version-on-merge.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sync-version-on-merge.yml b/.github/workflows/sync-version-on-merge.yml index 81a17a0..79cca55 100644 --- a/.github/workflows/sync-version-on-merge.yml +++ b/.github/workflows/sync-version-on-merge.yml @@ -8,7 +8,7 @@ # DEFGROUP: GitHub.Workflow # INGROUP: MokoStandards.Automation # 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 # VERSION: 04.04.01 # 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. -- 2.49.1 From 464e0a9d2885d5b5de7e98e8ca790a860e85ede0 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:25 -0500 Subject: [PATCH 22/54] chore: update .github/workflows/auto-release.yml from MokoStandards --- .github/workflows/auto-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index 7af55e4..bb3ec10 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -6,7 +6,7 @@ # DEFGROUP: GitHub.Workflow # INGROUP: MokoStandards.Release # REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /templates/workflows/shared/auto-release.yml.template +# PATH: /templates/workflows/shared/auto-release.yml # VERSION: 04.04.01 # BRIEF: Unified build & release pipeline — version branch, platform version, badges, tag, release # -- 2.49.1 From 91977859ce3a3dc134af28aaa9be3a4759ec562a Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:26 -0500 Subject: [PATCH 23/54] chore: update .github/workflows/repository-cleanup.yml from MokoStandards --- .github/workflows/repository-cleanup.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/repository-cleanup.yml b/.github/workflows/repository-cleanup.yml index 06b2a2e..83289b4 100644 --- a/.github/workflows/repository-cleanup.yml +++ b/.github/workflows/repository-cleanup.yml @@ -8,7 +8,7 @@ # DEFGROUP: GitHub.Workflow # INGROUP: MokoStandards.Maintenance # REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /templates/workflows/shared/repository-cleanup.yml.template +# PATH: /templates/workflows/shared/repository-cleanup.yml # VERSION: 04.04.01 # 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. -- 2.49.1 From 2d0e1a099d688c1fcbae1603fd4306a7336473d1 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:27 -0500 Subject: [PATCH 24/54] chore: update .github/workflows/auto-dev-issue.yml from MokoStandards --- .github/workflows/auto-dev-issue.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-dev-issue.yml b/.github/workflows/auto-dev-issue.yml index 3364016..8da1d1a 100644 --- a/.github/workflows/auto-dev-issue.yml +++ b/.github/workflows/auto-dev-issue.yml @@ -8,7 +8,7 @@ # DEFGROUP: GitHub.Workflow # INGROUP: MokoStandards.Automation # REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /templates/workflows/shared/auto-dev-issue.yml.template +# PATH: /templates/workflows/shared/auto-dev-issue.yml # VERSION: 04.04.01 # BRIEF: Auto-create tracking issue when a dev/** or rc/** branch is pushed # NOTE: Synced via bulk-repo-sync to .github/workflows/auto-dev-issue.yml in all governed repos. -- 2.49.1 From b1db95f3f311d602bd118707a1720e7eaf82f0f0 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:28 -0500 Subject: [PATCH 25/54] chore: update .github/workflows/repo_health.yml from MokoStandards --- .github/workflows/repo_health.yml | 95 ++++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 15 deletions(-) diff --git a/.github/workflows/repo_health.yml b/.github/workflows/repo_health.yml index d3daece..09206e7 100644 --- a/.github/workflows/repo_health.yml +++ b/.github/workflows/repo_health.yml @@ -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.04.01 # 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: @@ -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 tag)") + else + # Check tag exists + if ! grep -qP '' "${MANIFEST}"; then + joomla_findings+=("XML manifest: 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 tag + if ! grep -qP '' "${MANIFEST}"; then + joomla_findings+=("XML manifest: tag missing") + fi + # Check tag + if ! grep -qP '' "${MANIFEST}"; then + joomla_findings+=("XML manifest: tag missing") + fi + # Check for Joomla 5+ + if ! grep -qP ' 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 + + # update.xml must exist in root (Joomla update server) + if [ ! -f 'update.xml' ]; then + joomla_findings+=("update.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=() -- 2.49.1 From bd0462e808e653eb793470881fe0984f4a7c4535 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:28 -0500 Subject: [PATCH 26/54] chore: update .github/ISSUE_TEMPLATE/config.yml from MokoStandards -- 2.49.1 From 0bcd752fd465540f35f5071f57df53ed43dc0eed Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:29 -0500 Subject: [PATCH 27/54] chore: update .github/ISSUE_TEMPLATE/adr.md from MokoStandards -- 2.49.1 From aee8147d405d6df9fc028503adddb9ee80c2888b Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:30 -0500 Subject: [PATCH 28/54] chore: update .github/ISSUE_TEMPLATE/bug_report.md from MokoStandards -- 2.49.1 From 75f11a582596fe126476d81cfa4b522b4cf60546 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:31 -0500 Subject: [PATCH 29/54] chore: update .github/ISSUE_TEMPLATE/documentation.md from MokoStandards -- 2.49.1 From a143c265b76db3ab180869b546d85f628b73b6ab Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:32 -0500 Subject: [PATCH 30/54] chore: update .github/ISSUE_TEMPLATE/enterprise_support.md from MokoStandards -- 2.49.1 From 1149fb6797ca77c7b3553e6b9a6c1f25656bbb60 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:32 -0500 Subject: [PATCH 31/54] chore: update .github/ISSUE_TEMPLATE/feature_request.md from MokoStandards -- 2.49.1 From 8574c8f6a5bacd2f519321f2cd4eb28d8250d153 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:33 -0500 Subject: [PATCH 32/54] chore: update .github/ISSUE_TEMPLATE/firewall-request.md from MokoStandards --- .github/ISSUE_TEMPLATE/firewall-request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/firewall-request.md b/.github/ISSUE_TEMPLATE/firewall-request.md index 4a43395..38be866 100644 --- a/.github/ISSUE_TEMPLATE/firewall-request.md +++ b/.github/ISSUE_TEMPLATE/firewall-request.md @@ -3,7 +3,7 @@ name: Firewall Request about: Request firewall rule changes or access to external resources title: '[FIREWALL] [Resource Name] - [Brief Description]' labels: ['firewall-request', 'infrastructure', 'security'] -assignees: [] +assignees: ['jmiller-moko'] --- -- 2.49.1 From d287284295db0e7d8e290c467636382c4cd7f00c Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:34 -0500 Subject: [PATCH 33/54] chore: update .github/ISSUE_TEMPLATE/question.md from MokoStandards --- .github/ISSUE_TEMPLATE/question.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index e17850b..74df7a0 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -3,7 +3,7 @@ name: Question about: Ask a question about usage, features, or best practices title: '[QUESTION] ' labels: ['question'] -assignees: [] +assignees: ['jmiller-moko'] --- -- 2.49.1 From 8601cfb964fa386bda60c7a440623d11121ea64f Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:34 -0500 Subject: [PATCH 34/54] chore: update .github/ISSUE_TEMPLATE/request-license.md from MokoStandards --- .github/ISSUE_TEMPLATE/request-license.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/request-license.md b/.github/ISSUE_TEMPLATE/request-license.md index 52c3b74..a9c87a7 100644 --- a/.github/ISSUE_TEMPLATE/request-license.md +++ b/.github/ISSUE_TEMPLATE/request-license.md @@ -3,7 +3,7 @@ name: License Request about: Request an organization license for Sublime Text title: '[LICENSE REQUEST] Sublime Text - [Your Name]' labels: ['license-request', 'admin'] -assignees: [] +assignees: ['jmiller-moko'] --- -- 2.49.1 From 5aa46ac0145b5fe27245822898b6e23f1784d7c1 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:35 -0500 Subject: [PATCH 35/54] chore: update .github/ISSUE_TEMPLATE/rfc.md from MokoStandards -- 2.49.1 From 4de7b0ae505d0ba9e93d27b3af9de6ab2aa624b8 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:36 -0500 Subject: [PATCH 36/54] chore: update .github/ISSUE_TEMPLATE/security.md from MokoStandards -- 2.49.1 From 800acd395694ee644b1704e6d1b1bcac18c480df Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:37 -0500 Subject: [PATCH 37/54] chore: update .github/ISSUE_TEMPLATE/joomla_issue.md from MokoStandards -- 2.49.1 From 50d480e99da94e84178fabe0bc7af9ec1a91d44d Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:37 -0500 Subject: [PATCH 38/54] chore: update .github/CODEOWNERS from MokoStandards -- 2.49.1 From 3af596554d9053c8ae419a264b918c7f6ab8e573 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 10:18:38 -0500 Subject: [PATCH 39/54] chore: migrate .mokostandards to .github/ --- .github/.mokostandards | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/.mokostandards diff --git a/.github/.mokostandards b/.github/.mokostandards new file mode 100644 index 0000000..623a1d9 --- /dev/null +++ b/.github/.mokostandards @@ -0,0 +1,20 @@ +# Copyright (C) 2026 Moko Consulting +# 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" -- 2.49.1 From c4779a395e11b0ff3c3e88b1fd85dd5d49bbd314 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 20:20:42 -0500 Subject: [PATCH 40/54] fix: update .gitignore to platform-specific template (v04.05) [skip ci] Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 750 ++--------------------------------------------------- 1 file changed, 19 insertions(+), 731 deletions(-) diff --git a/.gitignore b/.gitignore index 75765e9..01449c1 100644 --- a/.gitignore +++ b/.gitignore @@ -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,730 +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/ - -# ── MokoStandards sync (auto-appended) ──────────────────────────────── -.claude/ -- 2.49.1 From 8e7ea807dab5e3ed24a5fe1fca543d095f133964 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 21:25:33 -0500 Subject: [PATCH 41/54] fix: update all workflows to latest MokoStandards v04.04 templates [skip ci] Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/auto-dev-issue.yml | 4 +- .github/workflows/auto-release.yml | 4 +- .github/workflows/codeql-analysis.yml | 4 +- .github/workflows/deploy-demo.yml | 4 +- .github/workflows/deploy-dev.yml | 69 ++----------------- .github/workflows/deploy-rs.yml | 4 +- .../workflows/enterprise-firewall-setup.yml | 4 +- .github/workflows/repository-cleanup.yml | 6 +- .github/workflows/standards-compliance.yml | 2 +- .github/workflows/sync-version-on-merge.yml | 4 +- 10 files changed, 22 insertions(+), 83 deletions(-) diff --git a/.github/workflows/auto-dev-issue.yml b/.github/workflows/auto-dev-issue.yml index 8da1d1a..c167000 100644 --- a/.github/workflows/auto-dev-issue.yml +++ b/.github/workflows/auto-dev-issue.yml @@ -8,8 +8,8 @@ # DEFGROUP: GitHub.Workflow # INGROUP: MokoStandards.Automation # REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /templates/workflows/shared/auto-dev-issue.yml -# VERSION: 04.04.01 +# PATH: /templates/workflows/shared/auto-dev-issue.yml.template +# VERSION: 04.05.00 # BRIEF: Auto-create tracking issue when a dev/** or rc/** branch is pushed # NOTE: Synced via bulk-repo-sync to .github/workflows/auto-dev-issue.yml in all governed repos. diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index bb3ec10..8951664 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -6,8 +6,8 @@ # DEFGROUP: GitHub.Workflow # INGROUP: MokoStandards.Release # REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /templates/workflows/shared/auto-release.yml -# VERSION: 04.04.01 +# PATH: /templates/workflows/shared/auto-release.yml.template +# VERSION: 04.05.00 # BRIEF: Unified build & release pipeline — version branch, platform version, badges, tag, release # # ╔════════════════════════════════════════════════════════════════════════╗ diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 7b591b6..3abfb02 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -8,8 +8,8 @@ # DEFGROUP: GitHub.Workflow.Template # INGROUP: MokoStandards.Security # REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /templates/workflows/generic/codeql-analysis.yml -# VERSION: 04.04.01 +# PATH: /templates/workflows/generic/codeql-analysis.yml.template +# VERSION: 04.05.00 # 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. diff --git a/.github/workflows/deploy-demo.yml b/.github/workflows/deploy-demo.yml index 4238ec6..4da8dab 100644 --- a/.github/workflows/deploy-demo.yml +++ b/.github/workflows/deploy-demo.yml @@ -21,8 +21,8 @@ # DEFGROUP: GitHub.Workflow # INGROUP: MokoStandards.Deploy # REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /templates/workflows/shared/deploy-demo.yml -# VERSION: 04.04.01 +# PATH: /templates/workflows/shared/deploy-demo.yml.template +# 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. diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 1c9815d..3d7986a 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -21,8 +21,8 @@ # DEFGROUP: GitHub.Workflow # INGROUP: MokoStandards.Deploy # REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /templates/workflows/shared/deploy-dev.yml -# VERSION: 04.04.01 +# PATH: /templates/workflows/shared/deploy-dev.yml.template +# 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. @@ -667,69 +667,8 @@ jobs: # 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() diff --git a/.github/workflows/deploy-rs.yml b/.github/workflows/deploy-rs.yml index 9f3c212..4cf3ed7 100644 --- a/.github/workflows/deploy-rs.yml +++ b/.github/workflows/deploy-rs.yml @@ -21,8 +21,8 @@ # DEFGROUP: GitHub.Workflow # INGROUP: MokoStandards.Deploy # REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /templates/workflows/shared/deploy-rs.yml -# VERSION: 04.04.01 +# PATH: /templates/workflows/shared/deploy-rs.yml.template +# 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. diff --git a/.github/workflows/enterprise-firewall-setup.yml b/.github/workflows/enterprise-firewall-setup.yml index dbce33c..8979107 100644 --- a/.github/workflows/enterprise-firewall-setup.yml +++ b/.github/workflows/enterprise-firewall-setup.yml @@ -21,8 +21,8 @@ # DEFGROUP: GitHub.Workflow # INGROUP: MokoStandards.Firewall # REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /templates/workflows/shared/enterprise-firewall-setup.yml -# VERSION: 04.04.01 +# PATH: /templates/workflows/shared/enterprise-firewall-setup.yml.template +# VERSION: 04.05.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. diff --git a/.github/workflows/repository-cleanup.yml b/.github/workflows/repository-cleanup.yml index 83289b4..e77c279 100644 --- a/.github/workflows/repository-cleanup.yml +++ b/.github/workflows/repository-cleanup.yml @@ -8,8 +8,8 @@ # DEFGROUP: GitHub.Workflow # INGROUP: MokoStandards.Maintenance # REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /templates/workflows/shared/repository-cleanup.yml -# VERSION: 04.04.01 +# PATH: /templates/workflows/shared/repository-cleanup.yml.template +# VERSION: 04.05.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 diff --git a/.github/workflows/standards-compliance.yml b/.github/workflows/standards-compliance.yml index 0d85ae4..ff7d35c 100644 --- a/.github/workflows/standards-compliance.yml +++ b/.github/workflows/standards-compliance.yml @@ -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.05.00 # BRIEF: MokoStandards compliance validation workflow # NOTE: Validates repository structure, documentation, and coding standards diff --git a/.github/workflows/sync-version-on-merge.yml b/.github/workflows/sync-version-on-merge.yml index 79cca55..46b4cc8 100644 --- a/.github/workflows/sync-version-on-merge.yml +++ b/.github/workflows/sync-version-on-merge.yml @@ -8,8 +8,8 @@ # DEFGROUP: GitHub.Workflow # INGROUP: MokoStandards.Automation # REPO: https://github.com/mokoconsulting-tech/MokoStandards -# PATH: /templates/workflows/shared/sync-version-on-merge.yml -# VERSION: 04.04.01 +# PATH: /templates/workflows/shared/sync-version-on-merge.yml.template +# VERSION: 04.05.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. -- 2.49.1 From 9ba968a83a8184c7d02eca994d2b08990ddfc6df Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Thu, 2 Apr 2026 22:46:59 -0500 Subject: [PATCH 42/54] fix: update all workflows to latest MokoStandards v04.04 templates [skip ci] Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/deploy-dev.yml | 4 ++ .github/workflows/standards-compliance.yml | 83 +++++++++++++++++----- 2 files changed, 71 insertions(+), 16 deletions(-) diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 3d7986a..9efa525 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -582,6 +582,10 @@ jobs: > /tmp/sftp-config.json fi + # Dev deploys skip minified files — use unminified sources for debugging + echo "*.min.js" >> .ftp_ignore + echo "*.min.css" >> .ftp_ignore + # ── Run deploy-sftp.php from MokoStandards ──────────────────────────── DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json) if [ "$USE_PASSPHRASE" = "true" ]; then diff --git a/.github/workflows/standards-compliance.yml b/.github/workflows/standards-compliance.yml index ff7d35c..9031292 100644 --- a/.github/workflows/standards-compliance.yml +++ b/.github/workflows/standards-compliance.yml @@ -163,7 +163,9 @@ 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 '/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 +522,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 +1967,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.05 --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 +2039,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.05 --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 -- 2.49.1 From 372fc0736174dea09f85e83ecc0064a9f11c0b7a Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Sat, 4 Apr 2026 14:25:55 -0500 Subject: [PATCH 43/54] ci: sync workflow files from main Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/repo_health.yml | 95 +++++-------------------------- 1 file changed, 15 insertions(+), 80 deletions(-) diff --git a/.github/workflows/repo_health.yml b/.github/workflows/repo_health.yml index 09206e7..d3daece 100644 --- a/.github/workflows/repo_health.yml +++ b/.github/workflows/repo_health.yml @@ -10,7 +10,7 @@ # INGROUP: MokoStandards.Validation # REPO: https://github.com/mokoconsulting-tech/MokoStandards # PATH: /.github/workflows/repo_health.yml -# VERSION: 04.04.01 +# VERSION: 04.01.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: 'Validation profile: all, release, scripts, or repo' + 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[...] required: true default: all type: choice @@ -39,7 +39,19 @@ on: - scripts - repo pull_request: + paths: + - .github/workflows/** + - scripts/** + - docs/** + - dev/** push: + branches: + - main + paths: + - .github/workflows/** + - scripts/** + - docs/** + - dev/** permissions: contents: read @@ -56,7 +68,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/ + REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.github/workflows/,src/ REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/ REPO_DISALLOWED_DIRS: REPO_DISALLOWED_FILES: TODO.md,todo.md @@ -70,7 +82,6 @@ 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: @@ -401,15 +412,6 @@ 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}" @@ -559,73 +561,6 @@ jobs: } >> "${GITHUB_STEP_SUMMARY}" fi - # ── Joomla-specific checks ─────────────────────────────────────── - joomla_findings=() - - # XML manifest: find any XML file containing tag)") - else - # Check tag exists - if ! grep -qP '' "${MANIFEST}"; then - joomla_findings+=("XML manifest: 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 tag - if ! grep -qP '' "${MANIFEST}"; then - joomla_findings+=("XML manifest: tag missing") - fi - # Check tag - if ! grep -qP '' "${MANIFEST}"; then - joomla_findings+=("XML manifest: tag missing") - fi - # Check for Joomla 5+ - if ! grep -qP ' 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 - - # update.xml must exist in root (Joomla update server) - if [ ! -f 'update.xml' ]; then - joomla_findings+=("update.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=() -- 2.49.1 From decd44dd047eea2e050c54e07dd66d4da577894c Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Sat, 4 Apr 2026 15:19:51 -0500 Subject: [PATCH 44/54] ci: sync workflows from main Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/.mokostandards | 21 +---- .github/workflows/auto-release.yml | 2 +- .github/workflows/deploy-demo.yml | 37 ++++---- .github/workflows/deploy-dev.yml | 44 +++++----- .github/workflows/deploy-rs.yml | 37 ++++---- .github/workflows/repo_health.yml | 95 +++++++++++++++++---- .github/workflows/standards-compliance.yml | 8 +- .github/workflows/sync-version-on-merge.yml | 2 +- 8 files changed, 144 insertions(+), 102 deletions(-) diff --git a/.github/.mokostandards b/.github/.mokostandards index 623a1d9..09b7828 100644 --- a/.github/.mokostandards +++ b/.github/.mokostandards @@ -1,20 +1 @@ -# Copyright (C) 2026 Moko Consulting -# 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" +platform: waas-component diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index 8951664..5462926 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -64,7 +64,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 diff --git a/.github/workflows/deploy-demo.yml b/.github/workflows/deploy-demo.yml index 4da8dab..7f785bf 100644 --- a/.github/workflows/deploy-demo.yml +++ b/.github/workflows/deploy-demo.yml @@ -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 diff --git a/.github/workflows/deploy-dev.yml b/.github/workflows/deploy-dev.yml index 9efa525..7019628 100644 --- a/.github/workflows/deploy-dev.yml +++ b/.github/workflows/deploy-dev.yml @@ -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 @@ -583,8 +576,8 @@ jobs: fi # Dev deploys skip minified files — use unminified sources for debugging - echo "*.min.js" >> .ftp_ignore - echo "*.min.css" >> .ftp_ignore + 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) @@ -666,8 +659,15 @@ 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 diff --git a/.github/workflows/deploy-rs.yml b/.github/workflows/deploy-rs.yml index 4cf3ed7..bf9a779 100644 --- a/.github/workflows/deploy-rs.yml +++ b/.github/workflows/deploy-rs.yml @@ -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 diff --git a/.github/workflows/repo_health.yml b/.github/workflows/repo_health.yml index d3daece..8433039 100644 --- a/.github/workflows/repo_health.yml +++ b/.github/workflows/repo_health.yml @@ -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.05.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: @@ -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 tag)") + else + # Check tag exists + if ! grep -qP '' "${MANIFEST}"; then + joomla_findings+=("XML manifest: 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 tag + if ! grep -qP '' "${MANIFEST}"; then + joomla_findings+=("XML manifest: tag missing") + fi + # Check tag + if ! grep -qP '' "${MANIFEST}"; then + joomla_findings+=("XML manifest: tag missing") + fi + # Check for Joomla 5+ + if ! grep -qP ' 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 + + # update.xml must exist in root (Joomla update server) + if [ ! -f 'update.xml' ]; then + joomla_findings+=("update.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=() diff --git a/.github/workflows/standards-compliance.yml b/.github/workflows/standards-compliance.yml index 9031292..df8413c 100644 --- a/.github/workflows/standards-compliance.yml +++ b/.github/workflows/standards-compliance.yml @@ -165,7 +165,9 @@ jobs: 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' | \ grep -v 'type="password"' | grep -v 'type="text"' | grep -v 'name="password"' | grep -v 'name="secretkey"' | \ - grep -v ' Date: Tue, 7 Apr 2026 14:21:37 -0500 Subject: [PATCH 45/54] ci: sync workflows from main Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/auto-update-sha.yml | 291 +++++++++++++------------- .github/workflows/repo_health.yml | 6 +- 2 files changed, 147 insertions(+), 150 deletions(-) diff --git a/.github/workflows/auto-update-sha.yml b/.github/workflows/auto-update-sha.yml index 2aa5fb6..713393e 100644 --- a/.github/workflows/auto-update-sha.yml +++ b/.github/workflows/auto-update-sha.yml @@ -1,147 +1,144 @@ -# Copyright (C) 2026 Moko Consulting -# 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|.*|${TAG}|" updates.xml - - # Update creation date - sed -i "s|.*|${DATE}|" updates.xml - - # Update download URL - sed -i "s|.*|https://github.com/${{ github.repository }}/releases/download/${TAG}/mokocassiopeia-src-${TAG}.zip|" updates.xml - - # Update or add SHA-256 hash - if grep -q "" updates.xml; then - sed -i "s|.*|sha256:${SHA256}|" updates.xml - else - # Add SHA-256 after downloadurl - sed -i "/<\/downloadurl>/a\ 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 +# 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|.*|${TAG}|" updates.xml + + # Update creation date + sed -i "s|.*|${DATE}|" updates.xml + + # Update download URL + sed -i "s|.*|https://github.com/${{ github.repository }}/releases/download/${TAG}/mokocassiopeia-src-${TAG}.zip|" updates.xml + + # Update or add SHA-256 hash + if grep -q "" updates.xml; then + sed -i "s|.*|sha256:${SHA256}|" updates.xml + else + # Add SHA-256 after downloadurl + sed -i "/<\/downloadurl>/a\ 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 diff --git a/.github/workflows/repo_health.yml b/.github/workflows/repo_health.yml index 8433039..0129292 100644 --- a/.github/workflows/repo_health.yml +++ b/.github/workflows/repo_health.yml @@ -162,7 +162,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 @@ -257,7 +257,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 @@ -371,7 +371,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 -- 2.49.1 From 097eca9f22b54bd4dd283989e18c6ffd6f76f9de Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Tue, 7 Apr 2026 18:39:22 -0500 Subject: [PATCH 46/54] ci: sync workflows from main Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/auto-assign.yml | 76 +++ .github/workflows/auto-dev-issue.yml | 176 ++++-- .github/workflows/auto-release.yml | 521 ++++++++++-------- .github/workflows/changelog-validation.yml | 101 ++++ .github/workflows/ci-joomla.yml | 391 +++++++++++++ .../workflows/enterprise-firewall-setup.yml | 2 +- .github/workflows/repository-cleanup.yml | 2 +- .github/workflows/sync-version-on-merge.yml | 2 +- .github/workflows/update-server.yml | 236 ++++++++ 9 files changed, 1241 insertions(+), 266 deletions(-) create mode 100644 .github/workflows/auto-assign.yml create mode 100644 .github/workflows/changelog-validation.yml create mode 100644 .github/workflows/ci-joomla.yml create mode 100644 .github/workflows/update-server.yml diff --git a/.github/workflows/auto-assign.yml b/.github/workflows/auto-assign.yml new file mode 100644 index 0000000..3752b66 --- /dev/null +++ b/.github/workflows/auto-assign.yml @@ -0,0 +1,76 @@ +# Copyright (C) 2026 Moko Consulting +# 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.05.11 +# 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 diff --git a/.github/workflows/auto-dev-issue.yml b/.github/workflows/auto-dev-issue.yml index c167000..38730ab 100644 --- a/.github/workflows/auto-dev-issue.yml +++ b/.github/workflows/auto-dev-issue.yml @@ -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.05.00 -# BRIEF: Auto-create tracking issue when a dev/** or rc/** branch is pushed +# VERSION: 04.05.13 +# 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,20 @@ 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/')) 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') @@ -58,45 +71,122 @@ 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 dev 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 to main|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 Main|Create PR from rc branch to main|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 + + # ── RC: Create or update draft release ──────────────────────────── + if [[ "$BRANCH" == rc/* ]]; then + MAJOR=$(echo "$VERSION" | awk -F. '{print $1}') + RELEASE_TAG="v${MAJOR}" + DRAFT_EXISTS=$(gh release view "$RELEASE_TAG" --json isDraft -q .isDraft 2>/dev/null || true) + + if [ -z "$DRAFT_EXISTS" ]; then + # No release exists — create draft + gh release create "$RELEASE_TAG" \ + --title "v${MAJOR} (RC: ${VERSION})" \ + --notes "## Release Candidate ${VERSION}\n\nRC branch: \`${BRANCH}\`\nTracking issue: ${PARENT_URL}" \ + --draft \ + --target main 2>/dev/null || true + echo "Draft release created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY + elif [ "$DRAFT_EXISTS" = "true" ]; then + # Draft exists — update title + gh release edit "$RELEASE_TAG" \ + --title "v${MAJOR} (RC: ${VERSION})" --draft 2>/dev/null || true + echo "Draft release updated: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY + else + # Release exists and is published — set back to draft for RC + gh release edit "$RELEASE_TAG" \ + --title "v${MAJOR} (RC: ${VERSION})" --draft 2>/dev/null || true + echo "Release ${RELEASE_TAG} set to draft for RC" >> $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 diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index 5462926..e9c2570 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -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.05.00 -# BRIEF: Unified build & release pipeline — version branch, platform version, badges, tag, release +# PATH: /templates/workflows/joomla/auto-release.yml.template +# VERSION: 04.05.13 +# BRIEF: Joomla build & release — ZIP package, update.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 )║ -# ║ 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 ) | +# | 4. Update [VERSION: XX.YY.ZZ] badges in markdown files | +# | 5. Write update.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 update.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 @@ -70,13 +72,13 @@ jobs: 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 +86,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 "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 +131,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 '/dev/null | head -1) + if [ -n "$MANIFEST" ]; then + XML_VER=$(grep -oP '\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 '/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 ']+type="\K[^"]+' "$MANIFEST" 2>/dev/null) - echo "✅ Extension type: ${TYPE:-unknown}" >> $GITHUB_STEP_SUMMARY - fi + # -- Joomla: extension type check -------- + TYPE=$(grep -oP ']+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 +243,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 +256,100 @@ jobs: fi done - # ── STEP 5: Write update files (Dolibarr: update.txt / Joomla: update.xml) - - name: "Step 5: Write update files" + # -- STEP 5: Write update.xml (Joomla update server) --------------------- + - name: "Step 5: Write update.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 '/dev/null | head -1) + if [ -z "$MANIFEST" ]; then + echo "Warning: No Joomla XML manifest found — skipping update.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 '/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 '\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "${{ github.event.repository.name }}") - EXT_TYPE=$(grep -oP ']+type="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "component") - EXT_ELEMENT=$(grep -oP '\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "") - EXT_CLIENT=$(grep -oP ']+client="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "") - EXT_FOLDER=$(grep -oP ']+group="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "") - TARGET_PLATFORM=$(grep -oP '' "$MANIFEST" 2>/dev/null | head -1 || echo "") - PHP_MINIMUM=$(grep -oP '\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "") + EXT_NAME=$(grep -oP '\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "${{ github.event.repository.name }}") + EXT_TYPE=$(grep -oP ']+type="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "component") + EXT_ELEMENT=$(grep -oP '\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "") + EXT_CLIENT=$(grep -oP ']+client="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "") + EXT_FOLDER=$(grep -oP ']+group="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "") + TARGET_PLATFORM=$(grep -oP '' "$MANIFEST" 2>/dev/null | head -1 || echo "") + PHP_MINIMUM=$(grep -oP '\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 site - CLIENT_TAG="" - if [ -n "$EXT_CLIENT" ]; then - CLIENT_TAG="${EXT_CLIENT}" - elif [ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]; then - CLIENT_TAG="site" - 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="${EXT_FOLDER}" - fi - - # Build targetplatform (fallback to Joomla 5+6 if not in manifest) - if [ -z "$TARGET_PLATFORM" ]; then - TARGET_PLATFORM=$(printf '' "/") - fi - - # Build php_minimum tag - PHP_TAG="" - if [ -n "$PHP_MINIMUM" ]; then - PHP_TAG="${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' '' - printf '%s\n' '' - printf '%s\n' ' ' - printf '%s\n' " ${EXT_NAME}" - printf '%s\n' " ${EXT_NAME} update" - printf '%s\n' " ${EXT_ELEMENT}" - printf '%s\n' " ${EXT_TYPE}" - printf '%s\n' " ${VERSION}" - [ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}" - [ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}" - printf '%s\n' ' ' - printf '%s\n' ' stable' - printf '%s\n' ' ' - printf '%s\n' " ${INFO_URL}" - printf '%s\n' ' ' - printf '%s\n' " ${DOWNLOAD_URL}" - printf '%s\n' ' ' - printf '%s\n' " ${TARGET_PLATFORM}" - [ -n "$PHP_TAG" ] && printf '%s\n' " ${PHP_TAG}" - printf '%s\n' ' Moko Consulting' - printf '%s\n' ' https://mokoconsulting.tech' - printf '%s\n' ' ' - printf '%s\n' '' - } > 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 site + CLIENT_TAG="" + if [ -n "$EXT_CLIENT" ]; then + CLIENT_TAG="${EXT_CLIENT}" + elif [ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]; then + CLIENT_TAG="site" + 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="${EXT_FOLDER}" + fi + + # Build targetplatform (fallback to Joomla 5 if not in manifest) + if [ -z "$TARGET_PLATFORM" ]; then + TARGET_PLATFORM=$(printf '' "/") + fi + + # Build php_minimum tag + PHP_TAG="" + if [ -n "$PHP_MINIMUM" ]; then + PHP_TAG="${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' '' + printf '%s\n' '' + printf '%s\n' ' ' + printf '%s\n' " ${EXT_NAME}" + printf '%s\n' " ${EXT_NAME} update" + printf '%s\n' " ${EXT_ELEMENT}" + printf '%s\n' " ${EXT_TYPE}" + printf '%s\n' " ${VERSION}" + [ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}" + [ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}" + printf '%s\n' ' ' + printf '%s\n' ' stable' + printf '%s\n' ' ' + printf '%s\n' " ${INFO_URL}" + printf '%s\n' ' ' + printf '%s\n' " ${DOWNLOAD_URL}" + printf '%s\n' ' ' + printf '%s\n' " ${TARGET_PLATFORM}" + [ -n "$PHP_TAG" ] && printf '%s\n' " ${PHP_TAG}" + printf '%s\n' ' Moko Consulting' + printf '%s\n' ' https://mokoconsulting.tech' + printf '%s\n' ' ' + printf '%s\n' '' + } > update.xml + + echo "update.xml: ${VERSION} (stable) — ${EXT_TYPE}/${EXT_ELEMENT}" >> $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 +360,25 @@ jobs: --author="github-actions[bot] " 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 +387,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 '/dev/null | head -1 || true) + [ -z "$MANIFEST" ] && exit 0 + + EXT_ELEMENT=$(grep -oP '\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}" . + 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 update.xml with SHA-256 for latest patch ------------- + if [ -f "update.xml" ]; then + if grep -q '' update.xml; then + sed -i "s|.*|sha256:${SHA256}|" update.xml + else + sed -i "s||\n sha256:${SHA256}|" update.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|]*>[^<]*|${DOWNLOAD_URL}|" update.xml + + git add update.xml + git commit -m "chore(release): SHA-256 + download URL for ${VERSION} [skip ci]" \ + --author="github-actions[bot] " || 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 diff --git a/.github/workflows/changelog-validation.yml b/.github/workflows/changelog-validation.yml new file mode 100644 index 0000000..9ed880a --- /dev/null +++ b/.github/workflows/changelog-validation.yml @@ -0,0 +1,101 @@ +# Copyright (C) 2026 Moko Consulting +# +# 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.05.13 +# 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 diff --git a/.github/workflows/ci-joomla.yml b/.github/workflows/ci-joomla.yml new file mode 100644 index 0000000..d7995d2 --- /dev/null +++ b/.github/workflows/ci-joomla.yml @@ -0,0 +1,391 @@ +# Copyright (C) 2026 Moko Consulting +# +# 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.05.13 +# 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.05 --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 /dev/null; then + MANIFEST="$XML_FILE" + break + fi + done + + if [ -z "$MANIFEST" ]; then + echo "No Joomla extension manifest found (XML file with \`> $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 "/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 "/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 matches README VERSION + MANIFEST_VERSION=$(grep -oP '\K[^<]+' "$MANIFEST" | head -1) + if [ -z "$MANIFEST_VERSION" ]; then + echo "No \`\` 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 ']*\btype="\K[^"]+' "$MANIFEST" | head -1) + if [ -z "$EXT_TYPE" ]; then + echo "Missing \`type\` attribute on \`\` 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 \`\` or \`\` 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 ']*\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 update.xml exists + if [ -f "update.xml" ] || [ -f "updates.xml" ]; then + echo "Update XML present." >> $GITHUB_STEP_SUMMARY + else + echo "No update.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 diff --git a/.github/workflows/enterprise-firewall-setup.yml b/.github/workflows/enterprise-firewall-setup.yml index 8979107..b6cf7ab 100644 --- a/.github/workflows/enterprise-firewall-setup.yml +++ b/.github/workflows/enterprise-firewall-setup.yml @@ -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.05.00 +# VERSION: 04.05.13 # 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. diff --git a/.github/workflows/repository-cleanup.yml b/.github/workflows/repository-cleanup.yml index e77c279..b078061 100644 --- a/.github/workflows/repository-cleanup.yml +++ b/.github/workflows/repository-cleanup.yml @@ -9,7 +9,7 @@ # INGROUP: MokoStandards.Maintenance # REPO: https://github.com/mokoconsulting-tech/MokoStandards # PATH: /templates/workflows/shared/repository-cleanup.yml.template -# VERSION: 04.05.00 +# VERSION: 04.05.13 # 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. diff --git a/.github/workflows/sync-version-on-merge.yml b/.github/workflows/sync-version-on-merge.yml index 5c60d37..7b2ef6c 100644 --- a/.github/workflows/sync-version-on-merge.yml +++ b/.github/workflows/sync-version-on-merge.yml @@ -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.05.00 +# VERSION: 04.05.13 # 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. diff --git a/.github/workflows/update-server.yml b/.github/workflows/update-server.yml new file mode 100644 index 0000000..91d9365 --- /dev/null +++ b/.github/workflows/update-server.yml @@ -0,0 +1,236 @@ +# Copyright (C) 2026 Moko Consulting +# +# 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.05.13 +# BRIEF: Update Joomla update server XML feed with stable/rc/dev entries +# +# Writes update.xml with multiple entries: +# - stable on push to main (from auto-release) +# - rc on push to rc/** +# - development on push to dev/** +# +# Joomla filters by user's "Minimum Stability" setting. + +name: Update Joomla Update Server XML Feed + +on: + push: + branches: + - 'dev/**' + - 'rc/**' + paths: + - 'src/**' + - 'htdocs/**' + workflow_dispatch: + inputs: + stability: + description: 'Stability tag (development, rc, stable)' + required: true + default: 'development' + type: choice + options: + - development + - rc + - stable + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +permissions: + contents: write + +jobs: + update-xml: + name: Update update.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.05 --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 update.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") + + # Determine stability from branch or input + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + STABILITY="${{ inputs.stability }}" + elif [[ "$BRANCH" == rc/* ]]; then + STABILITY="rc" + elif [[ "$BRANCH" == dev/* ]]; then + STABILITY="development" + else + STABILITY="stable" + fi + + # Parse manifest + MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '/dev/null | head -1) + if [ -z "$MANIFEST" ]; then + echo "No Joomla manifest found — skipping" + exit 0 + fi + + EXT_NAME=$(grep -oP '\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "${{ github.event.repository.name }}") + EXT_TYPE=$(grep -oP ']+type="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "component") + EXT_ELEMENT=$(grep -oP '\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || basename "$MANIFEST" .xml) + EXT_CLIENT=$(grep -oP ']+client="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "") + EXT_FOLDER=$(grep -oP ']+group="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "") + TARGET_PLATFORM=$(grep -oP '' "$MANIFEST" 2>/dev/null | head -1 || echo "") + PHP_MINIMUM=$(grep -oP '\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "") + + [ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(basename "$MANIFEST" .xml) + [ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '' "/") + + CLIENT_TAG="" + [ -n "$EXT_CLIENT" ] && CLIENT_TAG="${EXT_CLIENT}" + [ -z "$CLIENT_TAG" ] && ([ "$EXT_TYPE" = "module" ] || [ "$EXT_TYPE" = "plugin" ]) && CLIENT_TAG="site" + + FOLDER_TAG="" + [ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ] && FOLDER_TAG="${EXT_FOLDER}" + + PHP_TAG="" + [ -n "$PHP_MINIMUM" ] && PHP_TAG="${PHP_MINIMUM}" + + # Version suffix for non-stable + DISPLAY_VERSION="$VERSION" + [ "$STABILITY" = "rc" ] && DISPLAY_VERSION="${VERSION}-rc" + [ "$STABILITY" = "development" ] && DISPLAY_VERSION="${VERSION}-dev" + + MAJOR=$(echo "$VERSION" | awk -F. '{print $1}') + RELEASE_TAG="v${MAJOR}" + DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${EXT_ELEMENT}-${VERSION}.zip" + INFO_URL="https://github.com/${REPO}" + + # ── Build the new entry ─────────────────────────────────────── + NEW_ENTRY=$(cat < + ${EXT_NAME} + ${EXT_NAME} (${STABILITY}) + ${EXT_ELEMENT} + ${EXT_TYPE} + ${DISPLAY_VERSION} + $([ -n "$CLIENT_TAG" ] && echo " ${CLIENT_TAG}") + $([ -n "$FOLDER_TAG" ] && echo " ${FOLDER_TAG}") + + ${STABILITY} + + ${INFO_URL} + + ${DOWNLOAD_URL} + + ${TARGET_PLATFORM} + $([ -n "$PHP_TAG" ] && echo " ${PHP_TAG}") + Moko Consulting + https://mokoconsulting.tech + +XMLEOF +) + + # ── Merge into update.xml ───────────────────────────────────── + if [ ! -f "update.xml" ]; then + # Create fresh + printf '%s\n' '' > update.xml + printf '%s\n' '' >> update.xml + echo "$NEW_ENTRY" >> update.xml + printf '%s\n' '' >> update.xml + else + # Remove existing entry for this stability, add new one + # Use python for reliable XML manipulation + python3 -c " +import re, sys + +with open('update.xml', 'r') as f: + content = f.read() + +# Remove existing entry with this stability tag +pattern = r' .*?${STABILITY}.*?\n?' +content = re.sub(pattern, '', content, flags=re.DOTALL) + +# Insert new entry before +new_entry = '''${NEW_ENTRY}''' +content = content.replace('', new_entry + '\n') + +# Clean up empty lines +content = re.sub(r'\n{3,}', '\n\n', content) + +with open('update.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 'stable' update.xml; then + STABLE_ENTRY=$(sed -n '//,/<\/update>/{ /stable<\/tag>/,/<\/update>/p; //,/stable<\/tag>/p }' update.xml | sort -u) + fi + RC_ENTRY="" + if [ "$STABILITY" != "rc" ] && grep -q 'rc' update.xml; then + RC_ENTRY=$(python3 -c " +import re +with open('update.xml') as f: c = f.read() +m = re.search(r'(.*?rc.*?)', c, re.DOTALL) +if m: print(m.group(1)) +" 2>/dev/null || true) + fi + DEV_ENTRY="" + if [ "$STABILITY" != "development" ] && grep -q 'development' update.xml; then + DEV_ENTRY=$(python3 -c " +import re +with open('update.xml') as f: c = f.read() +m = re.search(r'(.*?development.*?)', c, re.DOTALL) +if m: print(m.group(1)) +" 2>/dev/null || true) + fi + + { + printf '%s\n' '' + printf '%s\n' '' + [ -n "$STABLE_ENTRY" ] && echo "$STABLE_ENTRY" + [ -n "$RC_ENTRY" ] && echo "$RC_ENTRY" + [ -n "$DEV_ENTRY" ] && echo "$DEV_ENTRY" + echo "$NEW_ENTRY" + printf '%s\n' '' + } > update.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 update.xml + git diff --cached --quiet || { + git commit -m "chore: update update.xml (${STABILITY}: ${DISPLAY_VERSION}) [skip ci]" \ + --author="github-actions[bot] " + git push + } + + 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 -- 2.49.1 From 3fb8380d8aea74deab031909ec08dfe56fbd850c Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Tue, 7 Apr 2026 18:57:58 -0500 Subject: [PATCH 47/54] ci: sync AI instructions from main Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/CLAUDE.md | 378 ++++++++++++++++------------- .github/copilot-instructions.md | 21 +- .github/workflows/auto-release.yml | 3 + 3 files changed, 227 insertions(+), 175 deletions(-) diff --git a/.github/CLAUDE.md b/.github/CLAUDE.md index 45f7606..e991394 100644 --- a/.github/CLAUDE.md +++ b/.github/CLAUDE.md @@ -1,3 +1,22 @@ + + > [!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/` | -> | `A modern enhancement layer for Joomla’s 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 `` element in `manifest.xml` at the repository root | > | `{{EXTENSION_TYPE}}` | The `type` attribute of the `` tag in `manifest.xml` (`component`, `module`, `plugin`, or `template`) | > | `{{EXTENSION_ELEMENT}}` | The `` 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 Joomla’s 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` | `` tag | -| `update.xml` | `` in the most recent `` 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 - - - - https://github.com/mokoconsulting-tech/MokoCassiopeia/raw/main/update.xml - - -``` - -**Rules:** -- Every release prepends a new `` block at the top — older entries are preserved. -- `` in `update.xml` must exactly match `` in `manifest.xml` and `README.md`. -- `` must be a publicly accessible GitHub Releases asset URL. -- `` — backslash is literal (Joomla regex syntax). - -Example `update.xml` entry for a new release: -```xml - - - {{EXTENSION_NAME}} - MokoCassiopeia - {{EXTENSION_ELEMENT}} - {{EXTENSION_TYPE}} - 01.02.04 - https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/tag/01.02.04 - - - https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/01.02.04/{{EXTENSION_ELEMENT}}-01.02.04.zip - - - - 7.4 - Moko Consulting - https://mokoconsulting.tech - - -``` - ---- - -# 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 + +``` + +**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: `(): ` — imperative, lower-case subject, no trailing period. +The version in `README.md` **must always match** the `` tag in `manifest.xml` and the latest entry in `update.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 + +01.02.04 -## Branch Naming - -Format: `/[/description]` - -Approved prefixes: `dev/` · `rc/` · `version/` · `patch/` · `copilot/` · `dependabot/` + + + + {{EXTENSION_NAME}} + 01.02.04 + + + https://github.com/mokoconsulting-tech/MokoCassiopeia/releases/download/01.02.04/{{EXTENSION_ELEMENT}}-01.02.04.zip + + + + + + +``` --- -# GitHub Actions — Token Usage +## Joomla Extension Structure + +``` +MokoCassiopeia/ +├── manifest.xml # Joomla installer manifest (root — required) +├── update.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 +``` + +--- + +## update.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. + +The `manifest.xml` must reference it via: +```xml + + + https://github.com/mokoconsulting-tech/MokoCassiopeia/raw/main/update.xml + + +``` + +**Rules:** +- Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below. +- The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. +- The `` must be a publicly accessible direct download link (GitHub Releases asset URL). +- `` — 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/`). +- `` tag must be kept in sync with `README.md` version and `update.xml`. +- Must include `` block pointing to this repo's `update.xml`. +- Must include `` and `` sections. +- Joomla 4.x requires `Moko\{{EXTENSION_NAME}}` 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 `` to `update.xml`; update `CHANGELOG.md`; bump `README.md` | -| New or changed workflow | `docs/workflows/.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 | \ No newline at end of file +| [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: `(): ` — imperative, lower-case subject, no trailing period. + +Valid types: `feat` · `fix` · `docs` · `chore` · `ci` · `refactor` · `style` · `test` · `perf` · `revert` · `build` + +--- + +## Branch Naming + +Format: `/[/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 `update.xml` version; bump README.md version | +| New release | Prepend `` block to `update.xml`; update CHANGELOG.md; bump README.md version | +| New or changed workflow | `docs/workflows/.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, `update.xml` version, and `README.md` version go out of sync diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 3968dc4..e991394 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,3 +1,22 @@ + + > [!IMPORTANT] > **🔧 AI Self-Update Required on First Use** > @@ -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 \ No newline at end of file +- Never let `manifest.xml` version, `update.xml` version, and `README.md` version go out of sync diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index e9c2570..528d4cb 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -39,6 +39,9 @@ on: branches: - main - master + paths: + - 'src/**' + - 'htdocs/**' env: FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true -- 2.49.1 From 321c51a5c8691f9053f57da09c49fcdeff0f08b0 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Tue, 7 Apr 2026 19:08:58 -0500 Subject: [PATCH 48/54] ci: sync Joomla workflows + updates.xml from main Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/auto-release.yml | 32 +++++++++++------------ .github/workflows/ci-joomla.yml | 6 ++--- .github/workflows/repo_health.yml | 6 ++--- .github/workflows/update-server.yml | 40 ++++++++++++++--------------- 4 files changed, 42 insertions(+), 42 deletions(-) diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index 528d4cb..5acbe07 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -8,7 +8,7 @@ # REPO: https://github.com/mokoconsulting-tech/MokoStandards # PATH: /templates/workflows/joomla/auto-release.yml.template # VERSION: 04.05.13 -# BRIEF: Joomla build & release — ZIP package, update.xml, SHA-256 checksum +# BRIEF: Joomla build & release — ZIP package, updates.xml, SHA-256 checksum # # +========================================================================+ # | BUILD & RELEASE PIPELINE (JOOMLA) | @@ -20,10 +20,10 @@ # | 1. Read version from README.md | # | 3. Set platform version (Joomla ) | # | 4. Update [VERSION: XX.YY.ZZ] badges in markdown files | -# | 5. Write update.xml (Joomla update server XML) | +# | 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 update.xml | +# | 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. | @@ -259,8 +259,8 @@ jobs: fi done - # -- STEP 5: Write update.xml (Joomla update server) --------------------- - - name: "Step 5: Write update.xml" + # -- 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' @@ -271,7 +271,7 @@ jobs: # -- Parse extension metadata from XML manifest ---------------- MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '/dev/null | head -1) if [ -z "$MANIFEST" ]; then - echo "Warning: No Joomla XML manifest found — skipping update.xml" >> $GITHUB_STEP_SUMMARY + echo "Warning: No Joomla XML manifest found — skipping updates.xml" >> $GITHUB_STEP_SUMMARY exit 0 fi @@ -316,7 +316,7 @@ jobs: 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) -------------------------- + # -- Write updates.xml (stable release) -------------------------- { printf '%s\n' '' printf '%s\n' '' @@ -341,9 +341,9 @@ jobs: printf '%s\n' ' https://mokoconsulting.tech' printf '%s\n' ' ' printf '%s\n' '' - } > update.xml + } > updates.xml - echo "update.xml: ${VERSION} (stable) — ${EXT_TYPE}/${EXT_ELEMENT}" >> $GITHUB_STEP_SUMMARY + echo "updates.xml: ${VERSION} (stable) — ${EXT_TYPE}/${EXT_ELEMENT}" >> $GITHUB_STEP_SUMMARY # -- Commit all changes --------------------------------------------------- - name: Commit release changes @@ -472,19 +472,19 @@ jobs: gh release upload "$RELEASE_TAG" "/tmp/${PACKAGE_NAME}" 2>/dev/null || true } - # -- Update update.xml with SHA-256 for latest patch ------------- - if [ -f "update.xml" ]; then - if grep -q '' update.xml; then - sed -i "s|.*|sha256:${SHA256}|" update.xml + # -- Update updates.xml with SHA-256 for latest patch ------------- + if [ -f "updates.xml" ]; then + if grep -q '' updates.xml; then + sed -i "s|.*|sha256:${SHA256}|" updates.xml else - sed -i "s||\n sha256:${SHA256}|" update.xml + sed -i "s||\n 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|]*>[^<]*|${DOWNLOAD_URL}|" update.xml + sed -i "s|]*>[^<]*|${DOWNLOAD_URL}|" updates.xml - git add update.xml + git add updates.xml git commit -m "chore(release): SHA-256 + download URL for ${VERSION} [skip ci]" \ --author="github-actions[bot] " || true git push || true diff --git a/.github/workflows/ci-joomla.yml b/.github/workflows/ci-joomla.yml index d7995d2..190846e 100644 --- a/.github/workflows/ci-joomla.yml +++ b/.github/workflows/ci-joomla.yml @@ -312,11 +312,11 @@ jobs: fi fi - # Check update.xml exists - if [ -f "update.xml" ] || [ -f "updates.xml" ]; then + # Check updates.xml exists + if [ -f "updates.xml" ] || [ -f "updates.xml" ]; then echo "Update XML present." >> $GITHUB_STEP_SUMMARY else - echo "No update.xml found." >> $GITHUB_STEP_SUMMARY + echo "No updates.xml found." >> $GITHUB_STEP_SUMMARY ERRORS=$((ERRORS + 1)) fi diff --git a/.github/workflows/repo_health.yml b/.github/workflows/repo_health.yml index 0129292..885203a 100644 --- a/.github/workflows/repo_health.yml +++ b/.github/workflows/repo_health.yml @@ -595,9 +595,9 @@ jobs: joomla_findings+=("No .ini language files found") fi - # update.xml must exist in root (Joomla update server) - if [ ! -f 'update.xml' ]; then - joomla_findings+=("update.xml missing in root (required for Joomla update server)") + # 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 diff --git a/.github/workflows/update-server.yml b/.github/workflows/update-server.yml index 91d9365..4d4fc97 100644 --- a/.github/workflows/update-server.yml +++ b/.github/workflows/update-server.yml @@ -10,7 +10,7 @@ # VERSION: 04.05.13 # BRIEF: Update Joomla update server XML feed with stable/rc/dev entries # -# Writes update.xml with multiple entries: +# Writes updates.xml with multiple entries: # - stable on push to main (from auto-release) # - rc on push to rc/** # - development on push to dev/** @@ -47,7 +47,7 @@ permissions: jobs: update-xml: - name: Update update.xml + name: Update updates.xml runs-on: ubuntu-latest steps: @@ -69,7 +69,7 @@ jobs: cd /tmp/mokostandards && composer install --no-dev --no-interaction --quiet 2>/dev/null || true fi - - name: Generate update.xml entry + - name: Generate updates.xml entry run: | BRANCH="${{ github.ref_name }}" REPO="${{ github.repository }}" @@ -149,20 +149,20 @@ jobs: XMLEOF ) - # ── Merge into update.xml ───────────────────────────────────── - if [ ! -f "update.xml" ]; then + # ── Merge into updates.xml ───────────────────────────────────── + if [ ! -f "updates.xml" ]; then # Create fresh - printf '%s\n' '' > update.xml - printf '%s\n' '' >> update.xml - echo "$NEW_ENTRY" >> update.xml - printf '%s\n' '' >> update.xml + printf '%s\n' '' > updates.xml + printf '%s\n' '' >> updates.xml + echo "$NEW_ENTRY" >> updates.xml + printf '%s\n' '' >> updates.xml else # Remove existing entry for this stability, add new one # Use python for reliable XML manipulation python3 -c " import re, sys -with open('update.xml', 'r') as f: +with open('updates.xml', 'r') as f: content = f.read() # Remove existing entry with this stability tag @@ -176,29 +176,29 @@ content = content.replace('', new_entry + '\n') # Clean up empty lines content = re.sub(r'\n{3,}', '\n\n', content) -with open('update.xml', 'w') as f: +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 'stable' update.xml; then - STABLE_ENTRY=$(sed -n '//,/<\/update>/{ /stable<\/tag>/,/<\/update>/p; //,/stable<\/tag>/p }' update.xml | sort -u) + if [ "$STABILITY" != "stable" ] && grep -q 'stable' updates.xml; then + STABLE_ENTRY=$(sed -n '//,/<\/update>/{ /stable<\/tag>/,/<\/update>/p; //,/stable<\/tag>/p }' updates.xml | sort -u) fi RC_ENTRY="" - if [ "$STABILITY" != "rc" ] && grep -q 'rc' update.xml; then + if [ "$STABILITY" != "rc" ] && grep -q 'rc' updates.xml; then RC_ENTRY=$(python3 -c " import re -with open('update.xml') as f: c = f.read() +with open('updates.xml') as f: c = f.read() m = re.search(r'(.*?rc.*?)', c, re.DOTALL) if m: print(m.group(1)) " 2>/dev/null || true) fi DEV_ENTRY="" - if [ "$STABILITY" != "development" ] && grep -q 'development' update.xml; then + if [ "$STABILITY" != "development" ] && grep -q 'development' updates.xml; then DEV_ENTRY=$(python3 -c " import re -with open('update.xml') as f: c = f.read() +with open('updates.xml') as f: c = f.read() m = re.search(r'(.*?development.*?)', c, re.DOTALL) if m: print(m.group(1)) " 2>/dev/null || true) @@ -212,16 +212,16 @@ if m: print(m.group(1)) [ -n "$DEV_ENTRY" ] && echo "$DEV_ENTRY" echo "$NEW_ENTRY" printf '%s\n' '' - } > update.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 update.xml + git add updates.xml git diff --cached --quiet || { - git commit -m "chore: update update.xml (${STABILITY}: ${DISPLAY_VERSION}) [skip ci]" \ + git commit -m "chore: update updates.xml (${STABILITY}: ${DISPLAY_VERSION}) [skip ci]" \ --author="github-actions[bot] " git push } -- 2.49.1 From 8c76d2952ae3b8b180a705adc729324f296ca845 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Tue, 7 Apr 2026 19:18:12 -0500 Subject: [PATCH 49/54] ci: sync workflows from main [skip ci] Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/update-server.yml | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/.github/workflows/update-server.yml b/.github/workflows/update-server.yml index 4d4fc97..cc34448 100644 --- a/.github/workflows/update-server.yml +++ b/.github/workflows/update-server.yml @@ -121,9 +121,32 @@ jobs: MAJOR=$(echo "$VERSION" | awk -F. '{print $1}') RELEASE_TAG="v${MAJOR}" - DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${EXT_ELEMENT}-${VERSION}.zip" + 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}" . + 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 "v${MAJOR}" --notes "Development release" --draft --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=$(cat < @@ -141,6 +164,7 @@ jobs: ${DOWNLOAD_URL} + $([ -n "$SHA256" ] && echo " sha256:${SHA256}") ${TARGET_PLATFORM} $([ -n "$PHP_TAG" ] && echo " ${PHP_TAG}") Moko Consulting -- 2.49.1 From 15a35c13478381c4aab5a313a45f7d38bdda2497 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Tue, 7 Apr 2026 19:38:04 -0500 Subject: [PATCH 50/54] ci: sync workflows [skip ci] Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/auto-release.yml | 4 ++-- .github/workflows/ci-joomla.yml | 2 +- .github/workflows/standards-compliance.yml | 6 +++--- .github/workflows/sync-version-on-merge.yml | 2 +- .github/workflows/update-server.yml | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index 5acbe07..f064ceb 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -69,7 +69,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.05 --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 @@ -93,7 +93,7 @@ jobs: MINOR_NUM=$(echo "$VERSION" | awk -F. '{print $2}') echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "branch=version/${MINOR}" >> "$GITHUB_OUTPUT" + echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT" echo "minor=$MINOR" >> "$GITHUB_OUTPUT" echo "major=$MAJOR" >> "$GITHUB_OUTPUT" echo "release_tag=v${MAJOR}" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/ci-joomla.yml b/.github/workflows/ci-joomla.yml index 190846e..6281236 100644 --- a/.github/workflows/ci-joomla.yml +++ b/.github/workflows/ci-joomla.yml @@ -57,7 +57,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} run: | - git clone --depth 1 --branch version/04.05 --quiet \ + git clone --depth 1 --branch version/04 --quiet \ "https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \ /tmp/mokostandards diff --git a/.github/workflows/standards-compliance.yml b/.github/workflows/standards-compliance.yml index df8413c..773279d 100644 --- a/.github/workflows/standards-compliance.yml +++ b/.github/workflows/standards-compliance.yml @@ -509,7 +509,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.05 --quiet \ + 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 @@ -1978,7 +1978,7 @@ jobs: else echo "No composer.json — pulling MokoStandards tools" if [ ! -d "/tmp/mokostandards" ]; then - git clone --depth 1 --branch version/04.05 --quiet \ + 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 @@ -2050,7 +2050,7 @@ jobs: else echo "No composer.json — pulling MokoStandards tools" if [ ! -d "/tmp/mokostandards" ]; then - git clone --depth 1 --branch version/04.05 --quiet \ + 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 diff --git a/.github/workflows/sync-version-on-merge.yml b/.github/workflows/sync-version-on-merge.yml index 7b2ef6c..052a7ca 100644 --- a/.github/workflows/sync-version-on-merge.yml +++ b/.github/workflows/sync-version-on-merge.yml @@ -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.05 --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 diff --git a/.github/workflows/update-server.yml b/.github/workflows/update-server.yml index cc34448..fad30ff 100644 --- a/.github/workflows/update-server.yml +++ b/.github/workflows/update-server.yml @@ -62,7 +62,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.05 --quiet \ + 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 -- 2.49.1 From 4a06cfec3810d3dc6426d916c9be5313852cf7d2 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Tue, 7 Apr 2026 19:55:16 -0500 Subject: [PATCH 51/54] ci: sync workflows [skip ci] Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/auto-dev-issue.yml | 26 ++++------- .github/workflows/auto-release.yml | 70 +++++++++++++++++++--------- .github/workflows/update-server.yml | 13 +++++- 3 files changed, 68 insertions(+), 41 deletions(-) diff --git a/.github/workflows/auto-dev-issue.yml b/.github/workflows/auto-dev-issue.yml index 38730ab..38c6a53 100644 --- a/.github/workflows/auto-dev-issue.yml +++ b/.github/workflows/auto-dev-issue.yml @@ -156,30 +156,22 @@ jobs: done fi - # ── RC: Create or update draft release ──────────────────────────── + # ── RC: Create or update release-candidate release ────────────── if [[ "$BRANCH" == rc/* ]]; then - MAJOR=$(echo "$VERSION" | awk -F. '{print $1}') - RELEASE_TAG="v${MAJOR}" - DRAFT_EXISTS=$(gh release view "$RELEASE_TAG" --json isDraft -q .isDraft 2>/dev/null || true) + RELEASE_TAG="release-candidate" + EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true) - if [ -z "$DRAFT_EXISTS" ]; then - # No release exists — create draft + if [ -z "$EXISTING" ]; then gh release create "$RELEASE_TAG" \ - --title "v${MAJOR} (RC: ${VERSION})" \ + --title "release-candidate (${VERSION})" \ --notes "## Release Candidate ${VERSION}\n\nRC branch: \`${BRANCH}\`\nTracking issue: ${PARENT_URL}" \ - --draft \ + --prerelease \ --target main 2>/dev/null || true - echo "Draft release created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY - elif [ "$DRAFT_EXISTS" = "true" ]; then - # Draft exists — update title - gh release edit "$RELEASE_TAG" \ - --title "v${MAJOR} (RC: ${VERSION})" --draft 2>/dev/null || true - echo "Draft release updated: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY + echo "RC release created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY else - # Release exists and is published — set back to draft for RC gh release edit "$RELEASE_TAG" \ - --title "v${MAJOR} (RC: ${VERSION})" --draft 2>/dev/null || true - echo "Release ${RELEASE_TAG} set to draft for RC" >> $GITHUB_STEP_SUMMARY + --title "release-candidate (${VERSION})" --prerelease 2>/dev/null || true + echo "RC release updated: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY fi fi diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index f064ceb..3e1caf3 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -316,34 +316,60 @@ jobs: 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 updates.xml (stable release) -------------------------- + # -- Build stable entry ────────────────────────────────────── + STABLE_ENTRY=$(cat < + ${EXT_NAME} + ${EXT_NAME} update + ${EXT_ELEMENT} + ${EXT_TYPE} + ${VERSION} + $([ -n "$CLIENT_TAG" ] && echo " ${CLIENT_TAG}") + $([ -n "$FOLDER_TAG" ] && echo " ${FOLDER_TAG}") + + stable + + ${INFO_URL} + + ${DOWNLOAD_URL} + + ${TARGET_PLATFORM} + $([ -n "$PHP_TAG" ] && echo " ${PHP_TAG}") + Moko Consulting + https://mokoconsulting.tech + +XMLEOF +) + + # -- Write updates.xml preserving dev/rc entries ────────────── + # Extract existing dev and rc entries if present + RC_ENTRY="" + DEV_ENTRY="" + if [ -f "updates.xml" ]; then + RC_ENTRY=$(python3 -c " +import re +with open('updates.xml') as f: c = f.read() +m = re.search(r'( .*?rc.*?)', c, re.DOTALL) +if m: print(m.group(1)) +" 2>/dev/null || true) + DEV_ENTRY=$(python3 -c " +import re +with open('updates.xml') as f: c = f.read() +m = re.search(r'( .*?development.*?)', c, re.DOTALL) +if m: print(m.group(1)) +" 2>/dev/null || true) + fi + { printf '%s\n' '' printf '%s\n' '' - printf '%s\n' ' ' - printf '%s\n' " ${EXT_NAME}" - printf '%s\n' " ${EXT_NAME} update" - printf '%s\n' " ${EXT_ELEMENT}" - printf '%s\n' " ${EXT_TYPE}" - printf '%s\n' " ${VERSION}" - [ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}" - [ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}" - printf '%s\n' ' ' - printf '%s\n' ' stable' - printf '%s\n' ' ' - printf '%s\n' " ${INFO_URL}" - printf '%s\n' ' ' - printf '%s\n' " ${DOWNLOAD_URL}" - printf '%s\n' ' ' - printf '%s\n' " ${TARGET_PLATFORM}" - [ -n "$PHP_TAG" ] && printf '%s\n' " ${PHP_TAG}" - printf '%s\n' ' Moko Consulting' - printf '%s\n' ' https://mokoconsulting.tech' - printf '%s\n' ' ' + echo "$STABLE_ENTRY" + [ -n "$RC_ENTRY" ] && echo "$RC_ENTRY" + [ -n "$DEV_ENTRY" ] && echo "$DEV_ENTRY" printf '%s\n' '' } > updates.xml - echo "updates.xml: ${VERSION} (stable) — ${EXT_TYPE}/${EXT_ELEMENT}" >> $GITHUB_STEP_SUMMARY + echo "updates.xml: ${VERSION} (stable + rc/dev preserved)" >> $GITHUB_STEP_SUMMARY # -- Commit all changes --------------------------------------------------- - name: Commit release changes diff --git a/.github/workflows/update-server.yml b/.github/workflows/update-server.yml index fad30ff..28a6dd7 100644 --- a/.github/workflows/update-server.yml +++ b/.github/workflows/update-server.yml @@ -120,7 +120,16 @@ jobs: [ "$STABILITY" = "development" ] && DISPLAY_VERSION="${VERSION}-dev" MAJOR=$(echo "$VERSION" | awk -F. '{print $1}') - RELEASE_TAG="v${MAJOR}" + + # Each stability level has its own release tag + if [ "$STABILITY" = "rc" ]; then + RELEASE_TAG="release-candidate" + elif [ "$STABILITY" = "development" ]; then + RELEASE_TAG="development" + else + RELEASE_TAG="v${MAJOR}" + fi + 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}" @@ -137,7 +146,7 @@ jobs: # Ensure draft release exists for this major gh release view "$RELEASE_TAG" --json tagName > /dev/null 2>&1 || \ - gh release create "$RELEASE_TAG" --title "v${MAJOR}" --notes "Development release" --draft --target main 2>/dev/null || true + 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 -- 2.49.1 From 96e64fcb6d036d060519f56160e79ac9d097c55a Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Tue, 7 Apr 2026 21:04:28 -0500 Subject: [PATCH 52/54] docs: update README badges to standard format [skip ci] Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7f9b559..b60174e 100644 --- a/README.md +++ b/README.md @@ -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. -- 2.49.1 From 3f7ac82ab60063846dfa6ee282de71c4ae47d079 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Tue, 7 Apr 2026 21:11:58 -0500 Subject: [PATCH 53/54] ci: sync workflows [skip ci] Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/auto-assign.yml | 2 +- .github/workflows/auto-dev-issue.yml | 51 ++++-- .github/workflows/auto-release.yml | 71 ++++---- .github/workflows/auto-update-sha.yml | 2 +- .github/workflows/changelog-validation.yml | 2 +- .github/workflows/ci-joomla.yml | 2 +- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/deploy-manual.yml | 132 ++++++++++++++ .../workflows/enterprise-firewall-setup.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/repo_health.yml | 2 +- .github/workflows/repository-cleanup.yml | 2 +- .github/workflows/standards-compliance.yml | 2 +- .github/workflows/sync-version-on-merge.yml | 2 +- .github/workflows/update-server.yml | 162 ++++++++---------- 15 files changed, 283 insertions(+), 155 deletions(-) create mode 100644 .github/workflows/deploy-manual.yml diff --git a/.github/workflows/auto-assign.yml b/.github/workflows/auto-assign.yml index 3752b66..d0b70f6 100644 --- a/.github/workflows/auto-assign.yml +++ b/.github/workflows/auto-assign.yml @@ -6,7 +6,7 @@ # INGROUP: MokoStandards.Workflows.Shared # REPO: https://github.com/mokoconsulting-tech/MokoStandards # 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 name: Auto-Assign Issues & PRs diff --git a/.github/workflows/auto-dev-issue.yml b/.github/workflows/auto-dev-issue.yml index 38c6a53..9b5fbe2 100644 --- a/.github/workflows/auto-dev-issue.yml +++ b/.github/workflows/auto-dev-issue.yml @@ -9,7 +9,7 @@ # INGROUP: MokoStandards.Automation # REPO: https://github.com/mokoconsulting-tech/MokoStandards # 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 # 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 if: >- (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: - name: Create tracking issue and sub-issues @@ -62,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" @@ -80,14 +93,20 @@ jobs: exit 0 fi - # ── Define sub-issues for the dev workflow ──────────────────────── + # ── 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 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" "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 SUB_ISSUES=( @@ -156,22 +175,26 @@ jobs: done fi - # ── RC: Create or update release-candidate release ────────────── - if [[ "$BRANCH" == rc/* ]]; then - RELEASE_TAG="release-candidate" - EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true) + # ── 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-candidate (${VERSION})" \ - --notes "## Release Candidate ${VERSION}\n\nRC branch: \`${BRANCH}\`\nTracking issue: ${PARENT_URL}" \ + --title "${RELEASE_TAG} (${VERSION})" \ + --notes "## ${BRANCH_TYPE} ${VERSION}\n\nBranch: \`${BRANCH}\`\nTracking issue: ${PARENT_URL}" \ --prerelease \ --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 gh release edit "$RELEASE_TAG" \ - --title "release-candidate (${VERSION})" --prerelease 2>/dev/null || true - echo "RC release updated: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY + --title "${RELEASE_TAG} (${VERSION})" --prerelease 2>/dev/null || true + echo "${BRANCH_TYPE} release updated: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY fi fi diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index 3e1caf3..22a7107 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -7,7 +7,7 @@ # INGROUP: MokoStandards.Release # REPO: https://github.com/mokoconsulting-tech/MokoStandards # 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 # # +========================================================================+ @@ -316,54 +316,47 @@ jobs: 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 ────────────────────────────────────── - STABLE_ENTRY=$(cat < - ${EXT_NAME} - ${EXT_NAME} update - ${EXT_ELEMENT} - ${EXT_TYPE} - ${VERSION} - $([ -n "$CLIENT_TAG" ] && echo " ${CLIENT_TAG}") - $([ -n "$FOLDER_TAG" ] && echo " ${FOLDER_TAG}") - - stable - - ${INFO_URL} - - ${DOWNLOAD_URL} - - ${TARGET_PLATFORM} - $([ -n "$PHP_TAG" ] && echo " ${PHP_TAG}") - Moko Consulting - https://mokoconsulting.tech - -XMLEOF -) + # -- Build stable entry to temp file ───────────────────────── + { + printf '%s\n' ' ' + printf '%s\n' " ${EXT_NAME}" + printf '%s\n' " ${EXT_NAME} update" + printf '%s\n' " ${EXT_ELEMENT}" + printf '%s\n' " ${EXT_TYPE}" + printf '%s\n' " ${VERSION}" + [ -n "$CLIENT_TAG" ] && printf '%s\n' " ${CLIENT_TAG}" + [ -n "$FOLDER_TAG" ] && printf '%s\n' " ${FOLDER_TAG}" + printf '%s\n' ' ' + printf '%s\n' ' stable' + printf '%s\n' ' ' + printf '%s\n' " ${INFO_URL}" + printf '%s\n' ' ' + printf '%s\n' " ${DOWNLOAD_URL}" + printf '%s\n' ' ' + printf '%s\n' " ${TARGET_PLATFORM}" + [ -n "$PHP_TAG" ] && printf '%s\n' " ${PHP_TAG}" + printf '%s\n' ' Moko Consulting' + printf '%s\n' ' https://mokoconsulting.tech' + printf '%s\n' ' ' + } > /tmp/stable_entry.xml # -- Write updates.xml preserving dev/rc entries ────────────── - # Extract existing dev and rc entries if present RC_ENTRY="" DEV_ENTRY="" if [ -f "updates.xml" ]; then - RC_ENTRY=$(python3 -c " -import re -with open('updates.xml') as f: c = f.read() -m = re.search(r'( .*?rc.*?)', c, re.DOTALL) -if m: print(m.group(1)) -" 2>/dev/null || true) - DEV_ENTRY=$(python3 -c " -import re -with open('updates.xml') as f: c = f.read() -m = re.search(r'( .*?development.*?)', c, re.DOTALL) -if m: print(m.group(1)) -" 2>/dev/null || true) + 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"( .*?" + re.escape(tag) + r".*?)", 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' '' printf '%s\n' '' - echo "$STABLE_ENTRY" + cat /tmp/stable_entry.xml [ -n "$RC_ENTRY" ] && echo "$RC_ENTRY" [ -n "$DEV_ENTRY" ] && echo "$DEV_ENTRY" printf '%s\n' '' diff --git a/.github/workflows/auto-update-sha.yml b/.github/workflows/auto-update-sha.yml index 713393e..bbcfaaf 100644 --- a/.github/workflows/auto-update-sha.yml +++ b/.github/workflows/auto-update-sha.yml @@ -5,7 +5,7 @@ # INGROUP: MokoCassiopeia.Automation # REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia # 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 # NOTE: Ensures updates.xml stays synchronized with release packages diff --git a/.github/workflows/changelog-validation.yml b/.github/workflows/changelog-validation.yml index 9ed880a..e2ec667 100644 --- a/.github/workflows/changelog-validation.yml +++ b/.github/workflows/changelog-validation.yml @@ -9,7 +9,7 @@ # INGROUP: MokoStandards.CI # REPO: https://github.com/mokoconsulting-tech/MokoStandards # PATH: /templates/workflows/shared/changelog-validation.yml.template -# VERSION: 04.05.13 +# VERSION: 04.06.00 # BRIEF: Validates CHANGELOG.md format and version consistency # NOTE: Deployed to .github/workflows/changelog-validation.yml in governed repos. diff --git a/.github/workflows/ci-joomla.yml b/.github/workflows/ci-joomla.yml index 6281236..01e1edb 100644 --- a/.github/workflows/ci-joomla.yml +++ b/.github/workflows/ci-joomla.yml @@ -9,7 +9,7 @@ # INGROUP: MokoStandards.CI # REPO: https://github.com/mokoconsulting-tech/MokoStandards # 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 # NOTE: Deployed to .github/workflows/ci-joomla.yml in governed Joomla extension repos. diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3abfb02..1639497 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -9,7 +9,7 @@ # INGROUP: MokoStandards.Security # REPO: https://github.com/mokoconsulting-tech/MokoStandards # 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) # NOTE: Deployed to .github/workflows/codeql-analysis.yml in governed repos. # CodeQL does not support PHP directly; JavaScript scans JSON/YAML/shell. diff --git a/.github/workflows/deploy-manual.yml b/.github/workflows/deploy-manual.yml new file mode 100644 index 0000000..e127f0e --- /dev/null +++ b/.github/workflows/deploy-manual.yml @@ -0,0 +1,132 @@ +# Copyright (C) 2026 Moko Consulting +# +# 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 diff --git a/.github/workflows/enterprise-firewall-setup.yml b/.github/workflows/enterprise-firewall-setup.yml index b6cf7ab..46ef7d2 100644 --- a/.github/workflows/enterprise-firewall-setup.yml +++ b/.github/workflows/enterprise-firewall-setup.yml @@ -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.05.13 +# 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. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0ba0908..4af1d59 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/.github/workflows/repo_health.yml b/.github/workflows/repo_health.yml index 885203a..73308be 100644 --- a/.github/workflows/repo_health.yml +++ b/.github/workflows/repo_health.yml @@ -10,7 +10,7 @@ # INGROUP: MokoStandards.Validation # REPO: https://github.com/mokoconsulting-tech/MokoStandards # 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. # NOTE: Field is user-managed. # ============================================================================ diff --git a/.github/workflows/repository-cleanup.yml b/.github/workflows/repository-cleanup.yml index b078061..6fbf6e4 100644 --- a/.github/workflows/repository-cleanup.yml +++ b/.github/workflows/repository-cleanup.yml @@ -9,7 +9,7 @@ # INGROUP: MokoStandards.Maintenance # REPO: https://github.com/mokoconsulting-tech/MokoStandards # 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 # 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. diff --git a/.github/workflows/standards-compliance.yml b/.github/workflows/standards-compliance.yml index 773279d..79aaedd 100644 --- a/.github/workflows/standards-compliance.yml +++ b/.github/workflows/standards-compliance.yml @@ -5,7 +5,7 @@ # INGROUP: MokoStandards.Compliance # REPO: https://github.com/mokoconsulting-tech/MokoStandards # PATH: /.github/workflows/standards-compliance.yml -# VERSION: 04.05.00 +# VERSION: 04.06.00 # BRIEF: MokoStandards compliance validation workflow # NOTE: Validates repository structure, documentation, and coding standards diff --git a/.github/workflows/sync-version-on-merge.yml b/.github/workflows/sync-version-on-merge.yml index 052a7ca..60715f6 100644 --- a/.github/workflows/sync-version-on-merge.yml +++ b/.github/workflows/sync-version-on-merge.yml @@ -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.05.13 +# 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. diff --git a/.github/workflows/update-server.yml b/.github/workflows/update-server.yml index 28a6dd7..fff9ec6 100644 --- a/.github/workflows/update-server.yml +++ b/.github/workflows/update-server.yml @@ -7,7 +7,7 @@ # INGROUP: MokoStandards.Joomla # REPO: https://github.com/mokoconsulting-tech/MokoStandards # 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 # # Writes updates.xml with multiple entries: @@ -23,6 +23,8 @@ on: push: branches: - 'dev/**' + - 'alpha/**' + - 'beta/**' - 'rc/**' paths: - 'src/**' @@ -30,12 +32,14 @@ on: workflow_dispatch: inputs: stability: - description: 'Stability tag (development, rc, stable)' + description: 'Stability tag' required: true default: 'development' type: choice options: - development + - alpha + - beta - rc - stable @@ -80,6 +84,10 @@ jobs: 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 @@ -116,19 +124,23 @@ jobs: # Version suffix for non-stable DISPLAY_VERSION="$VERSION" - [ "$STABILITY" = "rc" ] && DISPLAY_VERSION="${VERSION}-rc" - [ "$STABILITY" = "development" ] && DISPLAY_VERSION="${VERSION}-dev" + 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 - if [ "$STABILITY" = "rc" ]; then - RELEASE_TAG="release-candidate" - elif [ "$STABILITY" = "development" ]; then - RELEASE_TAG="development" - else - RELEASE_TAG="v${MAJOR}" - fi + 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}" @@ -157,95 +169,63 @@ jobs: fi # ── Build the new entry ─────────────────────────────────────── - NEW_ENTRY=$(cat < - ${EXT_NAME} - ${EXT_NAME} (${STABILITY}) - ${EXT_ELEMENT} - ${EXT_TYPE} - ${DISPLAY_VERSION} - $([ -n "$CLIENT_TAG" ] && echo " ${CLIENT_TAG}") - $([ -n "$FOLDER_TAG" ] && echo " ${FOLDER_TAG}") - - ${STABILITY} - - ${INFO_URL} - - ${DOWNLOAD_URL} - - $([ -n "$SHA256" ] && echo " sha256:${SHA256}") - ${TARGET_PLATFORM} - $([ -n "$PHP_TAG" ] && echo " ${PHP_TAG}") - Moko Consulting - https://mokoconsulting.tech - -XMLEOF -) + NEW_ENTRY="" + NEW_ENTRY="${NEW_ENTRY} \n" + NEW_ENTRY="${NEW_ENTRY} ${EXT_NAME}\n" + NEW_ENTRY="${NEW_ENTRY} ${EXT_NAME} (${STABILITY})\n" + NEW_ENTRY="${NEW_ENTRY} ${EXT_ELEMENT}\n" + NEW_ENTRY="${NEW_ENTRY} ${EXT_TYPE}\n" + NEW_ENTRY="${NEW_ENTRY} ${DISPLAY_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} \n" + NEW_ENTRY="${NEW_ENTRY} ${STABILITY}\n" + NEW_ENTRY="${NEW_ENTRY} \n" + NEW_ENTRY="${NEW_ENTRY} ${INFO_URL}\n" + NEW_ENTRY="${NEW_ENTRY} \n" + NEW_ENTRY="${NEW_ENTRY} ${DOWNLOAD_URL}\n" + NEW_ENTRY="${NEW_ENTRY} \n" + [ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} sha256:${SHA256}\n" + NEW_ENTRY="${NEW_ENTRY} ${TARGET_PLATFORM}\n" + [ -n "$PHP_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${PHP_TAG}\n" + NEW_ENTRY="${NEW_ENTRY} Moko Consulting\n" + NEW_ENTRY="${NEW_ENTRY} https://mokoconsulting.tech\n" + NEW_ENTRY="${NEW_ENTRY} " + + # ── Write new entry to temp file ─────────────────────────────── + printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml # ── Merge into updates.xml ───────────────────────────────────── if [ ! -f "updates.xml" ]; then - # Create fresh printf '%s\n' '' > updates.xml printf '%s\n' '' >> updates.xml - echo "$NEW_ENTRY" >> updates.xml - printf '%s\n' '' >> updates.xml + cat /tmp/new_entry.xml >> updates.xml + printf '\n%s\n' '' >> updates.xml else - # Remove existing entry for this stability, add new one - # Use python for reliable XML manipulation - python3 -c " -import re, sys - -with open('updates.xml', 'r') as f: - content = f.read() - -# Remove existing entry with this stability tag -pattern = r' .*?${STABILITY}.*?\n?' -content = re.sub(pattern, '', content, flags=re.DOTALL) - -# Insert new entry before -new_entry = '''${NEW_ENTRY}''' -content = content.replace('', new_entry + '\n') - -# 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 'stable' updates.xml; then - STABLE_ENTRY=$(sed -n '//,/<\/update>/{ /stable<\/tag>/,/<\/update>/p; //,/stable<\/tag>/p }' updates.xml | sort -u) - fi - RC_ENTRY="" - if [ "$STABILITY" != "rc" ] && grep -q 'rc' updates.xml; then - RC_ENTRY=$(python3 -c " -import re -with open('updates.xml') as f: c = f.read() -m = re.search(r'(.*?rc.*?)', c, re.DOTALL) -if m: print(m.group(1)) -" 2>/dev/null || true) - fi - DEV_ENTRY="" - if [ "$STABILITY" != "development" ] && grep -q 'development' updates.xml; then - DEV_ENTRY=$(python3 -c " -import re -with open('updates.xml') as f: c = f.read() -m = re.search(r'(.*?development.*?)', c, re.DOTALL) -if m: print(m.group(1)) -" 2>/dev/null || true) - fi - + # 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" .*?" + re.escape(stability) + r".*?\\n?"\n' >> /tmp/merge_xml.py + printf 'content = re.sub(pattern, "", content, flags=re.DOTALL)\n' >> /tmp/merge_xml.py + printf 'content = content.replace("", new_entry + "\\n")\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' '' printf '%s\n' '' - [ -n "$STABLE_ENTRY" ] && echo "$STABLE_ENTRY" - [ -n "$RC_ENTRY" ] && echo "$RC_ENTRY" - [ -n "$DEV_ENTRY" ] && echo "$DEV_ENTRY" - echo "$NEW_ENTRY" - printf '%s\n' '' - } > updates.xml + for TAG in stable rc development; do + [ "$TAG" = "${STABILITY}" ] && continue + if grep -q "${TAG}" updates.xml 2>/dev/null; then + sed -n "//,/<\/update>/{ /${TAG}<\/tag>/p; }" updates.xml + fi + done + cat /tmp/new_entry.xml + printf '\n%s\n' '' + } > /tmp/updates_new.xml + mv /tmp/updates_new.xml updates.xml } fi -- 2.49.1 From 7c6b1a8b85c6872f190827cb2217d9d5b5a2d575 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Tue, 7 Apr 2026 21:40:50 -0500 Subject: [PATCH 54/54] ci: sync from main [skip ci] Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/CLAUDE.md | 26 ++++---- .github/CODEOWNERS | 24 ++++++- .github/ISSUE_TEMPLATE/firewall-request.md | 2 +- .github/ISSUE_TEMPLATE/question.md | 2 +- .github/ISSUE_TEMPLATE/request-license.md | 2 +- .github/copilot-instructions.md | 26 ++++---- .github/dependabot.yml | 2 +- .github/workflows/auto-release.yml | 2 +- .github/workflows/update-server.yml | 74 +++++++++++++++++++++- 9 files changed, 125 insertions(+), 35 deletions(-) diff --git a/.github/CLAUDE.md b/.github/CLAUDE.md index e991394..4e7b705 100644 --- a/.github/CLAUDE.md +++ b/.github/CLAUDE.md @@ -122,13 +122,13 @@ BRIEF: One-line description ### Joomla Version Alignment -The version in `README.md` **must always match** the `` 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 `` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically. ```xml 01.02.04 - @@ -154,7 +154,7 @@ The version in `README.md` **must always match** the `` 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/ @@ -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: ```xml - https://github.com/mokoconsulting-tech/MokoCassiopeia/raw/main/update.xml + https://github.com/mokoconsulting-tech/MokoCassiopeia/raw/main/updates.xml ``` **Rules:** -- Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below. -- The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. +- Every release must prepend a new `` block at the top of `updates.xml` — old entries must be preserved below. +- The `` in `updates.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. - The `` must be a publicly accessible direct download link (GitHub Releases asset URL). - `` — 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 - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). -- `` tag must be kept in sync with `README.md` version and `update.xml`. -- Must include `` block pointing to this repo's `update.xml`. +- `` tag must be kept in sync with `README.md` version and `updates.xml`. +- Must include `` block pointing to this repo's `updates.xml`. - Must include `` and `` sections. - Joomla 4.x requires `Moko\{{EXTENSION_NAME}}` for namespaced extensions. @@ -286,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 `` 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 `` block to `updates.xml`; update CHANGELOG.md; bump README.md version | | New or changed workflow | `docs/workflows/.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 | @@ -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 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 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 93f049a..c70eb15 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -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 diff --git a/.github/ISSUE_TEMPLATE/firewall-request.md b/.github/ISSUE_TEMPLATE/firewall-request.md index 38be866..4a43395 100644 --- a/.github/ISSUE_TEMPLATE/firewall-request.md +++ b/.github/ISSUE_TEMPLATE/firewall-request.md @@ -3,7 +3,7 @@ name: Firewall Request about: Request firewall rule changes or access to external resources title: '[FIREWALL] [Resource Name] - [Brief Description]' labels: ['firewall-request', 'infrastructure', 'security'] -assignees: ['jmiller-moko'] +assignees: [] --- diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 74df7a0..e17850b 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -3,7 +3,7 @@ name: Question about: Ask a question about usage, features, or best practices title: '[QUESTION] ' labels: ['question'] -assignees: ['jmiller-moko'] +assignees: [] --- diff --git a/.github/ISSUE_TEMPLATE/request-license.md b/.github/ISSUE_TEMPLATE/request-license.md index a9c87a7..52c3b74 100644 --- a/.github/ISSUE_TEMPLATE/request-license.md +++ b/.github/ISSUE_TEMPLATE/request-license.md @@ -3,7 +3,7 @@ name: License Request about: Request an organization license for Sublime Text title: '[LICENSE REQUEST] Sublime Text - [Your Name]' labels: ['license-request', 'admin'] -assignees: ['jmiller-moko'] +assignees: [] --- diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e991394..4e7b705 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -122,13 +122,13 @@ BRIEF: One-line description ### Joomla Version Alignment -The version in `README.md` **must always match** the `` 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 `` tag in `manifest.xml` and the latest entry in `updates.xml`. The `make release` command / release workflow updates all three automatically. ```xml 01.02.04 - @@ -154,7 +154,7 @@ The version in `README.md` **must always match** the `` 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/ @@ -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: ```xml - https://github.com/mokoconsulting-tech/MokoCassiopeia/raw/main/update.xml + https://github.com/mokoconsulting-tech/MokoCassiopeia/raw/main/updates.xml ``` **Rules:** -- Every release must prepend a new `` block at the top of `update.xml` — old entries must be preserved below. -- The `` in `update.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. +- Every release must prepend a new `` block at the top of `updates.xml` — old entries must be preserved below. +- The `` in `updates.xml` must exactly match `` in `manifest.xml` and the version in `README.md`. - The `` must be a publicly accessible direct download link (GitHub Releases asset URL). - `` — 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 - Lives at the repo root as `manifest.xml` (not inside `site/` or `admin/`). -- `` tag must be kept in sync with `README.md` version and `update.xml`. -- Must include `` block pointing to this repo's `update.xml`. +- `` tag must be kept in sync with `README.md` version and `updates.xml`. +- Must include `` block pointing to this repo's `updates.xml`. - Must include `` and `` sections. - Joomla 4.x requires `Moko\{{EXTENSION_NAME}}` for namespaced extensions. @@ -286,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 `` 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 `` block to `updates.xml`; update CHANGELOG.md; bump README.md version | | New or changed workflow | `docs/workflows/.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 | @@ -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 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 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b94720f..667ae72 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -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 diff --git a/.github/workflows/auto-release.yml b/.github/workflows/auto-release.yml index 22a7107..e5d4c49 100644 --- a/.github/workflows/auto-release.yml +++ b/.github/workflows/auto-release.yml @@ -477,7 +477,7 @@ jobs: [ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ — skipping package"; exit 0; } cd "$SOURCE_DIR" - zip -r "/tmp/${PACKAGE_NAME}" . + 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") diff --git a/.github/workflows/update-server.yml b/.github/workflows/update-server.yml index fff9ec6..f0830c2 100644 --- a/.github/workflows/update-server.yml +++ b/.github/workflows/update-server.yml @@ -79,6 +79,20 @@ jobs: 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] " 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 }}" @@ -151,7 +165,7 @@ jobs: [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" if [ -d "$SOURCE_DIR" ]; then cd "$SOURCE_DIR" - zip -r "/tmp/${PACKAGE_NAME}" . + zip -r "/tmp/${PACKAGE_NAME}" . -x '.ftpignore' cd .. SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1) @@ -239,6 +253,64 @@ jobs: 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 -- 2.49.1