38 Commits

Author SHA1 Message Date
jmiller c872cd7032 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-28 20:11:22 +00:00
jmiller 7ae85bbe8b chore: sync pr-metadata-check.yml from Template-Joomla 2026-06-28 07:47:30 +00:00
jmiller 931da100e5 chore: sync SECURITY.md from Template-Joomla 2026-06-28 07:46:04 +00:00
jmiller 2072d6b011 chore: sync GOVERNANCE.md from Template-Joomla 2026-06-28 07:42:35 +00:00
jmiller 3e13452dbc chore: sync CONTRIBUTING.md from Template-Joomla 2026-06-28 07:40:49 +00:00
jmiller 35e60ef1c7 chore: sync CODE_OF_CONDUCT.md from Template-Joomla 2026-06-28 07:37:46 +00:00
jmiller fc3793e315 chore: sync composer.json from Template-Joomla 2026-06-28 07:35:46 +00:00
jmiller 10f82cd9db chore: sync phpstan.neon from Template-Joomla 2026-06-28 07:34:26 +00:00
jmiller 93bb9d5e0f chore: sync .editorconfig from Template-Joomla 2026-06-28 07:33:52 +00:00
jmiller 3679bb1f8e chore: sync ci-generic.yml from Template-Generic [skip ci] 2026-06-27 20:48:35 +00:00
jmiller f843f2c9a7 chore: sync rc-revert.yml from Template-Generic [skip ci] 2026-06-27 05:33:11 +00:00
jmiller b98e145109 chore: sync pre-release.yml from Template-Generic [skip ci] 2026-06-27 00:50:38 +00:00
jmiller 9e5d4b03cd chore: sync repo-health.yml from Template-Generic [skip ci] 2026-06-25 19:48:38 +00:00
jmiller faa8c9b2e8 chore: sync pr-check.yml from Template-Generic [skip ci] 2026-06-25 19:48:37 +00:00
jmiller b91cbcfb25 chore: sync ci-issue-reporter.yml from Template-Generic [skip ci] 2026-06-25 19:48:36 +00:00
jmiller cfdf0c88e9 chore: sync workflow-sync-trigger.yml from Template-Generic [skip ci] 2026-06-25 17:12:59 +00:00
jmiller 9fb2e62a1b chore: sync version-set.yml from Template-Generic [skip ci] 2026-06-25 17:12:58 +00:00
jmiller 8d3a2b7ed5 chore: sync repo-health.yml from Template-Generic [skip ci] 2026-06-25 17:12:58 +00:00
jmiller 8822485f4d chore: sync pre-release.yml from Template-Generic [skip ci] 2026-06-25 17:12:57 +00:00
jmiller a04e8d2da6 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-25 17:12:56 +00:00
jmiller 16bc82ecba chore: sync auto-bump.yml from Template-Generic [skip ci] 2026-06-25 17:12:55 +00:00
jmiller 1ebf34ce8a chore: sync pr-check.yml from Template-Generic [skip ci] 2026-06-25 16:45:45 +00:00
jmiller 959a278125 chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-25 16:45:45 +00:00
jmiller e4e5449bef chore: sync deploy-manual.yml from Template-Generic [skip ci] 2026-06-25 16:45:44 +00:00
jmiller 8010be007c chore: sync cleanup.yml from Template-Generic [skip ci] 2026-06-25 16:45:44 +00:00
jmiller d0d99db593 chore: add .claude to .gitignore [skip ci] 2026-06-25 08:44:04 -05:00
jmiller 38f28b3166 chore: sync version-set.yml from Template-Generic [skip ci] 2026-06-24 11:52:18 +00:00
jmiller fd9730a39c chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-24 11:52:17 +00:00
jmiller 42b8632cfc chore: sync deploy-manual.yml from Template-Generic [skip ci] 2026-06-23 18:47:34 +00:00
jmiller 9889f1b529 chore: remove security-audit.yml -- handled by MokoGitea 2026-06-23 18:27:29 +00:00
jmiller 8dcf26c708 chore: remove deploy-manual.yml -- no longer needed 2026-06-23 18:00:07 +00:00
jmiller 4ab94afe0c chore: sync deploy-manual.yml from Template-Generic [skip ci] 2026-06-23 17:52:23 +00:00
jmiller 3484f55591 chore: remove deprecated .mokogitea/workflows/composer-publish.yml [skip ci] 2026-06-23 17:37:41 +00:00
jmiller 5a1984a239 chore: remove deprecated .mokogitea/workflows/deploy-manual.yml [skip ci] 2026-06-23 17:37:39 +00:00
jmiller 5c2e18c22c chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-22 00:36:19 +00:00
jmiller f5d3911ac6 chore: sync auto-release.yml from Template-Generic [skip ci] 2026-06-21 22:04:02 +00:00
jmiller 4bbe54a688 chore: sync pre-release.yml from Template-Generic [skip ci] 2026-06-21 16:06:32 +00:00
jmiller 1f5e37d9fa chore: sync issue-branch.yml from Template-Generic [skip ci] 2026-06-21 16:06:31 +00:00
40 changed files with 2344 additions and 3611 deletions
+22 -12
View File
@@ -1,31 +1,41 @@
# EditorConfig https://editorconfig.org # EditorConfig helps maintain consistent coding styles across different editors and IDEs
# https://editorconfig.org/
root = true root = true
# Default settings — Tabs preferred, width = 2 spaces
[*] [*]
indent_style = tab
indent_size = 2
end_of_line = lf
charset = utf-8 charset = utf-8
trim_trailing_whitespace = true end_of_line = lf
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true
indent_style = tab
tab_width = 2
# PowerShell scripts — tabs, 2-space visual width
[*.ps1] [*.ps1]
indent_style = tab
tab_width = 2
end_of_line = crlf end_of_line = crlf
# Markdown files — keep trailing whitespace for line breaks
[*.md] [*.md]
trim_trailing_whitespace = false trim_trailing_whitespace = false
# JSON / YAML files — tabs, 2-space visual width
[*.{json,yml,yaml}] [*.{json,yml,yaml}]
indent_style = space
indent_size = 2
[*.{mak,Makefile}]
indent_style = tab indent_style = tab
tab_width = 2
[*.bat] # Makefiles — always tabs, default width
[Makefile]
indent_style = tab
tab_width = 2
# Windows batch scripts — keep CRLF endings
[*.{bat,cmd}]
end_of_line = crlf end_of_line = crlf
# Shell scripts — ensure LF endings
[*.sh] [*.sh]
end_of_line = lf end_of_line = lf
indent_style = space
indent_size = 2
+2
View File
@@ -0,0 +1,2 @@
# Claude Code
.claude/
-110
View File
@@ -1,110 +0,0 @@
---
name: Architecture Decision Record (ADR)
about: Propose or document an architectural decision
title: '[ADR] '
labels: 'architecture, decision'
assignees: ''
---
## ADR Number
ADR-XXXX
## Status
- [ ] Proposed
- [ ] Accepted
- [ ] Deprecated
- [ ] Superseded by ADR-XXXX
## Context
Describe the issue or problem that motivates this decision.
## Decision
State the architecture decision and provide rationale.
## Consequences
### Positive
- List positive consequences
### Negative
- List negative consequences or trade-offs
### Neutral
- List neutral aspects
## Alternatives Considered
### Alternative 1
- Description
- Pros
- Cons
- Why not chosen
### Alternative 2
- Description
- Pros
- Cons
- Why not chosen
## Implementation Plan
1. Step 1
2. Step 2
3. Step 3
## Stakeholders
- **Decision Makers**: @user1, @user2
- **Consulted**: @user3, @user4
- **Informed**: team-name
## Technical Details
### Architecture Diagram
```
[Add diagram or link]
```
### Dependencies
- Dependency 1
- Dependency 2
### Impact Analysis
- **Performance**: [Impact description]
- **Security**: [Impact description]
- **Scalability**: [Impact description]
- **Maintainability**: [Impact description]
## Testing Strategy
- [ ] Unit tests
- [ ] Integration tests
- [ ] Performance tests
- [ ] Security tests
## Documentation
- [ ] Architecture documentation updated
- [ ] API documentation updated
- [ ] Developer guide updated
- [ ] Runbook created
## Migration Path
Describe how to migrate from current state to new architecture.
## Rollback Plan
Describe how to rollback if issues occur.
## Timeline
- **Proposal Date**:
- **Decision Date**:
- **Implementation Start**:
- **Expected Completion**:
## References
- Related ADRs:
- External resources:
- RFCs:
## Review Checklist
- [ ] Aligns with enterprise architecture principles
- [ ] Security implications reviewed
- [ ] Performance implications reviewed
- [ ] Cost implications reviewed
- [ ] Compliance requirements met
- [ ] Team consensus achieved
-48
View File
@@ -1,48 +0,0 @@
---
name: Bug Report
about: Report a bug or issue with the project
title: '[BUG] '
labels: 'bug'
assignees: ''
---
## Bug Description
A clear and concise description of what the bug is.
## Steps to Reproduce
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See error
## Expected Behavior
A clear and concise description of what you expected to happen.
## Actual Behavior
A clear and concise description of what actually happened.
## Screenshots
If applicable, add screenshots to help explain your problem.
## Environment
- **Project**: [e.g., MokoDoliTools, moko-cassiopeia]
- **Version**: [e.g., 1.2.3]
- **Platform**: [e.g., Dolibarr 18.0, Joomla 5.0]
- **PHP Version**: [e.g., 8.1]
- **Database**: [e.g., MySQL 8.0, PostgreSQL 14]
- **Browser** (if applicable): [e.g., Chrome 120, Firefox 121]
- **OS**: [e.g., Ubuntu 22.04, Windows 11]
## Additional Context
Add any other context about the problem here.
## Possible Solution
If you have suggestions on how to fix the issue, please describe them here.
## Checklist
- [ ] I have searched for similar issues before creating this one
- [ ] I have provided all the requested information
- [ ] I have tested this on the latest stable version
- [ ] I have checked the documentation and couldn't find a solution
-18
View File
@@ -1,18 +0,0 @@
---
blank_issues_enabled: true
contact_links:
- name: 💼 Enterprise Support
url: https://mokoconsulting.tech/enterprise
about: Enterprise-level support and consultation services
- name: 💬 Ask a Question
url: https://mokoconsulting.tech/
about: Get help or ask questions through our website
- name: 📚 MokoStandards Documentation
url: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
about: View our coding standards and best practices
- name: 🔒 Report a Security Vulnerability
url: https://git.mokoconsulting.tech/mokoconsulting-tech/.github-private/security/advisories/new
about: Report security vulnerabilities privately (for critical issues)
- name: 💡 Community Discussions
url: https://github.com/orgs/mokoconsulting-tech/discussions
about: Join community discussions and Q&A
@@ -1,52 +0,0 @@
---
name: Documentation Issue
about: Report an issue with documentation
title: '[DOCS] '
labels: 'documentation'
assignees: ''
---
## Documentation Issue
**Location**:
<!-- Specify the file, page, or section with the issue -->
## Issue Type
<!-- Mark the relevant option with an "x" -->
- [ ] Typo or grammar error
- [ ] Outdated information
- [ ] Missing documentation
- [ ] Unclear explanation
- [ ] Broken links
- [ ] Missing examples
- [ ] Other (specify below)
## Description
<!-- Clearly describe the documentation issue -->
## Current Content
<!-- Quote or describe the current documentation (if applicable) -->
```
Current text here
```
## Suggested Improvement
<!-- Provide your suggestion for how to improve the documentation -->
```
Suggested text here
```
## Additional Context
<!-- Add any other context, screenshots, or references -->
## Standards Alignment
- [ ] Follows MokoStandards documentation guidelines
- [ ] Uses en_US/en_GB localization
- [ ] Includes proper SPDX headers where applicable
## Checklist
- [ ] I have searched for similar documentation issues
- [ ] I have provided a clear description
- [ ] I have suggested an improvement (if applicable)
@@ -1,51 +0,0 @@
---
name: Feature Request
about: Suggest a new feature or enhancement
title: '[FEATURE] '
labels: 'enhancement'
assignees: ''
---
## Feature Description
A clear and concise description of the feature you'd like to see.
## Problem or Use Case
Describe the problem this feature would solve or the use case it addresses.
Ex. I'm always frustrated when [...]
## Proposed Solution
A clear and concise description of what you want to happen.
## Alternative Solutions
A clear and concise description of any alternative solutions or features you've considered.
## Benefits
Describe how this feature would benefit users:
- Who would use this feature?
- What problems does it solve?
- What value does it add?
## Implementation Details (Optional)
If you have ideas about how this could be implemented, share them here:
- Technical approach
- Files/components that might need changes
- Any concerns or challenges you foresee
## Additional Context
Add any other context, mockups, or screenshots about the feature request here.
## Relevant Standards
Does this relate to any standards in [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)?
- [ ] Accessibility (WCAG 2.1 AA)
- [ ] Localization (en_US/en_GB)
- [ ] Security best practices
- [ ] Code quality standards
- [ ] Other: [specify]
## Checklist
- [ ] I have searched for similar feature requests before creating this one
- [ ] I have clearly described the use case and benefits
- [ ] I have considered alternative solutions
- [ ] This feature aligns with the project's goals and scope
-87
View File
@@ -1,87 +0,0 @@
---
name: Joomla Extension Issue
about: Report an issue with a Joomla extension
title: '[JOOMLA] '
labels: 'joomla'
assignees: ''
---
## Issue Type
- [ ] Component Issue
- [ ] Module Issue
- [ ] Plugin Issue
- [ ] Template Issue
## Extension Details
- **Extension Name**: [e.g., moko-cassiopeia]
- **Extension Version**: [e.g., 1.2.3]
- **Extension Type**: [Component / Module / Plugin / Template]
## Joomla Environment
- **Joomla Version**: [e.g., 4.4.0, 5.0.0]
- **PHP Version**: [e.g., 8.1.0]
- **Database**: [MySQL / PostgreSQL / MariaDB]
- **Database Version**: [e.g., 8.0]
- **Server**: [Apache / Nginx / IIS]
- **Hosting**: [Shared / VPS / Dedicated / Cloud]
## Issue Description
Provide a clear and detailed description of the issue.
## Steps to Reproduce
1. Go to '...'
2. Click on '...'
3. Configure '...'
4. See error
## Expected Behavior
What you expected to happen.
## Actual Behavior
What actually happened.
## Error Messages
```
# Paste any error messages from Joomla error logs
# Location: administrator/logs/error.php
```
## Browser Console Errors
```javascript
// Paste any JavaScript console errors (F12 in browser)
```
## Screenshots
Add screenshots to help explain the issue.
## Configuration
```ini
# Paste extension configuration (sanitize sensitive data)
```
## Installed Extensions
List other installed extensions that might conflict:
- Extension 1 (version)
- Extension 2 (version)
## Template Overrides
- [ ] Using template overrides
- [ ] Custom CSS
- [ ] Custom JavaScript
## Additional Context
- **Multilingual Site**: [Yes / No]
- **Cache Enabled**: [Yes / No]
- **Debug Mode**: [Yes / No]
- **SEF URLs**: [Yes / No]
## Checklist
- [ ] I have cleared Joomla cache
- [ ] I have disabled other extensions to test for conflicts
- [ ] I have checked Joomla error logs
- [ ] I have tested with a default Joomla template
- [ ] I have checked browser console for JavaScript errors
- [ ] I have searched for similar issues
- [ ] I am using a supported Joomla version
-82
View File
@@ -1,82 +0,0 @@
---
name: Question
about: Ask a question about usage, features, or best practices
title: '[QUESTION] '
labels: ['question']
assignees: ['jmiller']
---
## Question
**Your question:**
## Context
**What are you trying to accomplish?**
**What have you already tried?**
**Category**:
- [ ] Script usage
- [ ] Configuration
- [ ] Workflow setup
- [ ] Documentation interpretation
- [ ] Best practices
- [ ] Integration
- [ ] Other: __________
## Environment (if relevant)
**Your setup**:
- Operating System:
- Version:
## What You've Researched
**Documentation reviewed**:
- [ ] README.md
- [ ] Project documentation
- [ ] Other (specify): __________
**Similar issues/questions found**:
- #
- #
## Expected Outcome
**What result are you hoping for?**
## Code/Configuration Samples
**Relevant code or configuration** (if applicable):
```bash
# Your code here
```
## Additional Context
**Any other relevant information:**
**Screenshots** (if helpful):
## Urgency
- [ ] Urgent (blocking work)
- [ ] Normal (can work on other things meanwhile)
- [ ] Low priority (just curious)
## Checklist
- [ ] I have searched existing issues and discussions
- [ ] I have reviewed relevant documentation
- [ ] I have provided sufficient context
- [ ] I have included code/configuration samples if relevant
- [ ] This is a genuine question (not a bug report or feature request)
-126
View File
@@ -1,126 +0,0 @@
---
name: Request for Comments (RFC)
about: Propose a significant change for community discussion
title: '[RFC] '
labels: 'rfc, discussion'
assignees: ''
---
## RFC Summary
One-paragraph summary of the proposal.
## Motivation
Why are we doing this? What use cases does it support? What is the expected outcome?
## Detailed Design
### Overview
Provide a detailed explanation of the proposed change.
### API Changes (if applicable)
```php
// Before
function oldApi($param1) { }
// After
function newApi($param1, $param2) { }
```
### User Experience Changes
Describe how users will interact with this change.
### Implementation Approach
High-level implementation strategy.
## Drawbacks
Why should we *not* do this?
## Alternatives
What other designs have been considered? What is the impact of not doing this?
### Alternative 1
- Description
- Trade-offs
### Alternative 2
- Description
- Trade-offs
## Adoption Strategy
How will existing users adopt this? Is this a breaking change?
### Migration Guide
```bash
# Steps to migrate
```
### Deprecation Timeline
- **Announcement**:
- **Deprecation**:
- **Removal**:
## Unresolved Questions
- Question 1
- Question 2
## Future Possibilities
What future work does this enable?
## Impact Assessment
### Performance
Expected performance impact.
### Security
Security considerations and implications.
### Compatibility
- **Backward Compatible**: [Yes / No]
- **Breaking Changes**: [List]
### Maintenance
Long-term maintenance considerations.
## Community Input
### Stakeholders
- [ ] Core team
- [ ] Module developers
- [ ] End users
- [ ] Enterprise customers
### Feedback Period
**Duration**: [e.g., 2 weeks]
**Deadline**: [date]
## Implementation Timeline
### Phase 1: Design
- [ ] RFC discussion
- [ ] Design finalization
- [ ] Approval
### Phase 2: Implementation
- [ ] Core implementation
- [ ] Tests
- [ ] Documentation
### Phase 3: Release
- [ ] Beta release
- [ ] Feedback collection
- [ ] Stable release
## Success Metrics
How will we measure success?
- Metric 1
- Metric 2
## References
- Related RFCs:
- External documentation:
- Prior art:
## Open Questions for Community
1. Question 1?
2. Question 2?
---
**Note**: This RFC is open for community discussion. Please provide feedback in the comments below.
-51
View File
@@ -1,51 +0,0 @@
---
name: Security Vulnerability Report
about: Report a security vulnerability (use only for non-critical issues)
title: '[SECURITY] '
labels: 'security'
assignees: ''
---
## ⚠️ IMPORTANT: Private Disclosure Required
**For critical security vulnerabilities, DO NOT use this template.**
Follow the process in [SECURITY.md](../SECURITY.md) for responsible disclosure.
Use this template only for:
- Security improvements
- Non-critical security suggestions
- Security documentation updates
---
## Security Issue
**Severity**:
<!-- Low, Medium, or informational only -->
## Description
<!-- Describe the security concern or improvement suggestion -->
## Affected Components
<!-- List the affected files, features, or components -->
## Suggested Mitigation
<!-- Describe how this could be addressed -->
## Standards Reference
Does this relate to security standards in [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)?
- [ ] SPDX license identifiers
- [ ] Secret management
- [ ] Dependency security
- [ ] Access control
- [ ] Other: [specify]
## Additional Context
<!-- Add any other context about the security concern -->
## Checklist
- [ ] This is NOT a critical vulnerability requiring private disclosure
- [ ] I have reviewed the SECURITY.md policy
- [ ] I have provided sufficient detail for evaluation
-24
View File
@@ -1,24 +0,0 @@
---
name: Version Bump
about: Request or track a version change
title: '[VERSION] '
labels: 'version, type: version'
assignees: 'jmiller'
---
## Version Change
**Current version**: <!-- e.g., 01.02.03 -->
**Requested version**: <!-- e.g., 01.03.00 -->
**Change type**: <!-- patch / minor / major -->
## Reason
<!-- Why is this version bump needed? -->
## Checklist
- [ ] README.md `VERSION:` field updated
- [ ] CHANGELOG.md entry added
- [ ] Module descriptor version updated (Dolibarr: `$this->version`, Joomla: `<version>`)
- [ ] All file headers will be auto-propagated by `sync-version-on-merge` workflow
-251
View File
@@ -1,251 +0,0 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Automation
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /.gitea/workflows/branch-protection.yml
# BRIEF: Apply standardised branch protection rules to all governed repositories
#
# +========================================================================+
# | BRANCH PROTECTION SETUP |
# +========================================================================+
# | |
# | Applies protection rules for: main, dev, rc, beta, alpha |
# | |
# | main — Require PR, block rejected reviews, no force push |
# | dev — Allow push, no force push, no delete |
# | rc — Allow push, no force push, no delete |
# | beta — Allow push, no force push, no delete |
# | alpha — Allow push, no force push, no delete |
# | |
# | jmiller has override authority on all branches. |
# | |
# +========================================================================+
name: Branch Protection Setup
on:
schedule:
- cron: '0 2 * * 1' # Weekly Monday 02:00 UTC
workflow_dispatch:
inputs:
dry_run:
description: 'Preview mode (no changes)'
required: false
type: boolean
default: false
repos:
description: 'Comma-separated repo names (empty = all governed repos)'
required: false
type: string
default: ''
env:
GITEA_URL: https://git.mokoconsulting.tech
GITEA_ORG: MokoConsulting
permissions:
contents: read
jobs:
protect:
name: Apply Branch Protection Rules
runs-on: ubuntu-latest
steps:
- name: Determine target repos
id: repos
env:
GA_TOKEN: ${{ secrets.GA_TOKEN }}
run: |
API="${GITEA_URL}/api/v1"
# Platform/standards/infra repos to exclude
EXCLUDE="gitea-org-config org-profile gitea-private .mokogitea-private MokoStandards moko-platform MokoTesting"
EXCLUDE="$EXCLUDE MokoStandards-Template-Client MokoStandards-Template-Dolibarr MokoStandards-Template-Generic MokoStandards-Template-Joomla MokoDoliProjTemplate"
if [ -n "${{ inputs.repos }}" ]; then
# User-specified repos
REPOS=$(echo "${{ inputs.repos }}" | tr ',' ' ')
else
# Fetch all org repos
PAGE=1
REPOS=""
while true; do
BATCH=$(curl -sS \
-H "Authorization: token ${GA_TOKEN}" \
"${API}/orgs/${GITEA_ORG}/repos?page=${PAGE}&limit=50" \
| jq -r '.[].name // empty')
[ -z "$BATCH" ] && break
REPOS="$REPOS $BATCH"
PAGE=$((PAGE + 1))
done
# Filter out excluded repos
FILTERED=""
for REPO in $REPOS; do
SKIP=false
for EX in $EXCLUDE; do
if [ "$REPO" = "$EX" ]; then
SKIP=true
break
fi
done
if [ "$SKIP" = "false" ]; then
FILTERED="$FILTERED $REPO"
fi
done
REPOS="$FILTERED"
fi
echo "repos=$REPOS" >> "$GITHUB_OUTPUT"
COUNT=$(echo "$REPOS" | wc -w)
echo "📋 Target repos (${COUNT}): $REPOS"
- name: Apply protection rules
env:
GA_TOKEN: ${{ secrets.GA_TOKEN }}
DRY_RUN: ${{ inputs.dry_run || 'false' }}
run: |
API="${GITEA_URL}/api/v1"
REPOS="${{ steps.repos.outputs.repos }}"
SUCCESS=0
FAILED=0
SKIPPED=0
# ── Rule definitions ──────────────────────────────────────
# Only the CI bot (jmiller token) can push directly.
# All human contributors must use PRs.
# Force push disabled on all branches.
RULE_MAIN='{
"rule_name": "main",
"enable_push": true,
"enable_push_whitelist": true,
"push_whitelist_usernames": ["jmiller"],
"enable_force_push": false,
"enable_force_push_allowlist": false,
"force_push_allowlist_usernames": [],
"enable_merge_whitelist": false,
"required_approvals": 0,
"dismiss_stale_approvals": true,
"block_on_rejected_reviews": true,
"block_on_outdated_branch": false,
"priority": 1
}'
RULE_DEV='{
"rule_name": "dev",
"enable_push": true,
"enable_push_whitelist": true,
"push_whitelist_usernames": ["jmiller"],
"enable_force_push": false,
"enable_force_push_allowlist": false,
"force_push_allowlist_usernames": [],
"enable_merge_whitelist": false,
"required_approvals": 0,
"block_on_rejected_reviews": false,
"priority": 2
}'
RULE_RC='{
"rule_name": "rc",
"enable_push": true,
"enable_push_whitelist": true,
"push_whitelist_usernames": ["jmiller"],
"enable_force_push": false,
"enable_force_push_allowlist": false,
"force_push_allowlist_usernames": [],
"enable_merge_whitelist": false,
"required_approvals": 0,
"block_on_rejected_reviews": false,
"priority": 3
}'
RULE_BETA='{
"rule_name": "beta",
"enable_push": true,
"enable_push_whitelist": true,
"push_whitelist_usernames": ["jmiller"],
"enable_force_push": false,
"enable_force_push_allowlist": false,
"force_push_allowlist_usernames": [],
"enable_merge_whitelist": false,
"required_approvals": 0,
"block_on_rejected_reviews": false,
"priority": 4
}'
RULE_ALPHA='{
"rule_name": "alpha",
"enable_push": true,
"enable_push_whitelist": true,
"push_whitelist_usernames": ["jmiller"],
"enable_force_push": false,
"enable_force_push_allowlist": false,
"force_push_allowlist_usernames": [],
"enable_merge_whitelist": false,
"required_approvals": 0,
"block_on_rejected_reviews": false,
"priority": 5
}'
RULES=("$RULE_MAIN" "$RULE_DEV" "$RULE_RC" "$RULE_BETA" "$RULE_ALPHA")
RULE_NAMES=("main" "dev" "rc" "beta" "alpha")
# ── Apply rules to each repo ──────────────────────────────
for REPO in $REPOS; do
echo ""
echo "═══ ${REPO} ═══"
for i in "${!RULES[@]}"; do
RULE="${RULES[$i]}"
NAME="${RULE_NAMES[$i]}"
if [ "$DRY_RUN" = "true" ]; then
echo " [DRY RUN] Would apply rule: ${NAME}"
SKIPPED=$((SKIPPED + 1))
continue
fi
# Delete existing rule if present (idempotent recreate)
ENCODED_NAME=$(echo "$NAME" | sed 's|/|%2F|g')
curl -sS -o /dev/null -w "" \
-X DELETE \
-H "Authorization: token ${GA_TOKEN}" \
"${API}/repos/${GITEA_ORG}/${REPO}/branch_protections/${ENCODED_NAME}" 2>/dev/null || true
# Create rule
RESPONSE=$(curl -sS -w "\n%{http_code}" \
-X POST \
-H "Authorization: token ${GA_TOKEN}" \
-H "Content-Type: application/json" \
-d "$RULE" \
"${API}/repos/${GITEA_ORG}/${REPO}/branch_protections")
HTTP=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')
if [ "$HTTP" = "201" ]; then
echo " ✅ ${NAME}"
SUCCESS=$((SUCCESS + 1))
else
echo " ❌ ${NAME} (HTTP ${HTTP}): $(echo "$BODY" | jq -r '.message // .' 2>/dev/null | head -1)"
FAILED=$((FAILED + 1))
fi
done
done
# ── Summary ───────────────────────────────────────────────
echo ""
echo "════════════════════════════════════════"
echo " ✅ Success: ${SUCCESS}"
echo " ❌ Failed: ${FAILED}"
echo " ⏭️ Skipped: ${SKIPPED}"
echo "════════════════════════════════════════"
if [ "$FAILED" -gt 0 ]; then
echo "::warning::${FAILED} rule(s) failed to apply"
fi
+1 -1
View File
@@ -22,7 +22,7 @@ on:
env: env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
permissions: permissions:
contents: write contents: write
+66 -18
View File
@@ -7,12 +7,12 @@
# INGROUP: mokocli.Release # INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli # REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/mokocli
# PATH: /templates/workflows/universal/auto-release.yml.template # PATH: /templates/workflows/universal/auto-release.yml.template
# VERSION: 05.00.00 # VERSION: 05.01.00
# BRIEF: Universal build & release detects platform from manifest.xml # BRIEF: Universal build & release detects platform from manifest.xml
# #
# +========================================================================+ # +=======================================================================+
# | UNIVERSAL BUILD & RELEASE PIPELINE | # | UNIVERSAL BUILD & RELEASE PIPELINE |
# +========================================================================+ # +=======================================================================+
# | | # | |
# | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. | # | Reads manifest.xml (joomla|dolibarr|generic) to branch logic. |
# | | # | |
@@ -21,15 +21,24 @@
# | dolibarr: mod*.class.php, update.txt, dev version reset | # | dolibarr: mod*.class.php, update.txt, dev version reset |
# | generic: README-only, no update stream | # | generic: README-only, no update stream |
# | | # | |
# +========================================================================+ # +=======================================================================+
name: "Universal: Build & Release" name: "Universal: Build & Release"
on: on:
pull_request: pull_request:
types: [opened, closed] types: [opened, synchronize, closed]
branches: branches:
- main - main
paths-ignore:
- '.mokogitea/workflows/**'
- '*.md'
- 'wiki/**'
- '.editorconfig'
- '.gitignore'
- '.gitattributes'
- '.gitmessage'
- 'LICENSE'
workflow_dispatch: workflow_dispatch:
inputs: inputs:
action: action:
@@ -43,7 +52,7 @@ on:
env: env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }} GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }} GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
@@ -51,12 +60,13 @@ permissions:
contents: write contents: write
jobs: jobs:
# ── PR Opened → Rename branch to RC and build RC release ───────────────────── # ── PR Opened → Rename branch to RC and build RC release ─────────────────────────
promote-rc: promote-rc:
name: Promote to RC name: Promote to RC
runs-on: release runs-on: release
if: >- if: >-
(github.event.action == 'opened' && github.event.pull_request.merged != true) || (github.event.action == 'opened' && github.event.pull_request.merged != true) ||
(github.event.action == 'synchronize' && github.event.pull_request.merged != true) ||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc') (github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
steps: steps:
@@ -65,6 +75,7 @@ jobs:
with: with:
token: ${{ secrets.MOKOGITEA_TOKEN }} token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 1 fetch-depth: 1
submodules: recursive
- name: Setup mokocli tools - name: Setup mokocli tools
env: env:
@@ -92,7 +103,7 @@ jobs:
php ${MOKO_CLI}/branch_rename.php \ php ${MOKO_CLI}/branch_rename.php \
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \ --from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
--token "${{ secrets.MOKOGITEA_TOKEN }}" \ --token "${{ secrets.MOKOGITEA_TOKEN }}" \
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \ --api-base "${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
--pr "${{ github.event.pull_request.number }}" --pr "${{ github.event.pull_request.number }}"
- name: Checkout rc and configure git - name: Checkout rc and configure git
@@ -111,7 +122,7 @@ jobs:
- name: Update RC release notes from CHANGELOG.md - name: Update RC release notes from CHANGELOG.md
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Extract [Unreleased] section from changelog # Extract [Unreleased] section from changelog
@@ -149,7 +160,7 @@ jobs:
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY
# ── Merged PR → Build & Release (or promote RC to stable) ──────────────────── # ── Merged PR → Build & Release (or promote RC to stable) ─────────────────────────
release: release:
name: Build & Release Pipeline name: Build & Release Pipeline
runs-on: release runs-on: release
@@ -163,6 +174,7 @@ jobs:
with: with:
token: ${{ secrets.MOKOGITEA_TOKEN }} token: ${{ secrets.MOKOGITEA_TOKEN }}
fetch-depth: 0 fetch-depth: 0
submodules: recursive
- name: Configure git for bot pushes - name: Configure git for bot pushes
run: | run: |
@@ -241,14 +253,50 @@ jobs:
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//') VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
[ -z "$VERSION" ] && VERSION="00.00.00" && echo "skip=true" >> "$GITHUB_OUTPUT" [ -z "$VERSION" ] && VERSION="00.00.00" && echo "skip=true" >> "$GITHUB_OUTPUT"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT" echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "tag=stable" >> "$GITHUB_OUTPUT" PLATFORM="${{ steps.platform.outputs.platform }}"
echo "release_tag=stable" >> "$GITHUB_OUTPUT" if [[ "$PLATFORM" == joomla* ]]; then
echo "tag=stable" >> "$GITHUB_OUTPUT"
echo "release_tag=stable" >> "$GITHUB_OUTPUT"
else
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
echo "release_tag=v${VERSION}" >> "$GITHUB_OUTPUT"
fi
echo "branch=main" >> "$GITHUB_OUTPUT" echo "branch=main" >> "$GITHUB_OUTPUT"
echo "Published version: ${VERSION}" echo "Published version: ${VERSION}"
- name: "Create semver tag for non-Joomla repos"
id: semver
if: |
steps.version.outputs.skip != 'true' &&
!startsWith(steps.platform.outputs.platform, 'joomla')
run: |
VERSION="${{ steps.version.outputs.version }}"
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
SEMVER_TAG="v${VERSION}"
echo "Creating semver tag: ${SEMVER_TAG}"
# Create the git tag via API
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" \
-X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API_BASE}/tags" \
-d "{\"tag_name\":\"${SEMVER_TAG}\",\"target\":\"main\",\"message\":\"Release ${VERSION}\"}" 2>/dev/null || echo "000")
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
echo "Created semver tag: ${SEMVER_TAG}"
elif [ "$HTTP_CODE" = "409" ]; then
echo "Semver tag ${SEMVER_TAG} already exists (skipped)"
else
echo "::warning::Failed to create semver tag ${SEMVER_TAG} (HTTP ${HTTP_CODE})"
fi
echo "semver_tag=${SEMVER_TAG}" >> "$GITHUB_OUTPUT"
- name: Update release notes and promote changelog - name: Update release notes and promote changelog
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Get the stable release info (version and ID) # Get the stable release info (version and ID)
@@ -317,7 +365,7 @@ jobs:
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}" RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}" GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/release_mirror.php \ php ${MOKO_CLI}/release_mirror.php \
--version "$VERSION" --tag "$RELEASE_TAG" \ --version "$VERSION" --tag "$RELEASE_TAG" \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
@@ -346,7 +394,7 @@ jobs:
if: steps.version.outputs.skip != 'true' if: steps.version.outputs.skip != 'true'
continue-on-error: true continue-on-error: true
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Delete rc branch (ephemeral — created by promote-rc) # Delete rc branch (ephemeral — created by promote-rc)
@@ -370,7 +418,7 @@ jobs:
if: steps.version.outputs.skip != 'true' if: steps.version.outputs.skip != 'true'
continue-on-error: true continue-on-error: true
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
BRANCH_NAME="version/${VERSION}" BRANCH_NAME="version/${VERSION}"
@@ -391,7 +439,7 @@ jobs:
if: steps.version.outputs.skip != 'true' if: steps.version.outputs.skip != 'true'
continue-on-error: true continue-on-error: true
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php ${MOKO_CLI}/version_reset_dev.php \ php ${MOKO_CLI}/version_reset_dev.php \
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \ --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
--branch dev --path . 2>&1 || true --branch dev --path . 2>&1 || true
@@ -417,5 +465,5 @@ jobs:
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY echo "| Release | [View](${MOKOGITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
fi fi
+6
View File
@@ -13,6 +13,12 @@
name: "Generic: Project CI" name: "Generic: Project CI"
on: on:
pull_request:
branches:
- main
- dev
- dev/**
- rc/**
workflow_dispatch: workflow_dispatch:
permissions: permissions:
@@ -0,0 +1,68 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /.mokogitea/workflows/ci-issue-reporter.yml
# VERSION: 01.00.00
# BRIEF: Reusable workflow — creates/updates a Gitea issue when a CI gate fails.
# Clones MokoCLI and runs cli/ci_issue_reporter.sh.
name: "Universal: CI Issue Reporter"
on:
workflow_call:
inputs:
gate:
description: "CI gate name (e.g. PR Validation, Repository Health)"
required: true
type: string
details:
description: "Human-readable failure description"
required: true
type: string
severity:
description: "error or warning"
required: false
type: string
default: "error"
workflow:
description: "Workflow name for the issue title"
required: false
type: string
default: ""
secrets:
MOKOGITEA_TOKEN:
required: true
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
report:
name: "Report: ${{ inputs.gate }}"
runs-on: ubuntu-latest
steps:
- name: Clone MokoCLI
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: |
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 --filter=blob:none --sparse "${MOKOGITEA_URL}/MokoConsulting/MokoCLI.git" /tmp/mokocli
cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh
- name: Report CI failure
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
run: |
chmod +x /tmp/mokocli/cli/ci_issue_reporter.sh
/tmp/mokocli/cli/ci_issue_reporter.sh \
--gate "${{ inputs.gate }}" \
--details "${{ inputs.details }}" \
--severity "${{ inputs.severity }}" \
--workflow "${{ inputs.workflow }}"
-903
View File
@@ -1,903 +0,0 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# This file is part of a Moko Consulting project.
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow.Template
# INGROUP: MokoStandards.CI
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
# PATH: /templates/workflows/joomla/ci-joomla.yml.template
# VERSION: 04.06.00
# BRIEF: CI workflow for Joomla extensions — lint, validate, test
name: "Joomla: Extension CI"
on:
pull_request:
branches:
- main
- 'dev/**'
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@v4
- name: Setup PHP
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi
php -v && composer --version
- name: Setup mokocli tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.GA_TOKEN || github.token }}
MOKO_CLONE_HOST: ${{ secrets.MOKOGITEA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
run: |
if [ -d "/opt/mokocli" ] || [ -d "/tmp/mokocli" ]; then
echo "mokocli already available on runner — skipping clone"
else
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git" \
/tmp/mokocli 2>/dev/null || echo "mokocli clone skipped — continuing without it"
fi
- name: Install dependencies
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.GA_TOKEN || github.token }}"}}'
run: |
if [ -f "composer.json" ]; then
composer install \
--no-interaction \
--prefer-dist \
--optimize-autoloader
else
echo "No composer.json found — skipping dependency install"
fi
- name: PHP syntax check
run: |
ERRORS=0
for DIR in src/ htdocs/; do
if [ -d "$DIR" ]; then
FOUND=1
while IFS= read -r -d '' FILE; do
OUTPUT=$(php -l "$FILE" 2>&1)
if echo "$OUTPUT" | grep -q "Parse error"; then
echo "::error file=${FILE}::${OUTPUT}"
ERRORS=$((ERRORS + 1))
fi
done < <(find "$DIR" -name "*.php" -print0)
fi
done
echo "### PHP Syntax Check" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} syntax error(s) found.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY
fi
- name: XML manifest validation
run: |
echo "### XML Manifest Validation" >> $GITHUB_STEP_SUMMARY
ERRORS=0
# Find the extension manifest (XML with <extension tag)
MANIFEST=""
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
MANIFEST="$XML_FILE"
break
fi
done
if [ -z "$MANIFEST" ]; then
echo "No Joomla extension manifest found (XML file with \`<extension\` tag)." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Manifest found: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
# Validate well-formed XML
php -r "
\$xml = @simplexml_load_file('$MANIFEST');
if (\$xml === false) {
echo 'INVALID';
exit(1);
}
echo 'VALID';
" > /tmp/xml_result 2>&1
XML_RESULT=$(cat /tmp/xml_result)
if [ "$XML_RESULT" != "VALID" ]; then
echo "Manifest is not well-formed XML." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Manifest is well-formed XML." >> $GITHUB_STEP_SUMMARY
fi
# Check required tags: name, version, author
for TAG in name version author; 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
# Namespace is required for components/plugins but not packages
EXT_TYPE=$(grep -oP '<extension[^>]*\btype="\K[^"]+' "$MANIFEST" | head -1)
if [ "$EXT_TYPE" != "package" ]; then
if ! grep -q "<namespace" "$MANIFEST" 2>/dev/null; then
echo "Missing required tag: \`<namespace>\` (required for Joomla 5+ ${EXT_TYPE} extensions)" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Found required tag: \`<namespace>\`" >> $GITHUB_STEP_SUMMARY
fi
else
echo "Package extension — \`<namespace>\` not required." >> $GITHUB_STEP_SUMMARY
fi
fi
if [ "${ERRORS}" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "**${ERRORS} manifest issue(s) found.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Manifest validation passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: Check language files referenced in manifest
run: |
echo "### Language File Check" >> $GITHUB_STEP_SUMMARY
ERRORS=0
MANIFEST=""
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
MANIFEST="$XML_FILE"
break
fi
done
if [ -n "$MANIFEST" ]; then
# Extract language file references from manifest
LANG_FILES=$(grep -oP 'language\s+tag="[^"]*"[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null || true)
if [ -z "$LANG_FILES" ]; then
echo "No language file references found in manifest — skipping." >> $GITHUB_STEP_SUMMARY
else
while IFS= read -r LANG_FILE; do
LANG_FILE=$(echo "$LANG_FILE" | xargs)
if [ -z "$LANG_FILE" ]; then
continue
fi
# Check in common locations
FOUND=0
for BASE in "." "src" "htdocs"; do
if [ -f "${BASE}/${LANG_FILE}" ]; then
FOUND=1
break
fi
done
if [ "$FOUND" -eq 0 ]; then
echo "Missing language file: \`${LANG_FILE}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Language file present: \`${LANG_FILE}\`" >> $GITHUB_STEP_SUMMARY
fi
done <<< "$LANG_FILES"
fi
else
echo "No manifest found — skipping language check." >> $GITHUB_STEP_SUMMARY
fi
if [ "${ERRORS}" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "**${ERRORS} missing language file(s).**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Language file check passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: Check index.html files in directories
run: |
echo "### Index.html Check" >> $GITHUB_STEP_SUMMARY
MISSING=0
CHECKED=0
for DIR in src/ htdocs/; do
if [ -d "$DIR" ]; then
while IFS= read -r -d '' SUBDIR; do
CHECKED=$((CHECKED + 1))
if [ ! -f "${SUBDIR}/index.html" ]; then
echo "Missing index.html in: \`${SUBDIR}\`" >> $GITHUB_STEP_SUMMARY
MISSING=$((MISSING + 1))
fi
done < <(find "$DIR" -type d -print0)
fi
done
if [ "${CHECKED}" -eq 0 ]; then
echo "No src/ or htdocs/ directories found — skipping." >> $GITHUB_STEP_SUMMARY
elif [ "${MISSING}" -gt 0 ]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "**${MISSING} director(ies) missing index.html out of ${CHECKED} checked.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "All ${CHECKED} directories contain index.html." >> $GITHUB_STEP_SUMMARY
fi
- name: Check config.xml and access.xml for components
run: |
echo "### Component Config & ACL Check" >> $GITHUB_STEP_SUMMARY
ERRORS=0
# Find all component manifests (XML with type="component")
COMP_MANIFESTS=$(find . -maxdepth 4 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*" -exec grep -l '<extension[^>]*type="component"' {} ; 2>/dev/null || true)
if [ -z "$COMP_MANIFESTS" ]; then
echo "No component extensions found — skipping." >> $GITHUB_STEP_SUMMARY
else
for MANIFEST in $COMP_MANIFESTS; do
COMP_DIR=$(dirname "$MANIFEST")
COMP_NAME=$(basename "$COMP_DIR")
echo "Component: `${COMP_NAME}` (manifest: `${MANIFEST}`)" >> $GITHUB_STEP_SUMMARY
# Check access.xml exists
ACCESS_FILE=$(find "$COMP_DIR" -name "access.xml" -not -path "./.git/*" 2>/dev/null | head -1)
if [ -z "$ACCESS_FILE" ]; then
echo "- Missing `access.xml` — ACL permissions will not work." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
if command -v php &> /dev/null; then
if ! php -r "@simplexml_load_file('$ACCESS_FILE') ?: exit(1);" 2>/dev/null; then
echo "- `access.xml` is not well-formed XML." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
for ACTION in core.admin core.manage; do
if ! grep -q "name=\"${ACTION}\"" "$ACCESS_FILE" 2>/dev/null; then
echo "- `access.xml` missing required action: `${ACTION}`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done
echo "- `access.xml`: valid" >> $GITHUB_STEP_SUMMARY
fi
fi
fi
# Check config.xml exists
CONFIG_FILE=$(find "$COMP_DIR" -name "config.xml" -not -path "./.git/*" 2>/dev/null | head -1)
if [ -z "$CONFIG_FILE" ]; then
echo "- Missing `config.xml` — component Options page will be empty." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
if command -v php &> /dev/null; then
if ! php -r "@simplexml_load_file('$CONFIG_FILE') ?: exit(1);" 2>/dev/null; then
echo "- `config.xml` is not well-formed XML." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "- `config.xml`: valid" >> $GITHUB_STEP_SUMMARY
fi
fi
fi
done
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} config/ACL issue(s) found.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Component config & ACL check passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: SQL schema validation
run: |
echo "### SQL Schema Validation" >> $GITHUB_STEP_SUMMARY
ERRORS=0
# Find SQL files in source/htdocs
SQL_FILES=$(find . -name "*.sql" -path "*/sql/*" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null)
if [ -z "$SQL_FILES" ]; then
echo "No SQL files found — skipping." >> $GITHUB_STEP_SUMMARY
else
echo "Found $(echo "$SQL_FILES" | wc -l) SQL file(s)" >> $GITHUB_STEP_SUMMARY
for FILE in $SQL_FILES; do
# Basic syntax check: balanced parentheses, no empty files
SIZE=$(wc -c < "$FILE" | tr -d ' ')
if [ "$SIZE" -eq 0 ]; then
echo "- Empty SQL file: \`${FILE}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
continue
fi
# Check for common SQL errors
if grep -qP '^\s*$' "$FILE" && [ "$SIZE" -lt 5 ]; then
echo "- Whitespace-only SQL file: \`${FILE}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
continue
fi
echo "- \`${FILE}\`: ${SIZE} bytes" >> $GITHUB_STEP_SUMMARY
done
# Check update SQL files follow version numbering pattern
UPDATE_DIR=$(find . -path "*/sql/updates/mysql" -type d -not -path "./.git/*" 2>/dev/null | head -1)
if [ -n "$UPDATE_DIR" ]; then
BAD_NAMES=0
for UFILE in "$UPDATE_DIR"/*.sql; do
[ ! -f "$UFILE" ] && continue
BASENAME=$(basename "$UFILE" .sql)
if ! echo "$BASENAME" | grep -qP '^\d+\.\d+\.\d+'; then
echo "- Update file \`${UFILE}\` does not follow version naming (expected X.Y.Z.sql)" >> $GITHUB_STEP_SUMMARY
BAD_NAMES=$((BAD_NAMES + 1))
fi
done
if [ "$BAD_NAMES" -gt 0 ]; then
ERRORS=$((ERRORS + BAD_NAMES))
fi
fi
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} SQL issue(s) found.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**SQL schema validation passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: Manifest file references check
run: |
echo "### Manifest File References" >> $GITHUB_STEP_SUMMARY
ERRORS=0
MANIFEST=""
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
MANIFEST="$XML_FILE"
break
fi
done
if [ -z "$MANIFEST" ]; then
echo "No manifest found — skipping." >> $GITHUB_STEP_SUMMARY
else
MANIFEST_DIR=$(dirname "$MANIFEST")
# Check <filename> references
FILENAMES=$(grep -oP '<filename[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null || true)
for F in $FILENAMES; do
if [ ! -f "${MANIFEST_DIR}/${F}" ] && [ ! -d "${MANIFEST_DIR}/${F}" ]; then
echo "- Missing: \`${F}\` (referenced in manifest)" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done
# Check <folder> references
FOLDERS=$(grep -oP '<folder[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null || true)
for F in $FOLDERS; do
if [ ! -d "${MANIFEST_DIR}/${F}" ]; then
echo "- Missing folder: \`${F}\` (referenced in manifest)" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done
# Check <file> references in package manifests (ZIP files won't exist in source)
EXT_TYPE=$(grep -oP '<extension[^>]*\btype="\K[^"]+' "$MANIFEST" | head -1)
if [ "$EXT_TYPE" != "package" ]; then
FILES=$(grep -oP '<file[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null || true)
for F in $FILES; do
if [ ! -f "${MANIFEST_DIR}/${F}" ]; then
echo "- Missing file: \`${F}\` (referenced in manifest)" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done
fi
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} missing file reference(s).**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Manifest file references check passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: Form XML validation
run: |
echo "### Form XML Validation" >> $GITHUB_STEP_SUMMARY
ERRORS=0
FORM_FILES=$(find . -name "*.xml" -path "*/forms/*" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null)
if [ -z "$FORM_FILES" ]; then
echo "No form XML files found — skipping." >> $GITHUB_STEP_SUMMARY
else
echo "Found $(echo "$FORM_FILES" | wc -l) form file(s)" >> $GITHUB_STEP_SUMMARY
for FILE in $FORM_FILES; do
if command -v php &> /dev/null; then
if ! php -r "@simplexml_load_file('$FILE') ?: exit(1);" 2>/dev/null; then
echo "- \`${FILE}\`: malformed XML" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
# Check for valid Joomla form structure
if ! grep -qE '<form|<field|<fieldset' "$FILE" 2>/dev/null; then
echo "- \`${FILE}\`: no \`<form>\`, \`<field>\`, or \`<fieldset>\` elements found" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "- \`${FILE}\`: valid" >> $GITHUB_STEP_SUMMARY
fi
fi
fi
done
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} form XML issue(s).**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Form XML validation passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: Deprecated Joomla API check
continue-on-error: true
run: |
echo "### Deprecated Joomla API Check" >> $GITHUB_STEP_SUMMARY
WARNINGS=0
SRC_DIR=""
for DIR in source/ src/ htdocs/; do
[ -d "$DIR" ] && SRC_DIR="$DIR" && break
done
if [ -z "$SRC_DIR" ]; then
echo "No source directory found — skipping." >> $GITHUB_STEP_SUMMARY
else
# Joomla 3/4 deprecated patterns that break in Joomla 6
PATTERNS=(
'JFactory::'
'JText::'
'JHtml::'
'JRoute::'
'JUri::'
'JLog::'
'JTable::'
'JInput'
'CMSFactory::\$application'
'JApplicationCms'
)
for PATTERN in "${PATTERNS[@]}"; do
HITS=$(grep -rnl "$PATTERN" "$SRC_DIR" --include="*.php" 2>/dev/null || true)
if [ -n "$HITS" ]; then
COUNT=$(echo "$HITS" | wc -l)
echo "- \`${PATTERN}\` found in ${COUNT} file(s)" >> $GITHUB_STEP_SUMMARY
WARNINGS=$((WARNINGS + COUNT))
fi
done
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$WARNINGS" -gt 0 ]; then
echo "**${WARNINGS} deprecated API usage(s) found.** These will break in Joomla 6." >> $GITHUB_STEP_SUMMARY
else
echo "**No deprecated APIs found.**" >> $GITHUB_STEP_SUMMARY
fi
fi
- name: Template output escaping check
continue-on-error: true
run: |
echo "### Template Output Escaping" >> $GITHUB_STEP_SUMMARY
WARNINGS=0
TMPL_FILES=$(find . -name "*.php" -path "*/tmpl/*" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null)
if [ -z "$TMPL_FILES" ]; then
echo "No template files found — skipping." >> $GITHUB_STEP_SUMMARY
else
echo "Found $(echo "$TMPL_FILES" | wc -l) template file(s)" >> $GITHUB_STEP_SUMMARY
for FILE in $TMPL_FILES; do
# Check for unescaped output: <?= $var ?> or echo $var without escape()
UNESCAPED=$(grep -nP '<\?=\s*\$(?!this->escape)' "$FILE" 2>/dev/null || true)
if [ -n "$UNESCAPED" ]; then
HITS=$(echo "$UNESCAPED" | wc -l)
echo "- \`${FILE}\`: ${HITS} unescaped \`<?= \$var ?>\` output(s) — use \`<?= \$this->escape(\$var) ?>\`" >> $GITHUB_STEP_SUMMARY
WARNINGS=$((WARNINGS + HITS))
fi
# Check for echo without escaping in template context
RAW_ECHO=$(grep -nP '^\s*echo\s+\$(?!this->escape)' "$FILE" 2>/dev/null || true)
if [ -n "$RAW_ECHO" ]; then
HITS=$(echo "$RAW_ECHO" | wc -l)
echo "- \`${FILE}\`: ${HITS} raw \`echo \$var\` — consider \`echo \$this->escape(\$var)\`" >> $GITHUB_STEP_SUMMARY
WARNINGS=$((WARNINGS + HITS))
fi
done
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$WARNINGS" -gt 0 ]; then
echo "**${WARNINGS} potential XSS risk(s) in templates.** Review unescaped output." >> $GITHUB_STEP_SUMMARY
else
echo "**All template output appears properly escaped.**" >> $GITHUB_STEP_SUMMARY
fi
fi
- name: Namespace consistency check
run: |
echo "### Namespace Consistency" >> $GITHUB_STEP_SUMMARY
ERRORS=0
# Find component/plugin manifests with <namespace> tags
MANIFESTS=$(find . -maxdepth 4 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*" -exec grep -l '<namespace' {} \; 2>/dev/null || true)
if [ -z "$MANIFESTS" ]; then
echo "No manifests with \`<namespace>\` found — skipping." >> $GITHUB_STEP_SUMMARY
else
for MANIFEST in $MANIFESTS; do
NS_PATH=$(grep -oP '<namespace[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1)
[ -z "$NS_PATH" ] && continue
MANIFEST_DIR=$(dirname "$MANIFEST")
echo "Manifest: \`${MANIFEST}\` → namespace \`${NS_PATH}\`" >> $GITHUB_STEP_SUMMARY
# Check PHP files have matching namespace
while IFS= read -r -d '' PHP_FILE; do
FILE_NS=$(grep -oP '^\s*namespace\s+\K[^;]+' "$PHP_FILE" 2>/dev/null | head -1)
[ -z "$FILE_NS" ] && continue
# Namespace should start with the manifest namespace path
if ! echo "$FILE_NS" | grep -qF "${NS_PATH}"; then
echo "- \`${PHP_FILE}\`: namespace \`${FILE_NS}\` doesn't match manifest \`${NS_PATH}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done < <(find "$MANIFEST_DIR" -name "*.php" -path "*/src/*" -not -path "./vendor/*" -print0 2>/dev/null)
done
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} namespace mismatch(es).**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Namespace consistency check passed.**" >> $GITHUB_STEP_SUMMARY
fi
- name: SPDX license header check
continue-on-error: true
run: |
echo "### SPDX License Headers" >> $GITHUB_STEP_SUMMARY
MISSING=0
SRC_DIR=""
for DIR in source/ src/ htdocs/; do
[ -d "$DIR" ] && SRC_DIR="$DIR" && break
done
if [ -z "$SRC_DIR" ]; then
echo "No source directory found — skipping." >> $GITHUB_STEP_SUMMARY
else
TOTAL=0
while IFS= read -r -d '' FILE; do
TOTAL=$((TOTAL + 1))
if ! head -10 "$FILE" | grep -qi "SPDX"; then
echo "- Missing SPDX header: \`${FILE}\`" >> $GITHUB_STEP_SUMMARY
MISSING=$((MISSING + 1))
fi
done < <(find "$SRC_DIR" -name "*.php" -not -path "./vendor/*" -print0)
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$MISSING" -gt 0 ]; then
echo "**${MISSING}/${TOTAL} PHP file(s) missing SPDX license header.**" >> $GITHUB_STEP_SUMMARY
else
echo "**All ${TOTAL} PHP files have SPDX headers.**" >> $GITHUB_STEP_SUMMARY
fi
fi
- name: Service provider check
run: |
echo "### Service Provider Check" >> $GITHUB_STEP_SUMMARY
ERRORS=0
PROVIDERS=$(find . -name "provider.php" -path "*/services/*" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null)
if [ -z "$PROVIDERS" ]; then
echo "No service providers found — skipping." >> $GITHUB_STEP_SUMMARY
else
for FILE in $PROVIDERS; do
# Must return a ServiceProviderInterface
if ! grep -qP 'ServiceProviderInterface|ComponentInterface|MVCFactoryInterface|DispatcherInterface' "$FILE" 2>/dev/null; then
echo "- \`${FILE}\`: does not reference ServiceProviderInterface or component interfaces" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "- \`${FILE}\`: valid service provider" >> $GITHUB_STEP_SUMMARY
fi
# Must have return statement
if ! grep -qP '^\s*return\s+new\s+' "$FILE" 2>/dev/null; then
echo "- \`${FILE}\`: missing \`return new ...\` statement" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
done
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${ERRORS}" -gt 0 ]; then
echo "**${ERRORS} service provider issue(s).**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Service provider check passed.**" >> $GITHUB_STEP_SUMMARY
fi
release-readiness:
name: Release Readiness Check
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && github.base_ref == 'main'
continue-on-error: true
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Validate release readiness
run: |
echo "## Release Readiness" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
ERRORS=0
# Extract version from README.md
README_VERSION=$(grep -oP '^\s*VERSION:\s*\K[0-9]{2}\.[0-9]{2}\.[0-9]{2}' README.md | head -1)
if [ -z "$README_VERSION" ]; then
echo "No VERSION found in README.md FILE INFORMATION block." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "README version: \`${README_VERSION}\`" >> $GITHUB_STEP_SUMMARY
fi
# Find the extension manifest
MANIFEST=""
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
MANIFEST="$XML_FILE"
break
fi
done
if [ -z "$MANIFEST" ]; then
echo "No Joomla extension manifest found." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
# Check <version> matches README VERSION
MANIFEST_VERSION=$(grep -oP '<version>\K[^<]+' "$MANIFEST" | head -1)
if [ -z "$MANIFEST_VERSION" ]; then
echo "No \`<version>\` tag in manifest." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
elif [ -n "$README_VERSION" ] && [ "$MANIFEST_VERSION" != "$README_VERSION" ]; then
echo "Manifest version \`${MANIFEST_VERSION}\` does not match README \`${README_VERSION}\`." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Manifest version: \`${MANIFEST_VERSION}\`" >> $GITHUB_STEP_SUMMARY
fi
# Check extension type, element, client attributes
EXT_TYPE=$(grep -oP '<extension[^>]*\btype="\K[^"]+' "$MANIFEST" | head -1)
if [ -z "$EXT_TYPE" ]; then
echo "Missing \`type\` attribute on \`<extension>\` tag." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
else
echo "Extension type: \`${EXT_TYPE}\`" >> $GITHUB_STEP_SUMMARY
fi
# Element check (component/module/plugin name)
HAS_ELEMENT=$(grep -cP '<(element|name)>' "$MANIFEST" 2>/dev/null || echo "0")
if [ "$HAS_ELEMENT" -eq 0 ]; then
echo "Missing \`<element>\` or \`<name>\` in manifest." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
# Client attribute for site/admin modules and plugins
if echo "$EXT_TYPE" | grep -qP "^(module|plugin)$"; then
HAS_CLIENT=$(grep -cP '<extension[^>]*\bclient=' "$MANIFEST" 2>/dev/null || echo "0")
if [ "$HAS_CLIENT" -eq 0 ]; then
echo "Missing \`client\` attribute for ${EXT_TYPE} extension." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
fi
fi
# Check updates.xml exists
if [ -f "updates.xml" ] || [ -f "updates.xml" ]; then
echo "Update XML present." >> $GITHUB_STEP_SUMMARY
else
echo "No updates.xml found." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
# Check CHANGELOG.md exists
if [ -f "CHANGELOG.md" ]; then
echo "CHANGELOG.md present." >> $GITHUB_STEP_SUMMARY
else
echo "No CHANGELOG.md found." >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS + 1))
fi
echo "" >> $GITHUB_STEP_SUMMARY
if [ $ERRORS -gt 0 ]; then
echo "**${ERRORS} issue(s) must be resolved before release.**" >> $GITHUB_STEP_SUMMARY
exit 1
else
echo "**Extension is ready for release.**" >> $GITHUB_STEP_SUMMARY
fi
test:
name: Tests (PHP ${{ matrix.php }})
runs-on: ubuntu-latest
needs: lint-and-validate
strategy:
fail-fast: false
matrix:
php: ['8.2', '8.3']
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup PHP ${{ matrix.php }}
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi
php -v && composer --version
- name: Install dependencies
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.GA_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
static-analysis:
name: PHPStan Analysis
runs-on: ubuntu-latest
needs: lint-and-validate
continue-on-error: true
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup PHP
run: |
if ! command -v php &> /dev/null; then
sudo apt-get update -qq
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
fi
php -v && composer --version
- name: Install dependencies
env:
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || secrets.GA_TOKEN || github.token }}"}}'
run: |
if [ -f "composer.json" ]; then
composer install --no-interaction --prefer-dist --optimize-autoloader
fi
- name: Install PHPStan
run: |
if ! command -v vendor/bin/phpstan &> /dev/null; then
composer require --dev phpstan/phpstan --no-interaction 2>/dev/null || \
composer global require phpstan/phpstan --no-interaction
fi
- name: Run PHPStan
run: |
echo "### PHPStan Static Analysis" >> $GITHUB_STEP_SUMMARY
PHPSTAN="vendor/bin/phpstan"
if [ ! -f "$PHPSTAN" ]; then
PHPSTAN=$(composer global config bin-dir --absolute 2>/dev/null)/phpstan
fi
# Determine source directory
SRC_DIR=""
for DIR in src/ htdocs/ lib/; do
if [ -d "$DIR" ]; then
SRC_DIR="$DIR"
break
fi
done
if [ -z "$SRC_DIR" ]; then
echo "No source directory found (src/, htdocs/, lib/) — skipping." >> $GITHUB_STEP_SUMMARY
exit 0
fi
# Use repo phpstan.neon if present, otherwise use baseline config
ARGS="analyse ${SRC_DIR} --memory-limit=512M --no-progress --error-format=table"
if [ -f "phpstan.neon" ] || [ -f "phpstan.neon.dist" ]; then
echo "Using project PHPStan config." >> $GITHUB_STEP_SUMMARY
else
ARGS="$ARGS --level=3"
echo "No phpstan.neon found — using level 3 (type inference)." >> $GITHUB_STEP_SUMMARY
fi
$PHPSTAN $ARGS 2>&1 | tee /tmp/phpstan-output.txt
EXIT=${PIPESTATUS[0]}
if [ $EXIT -eq 0 ]; then
echo "**No errors found.**" >> $GITHUB_STEP_SUMMARY
else
ERRORS=$(grep -c "ERROR" /tmp/phpstan-output.txt 2>/dev/null || echo "some")
echo "**${ERRORS} error(s) found.** Review output above." >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
tail -30 /tmp/phpstan-output.txt >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
fi
exit $EXIT
pre-release:
name: Build RC Pre-Release
runs-on: ubuntu-latest
needs: [lint-and-validate, test]
if: github.event_name == 'pull_request'
steps:
- name: Trigger pre-release build
env:
GA_TOKEN: ${{ secrets.GA_TOKEN }}
REPO: ${{ github.repository }}
BRANCH: ${{ github.head_ref }}
run: |
curl -s -X POST \
"${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" \
-H "Authorization: token ${GA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
+10 -10
View File
@@ -21,7 +21,7 @@ permissions:
contents: write contents: write
env: env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
jobs: jobs:
cleanup: cleanup:
@@ -33,17 +33,17 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GA_TOKEN }} token: ${{ secrets.MOKOGITEA_TOKEN }}
- name: Delete merged branches - name: Delete merged branches
env: env:
GA_TOKEN: ${{ secrets.GA_TOKEN }} MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: | run: |
echo "=== Merged Branch Cleanup ===" echo "=== Merged Branch Cleanup ==="
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
# List branches via API # List branches via API
BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \ BRANCHES=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \
"${API}/branches?limit=50" | jq -r '.[].name') "${API}/branches?limit=50" | jq -r '.[].name')
DELETED=0 DELETED=0
@@ -56,7 +56,7 @@ jobs:
# Check if branch is merged into main # Check if branch is merged into main
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
echo " Deleting merged branch: ${BRANCH}" echo " Deleting merged branch: ${BRANCH}"
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \ curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \
"${API}/branches/${BRANCH}" 2>/dev/null || true "${API}/branches/${BRANCH}" 2>/dev/null || true
DELETED=$((DELETED + 1)) DELETED=$((DELETED + 1))
fi fi
@@ -66,20 +66,20 @@ jobs:
- name: Clean old workflow runs - name: Clean old workflow runs
env: env:
GA_TOKEN: ${{ secrets.GA_TOKEN }} MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: | run: |
echo "=== Workflow Run Cleanup ===" echo "=== Workflow Run Cleanup ==="
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ) CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
# Get old completed runs # Get old completed runs
RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \ RUNS=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \
"${API}/actions/runs?status=completed&limit=50" | \ "${API}/actions/runs?status=completed&limit=50" | \
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null) jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
DELETED=0 DELETED=0
for RUN_ID in $RUNS; do for RUN_ID in $RUNS; do
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \ curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true "${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
DELETED=$((DELETED + 1)) DELETED=$((DELETED + 1))
done done
+126
View File
@@ -0,0 +1,126 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Deploy
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API
# PATH: /templates/workflows/joomla/deploy-manual.yml.template
# VERSION: 04.07.00
# BRIEF: Manual SFTP deploy to dev server for Joomla repos
name: "Universal: Deploy to Dev (Manual)"
on:
workflow_dispatch:
inputs:
clear_remote:
description: 'Delete all remote files before uploading'
required: false
default: 'false'
type: boolean
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
permissions:
contents: read
jobs:
deploy:
name: SFTP Deploy to Dev
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup PHP
run: |
php -v && composer --version
- name: Setup MokoStandards tools
env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
MOKO_CLONE_HOST: ${{ secrets.MOKOGITEA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.MOKOGITEA_TOKEN || github.token }}"}}'
run: |
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
/tmp/mokostandards-api 2>/dev/null || true
if [ -d "/tmp/mokostandards-api" ] && [ -f "/tmp/mokostandards-api/composer.json" ]; then
cd /tmp/mokostandards-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
fi
- name: Check FTP configuration
id: check
env:
HOST: ${{ vars.DEV_FTP_HOST }}
PATH_VAR: ${{ vars.DEV_FTP_PATH }}
PORT: ${{ vars.DEV_FTP_PORT }}
run: |
if [ -z "$HOST" ] || [ -z "$PATH_VAR" ]; then
echo "DEV_FTP_HOST or DEV_FTP_PATH not configured -- cannot deploy"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "host=$HOST" >> "$GITHUB_OUTPUT"
REMOTE="${PATH_VAR%/}"
echo "remote=$REMOTE" >> "$GITHUB_OUTPUT"
[ -z "$PORT" ] && PORT="22"
echo "port=$PORT" >> "$GITHUB_OUTPUT"
- name: Deploy via SFTP
if: steps.check.outputs.skip != 'true'
env:
SFTP_KEY: ${{ secrets.DEV_FTP_KEY }}
SFTP_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
SFTP_USER: ${{ vars.DEV_FTP_USERNAME }}
run: |
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ -- nothing to deploy"; exit 0; }
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
"${{ steps.check.outputs.host }}" "${{ steps.check.outputs.port }}" "$SFTP_USER" "${{ steps.check.outputs.remote }}" \
> /tmp/sftp-config.json
if [ -n "$SFTP_KEY" ]; then
echo "$SFTP_KEY" > /tmp/deploy_key
chmod 600 /tmp/deploy_key
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
else
printf ',"password":"%s"}' "$SFTP_PASS" >> /tmp/sftp-config.json
fi
DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json)
[ "${{ inputs.clear_remote }}" = "true" ] && DEPLOY_ARGS+=(--clear-remote)
PLATFORM=$(php /tmp/mokostandards-api/cli/platform_detect.php --path . 2>/dev/null || true)
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/mokostandards-api/deploy/deploy-joomla.php" ]; then
php /tmp/mokostandards-api/deploy/deploy-joomla.php "${DEPLOY_ARGS[@]}"
else
php /tmp/mokostandards-api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
fi
rm -f /tmp/deploy_key /tmp/sftp-config.json
- name: Summary
if: always()
run: |
if [ "${{ steps.check.outputs.skip }}" = "true" ]; then
echo "### Deploy Skipped -- FTP not configured" >> $GITHUB_STEP_SUMMARY
else
echo "### Manual Dev Deploy Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Host | \`${{ steps.check.outputs.host }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Remote | \`${{ steps.check.outputs.remote }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Clear | ${{ inputs.clear_remote }} |" >> $GITHUB_STEP_SUMMARY
fi
+5 -5
View File
@@ -5,7 +5,7 @@
# FILE INFORMATION # FILE INFORMATION
# DEFGROUP: Gitea.Workflow # DEFGROUP: Gitea.Workflow
# INGROUP: mokocli.Automation # INGROUP: mokocli.Automation
# VERSION: 01.07.23 # VERSION: 01.00.00
# BRIEF: Auto-create feature branch when an issue is opened # BRIEF: Auto-create feature branch when an issue is opened
name: "Universal: Issue Branch" name: "Universal: Issue Branch"
@@ -19,7 +19,7 @@ permissions:
issues: write issues: write
env: env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
jobs: jobs:
create-branch: create-branch:
@@ -28,8 +28,8 @@ jobs:
steps: steps:
- name: Create branch and comment - name: Create branch and comment
run: | run: |
TOKEN="${{ secrets.GA_TOKEN }}" TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}" API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
ISSUE_NUM="${{ github.event.issue.number }}" ISSUE_NUM="${{ github.event.issue.number }}"
ISSUE_TITLE="${{ github.event.issue.title }}" ISSUE_TITLE="${{ github.event.issue.title }}"
@@ -58,7 +58,7 @@ jobs:
echo "Created branch: ${BRANCH}" echo "Created branch: ${BRANCH}"
# Comment on issue with branch link # Comment on issue with branch link
REPO_URL="${GITEA_URL}/${{ github.repository }}" REPO_URL="${MOKOGITEA_URL}/${{ github.repository }}"
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`" BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
curl -sf -X POST \ curl -sf -X POST \
+10 -23
View File
@@ -496,39 +496,26 @@ jobs:
steps: steps:
- name: Trigger RC pre-release - name: Trigger RC pre-release
env: env:
GA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
REPO: ${{ github.repository }} REPO: ${{ github.repository }}
BRANCH: ${{ github.head_ref }} BRANCH: ${{ github.head_ref }}
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
run: | run: |
curl -s -X POST "${GITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${GITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}" curl -s -X POST "${MOKOGITEA_URL}/api/v1/repos/${REPO}/actions/workflows/pre-release.yml/dispatches" -H "Authorization: token ${MOKOGITEA_TOKEN}" -H "Content-Type: application/json" -d "{\"ref\":\"${BRANCH}\",\"inputs\":{\"stability\":\"release-candidate\"}}"
echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY echo "### Pre-Release" >> $GITHUB_STEP_SUMMARY
echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY echo "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY
# ── Issue Reporter ────────────────────────────────────────────────────── # ── Issue Reporter ──────────────────────────────────────────────────────
report-issues: report-issues:
name: Report Issues name: Report Issues
runs-on: ubuntu-latest
needs: [branch-policy, validate] needs: [branch-policy, validate]
if: >- if: >-
always() && always() &&
needs.validate.result == 'failure' needs.validate.result == 'failure'
uses: ./.mokogitea/workflows/ci-issue-reporter.yml
steps: with:
- name: Checkout gate: "PR Validation"
uses: actions/checkout@v4 workflow: "PR Check"
with: severity: error
sparse-checkout: automation/ci-issue-reporter.sh details: "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed."
sparse-checkout-cone-mode: false secrets: inherit
- name: "File issue for PR validation failure"
env:
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
run: |
chmod +x automation/ci-issue-reporter.sh
./automation/ci-issue-reporter.sh \
--gate "PR Validation" \
--workflow "PR Check" \
--severity error \
--details "PR validation failed (syntax, manifest, changelog, or source checks). See the CI run for the specific check that failed."
+4 -4
View File
@@ -20,7 +20,7 @@ permissions:
contents: read contents: read
env: env:
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }} GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }} GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
@@ -55,14 +55,14 @@ jobs:
- name: Validate metadata against Joomla manifest - name: Validate metadata against Joomla manifest
env: env:
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: | run: |
php ${MOKO_CLI}/joomla_metadata_validate.php \ php ${MOKO_CLI}/joomla_metadata_validate.php \
--path . \ --path . \
--token "${GITEA_TOKEN}" \ --token "${MOKOGITEA_TOKEN}" \
--org "${GITEA_ORG}" \ --org "${GITEA_ORG}" \
--repo "${GITEA_REPO}" \ --repo "${GITEA_REPO}" \
--api-base "${GITEA_URL}/api/v1" \ --api-base "${MOKOGITEA_URL}/api/v1" \
--ci --ci
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
+22 -1
View File
@@ -7,7 +7,7 @@
# INGROUP: mokocli.Release # INGROUP: mokocli.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli # REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
# PATH: /templates/workflows/universal/pre-release.yml.template # PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.01.00 # VERSION: 05.02.00
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches # BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
name: "Universal: Pre-Release" name: "Universal: Pre-Release"
@@ -59,6 +59,11 @@ jobs:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.MOKOGITEA_TOKEN }} token: ${{ secrets.MOKOGITEA_TOKEN }}
ref: ${{ github.ref_name }} ref: ${{ github.ref_name }}
submodules: recursive
- name: Update submodules to main
run: |
git submodule foreach --quiet 'git checkout main && git pull --quiet origin main' 2>/dev/null || true
- name: Setup mokocli tools - name: Setup mokocli tools
env: env:
@@ -88,8 +93,20 @@ jobs:
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
php ${MOKO_CLI}/manifest_read.php --path . --github-output php ${MOKO_CLI}/manifest_read.php --path . --github-output
- name: Check platform eligibility (Joomla only)
id: eligibility
run: |
PLATFORM="${{ steps.platform.outputs.platform }}"
if [[ "$PLATFORM" == joomla* ]] || [[ "$PLATFORM" == "joomla" ]]; then
echo "proceed=true" >> "$GITHUB_OUTPUT"
else
echo "proceed=false" >> "$GITHUB_OUTPUT"
echo "::notice::Platform '$PLATFORM' — non-Joomla, skipping pre-release auto-bump"
fi
- name: Resolve metadata and bump version - name: Resolve metadata and bump version
id: meta id: meta
if: steps.eligibility.outputs.proceed == 'true'
run: | run: |
# Auto-detect stability from branch name on push, or use input on dispatch # Auto-detect stability from branch name on push, or use input on dispatch
if [ "${{ github.event_name }}" = "push" ]; then if [ "${{ github.event_name }}" = "push" ]; then
@@ -166,6 +183,7 @@ jobs:
- name: Create release - name: Create release
id: release id: release
if: steps.eligibility.outputs.proceed == 'true'
run: | run: |
TAG="${{ steps.meta.outputs.tag }}" TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}" VERSION="${{ steps.meta.outputs.version }}"
@@ -176,6 +194,7 @@ jobs:
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease --repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
- name: Update release notes from CHANGELOG.md - name: Update release notes from CHANGELOG.md
if: steps.eligibility.outputs.proceed == 'true'
run: | run: |
TAG="${{ steps.meta.outputs.tag }}" TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}" VERSION="${{ steps.meta.outputs.version }}"
@@ -212,6 +231,7 @@ jobs:
- name: Build package and upload - name: Build package and upload
id: package id: package
if: steps.eligibility.outputs.proceed == 'true'
run: | run: |
VERSION="${{ steps.meta.outputs.version }}" VERSION="${{ steps.meta.outputs.version }}"
TAG="${{ steps.meta.outputs.tag }}" TAG="${{ steps.meta.outputs.tag }}"
@@ -225,6 +245,7 @@ jobs:
# No need to build, commit, or sync updates.xml from workflows # No need to build, commit, or sync updates.xml from workflows
- name: "Delete lesser pre-release channels (cascade)" - name: "Delete lesser pre-release channels (cascade)"
if: steps.eligibility.outputs.proceed == 'true'
continue-on-error: true continue-on-error: true
run: | run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
+18 -13
View File
@@ -29,12 +29,20 @@ jobs:
steps: steps:
- name: Rename branch - name: Rename branch
env:
BRANCH: ${{ github.event.pull_request.head.ref }}
REPO: ${{ github.repository }}
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: | run: |
BRANCH="${{ github.event.pull_request.head.ref }}" set -euo pipefail
# BRANCH is attacker-controlled (PR head ref). Strict allowlist before ANY use.
if ! printf '%s' "$BRANCH" | grep -Eq '^rc/[A-Za-z0-9._/-]+$'; then
echo "::error::Refusing unsafe branch name: $BRANCH"; exit 1
fi
SUFFIX="${BRANCH#rc/}" SUFFIX="${BRANCH#rc/}"
DEV_BRANCH="dev/${SUFFIX}" DEV_BRANCH="dev/${SUFFIX}"
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches" API="${GITEA_URL}/api/v1/repos/${REPO}/branches"
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
# Create dev/ branch from rc/ branch # Create dev/ branch from rc/ branch
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \ STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
@@ -42,25 +50,22 @@ jobs:
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \ -d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \
"${API}" 2>/dev/null || true) "${API}" 2>/dev/null || true)
if [ "$STATUS" = "201" ]; then if [ "$STATUS" = "201" ]; then
echo "Created branch: ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY echo "Created branch: ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
else else
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})" echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"; exit 1
exit 1
fi fi
# Delete rc/ branch # Read BRANCH from the environment inside PHP (getenv, no string interpolation -> no PHP injection)
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');") ENCODED=$(php -r 'echo rawurlencode(getenv("BRANCH"));')
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \ STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
-H "Authorization: token ${TOKEN}" \ -H "Authorization: token ${TOKEN}" \
"${API}/${ENCODED}" 2>/dev/null || true) "${API}/${ENCODED}" 2>/dev/null || true)
if [ "$STATUS" = "204" ]; then if [ "$STATUS" = "204" ]; then
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY echo "Deleted branch: ${BRANCH}" >> "$GITHUB_STEP_SUMMARY"
else else
echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})" echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})"
fi fi
echo "### RC Reverted" >> $GITHUB_STEP_SUMMARY echo "### RC Reverted" >> "$GITHUB_STEP_SUMMARY"
echo "${BRANCH} → ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY echo "${BRANCH} → ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
+25 -37
View File
@@ -77,7 +77,7 @@ jobs:
- name: Check actor permission (admin only) - name: Check actor permission (admin only)
id: perm id: perm
env: env:
TOKEN: ${{ secrets.MOKOGITEA_TOKEN || secrets.MOKOGITEA_TOKEN || github.token }} TOKEN: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
REPO: ${{ github.repository }} REPO: ${{ github.repository }}
ACTOR: ${{ github.actor }} ACTOR: ${{ github.actor }}
run: | run: |
@@ -671,42 +671,30 @@ jobs:
# ═══════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════
# Issue Reporter — file issues for failed gates # Issue Reporter — file issues for failed gates
# ═══════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════
report-issues: report-scripts:
name: "Report Issues" name: "Report: Scripts Governance"
runs-on: ubuntu-latest needs: [access_check, scripts_governance]
needs: [access_check, scripts_governance, repo_health]
if: >- if: >-
always() && always() &&
(needs.scripts_governance.result == 'failure' || needs.scripts_governance.result == 'failure'
needs.repo_health.result == 'failure') uses: ./.mokogitea/workflows/ci-issue-reporter.yml
with:
gate: "Scripts Governance"
workflow: "Repo Health"
severity: error
details: "Scripts directory policy violations detected. Review required and allowed directories."
secrets: inherit
steps: report-health:
- name: Checkout name: "Report: Repository Health"
uses: actions/checkout@v4 needs: [access_check, repo_health]
with: if: >-
sparse-checkout: automation/ci-issue-reporter.sh always() &&
sparse-checkout-cone-mode: false needs.repo_health.result == 'failure'
uses: ./.mokogitea/workflows/ci-issue-reporter.yml
- name: "File issues for failed gates" with:
env: gate: "Repository Health"
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} workflow: "Repo Health"
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} severity: error
run: | details: "Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary."
chmod +x automation/ci-issue-reporter.sh secrets: inherit
REPORTER="./automation/ci-issue-reporter.sh"
WF="Repo Health"
report_gate() {
local gate="$1" result="$2" details="$3"
if [ "$result" = "failure" ]; then
"$REPORTER" --gate "$gate" --details "$details" --workflow "$WF" --severity error
fi
}
report_gate "Scripts Governance" \
"${{ needs.scripts_governance.result }}" \
"Scripts directory policy violations detected. Review required and allowed directories."
report_gate "Repository Health" \
"${{ needs.repo_health.result }}" \
"Repository health checks failed — missing required artifacts, disallowed files, or content warnings. Check the CI run summary."
+130
View File
@@ -0,0 +1,130 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow.Template
# INGROUP: MokoStandards.CI
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
# PATH: /.mokogitea/workflows/version-set.yml
# VERSION: 01.00.00
# BRIEF: Set or reset the extension version across all version-bearing files
name: "Joomla: Set Version"
on:
workflow_dispatch:
inputs:
version:
description: "Version number (e.g. 01.00.00)"
required: true
type: string
branch:
description: "Branch to update (default: current)"
required: false
type: string
permissions:
contents: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
set-version:
name: Set Version to ${{ inputs.version }}
runs-on: ubuntu-latest
steps:
- name: Validate version format
run: |
VERSION="${{ inputs.version }}"
if ! echo "$VERSION" | grep -qP '^\d{2}\.\d{2}\.\d{2}$'; then
echo "::error::Invalid version format '${VERSION}' — expected XX.YY.ZZ (e.g. 01.00.00)"
exit 1
fi
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
ref: ${{ inputs.branch || github.ref }}
fetch-depth: 1
- name: Update manifest version
run: |
MANIFEST=""
for XML_FILE in $(find . -maxdepth 3 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
MANIFEST="$XML_FILE"
break
fi
done
if [ -z "$MANIFEST" ]; then
echo "::warning::No Joomla extension manifest found — skipping manifest update"
else
OLD_VER=$(grep -oP '<version>\K[^<]+' "$MANIFEST" | head -1)
sed -i "s|<version>${OLD_VER}</version>|<version>${VERSION}</version>|" "$MANIFEST"
echo "Manifest: ${OLD_VER} → ${VERSION} (${MANIFEST})"
fi
- name: Update README.md version
run: |
if [ -f "README.md" ]; then
if grep -qP '^\s*VERSION:\s*\d' README.md; then
sed -i -E "s/(VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" README.md
echo "README.md version updated to ${VERSION}"
else
echo "::warning::No VERSION line found in README.md — skipping"
fi
fi
- name: Update CHANGELOG.md
run: |
if [ -f "CHANGELOG.md" ]; then
DATE=$(date +%Y-%m-%d)
# Check if this version already has an entry
if grep -q "^\#\# \[${VERSION}\]" CHANGELOG.md; then
echo "CHANGELOG.md already has entry for ${VERSION} — skipping"
else
# Insert new version entry after [Unreleased] or at the top after header
if grep -q '^\#\# \[Unreleased\]' CHANGELOG.md; then
sed -i "/^\#\# \[Unreleased\]/a\\\\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
else
sed -i "/^\# Changelog/a\\\\n## [Unreleased]\n\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
fi
echo "CHANGELOG.md: added entry for ${VERSION}"
fi
else
echo "::warning::No CHANGELOG.md found — skipping"
fi
- name: Update FILE INFORMATION blocks
run: |
# Update VERSION in file header blocks (# VERSION: XX.YY.ZZ)
find . -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" -o -name "*.php" -o -name "*.md" \) \
-not -path "./.git/*" -not -path "./vendor/*" -print0 2>/dev/null | \
while IFS= read -r -d '' FILE; do
if head -20 "$FILE" | grep -qP '^\s*#?\s*VERSION:\s*\d{2}\.\d{2}\.\d{2}'; then
sed -i -E "s/(#?\s*VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" "$FILE"
echo "Updated FILE INFORMATION VERSION in ${FILE}"
fi
done
- name: Commit and push
run: |
git config user.name "Moko Consulting [bot]"
git config user.email "hello@mokoconsulting.tech"
git add -A
if git diff --cached --quiet; then
echo "No version changes detected — nothing to commit"
else
git commit -m "chore: set version to ${VERSION} [skip bump]
Authored-by: Moko Consulting"
git push
echo "### Version Set" >> $GITHUB_STEP_SUMMARY
echo "Version updated to \`${VERSION}\` on branch \`${GITHUB_REF_NAME}\`" >> $GITHUB_STEP_SUMMARY
fi
+12 -4
View File
@@ -13,6 +13,7 @@
name: "Universal: Workflow Sync Trigger" name: "Universal: Workflow Sync Trigger"
on: on:
workflow_dispatch:
pull_request: pull_request:
types: [closed] types: [closed]
branches: branches:
@@ -26,8 +27,9 @@ jobs:
name: Sync workflows to live repos name: Sync workflows to live repos
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: >- if: >-
github.event.pull_request.merged == true && github.event_name == 'workflow_dispatch' ||
!contains(github.event.pull_request.title, '[skip sync]') (github.event.pull_request.merged == true &&
!contains(github.event.pull_request.title, '[skip sync]'))
steps: steps:
- name: Determine platform from repo name - name: Determine platform from repo name
@@ -49,8 +51,14 @@ jobs:
env: env:
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
run: | run: |
GITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}" MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
git clone --depth 1 "${GITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli git clone --depth 1 "${MOKOGITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
- name: Install PHP
run: |
if ! command -v php &> /dev/null; then
apt-get update -qq && apt-get install -y -qq php-cli php-json php-curl > /dev/null 2>&1
fi
- name: Install dependencies - name: Install dependencies
run: | run: |
+33 -22
View File
@@ -1,35 +1,46 @@
<!-- <!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: CC-BY-4.0 This file is part of a Moko Consulting project.
SPDX-LICENSE-IDENTIFIER: GPL-3.0-or-later
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License (./LICENSE).
# FILE INFORMATION
DEFGROUP: Template-Joomla
INGROUP: Template-Joomla.Documentation
REPO: https://github.com/mokoconsulting-tech/Template-Joomla/
VERSION: 01.01.00
PATH: ./CODE_OF_CONDUCT.md
BRIEF: Community expectations and enforcement guidelines
NOTE: Adapted with attribution from the Contributor Covenant v2.1
--> -->
# Contributor Covenant Code of Conduct # Contributor Covenant Code of Conduct
## Our Pledge ## Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone.
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, colour, religion, or sexual
identity and orientation.
## Our Standards ## Our Standards
- Be empathetic and kind
- Be respectful of differing opinions
- Accept constructive feedback
- Own mistakes and learn from them
Examples of behaviour that contributes to a positive environment: Unacceptable behavior includes sexualized language/imagery, trolling, harassment, doxing, and other inappropriate conduct.
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Accepting constructive criticism gracefully
- Focusing on what is best for the community
## Enforcement ## Enforcement
Report incidents to **hello@mokoconsulting.tech** or through GitHub Discussions if you prefer a community-visible approach. Private complaints will be reviewed promptly and fairly.
Instances of abusive, harassing, or otherwise unacceptable behaviour may be ## Enforcement Guidelines
reported by contacting the project team at 1. **Correction** — Private warning
[info@mokoconsulting.tech](mailto:info@mokoconsulting.tech). 2. **Warning** — Formal warning and limited interaction
3. **Temporary Ban** — Time-boxed exclusion
4. **Permanent Ban** — Removal from the community
## Attribution ## Attribution
Adapted from the Contributor Covenant v2.1.
This Code of Conduct is adapted from the
[Contributor Covenant](https://www.contributor-covenant.org), version 2.1.
+129 -56
View File
@@ -1,88 +1,161 @@
<!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later
-->
# Contributing to Moko Consulting Projects # Contributing to Moko Consulting Projects
Thank you for your interest in contributing! This guide explains our workflow, Thank you for your interest in contributing. All Moko Consulting repositories follow this universal workflow and version policy.
conventions, and how to get your changes merged.
## Branching Workflow ## Branching Workflow
We use a **stability-gated** branching model:
``` ```
feature/* ──── PR ───→ dev feature/* ──PR──> dev ──draft PR──> (renamed to rc) ──merge──> main
│ RC cut → rc → main
fix/* ───────── PR ────────────┘
``` ```
1. **Create a branch** from `dev`: ### Step by step
- `feature/<short-name>` for new functionality
- `fix/<short-name>` for bug fixes
- `chore/<short-name>` for maintenance
2. **Open a PR** into `dev`.
3. **CI must pass** before merge.
4. **Release cuts**: `dev → rc → main` are handled by maintainers.
> **Never commit directly to `main` or `dev`.** 1. **Create a feature branch** from `dev`:
```bash
git checkout dev && git pull
git checkout -b feature/my-change
```
2. **Work and commit** on your feature branch. Push to origin.
3. **Open a PR**: `feature/my-change` → `dev`. After review and checks, merge it.
4. **When ready for release**, open a **draft PR**: `dev` → `main`.
- This automatically renames the source branch to `rc` (release candidate)
- An RC pre-release is built and uploaded
5. **Alpha and beta branches** are created by manually renaming the branch before the RC stage:
- Rename `dev` to `alpha` for early testing → alpha pre-release is built
- Rename `alpha` to `beta` for feature-complete testing → beta pre-release is built
- When the draft PR is created, the branch is renamed to `rc`
6. **Once PR checks pass** on the `rc` branch, mark the PR as ready and merge to `main`.
7. **Merging to main** triggers the stable release pipeline:
- Minor version bump (e.g., `02.09.xx` → `02.10.00`)
- Stability suffix stripped (clean version)
- Gitea release created with ZIP/tar.gz packages
- `updates.xml` updated (Joomla extensions)
- `dev` branch recreated from `main`
### Branch summary
| Branch | Purpose | Created by |
|--------|---------|-----------|
| `feature/*` | New features and fixes | Developer |
| `dev` | Integration branch | Auto-recreated after release |
| `alpha` | Alpha pre-release testing | Manual rename from `dev` |
| `beta` | Beta pre-release testing | Manual rename from `alpha` |
| `rc` | Release candidate | Auto-renamed on draft PR to main |
| `main` | Stable releases | Protected, merge only |
| `version/XX.YY.ZZ` | Archived release snapshots | Auto-created by CI |
### Protected branches
| Branch | Direct push | Merge via |
|--------|------------|-----------|
| `main` | Blocked (CI bot whitelisted) | PR merge only |
| `dev` | Blocked (CI bot whitelisted) | PR merge from feature/* |
| `rc` | Blocked (CI bot whitelisted) | Auto-created on draft PR |
| `alpha` | Blocked (CI bot whitelisted) | Manual rename |
| `beta` | Blocked (CI bot whitelisted) | Manual rename |
| `feature/*` | Open | N/A (source branch) |
## Version Policy ## Version Policy
All repositories use the **XX.YY.ZZ** versioning scheme (two-digit segments): ### Format
- `XX` -- major (breaking changes) All versions use `XX.YY.ZZ` — three two-digit segments, zero-padded:
- `YY` -- minor (new features, backward-compatible)
- `ZZ` -- patch (bug fixes, security patches)
**Stability suffixes** may be appended during pre-release: - **XX** — Major version (breaking changes)
- **YY** — Minor version (new features, bumped on release to main)
- **ZZ** — Patch version (auto-incremented on every push to dev/feature branches)
| Suffix | Meaning | Example | Rollover: patch `99` → `00` increments minor; minor `99` → `00` increments major.
|---|---|---|
| `-alpha.N` | Early testing | `06.01.00-alpha.1` |
| `-beta.N` | Feature complete | `06.01.00-beta.2` |
| `-rc.N` | Release candidate | `06.01.00-rc.1` |
| *(none)* | Stable release | `06.01.00` |
## Auto-Bump ### Stability suffixes
Version bumps are **automated** via the `auto-bump` workflow: Each branch appends a suffix to indicate stability:
- Merges into `dev` trigger a minor/patch bump. | Branch | Suffix | Example |
- The workflow updates all version references (manifests, changelog, etc.). |--------|--------|---------|
- **Do not manually edit version numbers** -- let the workflow handle it. | `main` | (none) | `02.09.00` |
| `dev` | `-dev` | `02.09.01-dev` |
| `feature/*` | `-dev` | `02.09.01-dev` |
| `alpha` | `-alpha` | `02.09.01-alpha` |
| `beta` | `-beta` | `02.09.01-beta` |
| `rc` | `-rc` | `02.09.01-rc` |
### Auto version bump
On every push to `dev`, `feature/*`, or `patch/*`:
1. Patch version incremented
2. Stability suffix `-dev` applied
3. All version-bearing files updated (manifests, CHANGELOG, PHP headers, etc.)
4. Commit created with `[skip ci]` to avoid loops
### Release version flow
Version bumps happen at specific release events:
| Event | Bump | Example |
|-------|------|---------|
| Feature merged to dev | Patch bump after dev release | `02.09.01-dev` → release → `02.09.02-dev` |
| Dev promoted to RC | Minor bump | `02.09.02-dev` → `02.10.00-rc` |
| RC merged to main | Minor bump | `02.10.00-rc` → `02.11.00` (stable) |
| Dev recreated from main | Patch bump | `02.11.00` → `02.11.01-dev` |
### Release stream copies
When a higher-stability release is published, copies are created for all lesser streams with the same base version:
- **RC `02.10.00-rc`** also creates: `02.10.00-dev`, `02.10.00-alpha`, `02.10.00-beta`
- **Stable `02.11.00`** also creates: `02.11.00-dev`, `02.11.00-alpha`, `02.11.00-beta`, `02.11.00-rc`
This ensures Joomla sites on ANY stability channel see the update (Joomla only shows versions higher than what's installed).
### Version files
The version tools update all files containing version stamps:
- `.mokogitea/manifest.xml` (canonical source)
- Joomla XML manifests (`<version>` tag)
- `README.md`, `CHANGELOG.md` (`VERSION:` pattern)
- `package.json`, `pyproject.toml`
- Any text file with a `VERSION: XX.YY.ZZ` label
Files synced from other repos (with a `# REPO:` header) are not touched.
## Code Standards
- **PHP**: PSR-12, tabs for indentation
- **Copyright**: all files must include the Moko Consulting copyright header
- **License**: SPDX identifier `GPL-3.0-or-later` (or as specified per repo)
- **Attribution**: use `Authored-by: Moko Consulting` in commits, not individual names
## Commit Messages ## Commit Messages
We follow [Conventional Commits](https://www.conventionalcommits.org/): Use conventional commit format:
``` ```
<type>(<scope>): <short description> type(scope): short description
<optional body> Optional body with context.
<optional footer> Authored-by: Moko Consulting
``` ```
**Types**: `feat`, `fix`, `docs`, `style`, `refactor`, `perf`, `test`, `chore`, `ci`, `build`, `revert` Types: `feat`, `fix`, `chore`, `docs`, `style`, `refactor`, `test`, `ci`
## Pull Request Checklist Special flags in commit messages:
- `[skip ci]` — skip all CI workflows
- `[skip bump]` — skip auto version bump only
Before submitting a PR, ensure: ## Reporting Issues
- [ ] Branch is based on latest `dev` Use the repository's issue tracker with the appropriate template.
- [ ] Commit messages follow conventional commits
- [ ] CHANGELOG.md updated under `[Unreleased]`
- [ ] No `TODO.md`, `.claude/`, `.mcp.json`, or minified files included
- [ ] Code follows [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)
- [ ] All CI checks pass
## Code of Conduct ---
All contributors are expected to follow our [Code of Conduct](CODE_OF_CONDUCT.md). *Moko Consulting <hello@mokoconsulting.tech>*
## Questions?
Open a [Question issue](../../issues/new?template=question.md) or contact
us at [hello@mokoconsulting.tech](mailto:hello@mokoconsulting.tech).
+97 -27
View File
@@ -1,49 +1,119 @@
<!-- <!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech> Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
This file is part of a Moko Consulting project.
SPDX-License-Identifier: GPL-3.0-or-later SPDX-License-Identifier: GPL-3.0-or-later
This program is free software; you can redistribute it and/or modify it under the terms of
the GNU General Public License as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License (./LICENSE).
FILE INFORMATION
DEFGROUP: mokoconsulting-tech.Template-Joomla
INGROUP: MokoStandards.Governance
REPO: https://github.com/mokoconsulting-tech/Template-Joomla
VERSION: 01.01.00
PATH: /GOVERNANCE.md
BRIEF: Project governance rules, roles, and decision process for Template-Joomla
--> -->
# Governance [![MokoStandards](https://img.shields.io/badge/MokoStandards-04.00.04-blue)](https://github.com/mokoconsulting-tech/MokoStandards)
## Project Leadership # Project Governance
This repository is maintained by **Moko Consulting** under a **sole operator** model. ## Overview
- **Lead Maintainer**: Jonathan Miller (@jmiller) This document defines the governance model for the `Template-Joomla` repository within the
- **Organisation**: [Moko Consulting](https://mokoconsulting.tech) `mokoconsulting-tech` organization. It is automatically maintained by
[MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) v04.00.04.
## Decision Making Full governance policy is defined in the MokoStandards source repository:
[docs/policy/GOVERNANCE.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/GOVERNANCE.md)
- All architectural decisions are made by the lead maintainer. ---
- Community feedback is welcome via [RFC issues](../../issues/new?template=rfc.md).
- Breaking changes are documented via [ADR issues](../../issues/new?template=adr.md).
## Contribution Policy ## Roles and Responsibilities
- **All changes** must go through a pull request (PR). ### Maintainer
- **CI checks** are mandatory before merge.
- **Direct push** to `main` and `dev` is restricted to automated workflows.
## Code of Conduct **GitHub**: @mokoconsulting-tech
All participants must adhere to our [Code of Conduct](CODE_OF_CONDUCT.md). **Authority**: Final decision-making authority on all matters for this repository.
## Licensing **Responsibilities**:
- Review and merge pull requests
- Maintain code quality and standards compliance
- Manage releases and versioning
- Respond to issues and security reports
All contributions are licensed under the same license as the project ### Contributors
(GPL-3.0-or-later unless otherwise stated in the repository root).
## Security **Authority**: Submit changes via pull requests.
Security vulnerabilities should be reported privately. **Requirements**:
See [SECURITY.md](SECURITY.md) for details. - Read and accept `CODE_OF_CONDUCT.md`
- Follow `CONTRIBUTING.md` guidelines
## Dispute Resolution ---
Disputes are resolved by the lead maintainer. For escalation, ## Decision-Making
contact [info@mokoconsulting.tech](mailto:info@mokoconsulting.tech).
## Changes to Governance All changes must be submitted as pull requests. The maintainer (@mokoconsulting-tech)
reviews and approves all changes before they are merged.
This document may be updated at any time by the lead maintainer. ### Sole Operator Policy
Significant changes will be announced via an RFC issue.
This organization operates under a **sole operator** model. The maintainer (@mokoconsulting-tech)
is the sole employee and owner and may self-approve pull requests when no second reviewer is
available. The following requirements remain mandatory regardless:
1. **Pull Requests Required** — all changes to protected branches go through a PR.
2. **Automated Checks** — all CI checks must pass before merging.
3. **Audit Trail** — issues, pull requests, and commit history are preserved.
4. **Documentation** — changes are documented in `CHANGELOG.md`.
See the full policy:
[Sole Operator Policy](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/GOVERNANCE.md#sole-operator-policy)
---
## Change Management
| Change Type | Approval | Process |
|-------------|----------|---------|
| Routine (docs, bug fixes) | Maintainer | PR → CI pass → merge |
| Significant (new features) | Maintainer | PR with description → CI pass → merge |
| Major (breaking, architecture) | Maintainer | Issue discussion → PR → CI pass → merge |
| Emergency (security) | Maintainer | Labelled `EMERGENCY` → immediate merge → post-mortem |
---
## Reporting Issues
- **Bugs / Features**: Open a [GitHub Issue](https://github.com/mokoconsulting-tech/Template-Joomla/issues)
- **Security vulnerabilities**: See [SECURITY.md](./SECURITY.md)
- **Code of Conduct**: See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md)
- **Contact**: dev@mokoconsulting.tech
---
## Metadata
| Field | Value |
| ------------- | ----------------------------------------------- |
| Document Type | Policy |
| Domain | Governance |
| Applies To | mokoconsulting-tech/Template-Joomla |
| Jurisdiction | Tennessee, USA |
| Maintainer | @mokoconsulting-tech |
| Standards | MokoStandards v04.00.04 |
| Repo | https://github.com/mokoconsulting-tech/Template-Joomla |
| Path | /GOVERNANCE.md |
| Status | Active — auto-maintained by MokoStandards |
+205 -54
View File
@@ -1,90 +1,241 @@
<!-- <!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech> Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later
This file is part of a Moko Consulting project.
SPDX-License-Identifier: GPL-3.0-or-later
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
# FILE INFORMATION
DEFGROUP: Template-Joomla
INGROUP: Template-Joomla.Documentation
REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
PATH: /SECURITY.md
VERSION: 01.01.00
BRIEF: Security vulnerability reporting and handling policy
--> -->
# Security Policy # Security Policy
## Purpose and Scope
This document defines the security vulnerability reporting, response, and disclosure policy for this Joomla Plugin template repository. It establishes the authoritative process for responsible disclosure, assessment, remediation, and communication of security issues.
## Supported Versions ## Supported Versions
| Version | Supported | Security updates are provided for the following versions:
|---|---|
| Latest stable | ✅ Full support | | Version | Supported |
| Previous major | ⚠️ Critical fixes only | | ------- | ------------------ |
| Older | ❌ No support | | 01.x.x | :white_check_mark: |
| < 01.0 | :x: |
Only the current major version receives security updates. Users should upgrade to the latest supported version to receive security patches.
## Reporting a Vulnerability ## Reporting a Vulnerability
**Do not report security vulnerabilities via public issues.** ### Where to Report
Instead, please report them privately: **DO NOT** create public GitHub issues for security vulnerabilities.
1. **Email**: [security@mokoconsulting.tech](mailto:security@mokoconsulting.tech) Report security vulnerabilities privately to:
2. **Subject**: `[SECURITY] <Repository Name> - <Brief Description>`
**Email**: `security@mokoconsulting.tech`
**Subject Line**: `[SECURITY] Template-Joomla - Brief Description`
### What to Include ### What to Include
- Description of the vulnerability A complete vulnerability report should include:
- Steps to reproduce
- Affected versions 1. **Description**: Clear explanation of the vulnerability
- Potential impact 2. **Impact**: Potential security impact and severity assessment
- Suggested fix (if any) 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
### Response Timeline
* **Initial Response**: Within 3 business days
* **Assessment Complete**: Within 7 business days
* **Fix Timeline**: Depends on severity (see below)
* **Disclosure**: Coordinated with reporter
## Severity Classification ## Severity Classification
| Severity | Description | Response Time | Vulnerabilities are classified using the following severity levels:
|---|---|---|
| **Critical** | Remote code execution, SQL injection, auth bypass | 24 hours |
| **High** | XSS, CSRF, privilege escalation | 48 hours |
| **Medium** | Information disclosure, path traversal | 72 hours |
| **Low** | Best practice violation, hardening suggestion | Next release |
## Remediation Timeline ### Critical
* Remote code execution
* Authentication bypass
* Data breach or exposure of sensitive information
* **Fix Timeline**: 7 days
1. **Acknowledgement**: Within 24 hours of report ### High
2. **Assessment**: Within 72 hours * Privilege escalation
3. **Fix development**: Based on severity * SQL injection or command injection
4. **Release**: Patch release with security advisory * Cross-site scripting (XSS) with significant impact
5. **Disclosure**: Coordinated disclosure after fix is available * **Fix Timeline**: 14 days
### Medium
* Information disclosure (limited scope)
* Denial of service
* Security misconfigurations with moderate impact
* **Fix Timeline**: 30 days
### Low
* Security best practice violations
* Minor information leaks
* Issues requiring user interaction or complex preconditions
* **Fix Timeline**: 60 days or next release
## Remediation Process
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 Advisories
Security advisories are published via:
* GitHub Security Advisories
* Release notes and CHANGELOG.md
* Email notification to project users (if mailing list is established)
Advisories include:
* CVE identifier (if applicable)
* Severity rating
* Affected versions
* Fixed versions
* Mitigation steps
* Attribution (with reporter consent)
## Security Best Practices ## Security Best Practices
### For Contributors For projects using this template:
- Never commit secrets, credentials, or API keys ### Required Controls
- Use parameterised queries (no raw SQL concatenation)
- Validate and sanitise all user input
- Follow Joomla API for access control checks
- Use Joomla's `HTMLHelper` for output escaping
- Include SPDX license headers in all source files
### For Users * 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 MokoStandards
- Keep Joomla and all extensions updated ### Joomla Plugin Security
- Use strong, unique passwords
- Enable two-factor authentication
- Review file permissions regularly
- Monitor Joomla error logs
## Security Updates * Follow Joomla security best practices
* Validate and sanitize all user input
* Use Joomla's database API to prevent SQL injection
* Properly escape output to prevent XSS
* Implement proper access control checks
* Use Joomla's session and authentication APIs
* Keep Joomla and dependencies up to date
Security patches are delivered through the standard update channel. ### CI/CD Security
Critical fixes may receive an emergency out-of-band release.
## Responsible Disclosure * 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
We follow coordinated disclosure practices: #### Automated Security Scanning
- We will work with reporters to understand and reproduce the issue All repositories SHOULD implement:
- We will develop and test a fix
- We will credit reporters (with permission) in security advisories
- We ask that reporters allow reasonable time for a fix before public disclosure
## Contact **CodeQL Analysis**:
* Enabled for PHP and other supported languages
* Runs on: push to main, pull requests, weekly schedule
* Query sets: `security-extended` and `security-and-quality`
* Configuration: `.github/workflows/codeql-analysis.yml`
- **Security team**: [security@mokoconsulting.tech](mailto:security@mokoconsulting.tech) **Dependabot Security Updates**:
- **General**: [hello@mokoconsulting.tech](mailto:hello@mokoconsulting.tech) * Weekly scans for vulnerable dependencies
* Automated pull requests for security patches
* Configuration: `.github/dependabot.yml`
**Secret Scanning**:
* Enabled by default with push protection
* Prevents accidental credential commits
### Dependency Management
* Keep dependencies up to date
* Monitor security advisories for dependencies
* Remove unused dependencies
* Audit new dependencies before adoption
* Document security-critical dependencies
## Compliance and Governance
This security policy is aligned with MokoStandards. Deviations require documented justification.
Security policies are reviewed and updated at least annually or following significant security incidents.
## Attribution and Recognition
We acknowledge and appreciate responsible disclosure. With your permission, we will:
* Credit you in security advisories
* List you in CHANGELOG.md for the fix release
* Recognize your contribution publicly (if desired)
## Contact and Escalation
* **Security Team**: security@mokoconsulting.tech
* **Primary Contact**: hello@mokoconsulting.tech
* **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
--- ---
Thank you for helping keep Moko Consulting projects secure. ## Metadata
| Field | Value |
| ------------ | ------------------------------------------------------------------------------------------------------------ |
| Document | Security Policy |
| Path | /SECURITY.md |
| Repository | [https://github.com/mokoconsulting-tech/Template-Joomla](https://github.com/mokoconsulting-tech/Template-Joomla) |
| Owner | Moko Consulting |
| Scope | Security vulnerability handling |
| Status | Active |
| Effective | 2026-01-16 |
## Revision History
| Date | Change Description | Author |
| ---------- | ------------------------------------------------- | --------------- |
| 2026-01-16 | Initial creation for template repository | Moko Consulting |
+24 -23
View File
@@ -1,26 +1,27 @@
{ {
"name": "mokoconsulting/mokojoomgallery", "name": "mokoconsulting/mokojoomgallery",
"description": "Joomla extension by Moko Consulting", "description": "Photo gallery management for Joomla — galleries, images, thumbnails, lightbox, and frontend display",
"type": "joomla-package", "type": "joomla-package",
"license": "GPL-3.0-or-later", "version": "01.00.00",
"homepage": "https://mokoconsulting.tech", "license": "GPL-3.0-or-later",
"authors": [ "authors": [
{ {
"name": "Moko Consulting", "name": "Moko Consulting",
"email": "hello@mokoconsulting.tech" "email": "hello@mokoconsulting.tech",
"homepage": "https://mokoconsulting.tech"
}
],
"require": {
"php": ">=8.1"
},
"require-dev": {
"squizlabs/php_codesniffer": "^3.7",
"phpstan/phpstan": "^1.10",
"joomla/coding-standards": "3.0.x-dev"
},
"minimum-stability": "dev",
"prefer-stable": true,
"config": {
"sort-packages": true
} }
],
"require": {
"php": ">=8.1"
},
"require-dev": {
"squizlabs/php_codesniffer": "^3.0",
"phpstan/phpstan": "^2.0",
"joomla/coding-standards": "dev-main"
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
} }
+28 -16
View File
@@ -1,20 +1,32 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech> # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION #
# DEFGROUP: Config.StaticAnalysis # PHPStan configuration for Joomla extension repositories.
# INGROUP: Development # Extends the base MokoStandards config and adds Joomla framework class stubs
# BRIEF: PHPStan configuration for Joomla extension static analysis # so PHPStan can resolve Factory, CMSApplication, User, Table, etc.
# without requiring a full Joomla installation.
parameters: parameters:
level: 5 level: 5
paths:
- src paths:
scanDirectories: - src
# Joomla framework stubs (if available)
- vendor/joomla excludePaths:
ignoreErrors: - vendor
# Joomla service-container architecture: Factory/Container returns mixed - node_modules
- '#Call to an undefined method Joomla\\CMS\\Application\\.*::get#i'
- '#Call to method .* on an unknown class Joomla\\Cms\\Extension\\.*#' # Joomla framework stubs — resolved via the enterprise package from vendor/
# Joomla MVC pattern: Table::getInstance returns Table|bool stubFiles:
- '#Method Joomla\\CMS\\Table\\Table::getInstance#' - vendor/mokoconsulting-tech/enterprise/templates/stubs/joomla.php
# Suppress errors that are structural in Joomla's service-container architecture
ignoreErrors:
# Joomla's service-based dependency injection returns mixed from getApplication()
- '#Cannot call method .+ on Joomla\\CMS\\Application\\CMSApplication\|null#'
# Factory::getX() patterns are safe at runtime even when nullable in stubs
- '#Call to static method [a-zA-Z]+\(\) on an interface#'
reportUnmatchedIgnoredErrors: false
checkMissingIterableValueType: false
checkGenericClassInNonGenericObjectType: false
@@ -7,7 +7,7 @@
<license>GPL-3.0-or-later</license> <license>GPL-3.0-or-later</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl> <authorUrl>https://mokoconsulting.tech</authorUrl>
<version>01.07.23</version> <version>01.07.00</version>
<php_minimum>8.3</php_minimum> <php_minimum>8.3</php_minimum>
<description>MokoSuite NPO component</description> <description>MokoSuite NPO component</description>
<namespace path="src">Moko\Component\MokoSuiteNpo</namespace> <namespace path="src">Moko\Component\MokoSuiteNpo</namespace>
@@ -1,124 +0,0 @@
<?php
namespace Moko\Plugin\System\MokoSuiteNpo\Helper;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\Database\DatabaseInterface;
/**
* IRS Form 990 data preparation — revenue/expense classification, program services, compensation reporting.
*/
class Form990Helper
{
/**
* Get Part VIII revenue breakdown (contributions, program service, investment, other).
*/
public static function getRevenueBreakdown(int $fiscalYear = 0): object
{
$fiscalYear = $fiscalYear ?: (int) date('Y');
$db = Factory::getContainer()->get(DatabaseInterface::class);
// Contributions (Line 1)
$db->setQuery($db->getQuery(true)
->select('COALESCE(SUM(amount), 0) AS total')
->from('#__mokosuitenpo_donations')
->where('YEAR(donation_date) = ' . $fiscalYear));
$contributions = (float) $db->loadResult();
// Membership dues (Line 1b)
$db->setQuery($db->getQuery(true)
->select('COALESCE(SUM(amount), 0) AS total')
->from('#__mokosuitenpo_membership_payments')
->where('YEAR(payment_date) = ' . $fiscalYear));
$memberDues = (float) $db->loadResult();
// Grant revenue (Line 1f)
$db->setQuery($db->getQuery(true)
->select('COALESCE(SUM(amount_received), 0) AS total')
->from('#__mokosuitenpo_grant_payments')
->where('YEAR(received_date) = ' . $fiscalYear));
$grants = (float) $db->loadResult();
// Program service revenue (Line 2)
$db->setQuery($db->getQuery(true)
->select('COALESCE(SUM(amount), 0) AS total')
->from('#__mokosuitenpo_program_revenue')
->where('YEAR(revenue_date) = ' . $fiscalYear));
$programRevenue = (float) $db->loadResult();
// In-kind donations (Line 1g)
$db->setQuery($db->getQuery(true)
->select('COALESCE(SUM(fair_market_value), 0) AS total')
->from('#__mokosuitenpo_inkind_donations')
->where('YEAR(received_at) = ' . $fiscalYear));
$inKind = (float) $db->loadResult();
$totalRevenue = $contributions + $memberDues + $grants + $programRevenue + $inKind;
return (object) [
'fiscal_year' => $fiscalYear,
'contributions' => round($contributions, 2),
'membership_dues' => round($memberDues, 2),
'grants' => round($grants, 2),
'program_revenue' => round($programRevenue, 2),
'in_kind' => round($inKind, 2),
'total_revenue' => round($totalRevenue, 2),
];
}
/**
* Get Part IX expense breakdown (program services, management, fundraising).
*/
public static function getExpenseBreakdown(int $fiscalYear = 0): object
{
$fiscalYear = $fiscalYear ?: (int) date('Y');
$db = Factory::getContainer()->get(DatabaseInterface::class);
$db->setQuery($db->getQuery(true)
->select('classification')
->select('SUM(amount) AS total')
->from('#__mokosuitenpo_expenses')
->where('YEAR(expense_date) = ' . $fiscalYear)
->group('classification'));
$rows = $db->loadObjectList('classification') ?: [];
$program = round((float) ($rows['program_services']->total ?? 0), 2);
$management = round((float) ($rows['management_general']->total ?? 0), 2);
$fundraising = round((float) ($rows['fundraising']->total ?? 0), 2);
$total = $program + $management + $fundraising;
return (object) [
'fiscal_year' => $fiscalYear,
'program_services' => $program,
'management_general' => $management,
'fundraising' => $fundraising,
'total_expenses' => $total,
'program_pct' => $total > 0 ? round($program / $total * 100, 1) : 0,
];
}
/**
* Get Part VII compensation data for officers/key employees.
*/
public static function getCompensationSchedule(int $fiscalYear = 0): array
{
$fiscalYear = $fiscalYear ?: (int) date('Y');
$db = Factory::getContainer()->get(DatabaseInterface::class);
$db->setQuery($db->getQuery(true)
->select('cd.name, bm.role AS title, bm.weekly_hours')
->select('COALESCE(c.base_compensation, 0) AS compensation')
->select('COALESCE(c.benefits, 0) AS benefits')
->select('COALESCE(c.other_compensation, 0) AS other')
->from($db->quoteName('#__mokosuitenpo_board_members', 'bm'))
->join('LEFT', $db->quoteName('#__contact_details', 'cd') . ' ON cd.id = bm.contact_id')
->join('LEFT', $db->quoteName('#__mokosuitenpo_compensation', 'c')
. ' ON c.contact_id = bm.contact_id AND c.fiscal_year = ' . $fiscalYear)
->where($db->quoteName('bm.status') . ' = ' . $db->quote('active'))
->order('compensation DESC'));
return $db->loadObjectList() ?: [];
}
}
@@ -89,10 +89,10 @@ class RecurringDonationHelper
DonorHelper::recordDonation( DonorHelper::recordDonation(
(int) $pledge->donor_id, (int) $pledge->donor_id,
(float) $pledge->amount, (float) $pledge->amount,
(int) $pledge->fund_id,
'card', 'card',
null, (int) $pledge->fund_id,
['notes' => 'Recurring pledge #' . $pledge->id] 0,
'Recurring pledge #' . $pledge->id
); );
// Advance next charge date // Advance next charge date
+2 -2
View File
@@ -2,7 +2,7 @@
<extension type="package" method="upgrade"> <extension type="package" method="upgrade">
<name>Package - MokoSuite NPO</name> <name>Package - MokoSuite NPO</name>
<packagename>mokosuitenpo</packagename> <packagename>mokosuitenpo</packagename>
<version>01.07.23</version> <version>01.07.00</version>
<creationDate>2026-06-11</creationDate> <creationDate>2026-06-11</creationDate>
<author>Moko Consulting</author> <author>Moko Consulting</author>
<authorEmail>hello@mokoconsulting.tech</authorEmail> <authorEmail>hello@mokoconsulting.tech</authorEmail>
@@ -23,6 +23,6 @@
</files> </files>
<updateservers> <updateservers>
<server type="extension" name="MokoSuiteNPO Updates">https://git.mokoconsulting.tech/api/packages/MokoConsulting/generic/MokoSuiteNPO/latest/updates.xml</server> <server type="extension" priority="1" name="Package - MokoSuite NPO">https://git.mokoconsulting.tech/MokoConsulting/MokoSuiteNPO/updates.xml</server>
</updateservers> </updateservers>
</extension> </extension>
+3 -69
View File
@@ -1,74 +1,8 @@
<?php <?php
/**
* @package MokoSuiteNPO
* @subpackage pkg_mokosuitenpo
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
defined('_JEXEC') or die; defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Installer\InstallerAdapter; use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Log\Log; class Pkg_mokosuitenpoInstallerScript
/**
* Package installation script for MokoSuiteNPO.
*/
class Pkg_MokoSuiteNPOInstallerScript
{ {
public function postflight(string $type, InstallerAdapter $adapter): void public function preflight(string $type, InstallerAdapter $adapter): bool { return true; }
{ public function postflight(string $type, InstallerAdapter $adapter): void {}
$this->warnMissingLicenseKey();
}
private function warnMissingLicenseKey(): void
{
try
{
$db = Factory::getDbo();
$app = Factory::getApplication();
$query = $db->getQuery(true)
->select([$db->quoteName('update_site_id'), $db->quoteName('extra_query')])
->from($db->quoteName('#__update_sites'))
->where('(' . $db->quoteName('name') . ' LIKE ' . $db->quote('%MokoSuiteNPO%')
. ' OR ' . $db->quoteName('location') . ' LIKE ' . $db->quote('%MokoSuiteNPO%') . ')')
->setLimit(1);
$db->setQuery($query);
$site = $db->loadObject();
if ($site)
{
$extraQuery = (string) ($site->extra_query ?? '');
if (!empty($extraQuery) && strpos($extraQuery, 'dlid=') !== false)
{
parse_str($extraQuery, $parsed);
if (!empty($parsed['dlid']))
{
return;
}
}
$editUrl = 'index.php?option=com_installer&task=updatesite.edit&update_site_id=' . (int) $site->update_site_id;
}
else
{
$editUrl = 'index.php?option=com_installer&view=updatesites';
}
$app->enqueueMessage(
'<strong>Moko Consulting License Key Required</strong> — '
. 'No download key is configured. Updates will not be available until a valid license key is entered. '
. '<a href="' . $editUrl . '" class="btn btn-sm btn-warning ms-2">Enter License Key</a>',
'warning'
);
}
catch (\Throwable $e)
{
// Silent — avoid breaking install if update_sites query fails
}
}
} }
+9
View File
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<updates><update>
<name>Package - MokoSuite NPO</name>
<element>pkg_mokosuitenpo</element>
<type>package</type>
<version>01.01.00</version>
<targetplatform name="joomla" version="6.[0-9]" />
<php_minimum>8.3</php_minimum>
</update></updates>