diff --git a/.mokogitea/ISSUE_TEMPLATE/adr.md b/.mokogitea/ISSUE_TEMPLATE/adr.md deleted file mode 100644 index eb40760..0000000 --- a/.mokogitea/ISSUE_TEMPLATE/adr.md +++ /dev/null @@ -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 diff --git a/.mokogitea/ISSUE_TEMPLATE/bug_report.md b/.mokogitea/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 38a16a7..0000000 --- a/.mokogitea/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -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 diff --git a/.mokogitea/ISSUE_TEMPLATE/config.yml b/.mokogitea/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index d4d49ec..0000000 --- a/.mokogitea/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -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 diff --git a/.mokogitea/ISSUE_TEMPLATE/documentation.md b/.mokogitea/ISSUE_TEMPLATE/documentation.md deleted file mode 100644 index ed4dabc..0000000 --- a/.mokogitea/ISSUE_TEMPLATE/documentation.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -name: Documentation Issue -about: Report an issue with documentation -title: '[DOCS] ' -labels: 'documentation' -assignees: '' - ---- - - -## Documentation Issue - -**Location**: - - -## Issue Type - -- [ ] Typo or grammar error -- [ ] Outdated information -- [ ] Missing documentation -- [ ] Unclear explanation -- [ ] Broken links -- [ ] Missing examples -- [ ] Other (specify below) - -## Description - - -## Current Content - -``` -Current text here -``` - -## Suggested Improvement - -``` -Suggested text here -``` - -## Additional Context - - -## 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) diff --git a/.mokogitea/ISSUE_TEMPLATE/feature_request.md b/.mokogitea/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 7b76dc9..0000000 --- a/.mokogitea/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -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 diff --git a/.mokogitea/ISSUE_TEMPLATE/joomla_issue.md b/.mokogitea/ISSUE_TEMPLATE/joomla_issue.md deleted file mode 100644 index d808f79..0000000 --- a/.mokogitea/ISSUE_TEMPLATE/joomla_issue.md +++ /dev/null @@ -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 diff --git a/.mokogitea/ISSUE_TEMPLATE/question.md b/.mokogitea/ISSUE_TEMPLATE/question.md deleted file mode 100644 index 3175013..0000000 --- a/.mokogitea/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -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) diff --git a/.mokogitea/ISSUE_TEMPLATE/rfc.md b/.mokogitea/ISSUE_TEMPLATE/rfc.md deleted file mode 100644 index 6f09af7..0000000 --- a/.mokogitea/ISSUE_TEMPLATE/rfc.md +++ /dev/null @@ -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. diff --git a/.mokogitea/ISSUE_TEMPLATE/security.md b/.mokogitea/ISSUE_TEMPLATE/security.md deleted file mode 100644 index f57b284..0000000 --- a/.mokogitea/ISSUE_TEMPLATE/security.md +++ /dev/null @@ -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**: - - -## Description - - -## Affected Components - - -## Suggested Mitigation - - -## 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 - - -## Checklist -- [ ] This is NOT a critical vulnerability requiring private disclosure -- [ ] I have reviewed the SECURITY.md policy -- [ ] I have provided sufficient detail for evaluation diff --git a/.mokogitea/ISSUE_TEMPLATE/version.md b/.mokogitea/ISSUE_TEMPLATE/version.md deleted file mode 100644 index 6328421..0000000 --- a/.mokogitea/ISSUE_TEMPLATE/version.md +++ /dev/null @@ -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**: -**Requested version**: -**Change type**: - -## Reason - - - -## Checklist - -- [ ] README.md `VERSION:` field updated -- [ ] CHANGELOG.md entry added -- [ ] Module descriptor version updated (Dolibarr: `$this->version`, Joomla: ``) -- [ ] All file headers will be auto-propagated by `sync-version-on-merge` workflow diff --git a/.mokogitea/branch-protection.yml b/.mokogitea/branch-protection.yml deleted file mode 100644 index 2dff8b9..0000000 --- a/.mokogitea/branch-protection.yml +++ /dev/null @@ -1,251 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# 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 diff --git a/.mokogitea/workflows/auto-bump.yml b/.mokogitea/workflows/auto-bump.yml deleted file mode 100644 index a47661a..0000000 --- a/.mokogitea/workflows/auto-bump.yml +++ /dev/null @@ -1,66 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Release -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /.mokogitea/workflows/auto-bump.yml -# VERSION: 09.02.00 -# BRIEF: Auto patch-bump version on every push to dev (skips merge commits) - -name: "Universal: Auto Version Bump" - -on: - push: - branches: - - dev - - rc - - 'feature/**' - - 'patch/**' - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} - -permissions: - contents: write - -jobs: - bump: - name: Version Bump - runs-on: release - if: >- - !contains(github.event.head_commit.message, '[skip ci]') && - !contains(github.event.head_commit.message, '[skip bump]') && - !startsWith(github.event.head_commit.message, 'Merge pull request') - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - token: ${{ secrets.MOKOGITEA_TOKEN }} - fetch-depth: 1 - - - name: Setup MokoCLI tools - run: | - if ! command -v composer &> /dev/null; then - sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1 - fi - if [ -d "/opt/mokocli/cli" ]; then - echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV" - else - git clone --depth 1 --branch main --quiet \ - "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \ - /tmp/mokocli - cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet - echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV" - fi - - - name: Bump version - run: | - php ${MOKO_CLI}/version_auto_bump.php \ - --path . --branch "${GITHUB_REF_NAME}" \ - --token "${{ secrets.MOKOGITEA_TOKEN }}" \ - --repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" diff --git a/.mokogitea/workflows/auto-release.yml b/.mokogitea/workflows/auto-release.yml deleted file mode 100644 index ce7bc6c..0000000 --- a/.mokogitea/workflows/auto-release.yml +++ /dev/null @@ -1,469 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Release -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /templates/workflows/universal/auto-release.yml.template -# VERSION: 05.01.00 -# BRIEF: Universal build & release ๏ฟฝ detects platform from MokoGitea repo metadata (API) -# -# +=======================================================================+ -# | UNIVERSAL BUILD & RELEASE PIPELINE | -# +=======================================================================+ -# | | -# | Reads MokoGitea repo metadata (joomla|dolibarr|generic) to branch logic. | -# | | -# | Platform-specific: | -# | joomla: XML manifest, type-prefixed packages | -# | dolibarr: mod*.class.php, update.txt, dev version reset | -# | generic: README-only, no update stream | -# | | -# +=======================================================================+ - -name: "Universal: Build & Release" - -on: - pull_request: - types: [opened, synchronize, closed] - branches: - - main - paths-ignore: - - '.mokogitea/workflows/**' - - '*.md' - - 'wiki/**' - - '.editorconfig' - - '.gitignore' - - '.gitattributes' - - '.gitmessage' - - 'LICENSE' - workflow_dispatch: - inputs: - action: - description: 'Action to perform' - required: false - type: choice - default: release - options: - - release - - promote-rc - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} - GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }} - GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }} - -permissions: - contents: write - -jobs: - # โ”€โ”€ PR Opened โ†’ Rename branch to RC and build RC release โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - promote-rc: - name: Promote to RC - runs-on: release - # Skip on template repos (Template-*) โ€” they scaffold other repos and do not release. - if: >- - !startsWith(github.event.repository.name, 'Template-') && - ( - (github.event.action == 'opened' && github.event.pull_request.merged != true) || - (github.event.action == 'synchronize' && github.event.pull_request.merged != true) || - (github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc') - ) - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - token: ${{ secrets.MOKOGITEA_TOKEN }} - fetch-depth: 1 - submodules: recursive - - - name: Setup MokoCLI tools - env: - MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting - run: | - if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then - echo Using pre-installed /opt/mokocli - echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV - else - echo Falling back to fresh clone - if ! command -v composer > /dev/null 2>&1; then - sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1 - fi - rm -rf /tmp/mokocli - CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git - git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli - cd /tmp/mokocli - composer install --no-dev --no-interaction --quiet - echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV - fi - - - name: Rename branch to rc - run: | - php ${MOKO_CLI}/branch_rename.php \ - --from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \ - --token "${{ secrets.MOKOGITEA_TOKEN }}" \ - --api-base "${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \ - --pr "${{ github.event.pull_request.number }}" - - - name: Checkout rc and configure git - run: | - git fetch origin rc - git checkout rc - git config --local user.email "mokogitea-actions[bot]@mokoconsulting.tech" - git config --local user.name "mokogitea-actions[bot]" - git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" - - - name: Publish RC release - run: | - php ${MOKO_CLI}/release_publish.php \ - --path . --stability rc --bump minor --branch rc \ - --token "${{ secrets.MOKOGITEA_TOKEN }}" - - - name: Update RC release notes from CHANGELOG.md - run: | - API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" - - # Extract [Unreleased] section from changelog - NOTES="" - if [ -f "CHANGELOG.md" ]; then - NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md) - fi - [ -z "$NOTES" ] && NOTES="Release candidate" - - # Find the RC release and update its body - RELEASE_ID=$(curl -sf -H "Authorization: token ${TOKEN}" \ - "${API_BASE}/releases/tags/release-candidate" \ - | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) - - if [ -n "$RELEASE_ID" ]; then - python3 -c " - import json, urllib.request - body = open('/dev/stdin').read() - payload = json.dumps({'body': body}).encode() - req = urllib.request.Request( - '${API_BASE}/releases/${RELEASE_ID}', - data=payload, method='PATCH', - headers={ - 'Authorization': 'token ${TOKEN}', - 'Content-Type': 'application/json' - }) - urllib.request.urlopen(req) - " <<< "$NOTES" - echo "RC release notes updated from CHANGELOG.md" - fi - - - name: Summary - if: always() - run: | - echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY - echo "Branch renamed to rc, minor bump, RC release built" >> $GITHUB_STEP_SUMMARY - - # โ”€โ”€ Merged PR โ†’ Build & Release (or promote RC to stable) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - release: - name: Build & Release Pipeline - runs-on: release - # Skip on template repos (Template-*) โ€” they scaffold other repos and do not release. - if: >- - !startsWith(github.event.repository.name, 'Template-') && - ( - github.event.pull_request.merged == true || - (github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc') - ) - - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - token: ${{ secrets.MOKOGITEA_TOKEN }} - fetch-depth: 0 - submodules: recursive - - - name: Configure git for bot pushes - run: | - git config --local user.email "mokogitea-actions[bot]@mokoconsulting.tech" - git config --local user.name "mokogitea-actions[bot]" - git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" - - - name: Check for merge conflict markers - run: | - CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true) - if [ -n "$CONFLICTS" ]; then - echo "::error::Merge conflict markers found โ€” aborting release" - echo "## Release Blocked: Conflict Markers" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - exit 1 - fi - echo "No conflict markers found" - - - name: Setup MokoCLI tools - env: - MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_MIRROR_TOKEN }}"}}' - run: | - if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then - echo Using pre-installed /opt/mokocli - echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV - else - echo Falling back to fresh clone - if ! command -v composer > /dev/null 2>&1; then - sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1 - fi - rm -rf /tmp/mokocli - CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git - git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli - cd /tmp/mokocli - composer install --no-dev --no-interaction --quiet - echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV - fi - - - name: "Detect platform" - id: platform - run: | - php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true - php ${MOKO_CLI}/manifest_read.php --path . --github-output 2>/dev/null || true - - - name: "Determine version bump level" - id: bump - run: | - # Fix/patch branches: version was already bumped by pre-release, just strip suffix - # Feature/dev branches: bump minor for the new stable release - HEAD_REF="${{ github.event.pull_request.head.ref || 'dev' }}" - case "$HEAD_REF" in - fix/*|patch/*|hotfix/*|bugfix/*) BUMP="none" ;; - *) BUMP="minor" ;; - esac - echo "level=${BUMP}" >> "$GITHUB_OUTPUT" - echo "Bump level: ${BUMP} (from branch: ${HEAD_REF})" - - - name: "Publish stable release" - run: | - BUMP_FLAG="" - if [ "${{ steps.bump.outputs.level }}" != "none" ]; then - BUMP_FLAG="--bump ${{ steps.bump.outputs.level }}" - fi - php ${MOKO_CLI}/release_publish.php \ - --path . --stability stable ${BUMP_FLAG} --branch main \ - --token "${{ secrets.MOKOGITEA_TOKEN }}" - - - name: "Read published version" - id: version - run: | - VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "") - VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//') - [ -z "$VERSION" ] && VERSION="00.00.00" && echo "skip=true" >> "$GITHUB_OUTPUT" - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - PLATFORM="${{ steps.platform.outputs.platform }}" - if [[ "$PLATFORM" == joomla* ]]; then - echo "tag=stable" >> "$GITHUB_OUTPUT" - echo "release_tag=stable" >> "$GITHUB_OUTPUT" - else - echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT" - echo "release_tag=v${VERSION}" >> "$GITHUB_OUTPUT" - fi - echo "branch=main" >> "$GITHUB_OUTPUT" - echo "Published version: ${VERSION}" - - - name: "Create semver tag for non-Joomla repos" - id: semver - if: | - steps.version.outputs.skip != 'true' && - !startsWith(steps.platform.outputs.platform, 'joomla') - run: | - VERSION="${{ steps.version.outputs.version }}" - API_BASE="${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 - run: | - API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" - - # Get the stable release info (version and ID) - RELEASE_JSON=$(curl -sf -H "Authorization: token ${TOKEN}" \ - "${API_BASE}/releases/tags/stable" 2>/dev/null || echo '{}') - RELEASE_ID=$(python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" <<< "$RELEASE_JSON" 2>/dev/null || true) - # Extract version from release name (e.g. "06.17.00" or "v06.17.00") - VERSION=$(python3 -c " - import json, sys, re - r = json.load(sys.stdin) - name = r.get('name', '') - m = re.search(r'(\d+\.\d+\.\d+)', name) - print(m.group(1) if m else '') - " <<< "$RELEASE_JSON" 2>/dev/null || true) - - # Extract [Unreleased] section from changelog - NOTES="" - if [ -f "CHANGELOG.md" ]; then - NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md) - fi - [ -z "$NOTES" ] && NOTES="Stable release" - - # Update release body via API - if [ -n "$RELEASE_ID" ]; then - python3 -c " - import json, urllib.request - body = open('/dev/stdin').read() - payload = json.dumps({'body': body}).encode() - req = urllib.request.Request( - '${API_BASE}/releases/${RELEASE_ID}', - data=payload, method='PATCH', - headers={ - 'Authorization': 'token ${TOKEN}', - 'Content-Type': 'application/json' - }) - urllib.request.urlopen(req) - " <<< "$NOTES" - echo "Release notes updated from CHANGELOG.md" - fi - - # Promote [Unreleased] โ†’ [version] in CHANGELOG.md and reset - if [ -n "$VERSION" ] && [ -f "CHANGELOG.md" ]; then - DATE=$(date +%Y-%m-%d) - python3 -c " - import sys - version, date = sys.argv[1], sys.argv[2] - content = open('CHANGELOG.md').read() - old = '## [Unreleased]' - new = f'## [Unreleased]\n\n## [{version}] --- {date}' - content = content.replace(old, new, 1) - open('CHANGELOG.md', 'w').write(content) - " "$VERSION" "$DATE" - git add CHANGELOG.md - git commit -m "chore: promote changelog [Unreleased] โ†’ [${VERSION}]" || true - git push origin main || true - echo "Changelog promoted: [Unreleased] โ†’ [${VERSION}]" - fi - - # -- STEP 9: Mirror to GitHub (stable only) -------------------------------- - - name: "Step 9: Mirror release to GitHub" - if: >- - steps.version.outputs.skip != 'true' && - secrets.GH_MIRROR_TOKEN != '' - continue-on-error: true - run: | - VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" - RELEASE_TAG="${{ steps.version.outputs.release_tag }}" - GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}" - API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - php ${MOKO_CLI}/release_mirror.php \ - --version "$VERSION" --tag "$RELEASE_TAG" \ - --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ - --gh-token "${{ secrets.GH_MIRROR_TOKEN }}" --gh-repo "$GH_REPO" \ - --branch main 2>&1 || true - echo "GitHub mirror updated" >> $GITHUB_STEP_SUMMARY - - # -- STEP 10: Sync main branch to GitHub mirror ---------------------------- - - name: "Step 10: Push main to GitHub mirror" - if: >- - steps.version.outputs.skip != 'true' && - secrets.GH_MIRROR_TOKEN != '' - continue-on-error: true - run: | - GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}" - GH_ORG=$(echo "$GH_REPO" | cut -d/ -f1) - GH_NAME=$(echo "$GH_REPO" | cut -d/ -f2) - git remote add github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" 2>/dev/null || \ - git remote set-url github "https://x-access-token:${{ secrets.GH_MIRROR_TOKEN }}@github.com/${GH_ORG}/${GH_NAME}.git" - git fetch origin main --depth=1 - git push github origin/main:refs/heads/main --force 2>/dev/null \ - && echo "main branch pushed to GitHub mirror" \ - || echo "WARNING: GitHub mirror push failed" - - - name: "Step 11: Delete rc branch (dev reset moved to cascade-dev.yml)" - if: steps.version.outputs.skip != 'true' - continue-on-error: true - run: | - API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" - - # Delete rc branch (ephemeral โ€” created by promote-rc) - curl -sf -X DELETE -H "Authorization: token ${TOKEN}" \ - "${API_BASE}/branches/rc" 2>/dev/null \ - && echo "Deleted rc branch" || echo "rc branch not found" - - # dev is reset from main by the dedicated "Cascade Main -> Dev" workflow - # (cascade-dev.yml), which runs after this release completes. - echo "rc cleaned; dev reset handled by cascade-dev.yml" >> $GITHUB_STEP_SUMMARY - - - name: "Step 12: Create version branch from main" - if: steps.version.outputs.skip != 'true' - continue-on-error: true - run: | - API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" - VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" - BRANCH_NAME="version/${VERSION}" - MAIN_SHA=$(git rev-parse HEAD) - - # Delete old version branch if it exists (same version re-release) - curl -sf -X DELETE -H "Authorization: token ${TOKEN}" "${API_BASE}/branches/${BRANCH_NAME}" 2>/dev/null && echo "Deleted old ${BRANCH_NAME}" - - # Create version/XX.YY.ZZ from main - curl -sf -X POST -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/branches" -d "{\"new_branch_name\":\"${BRANCH_NAME}\",\"old_branch_name\":\"main\"}" 2>/dev/null && echo "Created ${BRANCH_NAME} from main (${MAIN_SHA})" || echo "WARNING: ${BRANCH_NAME} creation failed" - - echo "Version branch created: ${BRANCH_NAME} (${MAIN_SHA})" >> $GITHUB_STEP_SUMMARY - - - - # -- Dolibarr post-release: Reset dev version ----------------------------- - - name: "Post-release: Reset dev version" - if: steps.version.outputs.skip != 'true' - continue-on-error: true - run: | - API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - php ${MOKO_CLI}/version_reset_dev.php \ - --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \ - --branch dev --path . 2>&1 || true - - # -- Summary -------------------------------------------------------------- - - name: Pipeline Summary - if: always() - run: | - VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}" - PLATFORM="${{ steps.platform.outputs.platform }}" - if [ "${{ steps.version.outputs.skip }}" = "true" ]; then - echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY - echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY - elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then - echo "## Already Released โ€” ${VERSION}" >> $GITHUB_STEP_SUMMARY - else - echo "" >> $GITHUB_STEP_SUMMARY - echo "## Build & Release Complete (${PLATFORM})" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY - echo "|------|--------|" >> $GITHUB_STEP_SUMMARY - echo "| Platform | \`${PLATFORM}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Release | [View](${MOKOGITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY - fi diff --git a/.mokogitea/workflows/branch-cleanup.yml b/.mokogitea/workflows/branch-cleanup.yml deleted file mode 100644 index aca4a9d..0000000 --- a/.mokogitea/workflows/branch-cleanup.yml +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Universal -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /.mokogitea/workflows/branch-cleanup.yml -# VERSION: 01.00.00 -# BRIEF: Delete feature branches after PR merge - -name: "Branch Cleanup" - -on: - pull_request: - types: [closed] - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - cleanup: - name: Delete merged branch - runs-on: ubuntu-latest - if: >- - github.event.pull_request.merged == true && - github.event.pull_request.head.ref != 'dev' && - github.event.pull_request.head.ref != 'main' - - steps: - - name: Delete source branch - run: | - BRANCH="${{ github.event.pull_request.head.ref }}" - API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches" - # URL-encode the branch name's slashes (no PHP dependency on the runner) - ENCODED=$(printf '%s' "${BRANCH}" | sed 's|/|%2F|g') - - STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \ - -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \ - "${API}/${ENCODED}" 2>/dev/null || true) - - if [ "$STATUS" = "204" ]; then - echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY - elif [ "$STATUS" = "404" ]; then - echo "Branch already deleted: ${BRANCH}" >> $GITHUB_STEP_SUMMARY - else - echo "::warning::Failed to delete branch ${BRANCH} (HTTP ${STATUS})" - fi diff --git a/.mokogitea/workflows/cascade-dev.yml b/.mokogitea/workflows/cascade-dev.yml deleted file mode 100644 index 1b9f8a6..0000000 --- a/.mokogitea/workflows/cascade-dev.yml +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# FILE INFORMATION -# DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Release -# BRIEF: Reset dev to main after each release (cascade). Moved out of auto-release Step 11. -# -# +========================================================================+ -# | CASCADE MAIN -> DEV | -# +========================================================================+ -# | dev mirrors main and is reset on every release. | -# | delete+recreate cannot run against a protected branch, so this | -# | force-pushes main -> dev. The automation identity is force-push | -# | allowlisted on dev via branch-protection.yml. | -# | Runs AFTER the release workflow completes, so dev picks up the | -# | fully version-bumped + changelog-promoted main (not the pre-bump | -# | state). Force-push (not merge) => no version-file conflicts. | -# +========================================================================+ - -name: "Cascade Main -> Dev" - -on: - workflow_run: - workflows: ["Universal: Build & Release"] - types: [completed] - workflow_dispatch: - -concurrency: - group: cascade-dev-${{ github.repository }} - cancel-in-progress: true - -permissions: - contents: write - -jobs: - cascade: - name: Reset dev to main - if: >- - !startsWith(github.event.repository.name, 'Template-') && - (github.event_name == 'workflow_dispatch' || - github.event.workflow_run.conclusion == 'success') - runs-on: ubuntu-latest - steps: - - name: Force dev to main - env: - TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - SERVER: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} - REPO: ${{ github.repository }} - run: | - set -euo pipefail - git init -q sync && cd sync - git config user.email "mokogitea-actions[bot]@mokoconsulting.tech" - git config user.name "mokogitea-actions[bot]" - git remote add origin "https://x-access-token:${TOKEN}@${SERVER#https://}/${REPO}.git" - git fetch -q --depth=1 origin main - - if git ls-remote --exit-code --heads origin dev >/dev/null 2>&1; then - # dev exists -> force it to match main (dev is a mirror of main) - git push --force origin FETCH_HEAD:refs/heads/dev - echo "dev reset to main" >> "$GITHUB_STEP_SUMMARY" - else - # dev missing (e.g. renamed to rc mid-cycle) -> recreate from main - git push origin FETCH_HEAD:refs/heads/dev - echo "dev created from main" >> "$GITHUB_STEP_SUMMARY" - fi diff --git a/.mokogitea/workflows/ci-generic.yml b/.mokogitea/workflows/ci-generic.yml deleted file mode 100644 index 330e6f9..0000000 --- a/.mokogitea/workflows/ci-generic.yml +++ /dev/null @@ -1,202 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.CI -# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic -# PATH: /.mokogitea/workflows/ci-generic.yml -# VERSION: 01.00.00 -# BRIEF: CI pipeline โ€” lint, validate, and test for generic projects (PHP + Node.js) - -name: "Generic: Project CI" - -on: - pull_request: - branches: - - main - - dev - - dev/** - - rc/** - workflow_dispatch: - -permissions: - contents: read - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - # โ”€โ”€ Lint & Validate โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - lint: - name: Lint & Validate - runs-on: ubuntu-latest - # Skip on template repos (Template-*) โ€” they hold placeholder scaffolding, not buildable source. - if: ${{ !startsWith(github.event.repository.name, 'Template-') }} - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Detect toolchain - id: detect - run: | - HAS_PHP=false - HAS_NODE=false - [ -f "composer.json" ] && HAS_PHP=true - [ -f "package.json" ] && HAS_NODE=true - echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT" - echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT" - echo "Toolchain: PHP=$HAS_PHP Node=$HAS_NODE" - - - name: Setup PHP - if: steps.detect.outputs.has_php == 'true' - run: | - if ! command -v php &> /dev/null; then - sudo apt-get update -qq - sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1 - fi - php -v - - - name: Setup Node.js - if: steps.detect.outputs.has_node == 'true' - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install PHP dependencies - if: steps.detect.outputs.has_php == 'true' - run: | - if [ -f "composer.json" ]; then - composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true - fi - - - name: Install Node.js dependencies - if: steps.detect.outputs.has_node == 'true' - run: | - if [ -f "package.json" ]; then - npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true - fi - - - name: PHP syntax check - if: steps.detect.outputs.has_php == 'true' - run: | - ERRORS=0 - while IFS= read -r -d '' file; do - if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then - echo "::error file=${file}::PHP syntax error" - ERRORS=$((ERRORS + 1)) - fi - done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -not -path "./node_modules/*" -print0) - - echo "## PHP Lint" >> $GITHUB_STEP_SUMMARY - if [ "$ERRORS" -eq 0 ]; then - echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY - else - echo "${ERRORS} file(s) with syntax errors." >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - - name: TypeScript/JavaScript lint - if: steps.detect.outputs.has_node == 'true' - run: | - if [ -f "node_modules/.bin/eslint" ]; then - npx eslint src/ --quiet 2>&1 || { echo "::error::ESLint errors found"; exit 1; } - echo "## ESLint" >> $GITHUB_STEP_SUMMARY - echo "All files passed ESLint." >> $GITHUB_STEP_SUMMARY - elif [ -f ".eslintrc.json" ] || [ -f ".eslintrc.js" ] || [ -f "eslint.config.js" ]; then - echo "::warning::ESLint config found but eslint not installed" - else - echo "No ESLint configured โ€” skipping" - fi - - - name: TypeScript compile check - if: steps.detect.outputs.has_node == 'true' - run: | - if [ -f "tsconfig.json" ] && [ -f "node_modules/.bin/tsc" ]; then - npx tsc --noEmit 2>&1 || { echo "::error::TypeScript compilation errors"; exit 1; } - echo "## TypeScript" >> $GITHUB_STEP_SUMMARY - echo "TypeScript compilation passed." >> $GITHUB_STEP_SUMMARY - fi - - - name: PHPStan static analysis - if: steps.detect.outputs.has_php == 'true' - run: | - if [ -f "phpstan.neon" ] && [ -f "vendor/bin/phpstan" ]; then - vendor/bin/phpstan analyse --no-progress 2>&1 || { echo "::warning::PHPStan found issues"; } - fi - - # โ”€โ”€ Tests โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - test: - name: Tests - runs-on: ubuntu-latest - needs: lint - # Run only when lint succeeded; always() forces evaluation so a skipped - # lint (e.g. template repos) skips this job cleanly instead of hanging. - if: ${{ always() && needs.lint.result == 'success' }} - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Detect toolchain - id: detect - run: | - HAS_PHP=false - HAS_NODE=false - [ -f "composer.json" ] && HAS_PHP=true - [ -f "package.json" ] && HAS_NODE=true - echo "has_php=$HAS_PHP" >> "$GITHUB_OUTPUT" - echo "has_node=$HAS_NODE" >> "$GITHUB_OUTPUT" - - - name: Setup PHP - if: steps.detect.outputs.has_php == 'true' - run: | - if ! command -v php &> /dev/null; then - sudo apt-get update -qq - sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1 - fi - - - name: Setup Node.js - if: steps.detect.outputs.has_node == 'true' - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install dependencies - run: | - [ -f "composer.json" ] && composer install --no-interaction --prefer-dist --quiet 2>/dev/null || true - [ -f "package.json" ] && { npm ci --quiet 2>/dev/null || npm install --quiet 2>/dev/null || true; } - - - name: Run PHP tests - if: steps.detect.outputs.has_php == 'true' - run: | - if [ -f "vendor/bin/phpunit" ]; then - vendor/bin/phpunit --testdox 2>&1 - echo "## PHPUnit" >> $GITHUB_STEP_SUMMARY - echo "Tests passed." >> $GITHUB_STEP_SUMMARY - elif [ -f "phpunit.xml" ] || [ -f "phpunit.xml.dist" ]; then - echo "::warning::PHPUnit config found but phpunit not installed" - else - echo "No PHPUnit configured โ€” skipping" - fi - - - name: Run Node.js tests - if: steps.detect.outputs.has_node == 'true' - run: | - if jq -e '.scripts.test' package.json > /dev/null 2>&1; then - npm test 2>&1 - echo "## Node.js Tests" >> $GITHUB_STEP_SUMMARY - echo "Tests passed." >> $GITHUB_STEP_SUMMARY - else - echo "No test script in package.json โ€” skipping" - fi - - - name: Build check - run: | - if [ -f "Makefile" ]; then - make build 2>&1 || echo "::warning::Build failed or not configured" - elif [ -f "package.json" ] && jq -e '.scripts.build' package.json > /dev/null 2>&1; then - npm run build 2>&1 || echo "::warning::Build failed" - fi diff --git a/.mokogitea/workflows/ci-issue-reporter.yml b/.mokogitea/workflows/ci-issue-reporter.yml deleted file mode 100644 index 335e92f..0000000 --- a/.mokogitea/workflows/ci-issue-reporter.yml +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: MokoGitea.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 MokoGitea 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 }}" diff --git a/.mokogitea/workflows/ci-joomla.yml b/.mokogitea/workflows/ci-joomla.yml deleted file mode 100644 index e61ed5b..0000000 --- a/.mokogitea/workflows/ci-joomla.yml +++ /dev/null @@ -1,2198 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: MokoGitea.Workflow.Template -# INGROUP: MokoCLI.CI -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# 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 - - '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 - if: ${{ !startsWith(github.event.repository.name, 'Template-') }} - - 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 || 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.MOKOGITEA_TOKEN || github.token }}"}}' - run: | - if [ -f "composer.json" ]; then - composer install \ - --no-interaction \ - --prefer-dist \ - --optimize-autoloader - else - echo "No composer.json found โ€” skipping dependency install" - fi - - - name: PHP syntax check - run: | - ERRORS=0 - for DIR in src/ htdocs/; do - if [ -d "$DIR" ]; then - FOUND=1 - while IFS= read -r -d '' FILE; do - OUTPUT=$(php -l "$FILE" 2>&1) - if echo "$OUTPUT" | grep -q "Parse error"; then - echo "::error file=${FILE}::${OUTPUT}" - ERRORS=$((ERRORS + 1)) - fi - done < <(find "$DIR" -name "*.php" -print0) - fi - done - echo "### PHP Syntax Check" >> $GITHUB_STEP_SUMMARY - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} syntax error(s) found.**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "All PHP files passed syntax check." >> $GITHUB_STEP_SUMMARY - fi - - - name: XML manifest validation - run: | - echo "### XML Manifest Validation" >> $GITHUB_STEP_SUMMARY - ERRORS=0 - - # Find the extension manifest (XML with /dev/null; then - MANIFEST="$XML_FILE" - break - fi - done - - if [ -z "$MANIFEST" ]; then - echo "No Joomla extension manifest found (XML file with \`> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "Manifest found: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY - - # Validate well-formed XML - php -r " - \$xml = @simplexml_load_file('$MANIFEST'); - if (\$xml === false) { - echo 'INVALID'; - exit(1); - } - echo 'VALID'; - " > /tmp/xml_result 2>&1 - XML_RESULT=$(cat /tmp/xml_result) - if [ "$XML_RESULT" != "VALID" ]; then - echo "Manifest is not well-formed XML." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "Manifest is well-formed XML." >> $GITHUB_STEP_SUMMARY - fi - - # Check required tags: name, version, author - 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 ']*\btype="\K[^"]+' "$MANIFEST" | head -1) - if [ "$EXT_TYPE" != "package" ]; then - if ! grep -q "/dev/null; then - echo "Missing required tag: \`\` (required for Joomla 5+ ${EXT_TYPE} extensions)" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "Found required tag: \`\`" >> $GITHUB_STEP_SUMMARY - fi - else - echo "Package extension โ€” \`\` 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: Update server & packaging checks - continue-on-error: true - run: | - echo "### Update Server & Packaging" >> $GITHUB_STEP_SUMMARY - WARNINGS=0 - - # Find the extension manifest - MANIFEST="" - for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do - if grep -q "/dev/null; then - MANIFEST="$XML_FILE" - break - fi - done - - if [ -z "$MANIFEST" ]; then - echo "No manifest found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - EXT_TYPE=$(grep -oP ']*\btype="\K[^"]+' "$MANIFEST" | head -1) - - # 1. Check exists and uses MokoGitea update server - if ! grep -q '' "$MANIFEST" 2>/dev/null; then - echo "::warning file=${MANIFEST}::Missing \`\` tag โ€” extension will not receive OTA updates" - echo "- **Missing** \`\` โ€” extension will not receive OTA updates" >> $GITHUB_STEP_SUMMARY - WARNINGS=$((WARNINGS + 1)) - else - SERVER_URL=$(grep -oP ']*>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1) - if [ -z "$SERVER_URL" ]; then - echo "::warning file=${MANIFEST}::\`\` is empty โ€” no server URL defined" - echo "- **Empty** \`\` โ€” no server URL defined" >> $GITHUB_STEP_SUMMARY - WARNINGS=$((WARNINGS + 1)) - elif ! echo "$SERVER_URL" | grep -q 'git\.mokoconsulting\.tech'; then - echo "::warning file=${MANIFEST}::Update server does not use MokoGitea engine: ${SERVER_URL}" - echo "- **Non-MokoGitea update server:** \`${SERVER_URL}\`" >> $GITHUB_STEP_SUMMARY - echo " Expected: \`https://git.mokoconsulting.tech/{org}/{repo}/updates.xml\`" >> $GITHUB_STEP_SUMMARY - WARNINGS=$((WARNINGS + 1)) - else - echo "- \`\`: MokoGitea engine โœ“" >> $GITHUB_STEP_SUMMARY - fi - fi - - # 2. Check tag exists - if ! grep -q '/dev/null; then - echo "::warning file=${MANIFEST}::Missing \`\` tag โ€” download ID authentication is not configured" - echo "- **Missing** \`\` โ€” download ID authentication not configured" >> $GITHUB_STEP_SUMMARY - WARNINGS=$((WARNINGS + 1)) - else - echo "- \`\`: present โœ“" >> $GITHUB_STEP_SUMMARY - fi - - # 3. For packages: check tag - if [ "$EXT_TYPE" = "package" ]; then - if ! grep -q '' "$MANIFEST" 2>/dev/null; then - echo "::warning file=${MANIFEST}::Package is missing \`\` โ€” child extensions will not be removed on uninstall" - echo "- **Missing** \`\` โ€” child extensions will remain when package is uninstalled" >> $GITHUB_STEP_SUMMARY - WARNINGS=$((WARNINGS + 1)) - else - echo "- \`\`: present โœ“" >> $GITHUB_STEP_SUMMARY - fi - fi - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "$WARNINGS" -gt 0 ]; then - echo "**${WARNINGS} packaging warning(s).** These won't block CI but should be addressed." >> $GITHUB_STEP_SUMMARY - else - echo "**Update server & packaging checks passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: Check language files referenced in manifest - run: | - echo "### Language File Check" >> $GITHUB_STEP_SUMMARY - ERRORS=0 - - MANIFEST="" - for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do - if grep -q "/dev/null; then - MANIFEST="$XML_FILE" - break - fi - done - - if [ -n "$MANIFEST" ]; then - # Extract language file references from manifest - LANG_FILES=$(grep -oP 'language\s+tag="[^"]*"[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null || true) - if [ -z "$LANG_FILES" ]; then - echo "No language file references found in manifest โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - while IFS= read -r LANG_FILE; do - LANG_FILE=$(echo "$LANG_FILE" | xargs) - if [ -z "$LANG_FILE" ]; then - continue - fi - # Check in common locations - FOUND=0 - for BASE in "." "src" "htdocs"; do - if [ -f "${BASE}/${LANG_FILE}" ]; then - FOUND=1 - break - fi - done - if [ "$FOUND" -eq 0 ]; then - echo "Missing language file: \`${LANG_FILE}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "Language file present: \`${LANG_FILE}\`" >> $GITHUB_STEP_SUMMARY - fi - done <<< "$LANG_FILES" - fi - else - echo "No manifest found โ€” skipping language check." >> $GITHUB_STEP_SUMMARY - fi - - if [ "${ERRORS}" -gt 0 ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "**${ERRORS} missing language file(s).**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Language file check passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: Check index.html files in directories - run: | - echo "### Index.html Check" >> $GITHUB_STEP_SUMMARY - MISSING=0 - CHECKED=0 - - for DIR in src/ htdocs/; do - if [ -d "$DIR" ]; then - while IFS= read -r -d '' SUBDIR; do - CHECKED=$((CHECKED + 1)) - if [ ! -f "${SUBDIR}/index.html" ]; then - echo "Missing index.html in: \`${SUBDIR}\`" >> $GITHUB_STEP_SUMMARY - MISSING=$((MISSING + 1)) - fi - done < <(find "$DIR" -type d -print0) - fi - done - - if [ "${CHECKED}" -eq 0 ]; then - echo "No src/ or htdocs/ directories found โ€” skipping." >> $GITHUB_STEP_SUMMARY - elif [ "${MISSING}" -gt 0 ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "**${MISSING} director(ies) missing index.html out of ${CHECKED} checked.**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "All ${CHECKED} directories contain index.html." >> $GITHUB_STEP_SUMMARY - fi - - - 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") - # Uses maxdepth 10 to reach into nested package repos (packages/*/source/packages/com_*/...) - COMP_MANIFESTS=$(find . -maxdepth 10 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*" -not -path "./.claude/*" -exec grep -l ']*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 - # 1. Empty / whitespace-only file check - SIZE=$(wc -c < "$FILE" | tr -d ' ') - if [ "$SIZE" -eq 0 ]; then - echo "::error file=${FILE}::Empty SQL file" - echo "- **Empty** SQL file: \`${FILE}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - continue - fi - CONTENT_SIZE=$(grep -cP '\S' "$FILE" 2>/dev/null || echo "0") - if [ "$CONTENT_SIZE" -eq 0 ]; then - echo "::error file=${FILE}::Whitespace-only SQL file" - echo "- **Whitespace-only** SQL file: \`${FILE}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - continue - fi - - echo "- \`${FILE}\`: ${SIZE} bytes" >> $GITHUB_STEP_SUMMARY - - # 2. Joomla table prefix โ€” must use #__ not hardcoded prefixes - # Match table names in CREATE/ALTER/DROP/INSERT/UPDATE/DELETE that don't use #__ - HARDCODED=$(grep -nPi '(CREATE\s+TABLE|ALTER\s+TABLE|DROP\s+TABLE|INSERT\s+INTO|UPDATE|DELETE\s+FROM)\s+(`?)(?!#__)[a-z][a-z0-9_]+\2' "$FILE" 2>/dev/null || true) - if [ -n "$HARDCODED" ]; then - COUNT=$(echo "$HARDCODED" | wc -l) - echo "::error file=${FILE}::${COUNT} table reference(s) missing #__ prefix" - echo " - **${COUNT} hardcoded table name(s)** โ€” must use \`#__\` prefix" >> $GITHUB_STEP_SUMMARY - while IFS= read -r LINE; do - LINE_NUM=$(echo "$LINE" | cut -d: -f1) - LINE_TEXT=$(echo "$LINE" | cut -d: -f2- | sed 's/^ *//') - echo " - Line ${LINE_NUM}: \`${LINE_TEXT}\`" >> $GITHUB_STEP_SUMMARY - done <<< "$HARDCODED" - ERRORS=$((ERRORS + COUNT)) - fi - - # 3. CREATE TABLE must use IF NOT EXISTS - BAD_CREATE=$(grep -nPi 'CREATE\s+TABLE\s+(?!IF\s+NOT\s+EXISTS)' "$FILE" 2>/dev/null || true) - if [ -n "$BAD_CREATE" ]; then - COUNT=$(echo "$BAD_CREATE" | wc -l) - echo "::error file=${FILE}::${COUNT} CREATE TABLE without IF NOT EXISTS" - echo " - **${COUNT} CREATE TABLE** missing \`IF NOT EXISTS\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + COUNT)) - fi - - # 4. DROP TABLE must use IF EXISTS - BAD_DROP=$(grep -nPi 'DROP\s+TABLE\s+(?!IF\s+EXISTS)' "$FILE" 2>/dev/null || true) - if [ -n "$BAD_DROP" ]; then - COUNT=$(echo "$BAD_DROP" | wc -l) - echo "::error file=${FILE}::${COUNT} DROP TABLE without IF EXISTS" - echo " - **${COUNT} DROP TABLE** missing \`IF EXISTS\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + COUNT)) - fi - - # 5. Balanced parentheses in CREATE TABLE statements - OPEN=$(grep -oP '\(' "$FILE" 2>/dev/null | wc -l) - CLOSE=$(grep -oP '\)' "$FILE" 2>/dev/null | wc -l) - if [ "$OPEN" -ne "$CLOSE" ]; then - echo "::error file=${FILE}::Unbalanced parentheses (${OPEN} open vs ${CLOSE} close)" - echo " - **Unbalanced parentheses**: ${OPEN} \`(\` vs ${CLOSE} \`)\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - - # 6. ENGINE=InnoDB must be specified on CREATE TABLE - if grep -qPi 'CREATE\s+TABLE' "$FILE" 2>/dev/null; then - if ! grep -qPi 'ENGINE\s*=\s*InnoDB' "$FILE" 2>/dev/null; then - echo "::error file=${FILE}::CREATE TABLE missing ENGINE=InnoDB" - echo " - **Missing \`ENGINE=InnoDB\`** โ€” required for Joomla 4+/5" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - fi - - # 7. DEFAULT CHARSET=utf8mb4 must be specified on CREATE TABLE - if grep -qPi 'CREATE\s+TABLE' "$FILE" 2>/dev/null; then - if ! grep -qPi 'CHARSET\s*=\s*utf8mb4|CHARACTER\s+SET\s+utf8mb4' "$FILE" 2>/dev/null; then - echo "::error file=${FILE}::CREATE TABLE missing DEFAULT CHARSET=utf8mb4" - echo " - **Missing \`DEFAULT CHARSET=utf8mb4\`** โ€” required for Joomla 4+/5" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - fi - done - - # 8. Update SQL files must follow version numbering pattern - UPDATE_DIRS=$(find . -path "*/sql/updates/mysql" -type d -not -path "./.git/*" 2>/dev/null) - if [ -n "$UPDATE_DIRS" ]; then - while IFS= read -r UPDATE_DIR; do - for UFILE in "$UPDATE_DIR"/*.sql; do - [ ! -f "$UFILE" ] && continue - BASENAME=$(basename "$UFILE" .sql) - if ! echo "$BASENAME" | grep -qP '^\d+\.\d+\.\d+'; then - echo "::error file=${UFILE}::Update file does not follow version naming (expected X.Y.Z.sql)" - echo "- Update file \`${UFILE}\` does not follow version naming (expected X.Y.Z.sql)" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - done - done <<< "$UPDATE_DIRS" - fi - - # 9. Install/uninstall pairing โ€” if install exists, uninstall must too - INSTALL_FILES=$(find . -name "install.mysql.sql" -path "*/sql/*" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null) - if [ -n "$INSTALL_FILES" ]; then - while IFS= read -r INSTALL_FILE; do - SQL_DIR=$(dirname "$INSTALL_FILE") - if [ ! -f "${SQL_DIR}/uninstall.mysql.sql" ]; then - echo "::error file=${INSTALL_FILE}::install.mysql.sql found but uninstall.mysql.sql is missing" - echo "- **Missing \`uninstall.mysql.sql\`** in \`${SQL_DIR}/\` โ€” every install must have a matching uninstall" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - done <<< "$INSTALL_FILES" - fi - - # 10 & 11. Per-component schema/version consistency. Each component - # versions independently, so resolve every sql/updates/mysql dir - # against ITS OWN manifest (not one package-level version): - # (10) the component's manifest version must have a matching update file - # (11) no update file may exceed the component's manifest version - # (this is the guard that catches migration files drifting onto - # a different numbering lane and getting ahead of the release) - UPDATE_DIRS=$(find . -path "*/sql/updates/mysql" -type d -not -path "./.git/*" 2>/dev/null) - if [ -n "$UPDATE_DIRS" ]; then - while IFS= read -r UPDATE_DIR; do - # Component root is three levels up from /sql/updates/mysql - COMP_ROOT=$(dirname "$(dirname "$(dirname "$UPDATE_DIR")")") - # Resolve this component's OWN manifest (XML with at its root) - COMP_MANIFEST="" - for XML_FILE in "$COMP_ROOT"/*.xml; do - [ -f "$XML_FILE" ] || continue - if grep -q "/dev/null; then - COMP_MANIFEST="$XML_FILE" - break - fi - done - if [ -z "$COMP_MANIFEST" ]; then - echo "- No component manifest found for \`${UPDATE_DIR}\` โ€” skipping version match" >> $GITHUB_STEP_SUMMARY - continue - fi - COMP_VERSION=$(grep -oP '\K[^<]+' "$COMP_MANIFEST" 2>/dev/null | head -1) - [ -z "$COMP_VERSION" ] && continue - # Normalise: strip any pre-release suffix (-dev, -rc.N, ...) for comparison - CV_BASE=$(echo "$COMP_VERSION" | sed -E 's/-.*$//') - # (10) a matching update file must exist for the component version - if [ ! -f "${UPDATE_DIR}/${CV_BASE}.sql" ]; then - echo "::error file=${COMP_MANIFEST}::No update SQL file for version ${CV_BASE}" - echo "- **Missing \`${UPDATE_DIR}/${CV_BASE}.sql\`** โ€” update file must exist for manifest version \`${CV_BASE}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - # (11) no update file may exceed the component version - for UFILE in "$UPDATE_DIR"/*.sql; do - [ -f "$UFILE" ] || continue - UVER=$(basename "$UFILE" .sql) - echo "$UVER" | grep -qP '^\d+\.\d+\.\d+$' || continue - TOP=$(printf '%s\n%s\n' "$CV_BASE" "$UVER" | sort -V | tail -1) - if [ "$UVER" != "$CV_BASE" ] && [ "$TOP" = "$UVER" ]; then - echo "::error file=${UFILE}::Update file ${UVER} exceeds manifest version ${CV_BASE}" - echo "- **Update file \`${UVER}.sql\` exceeds manifest version \`${CV_BASE}\`** โ€” renumber onto the release lane (must be <= manifest)" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - done - done <<< "$UPDATE_DIRS" - 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 "/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 references - FILENAMES=$(grep -oP ']*>\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 references - FOLDERS=$(grep -oP ']*>\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 references in package manifests (ZIP files won't exist in source) - EXT_TYPE=$(grep -oP ']*\btype="\K[^"]+' "$MANIFEST" | head -1) - if [ "$EXT_TYPE" != "package" ]; then - FILES=$(grep -oP ']*>\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 '/dev/null; then - echo "- \`${FILE}\`: no \`
\`, \`\`, or \`
\` 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: 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 \`\` output(s) โ€” use \`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 tags - MANIFESTS=$(find . -maxdepth 4 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*" -exec grep -l '/dev/null || true) - - if [ -z "$MANIFESTS" ]; then - echo "No manifests with \`\` found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - for MANIFEST in $MANIFESTS; do - NS_PATH=$(grep -oP ']*>\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 - - - name: Script file reference check - run: | - echo "### Script File Reference" >> $GITHUB_STEP_SUMMARY - ERRORS=0 - - MANIFEST="" - for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do - if grep -q "/dev/null; then - MANIFEST="$XML_FILE" - break - fi - done - - if [ -z "$MANIFEST" ]; then - echo "No manifest found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - MANIFEST_DIR=$(dirname "$MANIFEST") - SCRIPT_FILE=$(grep -oP '\K[^<]+' "$MANIFEST" 2>/dev/null | head -1) - if [ -z "$SCRIPT_FILE" ]; then - echo "No \`\` referenced โ€” skipping." >> $GITHUB_STEP_SUMMARY - elif [ ! -f "${MANIFEST_DIR}/${SCRIPT_FILE}" ]; then - echo "::error file=${MANIFEST}::Manifest references \`${SCRIPT_FILE}\` but file does not exist" - echo "- **Missing** \`${SCRIPT_FILE}\` โ€” referenced in \`\` but not found" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "- \`${SCRIPT_FILE}\`: present โœ“" >> $GITHUB_STEP_SUMMARY - fi - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} script file issue(s).**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "**Script file reference check passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: Media folder validation - run: | - echo "### Media Folder Validation" >> $GITHUB_STEP_SUMMARY - ERRORS=0 - - MANIFEST="" - for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do - if grep -q "/dev/null; then - MANIFEST="$XML_FILE" - break - fi - done - - if [ -z "$MANIFEST" ]; then - echo "No manifest found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - MANIFEST_DIR=$(dirname "$MANIFEST") - - # Check tag and its folder/filename children - MEDIA_DEST=$(grep -oP ']*\bdestination="\K[^"]+' "$MANIFEST" 2>/dev/null | head -1) - MEDIA_FOLDER=$(grep -oP ']*\bfolder="\K[^"]+' "$MANIFEST" 2>/dev/null | head -1) - - if [ -z "$MEDIA_DEST" ] && [ -z "$MEDIA_FOLDER" ]; then - echo "No \`\` tag found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - if [ -n "$MEDIA_FOLDER" ] && [ ! -d "${MANIFEST_DIR}/${MEDIA_FOLDER}" ]; then - echo "::error file=${MANIFEST}::\`\` references missing directory" - echo "- **Missing** media folder \`${MEDIA_FOLDER}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "- Media folder \`${MEDIA_FOLDER:-(inline)}\`: present โœ“" >> $GITHUB_STEP_SUMMARY - - # Check child references inside block - if [ -n "$MEDIA_FOLDER" ]; then - MEDIA_FOLDERS=$(sed -n '//p' "$MANIFEST" | grep -oP '\K[^<]+' 2>/dev/null || true) - for F in $MEDIA_FOLDERS; do - if [ ! -d "${MANIFEST_DIR}/${MEDIA_FOLDER}/${F}" ]; then - echo "- **Missing** media subfolder \`${MEDIA_FOLDER}/${F}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - done - - MEDIA_FILES=$(sed -n '//p' "$MANIFEST" | grep -oP '\K[^<]+' 2>/dev/null || true) - for F in $MEDIA_FILES; do - if [ ! -f "${MANIFEST_DIR}/${MEDIA_FOLDER}/${F}" ]; then - echo "- **Missing** media file \`${MEDIA_FOLDER}/${F}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - done - fi - fi - fi - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} media reference issue(s).**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "**Media folder validation passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: Target platform check - continue-on-error: true - run: | - echo "### Target Platform Check" >> $GITHUB_STEP_SUMMARY - WARNINGS=0 - - MANIFEST="" - for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do - if grep -q "/dev/null; then - MANIFEST="$XML_FILE" - break - fi - done - - if [ -z "$MANIFEST" ]; then - echo "No manifest found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - # Check updates.xml for targetplatform if it exists - if [ -f "updates.xml" ]; then - if ! grep -q '/dev/null; then - echo "::warning file=updates.xml::No \`\` found โ€” Joomla updater cannot filter by compatible version" - echo "- **Missing** \`\` in updates.xml" >> $GITHUB_STEP_SUMMARY - WARNINGS=$((WARNINGS + 1)) - else - echo "- \`\` in updates.xml: present โœ“" >> $GITHUB_STEP_SUMMARY - fi - fi - - # Check manifest for minimum PHP/Joomla version hints - if ! grep -qP '|targetplatform|joomla.*version' "$MANIFEST" 2>/dev/null; then - echo "::warning file=${MANIFEST}::No minimum Joomla or PHP version constraint found in manifest" - echo "- **Missing** version constraints (\`\` or \`\`)" >> $GITHUB_STEP_SUMMARY - WARNINGS=$((WARNINGS + 1)) - else - echo "- Version constraints in manifest: present โœ“" >> $GITHUB_STEP_SUMMARY - fi - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "$WARNINGS" -gt 0 ]; then - echo "**${WARNINGS} target platform warning(s).**" >> $GITHUB_STEP_SUMMARY - else - echo "**Target platform check passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: Changelog URL check - continue-on-error: true - run: | - echo "### Changelog URL Check" >> $GITHUB_STEP_SUMMARY - WARNINGS=0 - - MANIFEST="" - for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do - if grep -q "/dev/null; then - MANIFEST="$XML_FILE" - break - fi - done - - if [ -z "$MANIFEST" ]; then - echo "No manifest found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - if ! grep -q '' "$MANIFEST" 2>/dev/null; then - echo "::warning file=${MANIFEST}::Missing \`\` โ€” Joomla updater will not display changelogs" - echo "- **Missing** \`\` โ€” Joomla 4+ shows changelogs in the update manager when this is set" >> $GITHUB_STEP_SUMMARY - WARNINGS=$((WARNINGS + 1)) - else - CHANGELOG_URL=$(grep -oP '\K[^<]+' "$MANIFEST" | head -1) - echo "- \`\`: \`${CHANGELOG_URL}\` โœ“" >> $GITHUB_STEP_SUMMARY - fi - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "$WARNINGS" -gt 0 ]; then - echo "**${WARNINGS} changelog URL warning(s).**" >> $GITHUB_STEP_SUMMARY - else - echo "**Changelog URL check passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: Duplicate file references check - continue-on-error: true - run: | - echo "### Duplicate File References" >> $GITHUB_STEP_SUMMARY - WARNINGS=0 - - MANIFEST="" - for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do - if grep -q "/dev/null; then - MANIFEST="$XML_FILE" - break - fi - done - - if [ -z "$MANIFEST" ]; then - echo "No manifest found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - # Extract all and references - ALL_REFS=$(grep -oP '<(filename|folder)[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null | sort || true) - if [ -z "$ALL_REFS" ]; then - echo "No file/folder references found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - DUPES=$(echo "$ALL_REFS" | uniq -d) - if [ -n "$DUPES" ]; then - while IFS= read -r DUP; do - COUNT=$(echo "$ALL_REFS" | grep -cx "$DUP") - echo "::warning file=${MANIFEST}::Duplicate reference: \`${DUP}\` appears ${COUNT} times (may be valid if in different sections)" - echo "- **Duplicate:** \`${DUP}\` (${COUNT}x) โ€” check if cross-section" >> $GITHUB_STEP_SUMMARY - WARNINGS=$((WARNINGS + 1)) - done <<< "$DUPES" - else - TOTAL=$(echo "$ALL_REFS" | wc -l) - echo "All ${TOTAL} file/folder references are unique." >> $GITHUB_STEP_SUMMARY - fi - fi - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "$WARNINGS" -gt 0 ]; then - echo "**${WARNINGS} duplicate reference(s) found.** Review for cross-section validity." >> $GITHUB_STEP_SUMMARY - else - echo "**Duplicate file references check passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: Empty language keys check - continue-on-error: true - run: | - echo "### Empty Language Keys" >> $GITHUB_STEP_SUMMARY - WARNINGS=0 - - LANG_FILES=$(find . -name "*.ini" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null) - if [ -z "$LANG_FILES" ]; then - echo "No .ini language files found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - TOTAL_FILES=0 - for FILE in $LANG_FILES; do - TOTAL_FILES=$((TOTAL_FILES + 1)) - # Find lines with KEY= but no value (empty or whitespace-only after =) - EMPTY_KEYS=$(grep -nP '^[A-Z_]+=\s*$' "$FILE" 2>/dev/null || true) - if [ -n "$EMPTY_KEYS" ]; then - COUNT=$(echo "$EMPTY_KEYS" | wc -l) - echo "::warning file=${FILE}::${COUNT} empty language key(s)" - echo "- \`${FILE}\`: ${COUNT} empty key(s)" >> $GITHUB_STEP_SUMMARY - while IFS= read -r LINE; do - LINE_NUM=$(echo "$LINE" | cut -d: -f1) - KEY=$(echo "$LINE" | cut -d: -f2 | cut -d= -f1) - echo " - Line ${LINE_NUM}: \`${KEY}\`" >> $GITHUB_STEP_SUMMARY - done <<< "$EMPTY_KEYS" - WARNINGS=$((WARNINGS + COUNT)) - fi - done - - if [ "$WARNINGS" -eq 0 ]; then - echo "All ${TOTAL_FILES} language file(s) have populated keys." >> $GITHUB_STEP_SUMMARY - fi - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "$WARNINGS" -gt 0 ]; then - echo "**${WARNINGS} empty language key(s) across ${TOTAL_FILES} file(s).**" >> $GITHUB_STEP_SUMMARY - else - echo "**Empty language keys check passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: Language key usage consistency - run: | - echo "### Language Key Usage Consistency" >> $GITHUB_STEP_SUMMARY - ERRORS=0 - - # Collect all defined language keys from .ini files - LANG_FILES=$(find . -name "*.ini" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null) - if [ -z "$LANG_FILES" ]; then - echo "No .ini language files found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - # Build list of defined keys - DEFINED_KEYS=$(cat $LANG_FILES 2>/dev/null | grep -oP '^[A-Z][A-Z0-9_]+(?==)' | sort -u) - if [ -z "$DEFINED_KEYS" ]; then - echo "No language keys defined โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - DEFINED_COUNT=$(echo "$DEFINED_KEYS" | wc -l) - echo "Found ${DEFINED_COUNT} defined key(s) across .ini files" >> $GITHUB_STEP_SUMMARY - - # Find keys used in PHP files (Text::_('KEY'), Text::sprintf('KEY'), etc.) - SRC_DIR="" - for DIR in source/ src/ htdocs/; do - [ -d "$DIR" ] && SRC_DIR="$DIR" && break - done - - if [ -n "$SRC_DIR" ]; then - USED_IN_PHP=$(grep -rohP "Text::(?:_|sprintf|plural|alt)\(\s*['\"]([A-Z][A-Z0-9_]+)['\"]" "$SRC_DIR" --include="*.php" 2>/dev/null | grep -oP "[A-Z][A-Z0-9_]+" | sort -u || true) - fi - - # Find keys used in XML files (label=, description=, etc.) - USED_IN_XML=$(find . -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*" -exec grep -ohP '(?:label|description|title|message|note)="([A-Z][A-Z0-9_]+)"' {} \; 2>/dev/null | grep -oP '[A-Z][A-Z0-9_]+' | sort -u || true) - - ALL_USED=$(printf '%s\n%s' "$USED_IN_PHP" "$USED_IN_XML" | sort -u | grep -v '^$' || true) - - if [ -n "$ALL_USED" ]; then - # Keys used but not defined - MISSING=$(comm -23 <(echo "$ALL_USED") <(echo "$DEFINED_KEYS") 2>/dev/null || true) - if [ -n "$MISSING" ]; then - COUNT=$(echo "$MISSING" | wc -l) - echo "::error::${COUNT} language key(s) used in code but not defined in .ini files" - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Used but not defined:**" >> $GITHUB_STEP_SUMMARY - while IFS= read -r KEY; do - echo "- \`${KEY}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - done <<< "$MISSING" - fi - else - echo "No language key usage found in source files." >> $GITHUB_STEP_SUMMARY - fi - fi - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} undefined language key(s) โ€” these will appear as raw key strings in the UI.**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "**Language key usage consistency passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: Orphan language keys - run: | - echo "### Orphan Language Keys" >> $GITHUB_STEP_SUMMARY - ERRORS=0 - - LANG_FILES=$(find . -name "*.ini" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null) - if [ -z "$LANG_FILES" ]; then - echo "No .ini language files found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - DEFINED_KEYS=$(cat $LANG_FILES 2>/dev/null | grep -oP '^[A-Z][A-Z0-9_]+(?==)' | sort -u) - if [ -z "$DEFINED_KEYS" ]; then - echo "No language keys defined โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - # Search all PHP, XML, and INI files for key references - ALL_SOURCE=$(find . \( -name "*.php" -o -name "*.xml" -o -name "*.ini" -o -name "*.js" \) -not -path "./.git/*" -not -path "./vendor/*" -not -path "./node_modules/*" 2>/dev/null) - ORPHANS="" - while IFS= read -r KEY; do - # Skip sys.ini _DESC/_TITLE keys โ€” these are Joomla convention keys - # Search for the key in all source files - if ! grep -rql "$KEY" --include="*.php" --include="*.xml" --include="*.js" . 2>/dev/null | grep -qv '\.ini' 2>/dev/null; then - ORPHANS="${ORPHANS}${KEY}\n" - fi - done <<< "$DEFINED_KEYS" - - ORPHANS=$(printf '%b' "$ORPHANS" | grep -v '^$' || true) - if [ -n "$ORPHANS" ]; then - COUNT=$(echo "$ORPHANS" | wc -l) - echo "::error::${COUNT} language key(s) defined in .ini but never referenced in code" - echo "**Orphan keys (defined but unreferenced):**" >> $GITHUB_STEP_SUMMARY - while IFS= read -r KEY; do - echo "- \`${KEY}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - done <<< "$ORPHANS" - fi - fi - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} orphan language key(s) โ€” remove unused keys to reduce translation burden.**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "**Orphan language keys check passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: PHP CodeSniffer (Joomla standard) - run: | - echo "### PHP CodeSniffer" >> $GITHUB_STEP_SUMMARY - ERRORS=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 - # Install PHPCS + Joomla coding standard if not already available - PHPCS="" - if [ -f "vendor/bin/phpcs" ]; then - PHPCS="vendor/bin/phpcs" - else - composer global require --no-interaction --quiet \ - squizlabs/php_codesniffer \ - joomla/coding-standards 2>/dev/null || true - GLOBAL_BIN=$(composer global config bin-dir --absolute 2>/dev/null) - if [ -f "${GLOBAL_BIN}/phpcs" ]; then - PHPCS="${GLOBAL_BIN}/phpcs" - # Register Joomla standard - GLOBAL_VENDOR=$(composer global config vendor-dir --absolute 2>/dev/null) - if [ -d "${GLOBAL_VENDOR}/joomla/coding-standards" ]; then - $PHPCS --config-set installed_paths "${GLOBAL_VENDOR}/joomla/coding-standards" 2>/dev/null || true - fi - fi - fi - - if [ -z "$PHPCS" ]; then - echo "Could not install PHP CodeSniffer โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - # Use repo phpcs.xml if present, otherwise use Joomla standard - ARGS="--report=summary --no-colors" - if [ -f "phpcs.xml" ] || [ -f "phpcs.xml.dist" ] || [ -f ".phpcs.xml" ]; then - echo "Using project PHPCS config." >> $GITHUB_STEP_SUMMARY - else - # Check if Joomla standard is available - if $PHPCS -i 2>/dev/null | grep -qi "Joomla"; then - ARGS="$ARGS --standard=Joomla" - echo "Using Joomla coding standard." >> $GITHUB_STEP_SUMMARY - else - ARGS="$ARGS --standard=PSR12" - echo "Joomla standard not available โ€” falling back to PSR-12." >> $GITHUB_STEP_SUMMARY - fi - ARGS="$ARGS ${SRC_DIR}" - fi - - $PHPCS $ARGS 2>&1 | tee /tmp/phpcs-output.txt - EXIT=${PIPESTATUS[0]} - - if [ $EXIT -eq 0 ]; then - echo "**No coding standard violations found.**" >> $GITHUB_STEP_SUMMARY - else - echo '```' >> $GITHUB_STEP_SUMMARY - cat /tmp/phpcs-output.txt >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "**Coding standard violations found.**" >> $GITHUB_STEP_SUMMARY - exit 1 - fi - fi - fi - - - name: Security patterns check - run: | - echo "### Security Patterns" >> $GITHUB_STEP_SUMMARY - ERRORS=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 - # 1. Direct superglobal access ($_GET, $_POST, $_REQUEST, $_COOKIE) - SUPERGLOBAL_HITS=$(grep -rnP '\$_(GET|POST|REQUEST|COOKIE|FILES)\b' "$SRC_DIR" --include="*.php" 2>/dev/null || true) - if [ -n "$SUPERGLOBAL_HITS" ]; then - COUNT=$(echo "$SUPERGLOBAL_HITS" | wc -l) - echo "::error::${COUNT} direct superglobal access(es) โ€” use Joomla Input API instead" - echo "**Direct superglobal access (use \`\$app->getInput()\` or \`InputFilter\`):**" >> $GITHUB_STEP_SUMMARY - while IFS= read -r HIT; do - FILE=$(echo "$HIT" | cut -d: -f1) - LINE=$(echo "$HIT" | cut -d: -f2) - echo "- \`${FILE}:${LINE}\`" >> $GITHUB_STEP_SUMMARY - done <<< "$SUPERGLOBAL_HITS" - ERRORS=$((ERRORS + COUNT)) - fi - - # 2. Raw SQL query construction (string concatenation in query) - RAW_SQL=$(grep -rnP '\$db->setQuery\(\s*["\x27]' "$SRC_DIR" --include="*.php" 2>/dev/null || true) - if [ -n "$RAW_SQL" ]; then - COUNT=$(echo "$RAW_SQL" | wc -l) - echo "::error::${COUNT} raw SQL string(s) in setQuery() โ€” use query builder or prepared statements" - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Raw SQL strings in \`setQuery()\` (use query builder):**" >> $GITHUB_STEP_SUMMARY - while IFS= read -r HIT; do - FILE=$(echo "$HIT" | cut -d: -f1) - LINE=$(echo "$HIT" | cut -d: -f2) - echo "- \`${FILE}:${LINE}\`" >> $GITHUB_STEP_SUMMARY - done <<< "$RAW_SQL" - ERRORS=$((ERRORS + COUNT)) - fi - - # 3. eval() usage - EVAL_HITS=$(grep -rnP '\beval\s*\(' "$SRC_DIR" --include="*.php" 2>/dev/null || true) - if [ -n "$EVAL_HITS" ]; then - COUNT=$(echo "$EVAL_HITS" | wc -l) - echo "::error::${COUNT} eval() usage(s) โ€” never use eval in extensions" - echo "" >> $GITHUB_STEP_SUMMARY - echo "**\`eval()\` usage (critical security risk):**" >> $GITHUB_STEP_SUMMARY - while IFS= read -r HIT; do - FILE=$(echo "$HIT" | cut -d: -f1) - LINE=$(echo "$HIT" | cut -d: -f2) - echo "- \`${FILE}:${LINE}\`" >> $GITHUB_STEP_SUMMARY - done <<< "$EVAL_HITS" - ERRORS=$((ERRORS + COUNT)) - fi - - # 4. Missing defined('_JEXEC') or JDEFINED guard - while IFS= read -r -d '' PHP_FILE; do - FIRST_PHP=$(grep -nP '^\s*<\?php' "$PHP_FILE" 2>/dev/null | head -1) - if [ -n "$FIRST_PHP" ]; then - # Check first 5 non-empty lines after /dev/null; then - # Skip service provider files (they use a different entry point) - if ! echo "$PHP_FILE" | grep -q "provider.php"; then - echo "::error file=${PHP_FILE}::Missing _JEXEC direct access guard" - echo "- \`${PHP_FILE}\`: missing \`defined('_JEXEC') or die;\` guard" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - fi - fi - done < <(find "$SRC_DIR" -name "*.php" -not -path "./vendor/*" -print0 2>/dev/null) - - if [ "$ERRORS" -eq 0 ]; then - echo "No security issues found." >> $GITHUB_STEP_SUMMARY - fi - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} security issue(s) found.**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "**Security patterns check passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: updates.xml structure validation - run: | - echo "### updates.xml Validation" >> $GITHUB_STEP_SUMMARY - ERRORS=0 - - UPDATE_XML=$(find . -maxdepth 2 -name "updates.xml" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null | head -1) - if [ -z "$UPDATE_XML" ]; then - echo "No updates.xml found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - echo "Found: \`${UPDATE_XML}\`" >> $GITHUB_STEP_SUMMARY - - # 1. Well-formed XML - if command -v php &> /dev/null; then - if ! php -r "@simplexml_load_file('$UPDATE_XML') ?: exit(1);" 2>/dev/null; then - echo "::error file=${UPDATE_XML}::updates.xml is not well-formed XML" - echo "- **Malformed XML** โ€” cannot be parsed" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "- Well-formed XML โœ“" >> $GITHUB_STEP_SUMMARY - - # 2. Must have root element - if ! grep -q '' "$UPDATE_XML" 2>/dev/null; then - echo "::error file=${UPDATE_XML}::Missing root element" - echo "- **Missing** \`\` root element" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "- \`\` root element โœ“" >> $GITHUB_STEP_SUMMARY - fi - - # 3. Each must have required children - UPDATE_COUNT=$(grep -c '' "$UPDATE_XML" 2>/dev/null || echo "0") - if [ "$UPDATE_COUNT" -eq 0 ]; then - echo "::error file=${UPDATE_XML}::No entries found" - echo "- **No \`\` entries** โ€” file is empty" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "- ${UPDATE_COUNT} \`\` entr(ies) found" >> $GITHUB_STEP_SUMMARY - - for TAG in name version type downloadurl; do - if ! grep -q "<${TAG}>" "$UPDATE_XML" 2>/dev/null; then - echo "::error file=${UPDATE_XML}::Missing required <${TAG}> in update entry" - echo "- **Missing** \`<${TAG}>\` in update entry" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "- \`<${TAG}>\` present โœ“" >> $GITHUB_STEP_SUMMARY - fi - done - - # 4. Must have - if ! grep -q '/dev/null; then - echo "::error file=${UPDATE_XML}::Missing โ€” Joomla cannot filter by compatible version" - echo "- **Missing** \`\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "- \`\` present โœ“" >> $GITHUB_STEP_SUMMARY - fi - fi - fi - fi - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} updates.xml issue(s).**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "**updates.xml validation passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: CSS/JS asset existence check - run: | - echo "### CSS/JS Asset Existence" >> $GITHUB_STEP_SUMMARY - ERRORS=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 - # Find WebAssetManager registrations in PHP - # registerAndUseScript/Style, useScript/Style with asset name - ASSET_REFS=$(grep -rohP "(?:registerAndUse|register|use)(?:Script|Style)\(\s*['\"]([^'\"]+)['\"]" "$SRC_DIR" --include="*.php" 2>/dev/null | grep -oP "['\"][^'\"]+['\"]" | tr -d "'\"" | sort -u || true) - - # Find joomla.asset.json files - ASSET_JSONS=$(find . -name "joomla.asset.json" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null) - - # Find media directories - MEDIA_DIRS=$(find . -type d -name "media" -not -path "./.git/*" -not -path "./vendor/*" -not -path "./node_modules/*" 2>/dev/null) - - if [ -z "$ASSET_REFS" ] && [ -z "$ASSET_JSONS" ]; then - echo "No asset registrations found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - # Validate joomla.asset.json is well-formed - if [ -n "$ASSET_JSONS" ]; then - for ASSET_JSON in $ASSET_JSONS; do - if command -v php &> /dev/null; then - VALID=$(php -r "echo json_decode(file_get_contents('$ASSET_JSON')) ? 'VALID' : 'INVALID';" 2>/dev/null) - if [ "$VALID" != "VALID" ]; then - echo "::error file=${ASSET_JSON}::joomla.asset.json is not valid JSON" - echo "- **Invalid JSON**: \`${ASSET_JSON}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "- \`${ASSET_JSON}\`: valid JSON โœ“" >> $GITHUB_STEP_SUMMARY - - # Check that referenced URIs in asset JSON point to existing files - URIS=$(php -r " - \$j = json_decode(file_get_contents('$ASSET_JSON'), true); - \$assets = array_merge(\$j['assets'] ?? [], []); - foreach (\$assets as \$a) { - if (isset(\$a['uri'])) echo \$a['uri'] . PHP_EOL; - if (isset(\$a['css'])) foreach ((array)\$a['css'] as \$c) echo (is_array(\$c) ? \$c['uri'] ?? '' : \$c) . PHP_EOL; - if (isset(\$a['js'])) foreach ((array)\$a['js'] as \$c) echo (is_array(\$c) ? \$c['uri'] ?? '' : \$c) . PHP_EOL; - } - " 2>/dev/null | grep -v '^$' | grep -v '^http' || true) - - ASSET_DIR=$(dirname "$ASSET_JSON") - for URI in $URIS; do - # Resolve relative path from asset JSON location - if [ ! -f "${ASSET_DIR}/${URI}" ]; then - echo "::error file=${ASSET_JSON}::Asset URI '${URI}' references missing file" - echo " - **Missing**: \`${URI}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - done - fi - fi - done - fi - fi - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} asset issue(s) found.**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "**CSS/JS asset existence check passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: MVC naming conventions check - run: | - echo "### MVC Naming Conventions" >> $GITHUB_STEP_SUMMARY - ERRORS=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 - # Check View classes โ€” file must match class name pattern - VIEW_FILES=$(find "$SRC_DIR" -name "*.php" -path "*/View/*" -not -path "./vendor/*" 2>/dev/null) - if [ -n "$VIEW_FILES" ]; then - for FILE in $VIEW_FILES; do - BASENAME=$(basename "$FILE" .php) - # View class should contain class declaration matching filename - CLASS_NAME=$(grep -oP '^\s*class\s+\K[A-Za-z0-9_]+' "$FILE" 2>/dev/null | head -1) - if [ -n "$CLASS_NAME" ]; then - # Class name should end with View (HtmlView, JsonView, etc.) - if ! echo "$CLASS_NAME" | grep -qP 'View$'; then - echo "::error file=${FILE}::View class '${CLASS_NAME}' does not end with 'View'" - echo "- \`${FILE}\`: class \`${CLASS_NAME}\` should end with \`View\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - fi - done - fi - - # Check Controller classes - CTRL_FILES=$(find "$SRC_DIR" -name "*.php" -path "*/Controller/*" -not -path "./vendor/*" 2>/dev/null) - if [ -n "$CTRL_FILES" ]; then - for FILE in $CTRL_FILES; do - CLASS_NAME=$(grep -oP '^\s*class\s+\K[A-Za-z0-9_]+' "$FILE" 2>/dev/null | head -1) - if [ -n "$CLASS_NAME" ]; then - if ! echo "$CLASS_NAME" | grep -qP 'Controller$'; then - echo "::error file=${FILE}::Controller class '${CLASS_NAME}' does not end with 'Controller'" - echo "- \`${FILE}\`: class \`${CLASS_NAME}\` should end with \`Controller\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - fi - done - fi - - # Check Model classes - MODEL_FILES=$(find "$SRC_DIR" -name "*.php" -path "*/Model/*" -not -path "./vendor/*" 2>/dev/null) - if [ -n "$MODEL_FILES" ]; then - for FILE in $MODEL_FILES; do - CLASS_NAME=$(grep -oP '^\s*class\s+\K[A-Za-z0-9_]+' "$FILE" 2>/dev/null | head -1) - if [ -n "$CLASS_NAME" ]; then - if ! echo "$CLASS_NAME" | grep -qP 'Model$'; then - echo "::error file=${FILE}::Model class '${CLASS_NAME}' does not end with 'Model'" - echo "- \`${FILE}\`: class \`${CLASS_NAME}\` should end with \`Model\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - fi - done - fi - - # Check Table classes - TABLE_FILES=$(find "$SRC_DIR" -name "*.php" -path "*/Table/*" -not -path "./vendor/*" 2>/dev/null) - if [ -n "$TABLE_FILES" ]; then - for FILE in $TABLE_FILES; do - CLASS_NAME=$(grep -oP '^\s*class\s+\K[A-Za-z0-9_]+' "$FILE" 2>/dev/null | head -1) - if [ -n "$CLASS_NAME" ]; then - if ! echo "$CLASS_NAME" | grep -qP 'Table$'; then - echo "::error file=${FILE}::Table class '${CLASS_NAME}' does not end with 'Table'" - echo "- \`${FILE}\`: class \`${CLASS_NAME}\` should end with \`Table\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - fi - done - fi - - if [ -z "$VIEW_FILES" ] && [ -z "$CTRL_FILES" ] && [ -z "$MODEL_FILES" ] && [ -z "$TABLE_FILES" ]; then - echo "No MVC classes found โ€” skipping." >> $GITHUB_STEP_SUMMARY - fi - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} MVC naming issue(s).**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "**MVC naming conventions check passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: Router validation - run: | - echo "### Router Validation" >> $GITHUB_STEP_SUMMARY - ERRORS=0 - - # Only applies to components - COMP_MANIFESTS=$(find . -maxdepth 10 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*" -not -path "./.claude/*" -exec grep -l ']*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}\`" >> $GITHUB_STEP_SUMMARY - - # Check for router class in src/Service/ - ROUTER_FILE=$(find "$COMP_DIR" -name "Router.php" -path "*/Service/*" -not -path "./vendor/*" 2>/dev/null | head -1) - if [ -z "$ROUTER_FILE" ]; then - # Also check for legacy router.php - ROUTER_FILE=$(find "$COMP_DIR" -name "router.php" -not -path "./vendor/*" 2>/dev/null | head -1) - fi - - if [ -z "$ROUTER_FILE" ]; then - echo "::error file=${MANIFEST}::Component '${COMP_NAME}' has no Router class" - echo "- **Missing** Router โ€” SEF URLs will not work" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "- Router found: \`${ROUTER_FILE}\`" >> $GITHUB_STEP_SUMMARY - - # Check router implements RouterServiceInterface or RouterInterface - if ! grep -qP 'RouterServiceInterface|RouterInterface|RouterBase' "$ROUTER_FILE" 2>/dev/null; then - echo "::error file=${ROUTER_FILE}::Router does not implement RouterServiceInterface or RouterInterface" - echo " - **Does not implement** required router interface" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo " - Implements router interface โœ“" >> $GITHUB_STEP_SUMMARY - fi - fi - done - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} router issue(s).**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "**Router validation passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: ACL language keys check - run: | - echo "### ACL Language Keys" >> $GITHUB_STEP_SUMMARY - ERRORS=0 - - ACCESS_FILES=$(find . -name "access.xml" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null) - if [ -z "$ACCESS_FILES" ]; then - echo "No access.xml files found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - # Collect all defined language keys - LANG_FILES=$(find . -name "*.ini" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null) - DEFINED_KEYS="" - if [ -n "$LANG_FILES" ]; then - DEFINED_KEYS=$(cat $LANG_FILES 2>/dev/null | grep -oP '^[A-Z][A-Z0-9_]+(?==)' | sort -u) - fi - - for ACCESS_FILE in $ACCESS_FILES; do - echo "Checking: \`${ACCESS_FILE}\`" >> $GITHUB_STEP_SUMMARY - - # Extract action names and titles/descriptions from access.xml - ACTIONS=$(grep -oP ']*name="\K[^"]+' "$ACCESS_FILE" 2>/dev/null || true) - TITLES=$(grep -oP ']*title="\K[^"]+' "$ACCESS_FILE" 2>/dev/null || true) - DESCS=$(grep -oP ']*description="\K[^"]+' "$ACCESS_FILE" 2>/dev/null || true) - - # Check title keys are defined - for KEY in $TITLES $DESCS; do - # Only check keys that look like language constants (ALL_CAPS_WITH_UNDERSCORES) - if echo "$KEY" | grep -qP '^[A-Z][A-Z0-9_]+$'; then - if [ -n "$DEFINED_KEYS" ]; then - if ! echo "$DEFINED_KEYS" | grep -qx "$KEY"; then - echo "::error file=${ACCESS_FILE}::ACL key '${KEY}' not defined in language files" - echo "- **Undefined** ACL key: \`${KEY}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - fi - fi - done - done - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} ACL language key issue(s) โ€” these will show raw key strings in permissions UI.**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "**ACL language keys check passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: Webservices plugin API manifest check - run: | - echo "### Webservices API Manifest" >> $GITHUB_STEP_SUMMARY - ERRORS=0 - - # Find webservices plugin manifests - WS_MANIFESTS=$(find . -maxdepth 10 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*" -not -path "./.claude/*" -exec grep -l ']*type="plugin"' {} \; 2>/dev/null | while read -r F; do - if grep -q 'group="webservices"' "$F" 2>/dev/null; then - echo "$F" - fi - done) - - if [ -z "$WS_MANIFESTS" ]; then - echo "No webservices plugins found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - for MANIFEST in $WS_MANIFESTS; do - WS_DIR=$(dirname "$MANIFEST") - WS_NAME=$(basename "$WS_DIR") - echo "Webservices plugin: \`${WS_NAME}\`" >> $GITHUB_STEP_SUMMARY - - # Find the plugin PHP file - WS_PHP=$(find "$WS_DIR" -name "*.php" -path "*/src/*" -not -path "./vendor/*" 2>/dev/null | head -1) - if [ -z "$WS_PHP" ]; then - WS_PHP=$(find "$WS_DIR" -name "*.php" -not -name "provider.php" -not -path "*/services/*" -not -path "./vendor/*" 2>/dev/null | head -1) - fi - - if [ -n "$WS_PHP" ]; then - # Check it implements WebserviceInterface or registers routes - if ! grep -qP 'WebserviceInterface|onBeforeApiRoute|ApiRouter' "$WS_PHP" 2>/dev/null; then - echo "::error file=${WS_PHP}::Webservices plugin does not implement WebserviceInterface or register API routes" - echo "- \`${WS_PHP}\`: missing \`WebserviceInterface\` or route registration" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "- \`${WS_PHP}\`: API routing โœ“" >> $GITHUB_STEP_SUMMARY - fi - else - echo "- No plugin PHP class found in \`${WS_DIR}\`" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - done - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} webservices API issue(s).**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "**Webservices API manifest check passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: ZIP dry-run build - run: | - echo "### ZIP Dry-Run Build" >> $GITHUB_STEP_SUMMARY - ERRORS=0 - - MANIFEST="" - for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do - if grep -q "/dev/null; then - MANIFEST="$XML_FILE" - break - fi - done - - if [ -z "$MANIFEST" ]; then - echo "No manifest found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - MANIFEST_DIR=$(dirname "$MANIFEST") - EXT_TYPE=$(grep -oP ']*\btype="\K[^"]+' "$MANIFEST" | head -1) - - # Collect all files that should be in the ZIP - MISSING_FILES="" - TOTAL_FILES=0 - - # Files from tags - for F in $(grep -oP ']*>\K[^<]+' "$MANIFEST" 2>/dev/null || true); do - TOTAL_FILES=$((TOTAL_FILES + 1)) - if [ ! -f "${MANIFEST_DIR}/${F}" ]; then - MISSING_FILES="${MISSING_FILES}${F}\n" - fi - done - - # Folders from tags - for F in $(grep -oP ']*>\K[^<]+' "$MANIFEST" 2>/dev/null || true); do - TOTAL_FILES=$((TOTAL_FILES + 1)) - if [ ! -d "${MANIFEST_DIR}/${F}" ]; then - MISSING_FILES="${MISSING_FILES}${F}/\n" - fi - done - - # Script file - SCRIPT=$(grep -oP '\K[^<]+' "$MANIFEST" 2>/dev/null | head -1) - if [ -n "$SCRIPT" ]; then - TOTAL_FILES=$((TOTAL_FILES + 1)) - if [ ! -f "${MANIFEST_DIR}/${SCRIPT}" ]; then - MISSING_FILES="${MISSING_FILES}${SCRIPT}\n" - fi - fi - - # Media folder - MEDIA_FOLDER=$(grep -oP ']*\bfolder="\K[^"]+' "$MANIFEST" 2>/dev/null | head -1) - if [ -n "$MEDIA_FOLDER" ]; then - TOTAL_FILES=$((TOTAL_FILES + 1)) - if [ ! -d "${MANIFEST_DIR}/${MEDIA_FOLDER}" ]; then - MISSING_FILES="${MISSING_FILES}${MEDIA_FOLDER}/\n" - fi - fi - - # SQL folder - SQL_FOLDER=$(grep -oP '\s*\s*]*>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1) - if [ -n "$SQL_FOLDER" ]; then - SQL_DIR=$(dirname "$SQL_FOLDER") - TOTAL_FILES=$((TOTAL_FILES + 1)) - if [ ! -f "${MANIFEST_DIR}/${SQL_FOLDER}" ]; then - MISSING_FILES="${MISSING_FILES}${SQL_FOLDER}\n" - fi - fi - - MISSING_FILES=$(printf '%b' "$MISSING_FILES" | grep -v '^$' || true) - if [ -n "$MISSING_FILES" ]; then - COUNT=$(echo "$MISSING_FILES" | wc -l) - echo "::error file=${MANIFEST}::ZIP build would fail โ€” ${COUNT} referenced file(s) missing" - echo "**Missing files that would cause ZIP build to fail:**" >> $GITHUB_STEP_SUMMARY - while IFS= read -r F; do - echo "- \`${F}\`" >> $GITHUB_STEP_SUMMARY - done <<< "$MISSING_FILES" - ERRORS=$((ERRORS + COUNT)) - fi - - echo "" >> $GITHUB_STEP_SUMMARY - echo "Checked ${TOTAL_FILES} manifest reference(s) for \`${EXT_TYPE}\` extension." >> $GITHUB_STEP_SUMMARY - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} ZIP build issue(s) โ€” package would be incomplete.**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "**ZIP dry-run build passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: JavaScript linting - run: | - echo "### JavaScript Linting" >> $GITHUB_STEP_SUMMARY - ERRORS=0 - - JS_FILES=$(find . -name "*.js" -not -name "*.min.js" -not -path "./.git/*" -not -path "./vendor/*" -not -path "./node_modules/*" 2>/dev/null) - if [ -z "$JS_FILES" ]; then - echo "No JavaScript files found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - JS_COUNT=$(echo "$JS_FILES" | wc -l) - echo "Found ${JS_COUNT} JavaScript file(s)" >> $GITHUB_STEP_SUMMARY - - # Check if Node.js is available - if command -v node &> /dev/null && command -v npx &> /dev/null; then - # Use project ESLint config if present, otherwise install and use defaults - if [ -f ".eslintrc.js" ] || [ -f ".eslintrc.json" ] || [ -f ".eslintrc.yml" ] || [ -f "eslint.config.js" ] || [ -f "eslint.config.mjs" ]; then - if [ -f "package.json" ]; then - npm install --silent 2>/dev/null || true - fi - npx eslint $JS_FILES 2>&1 | tee /tmp/eslint-output.txt - EXIT=${PIPESTATUS[0]} - else - # Basic syntax check without ESLint config - for FILE in $JS_FILES; do - if ! node --check "$FILE" 2>/tmp/js-syntax-err.txt; then - echo "::error file=${FILE}::JavaScript syntax error" - echo "- \`${FILE}\`: syntax error" >> $GITHUB_STEP_SUMMARY - cat /tmp/js-syntax-err.txt >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - done - EXIT=$ERRORS - fi - - if [ $EXIT -ne 0 ] && [ -f /tmp/eslint-output.txt ]; then - echo '```' >> $GITHUB_STEP_SUMMARY - cat /tmp/eslint-output.txt >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - else - echo "Node.js not available โ€” skipping JavaScript lint." >> $GITHUB_STEP_SUMMARY - fi - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} JavaScript issue(s).**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "**JavaScript linting passed.**" >> $GITHUB_STEP_SUMMARY - fi - - - name: CSS linting - run: | - echo "### CSS Linting" >> $GITHUB_STEP_SUMMARY - ERRORS=0 - - CSS_FILES=$(find . -name "*.css" -not -name "*.min.css" -not -path "./.git/*" -not -path "./vendor/*" -not -path "./node_modules/*" 2>/dev/null) - if [ -z "$CSS_FILES" ]; then - echo "No CSS files found โ€” skipping." >> $GITHUB_STEP_SUMMARY - else - CSS_COUNT=$(echo "$CSS_FILES" | wc -l) - echo "Found ${CSS_COUNT} CSS file(s)" >> $GITHUB_STEP_SUMMARY - - for FILE in $CSS_FILES; do - # Basic CSS validation: check for unbalanced braces - OPEN=$(grep -oP '\{' "$FILE" 2>/dev/null | wc -l) - CLOSE=$(grep -oP '\}' "$FILE" 2>/dev/null | wc -l) - if [ "$OPEN" -ne "$CLOSE" ]; then - echo "::error file=${FILE}::Unbalanced braces (${OPEN} open vs ${CLOSE} close)" - echo "- \`${FILE}\`: **unbalanced braces** (${OPEN} \`{\` vs ${CLOSE} \`}\`)" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - - # Check for empty file - SIZE=$(wc -c < "$FILE" | tr -d ' ') - if [ "$SIZE" -eq 0 ]; then - echo "::error file=${FILE}::Empty CSS file" - echo "- \`${FILE}\`: **empty file**" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - done - - # Use Stylelint if available and configured - if command -v npx &> /dev/null; then - if [ -f ".stylelintrc" ] || [ -f ".stylelintrc.json" ] || [ -f "stylelint.config.js" ] || [ -f "stylelint.config.mjs" ]; then - if [ -f "package.json" ]; then - npm install --silent 2>/dev/null || true - fi - npx stylelint "**/*.css" --ignore-pattern "*.min.css" --ignore-pattern "node_modules" --ignore-pattern "vendor" 2>&1 | tee /tmp/stylelint-output.txt - EXIT=${PIPESTATUS[0]} - if [ $EXIT -ne 0 ]; then - echo '```' >> $GITHUB_STEP_SUMMARY - cat /tmp/stylelint-output.txt >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - fi - fi - fi - - echo "" >> $GITHUB_STEP_SUMMARY - if [ "${ERRORS}" -gt 0 ]; then - echo "**${ERRORS} CSS issue(s).**" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "**CSS linting 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 "/dev/null; then - MANIFEST="$XML_FILE" - break - fi - done - - if [ -z "$MANIFEST" ]; then - echo "No Joomla extension manifest found." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY - - # Check matches README VERSION - MANIFEST_VERSION=$(grep -oP '\K[^<]+' "$MANIFEST" | head -1) - if [ -z "$MANIFEST_VERSION" ]; then - echo "No \`\` tag in manifest." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - elif [ -n "$README_VERSION" ] && [ "$MANIFEST_VERSION" != "$README_VERSION" ]; then - echo "Manifest version \`${MANIFEST_VERSION}\` does not match README \`${README_VERSION}\`." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "Manifest version: \`${MANIFEST_VERSION}\`" >> $GITHUB_STEP_SUMMARY - fi - - # Check extension type, element, client attributes - EXT_TYPE=$(grep -oP ']*\btype="\K[^"]+' "$MANIFEST" | head -1) - if [ -z "$EXT_TYPE" ]; then - echo "Missing \`type\` attribute on \`\` tag." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - else - echo "Extension type: \`${EXT_TYPE}\`" >> $GITHUB_STEP_SUMMARY - fi - - # Element check (component/module/plugin name) - HAS_ELEMENT=$(grep -cP '<(element|name)>' "$MANIFEST" 2>/dev/null || echo "0") - if [ "$HAS_ELEMENT" -eq 0 ]; then - echo "Missing \`\` or \`\` in manifest." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - - # Client attribute for site/admin modules and plugins - if echo "$EXT_TYPE" | grep -qP "^(module|plugin)$"; then - HAS_CLIENT=$(grep -cP ']*\bclient=' "$MANIFEST" 2>/dev/null || echo "0") - if [ "$HAS_CLIENT" -eq 0 ]; then - echo "Missing \`client\` attribute for ${EXT_TYPE} extension." >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + 1)) - fi - fi - fi - - # Check 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 - if: ${{ !startsWith(github.event.repository.name, 'Template-') }} - 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.MOKOGITEA_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 - if: ${{ !startsWith(github.event.repository.name, 'Template-') }} - 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.MOKOGITEA_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: - MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_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 ${MOKOGITEA_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 diff --git a/.mokogitea/workflows/cleanup.yml b/.mokogitea/workflows/cleanup.yml deleted file mode 100644 index d08e29e..0000000 --- a/.mokogitea/workflows/cleanup.yml +++ /dev/null @@ -1,87 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Maintenance -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /.mokogitea/workflows/cleanup.yml -# VERSION: 01.00.00 -# BRIEF: Scheduled cleanup โ€” delete merged branches and old workflow runs - -name: "Universal: Repository Cleanup" - -on: - schedule: - - cron: '0 3 * * 0' # Weekly on Sunday at 03:00 UTC - workflow_dispatch: - -permissions: - contents: write - -env: - MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} - -jobs: - cleanup: - name: Clean Merged Branches - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.MOKOGITEA_TOKEN }} - - - name: Delete merged branches - env: - MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - run: | - echo "=== Merged Branch Cleanup ===" - API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}" - - # List branches via API - BRANCHES=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \ - "${API}/branches?limit=50" | jq -r '.[].name') - - DELETED=0 - for BRANCH in $BRANCHES; do - # Skip protected branches - case "$BRANCH" in - main|master|dev|develop|rc|beta|alpha|release|release/*|production|stable|staging|hotfix/*|version/*) continue ;; - esac - - # Check if branch is merged into main - if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then - echo " Deleting merged branch: ${BRANCH}" - curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \ - "${API}/branches/${BRANCH}" 2>/dev/null || true - DELETED=$((DELETED + 1)) - fi - done - - echo "Deleted ${DELETED} merged branch(es)" - - - name: Clean old workflow runs - env: - MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - run: | - echo "=== Workflow Run Cleanup ===" - 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) - - # Get old completed runs - RUNS=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \ - "${API}/actions/runs?status=completed&limit=50" | \ - jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null) - - DELETED=0 - for RUN_ID in $RUNS; do - curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \ - "${API}/actions/runs/${RUN_ID}" 2>/dev/null || true - DELETED=$((DELETED + 1)) - done - - echo "Deleted ${DELETED} old workflow run(s)" diff --git a/.mokogitea/workflows/gitleaks.yml b/.mokogitea/workflows/gitleaks.yml deleted file mode 100644 index 23b843e..0000000 --- a/.mokogitea/workflows/gitleaks.yml +++ /dev/null @@ -1,92 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Security -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /templates/workflows/gitleaks.yml.template -# VERSION: 01.00.00 -# BRIEF: Secret scanning โ€” detect leaked credentials, API keys, and tokens -# -# +========================================================================+ -# | SECRET SCANNING | -# +========================================================================+ -# | | -# | Scans commits for leaked secrets using Gitleaks. | -# | | -# | - PR scan: only new commits in the PR | -# | - Scheduled: full repo scan weekly | -# | - Alerts via ntfy on findings | -# | | -# +========================================================================+ - -name: "Universal: Secret Scanning" - -on: - schedule: - - cron: '0 5 * * 1' # Weekly Monday 05:00 UTC - workflow_dispatch: - -permissions: - contents: read - -env: - NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }} - NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }} - -jobs: - gitleaks: - name: Gitleaks Secret Scan - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Install Gitleaks - run: | - GITLEAKS_VERSION="8.21.2" - curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ - | tar -xz -C /usr/local/bin gitleaks - gitleaks version - - - name: Scan for secrets - id: scan - run: | - echo "### Secret Scanning" >> $GITHUB_STEP_SUMMARY - ARGS="--source . --verbose --report-format json --report-path /tmp/gitleaks-report.json" - - if [ "${{ github.event_name }}" = "pull_request" ]; then - # Scan only PR commits - ARGS="$ARGS --log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}" - echo "Scanning PR commits only" >> $GITHUB_STEP_SUMMARY - else - echo "Full repository scan" >> $GITHUB_STEP_SUMMARY - fi - - if gitleaks detect $ARGS 2>&1; then - echo "result=clean" >> "$GITHUB_OUTPUT" - echo "**No secrets detected.**" >> $GITHUB_STEP_SUMMARY - else - echo "result=found" >> "$GITHUB_OUTPUT" - FINDINGS=$(jq length /tmp/gitleaks-report.json 2>/dev/null || echo "unknown") - echo "**${FINDINGS} potential secret(s) detected.**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Review the findings and rotate any exposed credentials immediately." >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - - name: Notify on findings - if: failure() && steps.scan.outputs.result == 'found' - run: | - REPO="${{ github.event.repository.name }}" - curl -sS \ - -H "Title: ${REPO} โ€” secrets detected in code" \ - -H "Tags: rotating_light,key" \ - -H "Priority: urgent" \ - -d "Gitleaks found potential secrets. Review and rotate credentials immediately." \ - "${NTFY_URL}/${NTFY_TOPIC}" || true diff --git a/.mokogitea/workflows/issue-branch.yml b/.mokogitea/workflows/issue-branch.yml deleted file mode 100644 index 12a4b11..0000000 --- a/.mokogitea/workflows/issue-branch.yml +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Automation -# VERSION: 01.00.00 -# BRIEF: Auto-create feature branch when an issue is opened - -name: "Universal: Issue Branch" - -on: - issues: - types: [opened] - -permissions: - contents: write - issues: write - -env: - MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} - -jobs: - create-branch: - name: Create feature branch - runs-on: ubuntu-latest - steps: - - name: Create branch and comment - run: | - TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" - API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}" - ISSUE_NUM="${{ github.event.issue.number }}" - ISSUE_TITLE="${{ github.event.issue.title }}" - - # Build slug from title: lowercase, replace non-alnum with dash, trim - SLUG=$(echo "${ISSUE_TITLE}" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//' | cut -c1-40) - BRANCH="feature/${ISSUE_NUM}-${SLUG}" - - # Check dev branch exists - DEV_EXISTS=$(curl -sf -o /dev/null -w '%{http_code}' \ - -H "Authorization: token ${TOKEN}" \ - "${API}/branches/dev" 2>/dev/null || echo "000") - - if [ "${DEV_EXISTS}" != "200" ]; then - echo "No dev branch -- skipping" - exit 0 - fi - - # Create branch from dev - HTTP=$(curl -sf -o /dev/null -w '%{http_code}' -X POST \ - -H "Authorization: token ${TOKEN}" \ - -H "Content-Type: application/json" \ - "${API}/branches" \ - -d "{\"new_branch_name\":\"${BRANCH}\",\"old_branch_name\":\"dev\"}" 2>/dev/null || echo "000") - - if [ "${HTTP}" = "201" ]; then - echo "Created branch: ${BRANCH}" - - # Comment on issue with branch link - 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\`\`\`" - - curl -sf -X POST \ - -H "Authorization: token ${TOKEN}" \ - -H "Content-Type: application/json" \ - "${API}/issues/${ISSUE_NUM}/comments" \ - -d "{\"body\":\"${BODY}\"}" > /dev/null 2>&1 - - echo "Commented on issue #${ISSUE_NUM}" - else - echo "Failed to create branch (HTTP ${HTTP}) -- may already exist" - fi diff --git a/.mokogitea/workflows/notify.yml b/.mokogitea/workflows/notify.yml deleted file mode 100644 index 6d029c2..0000000 --- a/.mokogitea/workflows/notify.yml +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Notifications -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /.mokogitea/workflows/notify.yml -# VERSION: 01.00.00 -# BRIEF: Push notifications via ntfy on release success or workflow failure - -name: "Universal: Notifications" - -on: - workflow_run: - workflows: - - "Universal: Build & Release" - - "Joomla: Extension CI" - - "Generic: Project CI" - types: - - completed - -permissions: - contents: read - -env: - NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }} - NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-releases' }} - -jobs: - notify: - name: Send Notification - runs-on: ubuntu-latest - if: >- - github.event.workflow_run.conclusion == 'success' || - github.event.workflow_run.conclusion == 'failure' - - steps: - - name: Notify on success (releases only) - if: >- - github.event.workflow_run.conclusion == 'success' && - contains(github.event.workflow_run.name, 'Release') - run: | - REPO="${{ github.event.repository.name }}" - WORKFLOW="${{ github.event.workflow_run.name }}" - URL="${{ github.event.workflow_run.html_url }}" - - curl -sS \ - -H "Title: ${REPO} released" \ - -H "Tags: white_check_mark,package" \ - -H "Priority: default" \ - -H "Click: ${URL}" \ - -d "${WORKFLOW} completed successfully." \ - "${NTFY_URL}/${NTFY_TOPIC}" - - - name: Notify on failure - if: github.event.workflow_run.conclusion == 'failure' - run: | - REPO="${{ github.event.repository.name }}" - WORKFLOW="${{ github.event.workflow_run.name }}" - URL="${{ github.event.workflow_run.html_url }}" - - curl -sS \ - -H "Title: ${REPO} workflow failed" \ - -H "Tags: x,warning" \ - -H "Priority: high" \ - -H "Click: ${URL}" \ - -d "${WORKFLOW} failed. Check the run for details." \ - "${NTFY_URL}/${NTFY_TOPIC}" diff --git a/.mokogitea/workflows/pr-check.yml b/.mokogitea/workflows/pr-check.yml deleted file mode 100644 index 2cd896c..0000000 --- a/.mokogitea/workflows/pr-check.yml +++ /dev/null @@ -1,527 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.CI -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /templates/workflows/universal/pr-check.yml.template -# VERSION: 09.23.00 -# BRIEF: PR gate โ€” branch policy + code validation before merge - -name: "Universal: PR Check" - -on: - pull_request: - types: [opened, synchronize, reopened, edited] - -permissions: - contents: read - pull-requests: write - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - # โ”€โ”€ Branch Policy โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - branch-policy: - name: Branch Policy - runs-on: ubuntu-latest - steps: - - name: Check branch merge target - run: | - HEAD="${{ github.head_ref }}" - BASE="${{ github.base_ref }}" - - echo "PR: ${HEAD} โ†’ ${BASE}" - - ALLOWED=true - REASON="" - - case "$HEAD" in - feature/*|feat/*) - if [ "$BASE" != "dev" ]; then - ALLOWED=false - REASON="Feature branches must target 'dev', not '${BASE}'" - fi - ;; - fix/*|bugfix/*) - if [ "$BASE" != "dev" ]; then - ALLOWED=false - REASON="Fix branches must target 'dev', not '${BASE}'" - fi - ;; - patch/*) - if [ "$BASE" != "dev" ] && [ "$BASE" != "rc" ]; then - ALLOWED=false - REASON="Patch branches must target 'dev' or 'rc', not '${BASE}'" - fi - ;; - hotfix/*) - if [ "$BASE" != "dev" ] && [ "$BASE" != "main" ]; then - ALLOWED=false - REASON="Hotfix branches can only target 'dev' or 'main', not '${BASE}'" - fi - ;; - rc) - if [ "$BASE" != "main" ]; then - ALLOWED=false - REASON="RC branch can only merge into 'main', not '${BASE}'" - fi - ;; - dev) - if [ "$BASE" != "main" ]; then - ALLOWED=false - REASON="Dev branch can only merge into 'main', not '${BASE}'" - fi - ;; - esac - - if [ "$ALLOWED" = false ]; then - echo "::error::${REASON}" - echo "## Branch Policy Violation" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "${REASON}" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Allowed merge paths:" >> $GITHUB_STEP_SUMMARY - echo "- \`feature/*\` โ†’ \`dev\`" >> $GITHUB_STEP_SUMMARY - echo "- \`fix/*\` โ†’ \`dev\`" >> $GITHUB_STEP_SUMMARY - echo "- \`hotfix/*\` โ†’ \`dev\` or \`main\`" >> $GITHUB_STEP_SUMMARY - echo "- \`dev\` โ†’ \`main\`" >> $GITHUB_STEP_SUMMARY - echo "- \`rc/*\` โ†’ \`main\`" >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - echo "Branch policy: OK (${HEAD} โ†’ ${BASE})" - echo "## Branch Policy: Passed" >> $GITHUB_STEP_SUMMARY - - # โ”€โ”€ Secret Scanning โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - gitleaks: - name: Secret Scan - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Install Gitleaks - run: | - GITLEAKS_VERSION="8.21.2" - curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \ - | tar -xz -C /usr/local/bin gitleaks - - - name: Scan PR commits for secrets - run: | - if gitleaks detect --source . --verbose \ - --log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} 2>&1; then - echo "**No secrets detected.**" >> $GITHUB_STEP_SUMMARY - else - echo "::error::Potential secrets detected in PR commits" - exit 1 - fi - - # โ”€โ”€ Code Validation โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - validate: - name: Validate PR - runs-on: ubuntu-latest - # Skip on template repos (Template-*) โ€” no real manifest/source/changelog to validate. - if: ${{ !startsWith(github.event.repository.name, 'Template-') }} - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Check for merge conflict markers - run: | - CONFLICTS=$(grep -rn '<<<<<<< \|>>>>>>> \|^=======$' --include='*.php' --include='*.xml' --include='*.css' --include='*.js' --include='*.json' --include='*.md' --include='*.yml' --include='*.yaml' --include='*.ini' --include='*.txt' . 2>/dev/null | grep -v '.git/' || true) - if [ -n "$CONFLICTS" ]; then - echo "::error::Merge conflict markers found in source files" - echo "## Conflict Markers Found" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "$CONFLICTS" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - exit 1 - fi - echo "No conflict markers found" - - - name: Detect platform - id: platform - run: | - # Platform comes from the MokoGitea metadata API (public GET); manifest.xml is no longer used. - API="${GITHUB_SERVER_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${GITHUB_REPOSITORY}/metadata" - PLATFORM="$(curl -sf "$API" 2>/dev/null | python3 -c "import sys, json; print(json.load(sys.stdin).get('platform') or '')" 2>/dev/null || true)" - [ -z "$PLATFORM" ] && PLATFORM="generic" - echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT" - echo "Detected platform: $PLATFORM" - - - name: Setup PHP - if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr' - run: | - if ! command -v php &> /dev/null; then - sudo apt-get update -qq - sudo apt-get install -y -qq php-cli php-mbstring php-xml >/dev/null 2>&1 - fi - - - name: PHP syntax check - if: steps.platform.outputs.platform == 'joomla' || steps.platform.outputs.platform == 'dolibarr' - run: | - ERRORS=0 - while IFS= read -r -d '' file; do - if ! php -l "$file" 2>&1 | grep -q "No syntax errors"; then - ERRORS=$((ERRORS + 1)) - fi - done < <(find . -name "*.php" -not -path "./.git/*" -not -path "./vendor/*" -print0) - echo "PHP lint: ${ERRORS} error(s)" - [ "$ERRORS" -eq 0 ] || { echo "::error::PHP syntax errors found"; exit 1; } - - - name: Joomla JEXEC guard check - if: steps.platform.outputs.platform == 'joomla' - run: | - ERRORS=0 - while IFS= read -r -d '' file; do - # Skip vendor, node_modules, and index.html stub files - case "$file" in ./vendor/*|./node_modules/*) continue ;; esac - # Check first 10 lines for JEXEC or JPATH guard - if ! head -20 "$file" | grep -qE "defined\s*\(\s*['\"](_JEXEC|JPATH_BASE|\\\\JPATH_PLATFORM)['\"]"; then - echo "::error file=${file}::Missing JEXEC guard: ${file}" - ERRORS=$((ERRORS + 1)) - fi - done < <(find . -name "*.php" -path "*/src/*" -not -path "./.git/*" -not -path "./vendor/*" -print0) - if [ "$ERRORS" -gt 0 ]; then - echo "::error::${ERRORS} PHP file(s) missing defined('_JEXEC') or die guard" - echo "## JEXEC Guard Check: Failed" >> $GITHUB_STEP_SUMMARY - echo "${ERRORS} file(s) in src/ are missing the Joomla execution guard." >> $GITHUB_STEP_SUMMARY - exit 1 - fi - echo "JEXEC guard: OK" - - - name: Joomla directory listing protection - if: steps.platform.outputs.platform == 'joomla' - run: | - MISSING=0 - SOURCE_DIR="src" - [ ! -d "$SOURCE_DIR" ] && exit 0 - while IFS= read -r dir; do - if [ ! -f "${dir}/index.html" ]; then - echo "::warning::Missing index.html in ${dir} (directory listing protection)" - MISSING=$((MISSING + 1)) - fi - done < <(find "$SOURCE_DIR" -type d -not -path "./.git/*" -not -path "*/vendor/*" -not -path "*/node_modules/*") - if [ "$MISSING" -gt 0 ]; then - echo "## Directory Protection" >> $GITHUB_STEP_SUMMARY - echo "${MISSING} director(ies) missing index.html" >> $GITHUB_STEP_SUMMARY - fi - echo "Directory protection: ${MISSING} missing (advisory)" - - - name: Joomla script file and asset checks - if: steps.platform.outputs.platform == 'joomla' - run: | - ERRORS=0 - MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1) - [ -z "$MANIFEST" ] && exit 0 - MANIFEST_DIR=$(dirname "$MANIFEST") - - # Check scriptfile exists if declared - SCRIPTFILE=$(sed -n 's/.*\([^<]*\)<\/scriptfile>.*/\1/p' "$MANIFEST" 2>/dev/null) - if [ -n "$SCRIPTFILE" ]; then - if [ ! -f "${MANIFEST_DIR}/${SCRIPTFILE}" ]; then - echo "::error::Manifest declares ${SCRIPTFILE} but file not found at ${MANIFEST_DIR}/${SCRIPTFILE}" - ERRORS=$((ERRORS + 1)) - else - echo "Script file: ${MANIFEST_DIR}/${SCRIPTFILE} (OK)" - fi - fi - - # Require joomla.asset.json and validate it - ASSET_JSON=$(find "$MANIFEST_DIR" -name "joomla.asset.json" -not -path "./.git/*" 2>/dev/null | head -1) - if [ -z "$ASSET_JSON" ]; then - echo "::error::joomla.asset.json not found โ€” Joomla asset system is required" - ERRORS=$((ERRORS + 1)) - else - if command -v php &> /dev/null; then - php -r "json_decode(file_get_contents('$ASSET_JSON')); if(json_last_error()!==JSON_ERROR_NONE){echo json_last_error_msg();exit(1);}" 2>&1 || { - echo "::error::joomla.asset.json is not valid JSON" - ERRORS=$((ERRORS + 1)) - } - fi - echo "joomla.asset.json: valid" - fi - - # Validate all XML files in src/ are well-formed - XML_ERRORS=0 - if command -v php &> /dev/null; then - while IFS= read -r -d '' xmlfile; do - if ! php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$xmlfile'); if(!\$x){foreach(libxml_get_errors() as \$e) echo trim(\$e->message) . ' in $xmlfile'; exit(1);}" 2>&1; then - XML_ERRORS=$((XML_ERRORS + 1)) - fi - done < <(find "$MANIFEST_DIR" -name "*.xml" -not -path "./.git/*" -print0) - fi - if [ "$XML_ERRORS" -gt 0 ]; then - echo "::error::${XML_ERRORS} XML file(s) are malformed" - ERRORS=$((ERRORS + 1)) - else - echo "XML well-formedness: OK" - fi - - [ "$ERRORS" -gt 0 ] && exit 1 - echo "Joomla asset checks: OK" - - - name: Validate platform manifest - run: | - PLATFORM="${{ steps.platform.outputs.platform }}" - case "$PLATFORM" in - joomla) - MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '/dev/null | head -1) - if [ -z "$MANIFEST" ]; then - echo "::warning::No Joomla manifest found (WaaS site)" - exit 0 - fi - echo "Manifest: ${MANIFEST}" - if command -v php &> /dev/null; then - php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('$MANIFEST'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::Manifest XML is malformed"; exit 1; } - fi - for ELEMENT in name version description; do - grep -q "<${ELEMENT}>" "$MANIFEST" || { echo "::error::Missing <${ELEMENT}> in manifest"; exit 1; } - done - # Block legacy raw/branch update server URLs on MokoGitea - RAW_URLS=$(grep -n 'raw/branch' "$MANIFEST" | grep -i 'mokoconsulting\|mokogitea\|git\.mokoconsulting\.tech' || true) - if [ -n "$RAW_URLS" ]; then - echo "::error::Manifest contains legacy raw/branch update server URL on MokoGitea. Use the MokoGitea Pages URL instead (e.g. /{REPO}/updates.xml not /{REPO}/raw/branch/main/updates.xml)" - echo "$RAW_URLS" - exit 1 - fi - echo "Joomla manifest valid" - ;; - dolibarr) - MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1) - if [ -z "$MOD_FILE" ]; then - echo "::error::No mod*.class.php found" - exit 1 - fi - echo "Dolibarr module: ${MOD_FILE}" - ;; - *) - echo "Generic platform โ€” no manifest validation" - ;; - esac - - - name: Check update stream format - run: | - PLATFORM="${{ steps.platform.outputs.platform }}" - case "$PLATFORM" in - joomla) - if [ -f "updates.xml" ]; then - if command -v php &> /dev/null; then - php -r "libxml_use_internal_errors(true); \$x = simplexml_load_file('updates.xml'); if(!\$x){foreach(libxml_get_errors() as \$e) echo \$e->message; exit(1);}" || { echo "::error::updates.xml is malformed"; exit 1; } - fi - echo "updates.xml valid" - fi - ;; - dolibarr) - [ -f "update.txt" ] && echo "update.txt present" || echo "::warning::No update.txt" - ;; - esac - - - name: Validate Joomla language files - if: steps.platform.outputs.platform == 'joomla' - run: | - ERRORS=0 - WARNINGS=0 - - # Require both en-GB and en-US language directories - LANG_ROOT=$(find . -path "*/language" -type d -not -path "./.git/*" 2>/dev/null | head -1) - if [ -z "$LANG_ROOT" ]; then - echo "No language/ directory found โ€” skipping" - exit 0 - fi - - if [ ! -d "$LANG_ROOT/en-GB" ]; then - echo "::error::Missing en-GB language directory (${LANG_ROOT}/en-GB)" - ERRORS=$((ERRORS + 1)) - fi - if [ ! -d "$LANG_ROOT/en-US" ]; then - echo "::error::Missing en-US language directory (${LANG_ROOT}/en-US)" - ERRORS=$((ERRORS + 1)) - fi - - # Check that en-GB and en-US have matching .ini files - if [ -d "$LANG_ROOT/en-GB" ] && [ -d "$LANG_ROOT/en-US" ]; then - for GB_INI in "$LANG_ROOT/en-GB"/*.ini; do - [ ! -f "$GB_INI" ] && continue - US_INI="$LANG_ROOT/en-US/$(basename "$GB_INI")" - if [ ! -f "$US_INI" ]; then - echo "::error::$(basename "$GB_INI") exists in en-GB but missing from en-US" - ERRORS=$((ERRORS + 1)) - fi - done - for US_INI in "$LANG_ROOT/en-US"/*.ini; do - [ ! -f "$US_INI" ] && continue - GB_INI="$LANG_ROOT/en-GB/$(basename "$US_INI")" - if [ ! -f "$GB_INI" ]; then - echo "::error::$(basename "$US_INI") exists in en-US but missing from en-GB" - ERRORS=$((ERRORS + 1)) - fi - done - fi - - # Find all .ini language files - INI_FILES=$(find . -path "*/language/*/*.ini" -not -path "./.git/*" 2>/dev/null) - if [ -z "$INI_FILES" ]; then - echo "No .ini language files found" - [ "$ERRORS" -gt 0 ] && exit 1 - exit 0 - fi - - echo "Found $(echo "$INI_FILES" | wc -l) language file(s)" - - for FILE in $INI_FILES; do - FNAME=$(basename "$FILE") - LINENUM=0 - SEEN_KEYS="" - - while IFS= read -r line || [ -n "$line" ]; do - LINENUM=$((LINENUM + 1)) - - # Skip empty lines and comments - [ -z "$line" ] && continue - echo "$line" | grep -qE '^\s*;' && continue - echo "$line" | grep -qE '^\s*$' && continue - - # Must match KEY="VALUE" format - if ! echo "$line" | grep -qE '^[A-Z_][A-Z0-9_]*=".*"$'; then - echo "::error file=${FILE},line=${LINENUM}::Malformed line: ${line}" - ERRORS=$((ERRORS + 1)) - continue - fi - - # Extract key and check for duplicates - KEY=$(echo "$line" | sed 's/=.*//') - if echo "$SEEN_KEYS" | grep -qx "$KEY"; then - echo "::error file=${FILE},line=${LINENUM}::Duplicate key: ${KEY}" - ERRORS=$((ERRORS + 1)) - fi - SEEN_KEYS="${SEEN_KEYS} - ${KEY}" - done < "$FILE" - - echo " ${FILE}: checked ${LINENUM} lines" - done - - # Cross-check en-GB vs en-US key consistency - GB_DIR=$(find . -path "*/language/en-GB" -type d -not -path "./.git/*" 2>/dev/null | head -1) - US_DIR=$(find . -path "*/language/en-US" -type d -not -path "./.git/*" 2>/dev/null | head -1) - - if [ -n "$GB_DIR" ] && [ -n "$US_DIR" ]; then - for GB_FILE in "$GB_DIR"/*.ini; do - [ ! -f "$GB_FILE" ] && continue - FNAME=$(basename "$GB_FILE") - US_FILE="$US_DIR/$FNAME" - [ ! -f "$US_FILE" ] && continue - - GB_KEYS=$(grep -oP '^[A-Z_][A-Z0-9_]*(?==)' "$GB_FILE" 2>/dev/null | sort) - US_KEYS=$(grep -oP '^[A-Z_][A-Z0-9_]*(?==)' "$US_FILE" 2>/dev/null | sort) - - # Keys in en-GB but not en-US - MISSING_US=$(comm -23 <(echo "$GB_KEYS") <(echo "$US_KEYS")) - if [ -n "$MISSING_US" ]; then - echo "::warning::Keys in en-GB/$FNAME but missing from en-US/$FNAME:" - echo "$MISSING_US" | while read -r k; do echo " - $k"; done - WARNINGS=$((WARNINGS + 1)) - fi - - # Keys in en-US but not en-GB - MISSING_GB=$(comm -13 <(echo "$GB_KEYS") <(echo "$US_KEYS")) - if [ -n "$MISSING_GB" ]; then - echo "::warning::Keys in en-US/$FNAME but missing from en-GB/$FNAME:" - echo "$MISSING_GB" | while read -r k; do echo " - $k"; done - WARNINGS=$((WARNINGS + 1)) - fi - done - fi - - { - echo "### Language File Validation" - echo "| Metric | Count |" - echo "|---|---|" - echo "| Files checked | $(echo "$INI_FILES" | wc -l) |" - echo "| Errors | ${ERRORS} |" - echo "| Warnings | ${WARNINGS} |" - } >> $GITHUB_STEP_SUMMARY - - if [ "$ERRORS" -gt 0 ]; then - echo "::error::Language validation failed with ${ERRORS} error(s)" - exit 1 - fi - echo "Language files: OK (${WARNINGS} warning(s))" - - - name: Check changelog has unreleased entry - run: | - if [ ! -f "CHANGELOG.md" ]; then - echo "::warning::No CHANGELOG.md found" - exit 0 - fi - # Check for content under [Unreleased] section - if ! grep -q "## \[Unreleased\]" CHANGELOG.md; then - echo "::error::CHANGELOG.md missing [Unreleased] section" - exit 1 - fi - # Check there's at least one entry (Added/Changed/Fixed/Removed) under Unreleased - UNRELEASED_CONTENT=$(sed -n '/## \[Unreleased\]/,/## \[/p' CHANGELOG.md | grep -cE '^\s*-\s' || true) - if [ "$UNRELEASED_CONTENT" -eq 0 ]; then - echo "::error::CHANGELOG.md [Unreleased] section has no entries. Add a changelog entry describing your changes." - echo "## Changelog Check: Failed" >> $GITHUB_STEP_SUMMARY - echo "The \`[Unreleased]\` section in CHANGELOG.md has no entries." >> $GITHUB_STEP_SUMMARY - echo "Add a line like \`- Description of your change\` under a heading (\`### Added\`, \`### Changed\`, \`### Fixed\`, etc.)" >> $GITHUB_STEP_SUMMARY - exit 1 - fi - echo "Changelog: ${UNRELEASED_CONTENT} entry/entries in [Unreleased]" - - - name: Verify package source - run: | - SOURCE_DIR="src" - [ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs" - if [ ! -d "$SOURCE_DIR" ]; then - echo "::warning::No src/ or htdocs/ directory" - exit 0 - fi - FILE_COUNT=$(find "$SOURCE_DIR" -type f | wc -l) - echo "Source: ${FILE_COUNT} files" - [ "$FILE_COUNT" -gt 0 ] || { echo "::error::Source directory is empty"; exit 1; } - - # โ”€โ”€ Pre-Release RC Build โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - pre-release: - name: Build RC Package - runs-on: ubuntu-latest - needs: [branch-policy, validate] - # Run only when both gates succeeded; always() forces evaluation so a skipped - # validate (e.g. template repos) skips this job cleanly instead of hanging. - if: ${{ always() && needs.branch-policy.result == 'success' && needs.validate.result == 'success' }} - - steps: - - name: Trigger RC pre-release - env: - MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - REPO: ${{ github.repository }} - BRANCH: ${{ github.head_ref }} - MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} - run: | - 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 "Triggered RC build on branch \`${BRANCH}\`" >> $GITHUB_STEP_SUMMARY - - # โ”€โ”€ Issue Reporter โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ - report-issues: - name: Report Issues - needs: [branch-policy, validate] - if: >- - always() && - needs.validate.result == 'failure' - uses: ./.mokogitea/workflows/ci-issue-reporter.yml - with: - 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." - secrets: inherit diff --git a/.mokogitea/workflows/pr-metadata-check.yml b/.mokogitea/workflows/pr-metadata-check.yml deleted file mode 100644 index a706c41..0000000 --- a/.mokogitea/workflows/pr-metadata-check.yml +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Validation -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /templates/workflows/joomla/pr-metadata-check.yml.template -# VERSION: 01.00.00 -# BRIEF: Validate MokoGitea metadata matches Joomla extension manifest on PRs - -name: "Joomla: Metadata Validation" - -on: - pull_request: - types: [opened, synchronize, reopened, converted_to_draft, ready_for_review] - -permissions: - contents: read - -env: - MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} - GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }} - GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }} - -jobs: - validate-metadata: - name: "Validate Joomla Metadata" - runs-on: ubuntu-latest - # Skip on template repos (Template-*) โ€” they have no real extension manifest to validate. - if: ${{ !startsWith(github.event.repository.name, 'Template-') }} - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup MokoCLI tools - env: - MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting - run: | - if [ -f /opt/mokocli/cli/joomla_metadata_validate.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then - echo Using pre-installed /opt/mokocli - echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV - else - echo Falling back to fresh clone - if ! command -v composer > /dev/null 2>&1; then - sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1 - fi - rm -rf /tmp/mokocli - CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git - git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli - cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet - echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV - fi - - - name: Validate metadata against Joomla manifest - env: - MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - run: | - php ${MOKO_CLI}/joomla_metadata_validate.php \ - --path . \ - --token "${MOKOGITEA_TOKEN}" \ - --org "${GITEA_ORG}" \ - --repo "${GITEA_REPO}" \ - --api-base "${MOKOGITEA_URL}/api/v1" \ - --ci - - if [ $? -ne 0 ]; then - echo "::error::Joomla metadata mismatch โ€” update delivery will fail. Run 'php cli/joomla_metadata_validate.php' locally to see details." - exit 1 - fi diff --git a/.mokogitea/workflows/pre-release.yml b/.mokogitea/workflows/pre-release.yml deleted file mode 100644 index 2c4d3c9..0000000 --- a/.mokogitea/workflows/pre-release.yml +++ /dev/null @@ -1,277 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Release -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /templates/workflows/universal/pre-release.yml.template -# VERSION: 05.02.00 -# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches - -name: "Universal: Pre-Release" - -on: - push: - branches: - - dev - - 'fix/**' - - 'patch/**' - - 'hotfix/**' - - 'bugfix/**' - - 'chore/**' - - alpha - - beta - - rc - workflow_dispatch: - inputs: - stability: - description: 'Pre-release channel' - required: true - type: choice - options: - - development - - alpha - - beta - - release-candidate - -permissions: - contents: write - -env: - GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} - GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }} - GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }} - -jobs: - build: - name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})" - runs-on: release - # Skip on template repos (Template-*) โ€” they scaffold other repos and do not release. - if: >- - !startsWith(github.event.repository.name, 'Template-') && - ( - github.event_name == 'workflow_dispatch' || - github.event_name == 'push' - ) - - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.MOKOGITEA_TOKEN }} - 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 - env: - MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting - run: | - # Use pre-installed /opt/mokocli if available (updated by cron every 6h) - if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/cli/manifest_element.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then - echo Using pre-installed /opt/mokocli - echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV - else - echo Falling back to fresh clone - if ! command -v composer > /dev/null 2>&1; then - sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1 - fi - rm -rf /tmp/mokocli - CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git - git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli - cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet - echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV - fi - - - name: Detect platform - id: platform - run: | - # Auto-detect and update platform if not set in manifest - php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true - php ${MOKO_CLI}/manifest_read.php --path . --github-output - - - 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 - id: meta - if: steps.eligibility.outputs.proceed == 'true' - run: | - # Auto-detect stability from branch name on push, or use input on dispatch - if [ "${{ github.event_name }}" = "push" ]; then - case "${{ github.ref_name }}" in - rc) STABILITY="release-candidate" ;; - alpha) STABILITY="alpha" ;; - beta) STABILITY="beta" ;; - *) STABILITY="development" ;; - esac - else - STABILITY="${{ inputs.stability || 'development' }}" - fi - - case "$STABILITY" in - development) SUFFIX="-dev"; TAG="development" ;; - alpha) SUFFIX="-alpha"; TAG="alpha" ;; - beta) SUFFIX="-beta"; TAG="beta" ;; - release-candidate) SUFFIX="-rc"; TAG="release-candidate" ;; - esac - - # Bump version via CLI: patch for dev/alpha/beta, minor for RC - case "$STABILITY" in - release-candidate) BUMP="minor" ;; - *) BUMP="patch" ;; - esac - - php ${MOKO_CLI}/version_bump.php --path . $([ "$BUMP" = "minor" ] && echo "--minor") 2>/dev/null || true - - # Set stability suffix and verify consistency - VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "00.00.01") - VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//') - - php ${MOKO_CLI}/version_set_platform.php \ - --path . --version "$VERSION" --branch "${{ github.ref_name }}" --stability "$STABILITY" 2>/dev/null || true - php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true - - # Ensure licensing tags (updateservers, dlid) if enabled in manifest.xml - php ${MOKO_CLI}/manifest_licensing.php --path . --fix 2>/dev/null || true - - # Append suffix for output - if [ -n "$SUFFIX" ]; then - VERSION="${VERSION}${SUFFIX}" - fi - - # Commit version bump - git config --local user.email "mokogitea-actions[bot]@mokoconsulting.tech" - git config --local user.name "mokogitea-actions[bot]" - git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git" - git add -A - git diff --cached --quiet || { - git commit -m "chore(version): pre-release bump to ${VERSION} [skip ci]" - git push origin HEAD 2>&1 - } - - # Auto-detect element via manifest_element.php - php ${MOKO_CLI}/manifest_element.php \ - --path . --version "$VERSION" --stability "$STABILITY" \ - --repo "${GITEA_REPO}" --github-output - - # Read back element outputs - EXT_ELEMENT=$(grep '^ext_element=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2) - ZIP_NAME=$(grep '^zip_name=' "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2) - [ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') - [ -z "$ZIP_NAME" ] && ZIP_NAME="${EXT_ELEMENT}-${VERSION}.zip" - - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT" - echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT" - echo "tag=${TAG}" >> "$GITHUB_OUTPUT" - echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT" - echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT" - - echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ===" - - - name: Create release - id: release - if: steps.eligibility.outputs.proceed == 'true' - run: | - TAG="${{ steps.meta.outputs.tag }}" - VERSION="${{ steps.meta.outputs.version }}" - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - php ${MOKO_CLI}/release_create.php \ - --path . --version "$VERSION" --tag "$TAG" \ - --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ - --repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease - - - name: Update release notes from CHANGELOG.md - if: steps.eligibility.outputs.proceed == 'true' - run: | - TAG="${{ steps.meta.outputs.tag }}" - VERSION="${{ steps.meta.outputs.version }}" - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - - # Extract [Unreleased] section from changelog (everything between [Unreleased] and next ## heading) - if [ -f "CHANGELOG.md" ]; then - NOTES=$(awk '/^## \[Unreleased\]/{found=1; next} /^## \[/{if(found) exit} found{print}' CHANGELOG.md) - [ -z "$NOTES" ] && NOTES="Release ${VERSION}" - else - NOTES="Release ${VERSION}" - fi - - # Update release body via API - RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \ - "${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true) - - if [ -n "$RELEASE_ID" ]; then - python3 -c " - import json, urllib.request - body = open('/dev/stdin').read() - payload = json.dumps({'body': body}).encode() - req = urllib.request.Request( - '${API_BASE}/releases/${RELEASE_ID}', - data=payload, method='PATCH', - headers={ - 'Authorization': 'token ${{ secrets.MOKOGITEA_TOKEN }}', - 'Content-Type': 'application/json' - }) - urllib.request.urlopen(req) - " <<< "$NOTES" - echo "Release notes updated from CHANGELOG.md" - fi - - - name: Build package and upload - id: package - if: steps.eligibility.outputs.proceed == 'true' - run: | - VERSION="${{ steps.meta.outputs.version }}" - TAG="${{ steps.meta.outputs.tag }}" - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - php ${MOKO_CLI}/release_package.php \ - --path . --version "$VERSION" --tag "$TAG" \ - --token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \ - --repo "${GITEA_REPO}" --output /tmp || true - - # updates.xml is generated dynamically by MokoGitea license server - # No need to build, commit, or sync updates.xml from workflows - - - name: "Delete lesser pre-release channels (cascade)" - if: steps.eligibility.outputs.proceed == 'true' - continue-on-error: true - run: | - API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" - TOKEN="${{ secrets.MOKOGITEA_TOKEN }}" - - php ${MOKO_CLI}/release_cascade.php \ - --stability "${{ steps.meta.outputs.stability }}" \ - --token "${TOKEN}" \ - --api-base "${API_BASE}" - - - name: Summary - if: always() - run: | - VERSION="${{ steps.meta.outputs.version }}" - STABILITY="${{ steps.meta.outputs.stability }}" - ZIP_NAME="${{ steps.meta.outputs.zip_name }}" - SHA256="${{ steps.package.outputs.sha256_zip }}" - echo "## Pre-Release Complete" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY - echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY - echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY - echo "| Channel | ${STABILITY} |" >> $GITHUB_STEP_SUMMARY - echo "| Package | \`${ZIP_NAME}\` |" >> $GITHUB_STEP_SUMMARY - echo "| SHA-256 | \`${SHA256:-n/a}\` |" >> $GITHUB_STEP_SUMMARY diff --git a/.mokogitea/workflows/rc-revert.yml b/.mokogitea/workflows/rc-revert.yml deleted file mode 100644 index 8874441..0000000 --- a/.mokogitea/workflows/rc-revert.yml +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Universal -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /.mokogitea/workflows/rc-revert.yml -# VERSION: 09.23.00 -# BRIEF: Rename rc/ branch back to dev/ when PR is closed without merge - -name: "RC Revert" - -on: - pull_request: - types: [closed] - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - revert: - name: Rename rc/ back to dev/ - runs-on: ubuntu-latest - if: >- - github.event.pull_request.merged == false && - startsWith(github.event.pull_request.head.ref, 'rc/') - - steps: - - name: Rename branch - env: - BRANCH: ${{ github.event.pull_request.head.ref }} - REPO: ${{ github.repository }} - GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }} - TOKEN: ${{ secrets.MOKOGITEA_TOKEN }} - run: | - set -euo pipefail - # BRANCH is attacker-controlled (PR head ref). Strict allowlist before ANY use. - if ! printf '%s' "$BRANCH" | grep -Eq '^rc/[A-Za-z0-9._/-]+$'; then - echo "::error::Refusing unsafe branch name: $BRANCH"; exit 1 - fi - SUFFIX="${BRANCH#rc/}" - DEV_BRANCH="dev/${SUFFIX}" - API="${GITEA_URL}/api/v1/repos/${REPO}/branches" - - # Create dev/ branch from rc/ branch - STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \ - -H "Authorization: token ${TOKEN}" \ - -H "Content-Type: application/json" \ - -d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \ - "${API}" 2>/dev/null || true) - if [ "$STATUS" = "201" ]; then - echo "Created branch: ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY" - else - echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"; exit 1 - fi - - # Read BRANCH from the environment inside PHP (getenv, no string interpolation -> no PHP injection) - ENCODED=$(php -r 'echo rawurlencode(getenv("BRANCH"));') - STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \ - -H "Authorization: token ${TOKEN}" \ - "${API}/${ENCODED}" 2>/dev/null || true) - if [ "$STATUS" = "204" ]; then - echo "Deleted branch: ${BRANCH}" >> "$GITHUB_STEP_SUMMARY" - else - echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})" - fi - - echo "### RC Reverted" >> "$GITHUB_STEP_SUMMARY" - echo "${BRANCH} โ†’ ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY" diff --git a/.mokogitea/workflows/repo-health.yml b/.mokogitea/workflows/repo-health.yml deleted file mode 100644 index 31736e8..0000000 --- a/.mokogitea/workflows/repo-health.yml +++ /dev/null @@ -1,700 +0,0 @@ -# ============================================================================ -# Copyright (C) 2025 Moko Consulting -# -# This file is part of a Moko Consulting project. -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: MokoGitea.Workflow -# INGROUP: MokoCLI.Validation -# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli -# PATH: /templates/workflows/joomla/repo_health.yml.template -# VERSION: 09.23.00 -# BRIEF: Enforces repository guardrails by validating scripts governance, tooling availability, and core repository health artifacts. -# ============================================================================ - -name: "Generic: Repo Health" - -defaults: - run: - shell: bash - -on: - workflow_dispatch: - inputs: - profile: - description: 'Validation profile: all, scripts, or repo' - required: true - default: all - type: choice - options: - - all - - scripts - - repo - pull_request: - branches: - - main - -permissions: - contents: read - -env: - # Scripts governance policy - SCRIPTS_REQUIRED_DIRS: - SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate - - # Repo health policy - REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,.mokogitea/workflows/ - REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore,README.md,docs/ - REPO_DISALLOWED_DIRS: - REPO_DISALLOWED_FILES: TODO.md,todo.md - - # Extended checks toggles - EXTENDED_CHECKS: "true" - - # File / directory variables - DOCS_INDEX: docs/docs-index.md - SCRIPT_DIR: scripts - WORKFLOWS_DIR: .mokogitea/workflows - SHELLCHECK_PATTERN: '*.sh' - SPDX_FILE_GLOBS: '*.sh,*.php,*.js,*.ts,*.css,*.xml,*.yml,*.yaml' - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - access_check: - name: Access control - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - contents: read - - outputs: - allowed: ${{ steps.perm.outputs.allowed }} - permission: ${{ steps.perm.outputs.permission }} - - steps: - - name: Check actor permission (admin only) - id: perm - env: - TOKEN: ${{ secrets.MOKOGITEA_TOKEN || github.token }} - REPO: ${{ github.repository }} - ACTOR: ${{ github.actor }} - run: | - set -euo pipefail - ALLOWED=false - PERMISSION=unknown - METHOD="" - - # Hardcoded authorized users โ€” always allowed - case "$ACTOR" in - jmiller|mokogitea-actions[bot]) - ALLOWED=true - PERMISSION=admin - METHOD="hardcoded allowlist" - ;; - *) - # Detect platform and check permissions via API - API_BASE="${GITHUB_API_URL:-${GITEA_API_URL:-https://api.github.com}}" - RESP=$(curl -sf -H "Authorization: token ${TOKEN}" \ - "${API_BASE}/repos/${REPO}/collaborators/${ACTOR}/permission" 2>/dev/null || echo '{}') - PERMISSION=$(echo "$RESP" | grep -oP '"permission"\s*:\s*"\K[^"]+' || echo "unknown") - if [ "$PERMISSION" = "admin" ] || [ "$PERMISSION" = "maintain" ] || [ "$PERMISSION" = "owner" ]; then - ALLOWED=true - fi - METHOD="collaborator API" - ;; - esac - - echo "permission=${PERMISSION}" >> "$GITHUB_OUTPUT" - echo "allowed=${ALLOWED}" >> "$GITHUB_OUTPUT" - - { - echo "## Access Authorization" - echo "" - echo "| Field | Value |" - echo "|-------|-------|" - echo "| **Actor** | \`${ACTOR}\` |" - echo "| **Repository** | \`${REPO}\` |" - echo "| **Permission** | \`${PERMISSION}\` |" - echo "| **Method** | ${METHOD} |" - echo "| **Authorized** | ${ALLOWED} |" - echo "" - if [ "$ALLOWED" = "true" ]; then - echo "${ACTOR} authorized (${METHOD})" - else - echo "${ACTOR} is NOT authorized. Requires admin or maintain role." - fi - } >> "${GITHUB_STEP_SUMMARY}" - - - name: Deny execution when not permitted - if: ${{ steps.perm.outputs.allowed != 'true' }} - run: | - set -euo pipefail - printf '%s\n' 'ERROR: Access denied. Admin permission required.' >> "${GITHUB_STEP_SUMMARY}" - exit 1 - - scripts_governance: - name: Scripts governance - needs: access_check - if: ${{ needs.access_check.outputs.allowed == 'true' }} - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: read - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - fetch-depth: 0 - - - name: Scripts folder checks - env: - PROFILE_RAW: ${{ github.event.inputs.profile }} - run: | - set -euo pipefail - - profile="${PROFILE_RAW:-all}" - case "${profile}" in - all|scripts|repo) ;; - *) - printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}" - exit 1 - ;; - esac - - if [ "${profile}" = 'repo' ]; then - { - printf '%s\n' '### Scripts governance' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' 'Status: SKIPPED' - printf '%s\n' 'Reason: profile excludes scripts governance' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - exit 0 - fi - - if [ ! -d "${SCRIPT_DIR}" ]; then - { - printf '%s\n' '### Scripts governance' - printf '%s\n' 'Status: OK (advisory)' - printf '%s\n' 'scripts/ directory not present. No scripts governance enforced.' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - exit 0 - fi - - if [ -n "${SCRIPTS_REQUIRED_DIRS:-}" ]; then IFS=',' read -r -a required_dirs <<< "${SCRIPTS_REQUIRED_DIRS}"; else required_dirs=(); fi - IFS=',' read -r -a allowed_dirs <<< "${SCRIPTS_ALLOWED_DIRS}" - - missing_dirs=() - unapproved_dirs=() - - for d in "${required_dirs[@]}"; do - req="${d%/}" - [ ! -d "${req}" ] && missing_dirs+=("${req}/") - done - - while IFS= read -r d; do - allowed=false - for a in "${allowed_dirs[@]}"; do - a_norm="${a%/}" - [ "${d%/}" = "${a_norm}" ] && allowed=true - done - [ "${allowed}" = false ] && unapproved_dirs+=("${d%/}/") - done < <(find "${SCRIPT_DIR}" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sed 's#^\./##') - - { - printf '%s\n' '### Scripts governance' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' '| Area | Status | Notes |' - printf '%s\n' '|---|---|---|' - - if [ "${#missing_dirs[@]}" -gt 0 ]; then - printf '%s\n' '| Required directories | Warning | Missing required subfolders |' - else - printf '%s\n' '| Required directories | OK | All required subfolders present |' - fi - - if [ "${#unapproved_dirs[@]}" -gt 0 ]; then - printf '%s\n' '| Directory policy | Warning | Unapproved directories detected |' - else - printf '%s\n' '| Directory policy | OK | No unapproved directories |' - fi - - printf '%s\n' '| Enforcement mode | Advisory | scripts folder is optional |' - printf '\n' - - if [ "${#missing_dirs[@]}" -gt 0 ]; then - printf '%s\n' 'Missing required script directories:' - for m in "${missing_dirs[@]}"; do printf '%s\n' "- ${m}"; done - printf '\n' - else - printf '%s\n' 'Missing required script directories: none.' - printf '\n' - fi - - if [ "${#unapproved_dirs[@]}" -gt 0 ]; then - printf '%s\n' 'Unapproved script directories detected:' - for m in "${unapproved_dirs[@]}"; do printf '%s\n' "- ${m}"; done - printf '\n' - else - printf '%s\n' 'Unapproved script directories detected: none.' - printf '\n' - fi - - printf '%s\n' 'Scripts governance completed in advisory mode.' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - - repo_health: - name: Repository health - needs: access_check - if: ${{ needs.access_check.outputs.allowed == 'true' }} - runs-on: ubuntu-latest - timeout-minutes: 20 - permissions: - contents: read - - steps: - - name: Checkout - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - fetch-depth: 0 - - - name: Repository health checks - env: - PROFILE_RAW: ${{ github.event.inputs.profile }} - run: | - set -euo pipefail - - profile="${PROFILE_RAW:-all}" - case "${profile}" in - all|scripts|repo) ;; - *) - printf '%s\n' "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}" - exit 1 - ;; - esac - - if [ "${profile}" = 'scripts' ]; then - { - printf '%s\n' '### Repository health' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' 'Status: SKIPPED' - printf '%s\n' 'Reason: profile excludes repository health' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - exit 0 - fi - - IFS=',' read -r -a required_artifacts <<< "${REPO_REQUIRED_ARTIFACTS}" - IFS=',' read -r -a optional_files <<< "${REPO_OPTIONAL_FILES}" - if [ -n "${REPO_DISALLOWED_DIRS:-}" ]; then IFS=',' read -r -a disallowed_dirs <<< "${REPO_DISALLOWED_DIRS}"; else disallowed_dirs=(); fi - IFS=',' read -r -a disallowed_files <<< "${REPO_DISALLOWED_FILES:-}" - - missing_required=() - missing_optional=() - - # Source directory: src/ or htdocs/ (either is valid for extension repos) - SOURCE_DIR="" - if [ -d "src" ]; then - SOURCE_DIR="src" - elif [ -d "htdocs" ]; then - SOURCE_DIR="htdocs" - elif [ -d "deploy" ] || [ -d "cli" ] || [ -d "monitoring" ]; then - # Platform/tooling repos don't need src/ - SOURCE_DIR="" - else - missing_required+=("src/ or htdocs/ (source directory required)") - fi - - for item in "${required_artifacts[@]}"; do - if printf '%s' "${item}" | grep -q '/$'; then - d="${item%/}" - [ ! -d "${d}" ] && missing_required+=("${item}") - else - [ ! -f "${item}" ] && missing_required+=("${item}") - fi - done - - for f in "${optional_files[@]}"; do - if printf '%s' "${f}" | grep -q '/$'; then - d="${f%/}" - [ ! -d "${d}" ] && missing_optional+=("${f}") - else - [ ! -f "${f}" ] && missing_optional+=("${f}") - fi - done - - for d in "${disallowed_dirs[@]}"; do - d_norm="${d%/}" - [ -d "${d_norm}" ] && missing_required+=("${d_norm}/ (disallowed)") - done - - for f in "${disallowed_files[@]}"; do - [ -f "${f}" ] && missing_required+=("${f} (disallowed)") - done - - git fetch origin --prune - - dev_paths=() - dev_branches=() - - while IFS= read -r b; do - name="${b#origin/}" - if [ "${name}" = 'dev' ]; then - dev_branches+=("${name}") - else - dev_paths+=("${name}") - fi - done < <(git branch -r --list 'origin/dev*' | sed 's/^ *//') - - if [ "${#dev_paths[@]}" -eq 0 ] && [ "${#dev_branches[@]}" -eq 0 ]; then - missing_required+=("dev or dev/* branch") - fi - - content_warnings=() - - if [ -f 'CHANGELOG.md' ] && ! grep -Eq '^# Changelog' CHANGELOG.md; then - content_warnings+=("CHANGELOG.md missing '# Changelog' header") - fi - - if [ -f 'CHANGELOG.md' ] && grep -Eq '^[# ]*Unreleased' CHANGELOG.md; then - content_warnings+=("CHANGELOG.md contains Unreleased section (review release readiness)") - fi - - if [ -f 'LICENSE' ] && ! grep -qiE 'GNU GENERAL PUBLIC LICENSE|GPL' LICENSE; then - content_warnings+=("LICENSE does not look like a GPL text") - fi - - if [ -f 'README.md' ] && ! grep -qiE 'moko|Moko' README.md; then - content_warnings+=("README.md missing expected brand keyword") - fi - - export PROFILE_RAW="${profile}" - export MISSING_REQUIRED="$(printf '%s\n' "${missing_required[@]:-}")" - export MISSING_OPTIONAL="$(printf '%s\n' "${missing_optional[@]:-}")" - export CONTENT_WARNINGS="$(printf '%s\n' "${content_warnings[@]:-}")" - - report_json=$(printf '{"profile":"%s","missing_required":%d,"missing_optional":%d,"content_warnings":%d}' "$profile" "${#missing_required[@]}" "${#missing_optional[@]}" "${#content_warnings[@]}") - - { - printf '%s\n' '### Repository health' - printf '%s\n' "Profile: ${profile}" - printf '%s\n' '| Metric | Value |' - printf '%s\n' '|---|---|' - printf '%s\n' "| Missing required | ${#missing_required[@]} |" - printf '%s\n' "| Missing optional | ${#missing_optional[@]} |" - printf '%s\n' "| Content warnings | ${#content_warnings[@]} |" - printf '\n' - - printf '%s\n' '### Guardrails report (JSON)' - printf '%s\n' '```json' - printf '%s\n' "${report_json}" - printf '%s\n' '```' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - - if [ "${#missing_required[@]}" -gt 0 ]; then - { - printf '%s\n' '### Missing required repo artifacts' - for m in "${missing_required[@]}"; do printf '%s\n' "- ${m}"; done - printf '%s\n' 'ERROR: Guardrails failed. Missing required repository artifacts.' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - exit 1 - fi - - if [ "${#missing_optional[@]}" -gt 0 ]; then - { - printf '%s\n' '### Missing optional repo artifacts' - for m in "${missing_optional[@]}"; do printf '%s\n' "- ${m}"; done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - if [ "${#content_warnings[@]}" -gt 0 ]; then - { - printf '%s\n' '### Repo content warnings' - for m in "${content_warnings[@]}"; do printf '%s\n' "- ${m}"; done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - # -- Joomla-specific checks -- - joomla_findings=() - - MANIFEST="$(find . -maxdepth 2 -name '*.xml' -exec grep -l '/dev/null | head -1 || true)" - if [ -z "${MANIFEST}" ]; then - joomla_findings+=("Joomla XML manifest not found (no *.xml with tag)") - else - if ! grep -qP '' "${MANIFEST}"; then - joomla_findings+=("XML manifest: tag missing") - fi - if ! grep -qP 'type="(component|module|plugin|library|package|template|language)"' "${MANIFEST}"; then - joomla_findings+=("XML manifest: type attribute missing or invalid") - fi - if ! grep -qP '' "${MANIFEST}"; then - joomla_findings+=("XML manifest: tag missing") - fi - if ! grep -qP '' "${MANIFEST}"; then - joomla_findings+=("XML manifest: tag missing") - fi - if ! grep -qP ' missing (required for Joomla 5+)") - fi - fi - - INI_COUNT="$(find . -name '*.ini' -type f 2>/dev/null | wc -l)" - if [ "${INI_COUNT}" -eq 0 ]; then - joomla_findings+=("No .ini language files found") - fi - - if [ ! -f 'updates.xml' ]; then - joomla_findings+=("updates.xml missing in root (required for Joomla update server)") - fi - - if [ -n "${SOURCE_DIR}" ]; then - INDEX_DIRS=("${SOURCE_DIR}" "${SOURCE_DIR}/admin" "${SOURCE_DIR}/site") - for dir in "${INDEX_DIRS[@]}"; do - if [ -d "${dir}" ] && [ ! -f "${dir}/index.html" ]; then - joomla_findings+=("${dir}/index.html missing (directory listing protection)") - fi - done - fi - - if [ "${#joomla_findings[@]}" -gt 0 ]; then - { - printf '%s\n' '### Joomla extension checks' - printf '%s\n' '| Check | Status |' - printf '%s\n' '|---|---|' - for f in "${joomla_findings[@]}"; do - printf '%s\n' "| ${f} | Warning |" - done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - else - { - printf '%s\n' '### Joomla extension checks' - printf '%s\n' 'All Joomla-specific checks passed.' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - extended_enabled="${EXTENDED_CHECKS:-true}" - extended_findings=() - - if [ "${extended_enabled}" = 'true' ]; then - if [ -f '.github/CODEOWNERS' ] || [ -f 'CODEOWNERS' ] || [ -f 'docs/CODEOWNERS' ]; then - : - else - extended_findings+=("CODEOWNERS not found (.github/CODEOWNERS preferred)") - fi - - if ls "${WORKFLOWS_DIR}"/*.yml >/dev/null 2>&1 || ls "${WORKFLOWS_DIR}"/*.yaml >/dev/null 2>&1; then - bad_refs="$(grep -RIn --include='*.yml' --include='*.yaml' -E '^[[:space:]]*uses:[[:space:]]*[^#]+@(main|master)\b' "${WORKFLOWS_DIR}" 2>/dev/null || true)" - if [ -n "${bad_refs}" ]; then - extended_findings+=("Workflows reference actions @main/@master (pin versions): see log excerpt") - { - printf '%s\n' '### Workflow pinning advisory' - printf '%s\n' 'Found uses: entries pinned to main/master:' - printf '%s\n' '```' - printf '%s\n' "${bad_refs}" - printf '%s\n' '```' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - fi - - if [ -f "${DOCS_INDEX}" ]; then - missing_links="" - while IFS= read -r docline; do - for link in $(echo "$docline" | grep -oE '\]\([^)]+\)' | sed 's/\](//' | sed 's/)$//' || true); do - case "$link" in http://*|https://*|"#"*|mailto:*) continue ;; esac - linkpath="${link%%#*}" - linkpath="${linkpath%%\?*}" - [ -z "$linkpath" ] && continue - if [ "${linkpath:0:1}" = "/" ]; then - testpath="${linkpath#/}" - else - testpath="$(dirname "${DOCS_INDEX}")/${linkpath}" - fi - [ ! -e "$testpath" ] && missing_links="${missing_links}${testpath} " - done - done < "${DOCS_INDEX}" - if [ -n "${missing_links}" ]; then - extended_findings+=("docs/docs-index.md contains broken relative links") - { - printf '%s\n' '### Docs index link integrity' - printf '%s\n' 'Broken relative links:' - for bl in ${missing_links}; do - printf '%s\n' "- ${bl}" - done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - fi - - if [ -d "${SCRIPT_DIR}" ]; then - if ! command -v shellcheck >/dev/null 2>&1; then - sudo apt-get update -qq - sudo apt-get install -y shellcheck >/dev/null - fi - - sc_out='' - while IFS= read -r shf; do - [ -z "${shf}" ] && continue - out_one="$(shellcheck -S warning -x "${shf}" 2>/dev/null || true)" - if [ -n "${out_one}" ]; then - sc_out="${sc_out}${out_one}\n" - fi - done < <(find "${SCRIPT_DIR}" -type f -name "${SHELLCHECK_PATTERN}" 2>/dev/null | sort) - - if [ -n "${sc_out}" ]; then - extended_findings+=("ShellCheck warnings detected (advisory)") - sc_head="$(printf '%s' "${sc_out}" | head -n 200)" - { - printf '%s\n' '### ShellCheck (advisory)' - printf '%s\n' '```' - printf '%s\n' "${sc_head}" - printf '%s\n' '```' - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - fi - - spdx_missing=() - IFS=',' read -r -a spdx_globs <<< "${SPDX_FILE_GLOBS}" - spdx_args=() - for g in "${spdx_globs[@]}"; do spdx_args+=("${g}"); done - - while IFS= read -r f; do - [ -z "${f}" ] && continue - if ! head -n 40 "${f}" | grep -q 'SPDX-License-Identifier:'; then - spdx_missing+=("${f}") - fi - done < <(git ls-files "${spdx_args[@]}" 2>/dev/null || true) - - if [ "${#spdx_missing[@]}" -gt 0 ]; then - extended_findings+=("SPDX header missing in some tracked files (advisory)") - { - printf '%s\n' '### SPDX header advisory' - printf '%s\n' 'Files missing SPDX-License-Identifier (first 40 lines scan):' - for f in "${spdx_missing[@]}"; do printf '%s\n' "- ${f}"; done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - stale_cutoff_days=180 - stale_branches="$(git for-each-ref --format='%(refname:short) %(committerdate:unix)' refs/remotes/origin 2>/dev/null | awk -v now="$(date +%s)" -v days="${stale_cutoff_days}" '{if (now-$2 > days*86400) print $1}' | head -50)" - if [ -n "${stale_branches}" ]; then - extended_findings+=("Stale remote branches detected (advisory)") - { - printf '%s\n' '### Git hygiene advisory' - printf '%s\n' "Branches with last commit older than ${stale_cutoff_days} days (sample up to 50):" - while IFS= read -r b; do [ -n "${b}" ] && printf '%s\n' "- ${b}"; done <<< "${stale_branches}" - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - fi - - { - printf '%s\n' '### Guardrails coverage matrix' - printf '%s\n' '| Domain | Status | Notes |' - printf '%s\n' '|---|---|---|' - printf '%s\n' '| Access control | OK | Admin-only execution gate |' - printf '%s\n' '| Release policy | N/A | Releases handled by MokoGitea |' - printf '%s\n' '| Scripts governance | OK | Directory policy and advisory reporting |' - printf '%s\n' '| Repo required artifacts | OK | Required, optional, disallowed enforcement |' - printf '%s\n' '| Repo content heuristics | OK | Brand, license, changelog structure |' - if [ "${extended_enabled}" = 'true' ]; then - if [ "${#extended_findings[@]}" -gt 0 ]; then - printf '%s\n' '| Extended checks | Warning | See extended findings below |' - else - printf '%s\n' '| Extended checks | OK | No findings |' - fi - else - printf '%s\n' '| Extended checks | SKIPPED | EXTENDED_CHECKS disabled |' - fi - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - - if [ "${extended_enabled}" = 'true' ] && [ "${#extended_findings[@]}" -gt 0 ]; then - { - printf '%s\n' '### Extended findings (advisory)' - for f in "${extended_findings[@]}"; do printf '%s\n' "- ${f}"; done - printf '\n' - } >> "${GITHUB_STEP_SUMMARY}" - fi - - printf '%s\n' 'Repository health guardrails passed.' >> "${GITHUB_STEP_SUMMARY}" - - - site-health: - name: Site Health - runs-on: ubuntu-latest - if: github.event_name == 'workflow_dispatch' - steps: - - uses: actions/checkout@v4 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.3' - - - name: Uptime check - if: env.URLS != '' - run: | - echo "$URLS" > /tmp/urls.txt - php monitoring/uptime-probe.php --urls /tmp/urls.txt --timeout 15 || echo "::warning::Some sites are down" - rm -f /tmp/urls.txt - env: - URLS: ${{ vars.MONITORED_URLS }} - - - name: SSL certificate check - if: env.DOMAINS != '' - run: | - echo "$DOMAINS" > /tmp/domains.txt - php monitoring/ssl-check.php --domains /tmp/domains.txt --warn-days 30 || echo "::warning::SSL certificates expiring soon" - rm -f /tmp/domains.txt - env: - DOMAINS: ${{ vars.MONITORED_DOMAINS }} - - - name: Summary - if: always() - run: | - echo "### Site Health" >> $GITHUB_STEP_SUMMARY - echo "Uptime and SSL checks completed." >> $GITHUB_STEP_SUMMARY - - # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - # Issue Reporter โ€” file issues for failed gates - # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - report-scripts: - name: "Report: Scripts Governance" - needs: [access_check, scripts_governance] - if: >- - always() && - needs.scripts_governance.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 - - report-health: - name: "Report: Repository Health" - needs: [access_check, repo_health] - if: >- - always() && - needs.repo_health.result == 'failure' - uses: ./.mokogitea/workflows/ci-issue-reporter.yml - with: - gate: "Repository Health" - workflow: "Repo Health" - severity: error - details: "Repository health checks failed โ€” missing required artifacts, disallowed files, or content warnings. Check the CI run summary." - secrets: inherit diff --git a/.mokogitea/workflows/standards-compliance.yml b/.mokogitea/workflows/standards-compliance.yml deleted file mode 100644 index 2a7abc3..0000000 --- a/.mokogitea/workflows/standards-compliance.yml +++ /dev/null @@ -1,2613 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# SPDX-License-Identifier: GPL-3.0-or-later -# FILE INFORMATION -# DEFGROUP: GitHub.Workflow -# INGROUP: MokoCLI.Compliance -# REPO: https://github.com/mokoconsulting-tech/mokocli -# PATH: /.mokogitea/workflows/standards-compliance.yml -# VERSION: 04.06.00 -# BRIEF: MokoCLI compliance validation workflow -# NOTE: Validates repository structure, documentation, and coding standards - -name: "Generic: Standards Compliance" - -# โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— -# โ•‘ MOKOSTANDARDS COMPLIANCE WORKFLOW โ•‘ -# โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ -# โ•‘ โ•‘ -# โ•‘ 28 checks across 4 priority tiers: โ•‘ -# โ•‘ โ•‘ -# โ•‘ TIER 1 โ€” CRITICAL (must pass) โ•‘ -# โ•‘ secret-scanning, license-compliance, repository-structure, โ•‘ -# โ•‘ coding-standards, version-consistency โ•‘ -# โ•‘ โ•‘ -# โ•‘ TIER 2 โ€” IMPORTANT (should pass) โ•‘ -# โ•‘ workflow-validation, documentation-quality, readme-completeness, โ•‘ -# โ•‘ git-hygiene, script-integrity โ•‘ -# โ•‘ โ•‘ -# โ•‘ TIER 3 โ€” QUALITY (code metrics) โ•‘ -# โ•‘ line-length, file-naming, insecure-patterns, complexity, โ•‘ -# โ•‘ duplication, dead-code โ•‘ -# โ•‘ โ•‘ -# โ•‘ TIER 4 โ€” SUPPLEMENTARY (informational) โ•‘ -# โ•‘ file-size, binary, todo, deps, links, api-docs, accessibility, โ•‘ -# โ•‘ performance, enterprise, health, terraform โ•‘ -# โ•‘ โ•‘ -# โ•‘ File size: warning >15MB, critical >20MB โ•‘ -# โ•‘ Exempt: .mmdb, .woff2, .woff, .ttf, .otf โ•‘ -# โ•‘ โ•‘ -# โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - -env: - WORKFLOW_VERSION: "04.04.01" - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -# MokoCLI Policy Compliance: -# - File formatting: Enforces organizational coding standards -# - Reference: docs/policy/file-formatting.md - -# โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -# โ”‚ WORKFLOW FLOW DIAGRAM โ”‚ -# โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -# -# TRIGGER: Push/PR to main/dev/rc branches -# โ”‚ -# โ–ผ -# โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -# โ”‚ PARALLEL VALIDATION CHECKS โ”‚ -# โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -# โ”‚ -# โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -# โ–ผ โ–ผ โ–ผ โ–ผ โ–ผ -# โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -# โ”‚Repository โ”‚File Header โ”‚Code Styleโ”‚ โ”‚ Docs โ”‚ โ”‚ License โ”‚ -# โ”‚Structureโ”‚ โ”‚ Validationโ”‚ โ”‚ Check โ”‚ โ”‚ Check โ”‚ โ”‚ Check โ”‚ -# โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -# โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -# โ–ผ โ–ผ โ–ผ โ–ผ โ–ผ -# โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -# โ”‚ Check โ”‚ โ”‚ Verify โ”‚ โ”‚ Run โ”‚ โ”‚ Check โ”‚ โ”‚ Verify โ”‚ -# โ”‚Required โ”‚ โ”‚Copyright โ”‚ โ”‚ Linters โ”‚ โ”‚README โ”‚ โ”‚SPDX-ID โ”‚ -# โ”‚ Dirs โ”‚ โ”‚ Header โ”‚ โ”‚(Python, โ”‚ โ”‚ Exists โ”‚ โ”‚ Present โ”‚ -# โ”‚ โ”‚ โ”‚ Format โ”‚ โ”‚PHP,YAML) โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -# โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -# โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ -# โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -# โ”‚ -# โ–ผ -# โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -# โ”‚ All Checks Pass?โ”‚ -# โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ -# โ”‚ โ”‚ -# YES โ”‚ โ”‚ NO -# โ–ผ โ–ผ -# โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” -# โ”‚ SUCCESS โ”‚ โ”‚ CREATE ISSUE โ”‚ -# โ”‚ Summary โ”‚ โ”‚ with Failure โ”‚ -# โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ Details โ”‚ -# โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ - -on: - push: - branches: [main, dev/**, rc/**, version/**] - pull_request: - branches: [main, dev/**, rc/**] - workflow_dispatch: - -permissions: - contents: read - pull-requests: write - issues: write - -jobs: - # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - # TIER 1 โ€” CRITICAL (must pass, blocks merge) - # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - secret-scanning: - name: Secret Scanning - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Scan for Secrets - run: | - set -x - echo "## ๐Ÿ”’ Secret Scanning" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Scanning for hardcoded secrets and credentials." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Define secret patterns - VIOLATIONS=0 - - # Check for common secret patterns - echo "### Secret Patterns" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Helper: scan with a pattern, show results with file:line, return count - scan_pattern() { - local label="$1" icon="$2" tmpfile="$3" - local count=0 - if [ -f "$tmpfile" ]; then - count=$(wc -l < "$tmpfile") - fi - if [ "$count" -gt 0 ]; then - echo "${icon} **${label}**: ${count} finding(s)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "View locations" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| File | Line | Match |" >> $GITHUB_STEP_SUMMARY - echo "|------|------|-------|" >> $GITHUB_STEP_SUMMARY - head -20 "$tmpfile" | while IFS= read -r line; do - FILE=$(echo "$line" | cut -d: -f1 | sed 's|^\./||') - LINENO=$(echo "$line" | cut -d: -f2) - MATCH=$(echo "$line" | cut -d: -f3- | head -c 80 | sed 's/|/\\|/g') - echo "| \`${FILE}\` | ${LINENO} | \`${MATCH}\` |" >> $GITHUB_STEP_SUMMARY - done - if [ "$count" -gt 20 ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "*... and $((count - 20)) more*" >> $GITHUB_STEP_SUMMARY - fi - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - VIOLATIONS=$((VIOLATIONS + count)) - fi - } - - # Pattern 1: password/secret assignments - grep -r -n -E "(password|passwd|pwd|secret|api[_-]?key|token).*=.*['\"]" . \ - --include="*.php" --include="*.py" --include="*.js" --include="*.ts" \ - --exclude-dir=".git" --exclude-dir="vendor" --exclude-dir="node_modules" 2>/dev/null | \ - grep -v -E '(test|example|sample|getenv|getString|getArgument|config\[|/\.\*/|^\s*//|^\s*\*|CREDENTIAL_PATTERNS|SecurityValidator|SECRET_PATTERN|===|!==|ApiClient|str_contains|gen_wrappers)' | \ - grep -v "= ''" | grep -v '= ""' | grep -v '\$this->config' | \ - grep -v 'type="password"' | grep -v 'type="text"' | grep -v 'name="password"' | grep -v 'name="secretkey"' | \ - grep -v '/dev/null > /tmp/secrets2.txt || true - scan_pattern "Private keys" "โŒ" /tmp/secrets2.txt - - # Pattern 3: AWS keys - grep -r -n -E "AKIA[0-9A-Z]{16}" . \ - --include="*.php" --include="*.py" --include="*.js" --include="*.txt" --include="*.env" \ - --exclude-dir=".git" --exclude-dir="vendor" --exclude-dir="node_modules" 2>/dev/null > /tmp/secrets3.txt || true - scan_pattern "AWS access keys" "โŒ" /tmp/secrets3.txt - - # Pattern 4: GitHub tokens - grep -r -n -E "gh[ps]_[a-zA-Z0-9]{36}" . \ - --include="*.php" --include="*.py" --include="*.js" --include="*.txt" --include="*.env" \ - --exclude-dir=".git" --exclude-dir="vendor" --exclude-dir="node_modules" 2>/dev/null > /tmp/secrets4.txt || true - scan_pattern "GitHub tokens" "โŒ" /tmp/secrets4.txt - - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "$VIOLATIONS" -gt 0 ]; then - echo "**Total Violations**: $VIOLATIONS" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "View detected secrets (file paths only)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - cat /tmp/secrets*.txt 2>/dev/null | cut -d: -f1 | sort -u >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Action Required**: Remove hardcoded secrets immediately!" >> $GITHUB_STEP_SUMMARY - echo "Use environment variables or secrets management instead." >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "โœ… No hardcoded secrets detected" >> $GITHUB_STEP_SUMMARY - fi - - license-compliance: - name: License Header Validation - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Check SPDX Headers - run: | - set -x - echo "### SPDX License Header Check" >> $GITHUB_STEP_SUMMARY - - # Count source files with and without SPDX headers - TOTAL_PHP=0 - WITH_SPDX_PHP=0 - - if find . -name "*.php" -type f ! -path "./vendor/*" | head -1 | grep -q .; then - TOTAL_PHP=$(find . -name "*.php" -type f ! -path "./vendor/*" | wc -l) - WITH_SPDX_PHP=$(find . -name "*.php" -type f ! -path "./vendor/*" -exec grep -l "SPDX-License-Identifier" {} \; | wc -l) - fi - - if [ "$TOTAL_PHP" -gt 0 ]; then - PERCENT=$((WITH_SPDX_PHP * 100 / TOTAL_PHP)) - echo "- PHP files: $WITH_SPDX_PHP/$TOTAL_PHP ($PERCENT%) with SPDX headers" >> $GITHUB_STEP_SUMMARY - - if [ "$PERCENT" -lt 80 ]; then - echo "โš ๏ธ Less than 80% of PHP files have SPDX headers" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… Good SPDX header coverage" >> $GITHUB_STEP_SUMMARY - fi - fi - - - name: Validate License File - run: | - set -x - echo "" >> $GITHUB_STEP_SUMMARY - echo "### License File Validation" >> $GITHUB_STEP_SUMMARY - - if [ ! -f "LICENSE" ]; then - echo "โŒ LICENSE file not found" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### โŒ Validation Failed: LICENSE File Missing" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Error:** LICENSE file is required for all MokoCLI-compliant repositories" >> $GITHUB_STEP_SUMMARY - echo "**Action Required:** Add LICENSE file with appropriate open-source license (GPL-3.0-or-later recommended)" >> $GITHUB_STEP_SUMMARY - echo "" - echo "โŒ ERROR: LICENSE file not found - This is a critical requirement" - exit 1 - fi - - # Check license type - if grep -qi "GNU GENERAL PUBLIC LICENSE" LICENSE; then - VERSION=$(grep -i "Version 3" LICENSE || echo "") - if [ -n "$VERSION" ]; then - echo "โœ… GPL-3.0-or-later license detected" >> $GITHUB_STEP_SUMMARY - else - echo "โš ๏ธ GPL license detected but version unclear" >> $GITHUB_STEP_SUMMARY - fi - elif grep -qi "MIT License" LICENSE; then - echo "โœ… MIT license detected" >> $GITHUB_STEP_SUMMARY - elif grep -qi "Apache License" LICENSE; then - echo "โœ… Apache license detected" >> $GITHUB_STEP_SUMMARY - else - echo "โ„น๏ธ License type could not be automatically detected" >> $GITHUB_STEP_SUMMARY - fi - - repository-structure: - name: Repository Structure Validation - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Check Required Directories - run: | - set -x - echo "## ๐Ÿ“ Repository Structure Validation" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - MISSING=0 - PRESENT=0 - TOTAL=1 - - echo "### Required Directories" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Directory | Status | Files | Size | Notes |" >> $GITHUB_STEP_SUMMARY - echo "|-----------|--------|-------|------|-------|" >> $GITHUB_STEP_SUMMARY - - # Check required directories - for dir in .mokogitea; do - if [ -d "$dir" ]; then - FILE_COUNT=$(find "$dir" -type f 2>/dev/null | wc -l) - DIR_SIZE=$(du -sh "$dir" 2>/dev/null | cut -f1) - echo "| $dir/ | โœ… Pass | $FILE_COUNT files | $DIR_SIZE | Complete |" >> $GITHUB_STEP_SUMMARY - PRESENT=$((PRESENT + 1)) - else - echo "| $dir/ | โŒ **Missing** | - | - | **Action Required** |" >> $GITHUB_STEP_SUMMARY - MISSING=$((MISSING + 1)) - fi - done - - echo "" >> $GITHUB_STEP_SUMMARY - PERCENT=$((PRESENT * 100 / TOTAL)) - echo "**Compliance Score:** $PERCENT% ($PRESENT/$TOTAL directories present)" >> $GITHUB_STEP_SUMMARY - - if [ "$MISSING" -gt 0 ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "### ๐Ÿ”ด Critical Issues: $MISSING" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Remediation Steps:**" >> $GITHUB_STEP_SUMMARY - [ ! -d ".mokogitea" ] && echo "- Create .mokogitea directory: \`mkdir -p .mokogitea/workflows\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "๐Ÿ“š Reference: [MokoCLI Repository Structure](https://github.com/mokoconsulting-tech/mokocli/tree/main/docs/policy/core-structure.md)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### โŒ Validation Failed: Required Directories Missing" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Status:** Repository structure does not meet MokoCLI requirements" >> $GITHUB_STEP_SUMMARY - echo "**Missing:** $MISSING required director(y|ies)" >> $GITHUB_STEP_SUMMARY - echo "**Compliance:** $PERCENT% ($PRESENT/$TOTAL directories present)" >> $GITHUB_STEP_SUMMARY - echo "" - echo "โŒ ERROR: Required directories missing - See job summary for remediation steps" - exit 1 - fi - - - name: Check Required Files - run: | - set -x - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Required Files" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - MISSING=0 - PRESENT=0 - TOTAL=5 - - echo "| File | Status | Size | Last Modified | Notes |" >> $GITHUB_STEP_SUMMARY - echo "|------|--------|------|---------------|-------|" >> $GITHUB_STEP_SUMMARY - - # Check required files (CHANGELOG handled separately via find -iname to support src/ChangeLog.md) - for file in README.md LICENSE CONTRIBUTING.md SECURITY.md .editorconfig; do - if [ -f "$file" ]; then - FILE_SIZE=$(wc -c < "$file" 2>/dev/null | awk '{printf "%.1f KB", $1/1024}') - LAST_MOD=$(stat -c %y "$file" 2>/dev/null | cut -d' ' -f1 || echo "Unknown") - CONTENT_CHECK="" - - # Basic content validation - case "$file" in - "README.md") - LINES=$(wc -l < "$file") - [ "$LINES" -lt 10 ] && CONTENT_CHECK="โš ๏ธ Too short" - ;; - "LICENSE") - [ $(wc -c < "$file") -lt 100 ] && CONTENT_CHECK="โš ๏ธ Incomplete?" - ;; - esac - - echo "| $file | โœ… Pass | $FILE_SIZE | $LAST_MOD | Complete $CONTENT_CHECK |" >> $GITHUB_STEP_SUMMARY - PRESENT=$((PRESENT + 1)) - else - echo "| $file | โŒ **Missing** | - | - | **Required** |" >> $GITHUB_STEP_SUMMARY - MISSING=$((MISSING + 1)) - fi - done - - echo "" >> $GITHUB_STEP_SUMMARY - PERCENT=$((PRESENT * 100 / TOTAL)) - echo "**Compliance Score:** $PERCENT% ($PRESENT/$TOTAL files present)" >> $GITHUB_STEP_SUMMARY - - if [ "$MISSING" -gt 0 ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "### ๐Ÿ”ด Critical Issues: $MISSING" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Remediation Steps:**" >> $GITHUB_STEP_SUMMARY - [ ! -f "README.md" ] && echo "- Create README.md: Use [template](https://github.com/mokoconsulting-tech/mokocli/tree/main/templates/docs/required/README.md)" >> $GITHUB_STEP_SUMMARY - [ ! -f "LICENSE" ] && echo "- Add LICENSE file: Choose from [OSI-approved licenses](https://opensource.org/licenses)" >> $GITHUB_STEP_SUMMARY - [ ! -f "CONTRIBUTING.md" ] && echo "- Create CONTRIBUTING.md: Use [template](https://github.com/mokoconsulting-tech/mokocli/tree/main/templates/docs/required/CONTRIBUTING.md)" >> $GITHUB_STEP_SUMMARY - [ ! -f "SECURITY.md" ] && echo "- Create SECURITY.md: Use [template](https://github.com/mokoconsulting-tech/mokocli/tree/main/templates/docs/required/SECURITY.md)" >> $GITHUB_STEP_SUMMARY - [ ! -f ".editorconfig" ] && echo "- Add .editorconfig: Use [template](https://github.com/mokoconsulting-tech/mokocli/tree/main/templates/.editorconfig)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "๐Ÿ“š Reference: [MokoCLI File Requirements](https://github.com/mokoconsulting-tech/mokocli/tree/main/docs/policy/file-header-standards.md)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### โŒ Validation Failed: Required Files Missing" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Status:** Repository files do not meet MokoCLI requirements" >> $GITHUB_STEP_SUMMARY - echo "**Missing:** $MISSING required file(s)" >> $GITHUB_STEP_SUMMARY - echo "**Compliance:** $PERCENT% ($PRESENT/$TOTAL files present)" >> $GITHUB_STEP_SUMMARY - echo "" - echo "โŒ ERROR: Required files missing - See job summary for remediation steps" - exit 1 - fi - - coding-standards: - name: Coding Standards Check - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Check for Tab Characters - run: | - set -x - echo "### Tab Character Detection" >> $GITHUB_STEP_SUMMARY - - # Policy: Tabs are DEFAULT. Only check for tabs in files that REQUIRE spaces. - # Languages requiring spaces: YAML, Python, Haskell, F#, CoffeeScript, Nim, JSON, RST - TABS_IN_SPACES_FILES=$(find . -type f \ - \( -name "*.yml" -o -name "*.yaml" \ - -o -name "*.py" \ - -o -name "*.hs" -o -name "*.lhs" \ - -o -name "*.fs" -o -name "*.fsx" -o -name "*.fsi" \ - -o -name "*.coffee" -o -name "*.litcoffee" \ - -o -name "*.nim" -o -name "*.nims" -o -name "*.nimble" \ - -o -name "*.json" \ - -o -name "*.rst" \) \ - ! -path "./vendor/*" \ - ! -path "./node_modules/*" \ - ! -path "./.git/*" \ - -exec grep -l $'\t' {} \; 2>/dev/null | head -10) - - if [ -n "$TABS_IN_SPACES_FILES" ]; then - echo "โš ๏ธ Tab characters found in files that require spaces:" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "$TABS_IN_SPACES_FILES" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "These languages require spaces (tabs will break): YAML, Python, Haskell, F#, CoffeeScript, Nim, JSON, RST" >> $GITHUB_STEP_SUMMARY - echo "All other files (including .md, .ps1, LICENSE, etc.) may use tabs per MokoCLI policy" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… No tabs found in files requiring spaces" >> $GITHUB_STEP_SUMMARY - echo "Note: Tabs are allowed in most files (policy default). Only checked files requiring spaces." >> $GITHUB_STEP_SUMMARY - fi - - - name: Check File Encoding - run: | - set -x - echo "" >> $GITHUB_STEP_SUMMARY - echo "### File Encoding Check" >> $GITHUB_STEP_SUMMARY - - # Check for UTF-8 encoding (ASCII is a subset of UTF-8 and is acceptable) - NON_UTF8=$(find . -type f \( -name "*.php" -o -name "*.js" -o -name "*.md" \) \ - ! -path "./vendor/*" \ - ! -path "./node_modules/*" \ - ! -path "./.git/*" \ - -exec file {} \; | grep -v "UTF-8" | grep -v "ASCII" | head -5 || true) - - if [ -n "$NON_UTF8" ]; then - echo "โš ๏ธ Non-UTF-8 files detected:" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "$NON_UTF8" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… All source files appear to be UTF-8 encoded" >> $GITHUB_STEP_SUMMARY - fi - - - name: Check Line Endings - run: | - set -x - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Line Ending Check" >> $GITHUB_STEP_SUMMARY - - # Check for CRLF line endings - CRLF_FILES=$(find . -type f \( -name "*.php" -o -name "*.js" -o -name "*.md" \) \ - ! -path "./vendor/*" \ - ! -path "./node_modules/*" \ - ! -path "./.git/*" \ - -exec file {} \; | grep "CRLF" | head -5 || true) - - if [ -n "$CRLF_FILES" ]; then - echo "โš ๏ธ Files with CRLF line endings found:" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "$CRLF_FILES" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "MokoCLI requires LF line endings" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… Line endings are consistent (LF)" >> $GITHUB_STEP_SUMMARY - fi - - version-consistency: - name: Version Consistency Check - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Set up PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.31.0 - with: - php-version: '8.1' - extensions: json - tools: composer - coverage: none - - - name: Setup MokoCLI tools - env: - GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' - run: | - git clone --depth 1 --branch version/04 --quiet \ - "https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/mokocli.git" \ - /tmp/mokostandards 2>/dev/null || true - if [ -d "/tmp/mokostandards" ] && [ -f "/tmp/mokostandards/composer.json" ]; then - cd /tmp/mokostandards - composer install --no-dev --no-interaction --quiet 2>/dev/null || true - fi - - - name: Run Version Consistency Check - id: version_check - run: | - set -x - echo "## ๐Ÿ”ข Version Consistency Validation" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Use MokoCLI tools (no Composer needed on the governed repo) - if [ -f "/tmp/mokostandards/api/validate/check_version_consistency.php" ]; then - php /tmp/mokostandards/api/validate/check_version_consistency.php --path . --verbose 2>&1 | tee /tmp/version-check.log - EXIT_CODE=${PIPESTATUS[0]} - elif [ -f "api/validate/check_version_consistency.php" ]; then - php api/validate/check_version_consistency.php --path . --verbose 2>&1 | tee /tmp/version-check.log - EXIT_CODE=${PIPESTATUS[0]} - else - echo "โญ๏ธ MokoCLI tools not available โ€” skipping version check" >> $GITHUB_STEP_SUMMARY - exit 0 - fi - - echo '```' >> $GITHUB_STEP_SUMMARY - cat /tmp/version-check.log >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - - if [ "$EXIT_CODE" -eq 0 ]; then - echo "โœ… All version numbers are consistent" >> $GITHUB_STEP_SUMMARY - else - echo "โŒ Version drift detected" >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - - # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - # TIER 2 โ€” IMPORTANT (should pass) - # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - workflow-validation: - name: Workflow Configuration Check - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Check Required Workflows - run: | - set -x - echo "### GitHub Actions Workflows" >> $GITHUB_STEP_SUMMARY - - WORKFLOWS_DIR=".mokogitea/workflows" - - if [ ! -d "$WORKFLOWS_DIR" ]; then - echo "โŒ No workflows directory found" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### โŒ Validation Failed: Workflows Directory Missing" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Error:** .mokogitea/workflows directory is required for CI/CD automation" >> $GITHUB_STEP_SUMMARY - echo "**Action Required:** Create .mokogitea/workflows directory and add GitHub Actions workflows" >> $GITHUB_STEP_SUMMARY - echo "" - echo "โŒ ERROR: .mokogitea/workflows directory not found" - exit 1 - fi - - # Check for recommended workflows - CI_FOUND=false - for wf in ci.yml build.yml ci-dolibarr.yml ci-joomla.yml; do - if [ -f "$WORKFLOWS_DIR/$wf" ]; then - echo "โœ… CI workflow present ($wf)" >> $GITHUB_STEP_SUMMARY - CI_FOUND=true - break - fi - done - if [ "$CI_FOUND" = "false" ]; then - echo "โš ๏ธ No CI workflow found (ci.yml, build.yml, ci-dolibarr.yml, or ci-joomla.yml)" >> $GITHUB_STEP_SUMMARY - fi - - if [ -f "$WORKFLOWS_DIR/codeql-analysis.yml" ]; then - echo "โœ… CodeQL security scanning present" >> $GITHUB_STEP_SUMMARY - else - echo "โš ๏ธ CodeQL workflow not found" >> $GITHUB_STEP_SUMMARY - fi - - # Check for MokoCLI-synced workflows - for wf in deploy-dev.yml deploy-demo.yml deploy-rs.yml sync-version-on-merge.yml auto-release.yml standards-compliance.yml enterprise-firewall-setup.yml; do - if [ -f "$WORKFLOWS_DIR/$wf" ]; then - echo "โœ… ${wf}" >> $GITHUB_STEP_SUMMARY - else - echo "โš ๏ธ ${wf} not found (synced from MokoCLI)" >> $GITHUB_STEP_SUMMARY - fi - done - - - name: Validate Workflow Syntax - run: | - set -x - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Workflow YAML Syntax" >> $GITHUB_STEP_SUMMARY - - INVALID=0 - for workflow in $(find .mokogitea/workflows -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" \) 2>/dev/null); do - if [ -f "$workflow" ]; then - if python3 -c "import yaml, sys; yaml.safe_load(open(sys.argv[1]))" "$workflow" 2>/dev/null; then - echo "โœ… $(basename $workflow)" >> $GITHUB_STEP_SUMMARY - else - echo "โŒ $(basename $workflow) - invalid YAML" >> $GITHUB_STEP_SUMMARY - INVALID=$((INVALID + 1)) - fi - fi - done - - if [ "$INVALID" -gt 0 ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "### โŒ Validation Failed: Invalid Workflow YAML Syntax" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Error:** $INVALID workflow file(s) have invalid YAML syntax" >> $GITHUB_STEP_SUMMARY - echo "**Action Required:** Fix YAML syntax errors in the marked workflow files" >> $GITHUB_STEP_SUMMARY - echo "**Tool:** Run \`python3 -c \"import yaml; yaml.safe_load(open('.mokogitea/workflows/FILE.yml'))\"\` locally" >> $GITHUB_STEP_SUMMARY - echo "" - echo "โŒ ERROR: $INVALID workflow file(s) with invalid YAML syntax" - exit 1 - fi - - echo "" >> $GITHUB_STEP_SUMMARY - echo "### โœ… All Workflow Files Have Valid YAML Syntax" >> $GITHUB_STEP_SUMMARY - echo "" - echo "โœ… SUCCESS: All workflow files passed YAML validation" - - - name: Validate CodeQL Configuration - if: hashFiles('.mokogitea/workflows/codeql-analysis.yml') != '' - run: | - set -e - echo "" >> $GITHUB_STEP_SUMMARY - echo "### CodeQL Language Configuration" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Inline validation (rewritten from Python to bash for PHP-only architecture) - CODEQL_FILE=".mokogitea/workflows/codeql-analysis.yml" - - if [ ! -f "$CODEQL_FILE" ]; then - echo "โš ๏ธ CodeQL workflow file not found" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### โš ๏ธ CodeQL Workflow Not Found" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Status:** CodeQL workflow file not present - skipping language validation" >> $GITHUB_STEP_SUMMARY - echo "" - echo "โš ๏ธ INFO: CodeQL workflow not found - Skipping validation" - exit 0 - fi - - echo "**CodeQL Configuration Analysis**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Extract configured languages from workflow - LANGUAGES=$(grep -A5 "language:" "$CODEQL_FILE" | grep -oP "(?<=')[^']+(?=')" | tr '\n' ' ' || echo "") - - # Check if this is a configuration-only scan (no languages specified) - if grep -q "category.*language:config" "$CODEQL_FILE"; then - echo "**Scan Type:** Configuration-only (no language matrix)" >> $GITHUB_STEP_SUMMARY - echo "**Status:** โœ… Valid configuration for PHP-only repository" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "This CodeQL workflow scans YAML, JSON, shell scripts for security issues." >> $GITHUB_STEP_SUMMARY - echo "PHP security is handled by SecurityValidator enterprise library." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "โœ… SUCCESS: CodeQL configuration-only scan properly configured" - exit 0 - fi - - if [ -z "$LANGUAGES" ]; then - echo "โŒ No languages configured in CodeQL workflow" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### โŒ Validation Failed: CodeQL Languages Not Configured" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Error:** CodeQL workflow exists but has no languages configured" >> $GITHUB_STEP_SUMMARY - echo "**Action Required:** Configure appropriate languages in codeql-analysis.yml" >> $GITHUB_STEP_SUMMARY - echo "" - echo "โŒ ERROR: No languages configured in CodeQL workflow" - exit 1 - fi - - echo "**Configured Languages:** $LANGUAGES" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Validate language presence in repository - INVALID_LANGS="" - VALID_LANGS="" - - for LANG in $LANGUAGES; do - case "$LANG" in - python) - # Check for Python files (should be none in v04.00.04) - if find . -name "*.py" -type f ! -path "./.git/*" | grep -q .; then - VALID_LANGS="$VALID_LANGS python" - echo "โœ… Python: Found Python files" >> $GITHUB_STEP_SUMMARY - else - INVALID_LANGS="$INVALID_LANGS python" - echo "โŒ Python: No Python files found (PHP-only repository)" >> $GITHUB_STEP_SUMMARY - fi - ;; - javascript|typescript) - # Check for JS/TS files - if find . \( -name "*.js" -o -name "*.ts" -o -name "*.json" \) -type f ! -path "./.git/*" ! -path "./node_modules/*" | grep -q .; then - VALID_LANGS="$VALID_LANGS $LANG" - echo "โœ… $LANG: Found JavaScript/TypeScript/JSON files" >> $GITHUB_STEP_SUMMARY - else - INVALID_LANGS="$INVALID_LANGS $LANG" - echo "โš ๏ธ $LANG: No JavaScript/TypeScript files found" >> $GITHUB_STEP_SUMMARY - fi - ;; - java) - if find . -name "*.java" -type f ! -path "./.git/*" | grep -q .; then - VALID_LANGS="$VALID_LANGS java" - echo "โœ… Java: Found Java files" >> $GITHUB_STEP_SUMMARY - else - INVALID_LANGS="$INVALID_LANGS java" - echo "โš ๏ธ Java: No Java files found" >> $GITHUB_STEP_SUMMARY - fi - ;; - go) - if find . -name "*.go" -type f ! -path "./.git/*" | grep -q .; then - VALID_LANGS="$VALID_LANGS go" - echo "โœ… Go: Found Go files" >> $GITHUB_STEP_SUMMARY - else - INVALID_LANGS="$INVALID_LANGS go" - echo "โš ๏ธ Go: No Go files found" >> $GITHUB_STEP_SUMMARY - fi - ;; - cpp|c) - if find . \( -name "*.cpp" -o -name "*.c" -o -name "*.h" \) -type f ! -path "./.git/*" | grep -q .; then - VALID_LANGS="$VALID_LANGS $LANG" - echo "โœ… $LANG: Found C/C++ files" >> $GITHUB_STEP_SUMMARY - else - INVALID_LANGS="$INVALID_LANGS $LANG" - echo "โš ๏ธ $LANG: No C/C++ files found" >> $GITHUB_STEP_SUMMARY - fi - ;; - ruby) - if find . -name "*.rb" -type f ! -path "./.git/*" | grep -q .; then - VALID_LANGS="$VALID_LANGS ruby" - echo "โœ… Ruby: Found Ruby files" >> $GITHUB_STEP_SUMMARY - else - INVALID_LANGS="$INVALID_LANGS ruby" - echo "โš ๏ธ Ruby: No Ruby files found" >> $GITHUB_STEP_SUMMARY - fi - ;; - *) - echo "โš ๏ธ $LANG: Unknown language, skipping validation" >> $GITHUB_STEP_SUMMARY - ;; - esac - done - - echo "" >> $GITHUB_STEP_SUMMARY - - # Report results - if [ -n "$INVALID_LANGS" ]; then - echo "**โš ๏ธ Warning:** Some configured languages may not have corresponding files:" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "Invalid languages: $INVALID_LANGS" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Note:** This is informational. CodeQL will skip languages without source files." >> $GITHUB_STEP_SUMMARY - echo "For PHP repository (v04.00.04), JavaScript language covers JSON/YAML/shell scripts." >> $GITHUB_STEP_SUMMARY - else - echo "โœ… **All configured CodeQL languages have corresponding source files**" >> $GITHUB_STEP_SUMMARY - fi - - # Always succeed - this is informational only - echo "" >> $GITHUB_STEP_SUMMARY - echo "### โœ… CodeQL Configuration Validation Complete" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Status:** CodeQL language configuration reviewed successfully" >> $GITHUB_STEP_SUMMARY - echo "" - echo "โœ… SUCCESS: CodeQL validation complete" - exit 0 - - documentation-quality: - name: Documentation Quality Check - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Validate README.md - run: | - set -x - echo "## ๐Ÿ“š Documentation Quality Check" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### README.md Analysis" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ ! -f "README.md" ]; then - echo "โŒ **Critical:** README.md not found" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### โŒ Validation Failed: README.md Missing" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Error:** README.md is required for all MokoCLI-compliant repositories" >> $GITHUB_STEP_SUMMARY - echo "**Action Required:** Create README.md with project description, setup instructions, and usage examples" >> $GITHUB_STEP_SUMMARY - echo "" - echo "โŒ ERROR: README.md not found - This is a critical requirement" - exit 1 - fi - - # Detailed content analysis - SIZE=$(wc -c < README.md) - LINES=$(wc -l < README.md) - WORDS=$(wc -w < README.md) - HEADINGS=$(grep -c "^#" README.md || echo 0) - LINKS=$(grep -c "\[.*\](.*)" README.md || echo 0) - CODE_BLOCKS=$(grep -c '```' README.md || echo 0) - - echo "| Metric | Value | Status | Recommendation |" >> $GITHUB_STEP_SUMMARY - echo "|--------|-------|--------|----------------|" >> $GITHUB_STEP_SUMMARY - - # Size check - SIZE_STATUS="โœ… Good" - SIZE_REC="Adequate length" - if [ "$SIZE" -lt 500 ]; then - SIZE_STATUS="โš ๏ธ Warning" - SIZE_REC="Add more content (min 500 bytes)" - elif [ "$SIZE" -gt 50000 ]; then - SIZE_STATUS="โš ๏ธ Warning" - SIZE_REC="Consider splitting into multiple docs" - fi - echo "| Size | $SIZE bytes | $SIZE_STATUS | $SIZE_REC |" >> $GITHUB_STEP_SUMMARY - - # Line count - LINES_STATUS="โœ… Good" - LINES_REC="Good size" - if [ "$LINES" -lt 20 ]; then - LINES_STATUS="โš ๏ธ Warning" - LINES_REC="Add more sections (min 20 lines)" - fi - echo "| Lines | $LINES | $LINES_STATUS | $LINES_REC |" >> $GITHUB_STEP_SUMMARY - - # Word count - WORDS_STATUS="โœ… Good" - WORDS_REC="Good detail" - if [ "$WORDS" -lt 100 ]; then - WORDS_STATUS="โš ๏ธ Warning" - WORDS_REC="Add more description (min 100 words)" - fi - echo "| Words | $WORDS | $WORDS_STATUS | $WORDS_REC |" >> $GITHUB_STEP_SUMMARY - - # Headings - HEADINGS_STATUS="โœ… Good" - HEADINGS_REC="Well structured" - if [ "$HEADINGS" -lt 3 ]; then - HEADINGS_STATUS="โš ๏ธ Warning" - HEADINGS_REC="Add more sections (min 3 headings)" - fi - echo "| Headings | $HEADINGS | $HEADINGS_STATUS | $HEADINGS_REC |" >> $GITHUB_STEP_SUMMARY - - # Links - LINKS_STATUS="โœ… Good" - LINKS_REC="Includes references" - if [ "$LINKS" -lt 1 ]; then - LINKS_STATUS="โ„น๏ธ Info" - LINKS_REC="Consider adding useful links" - fi - echo "| Links | $LINKS | $LINKS_STATUS | $LINKS_REC |" >> $GITHUB_STEP_SUMMARY - - # Code blocks - CODE_STATUS="โœ… Good" - CODE_REC="Includes examples" - if [ "$CODE_BLOCKS" -eq 0 ]; then - CODE_STATUS="โ„น๏ธ Info" - CODE_REC="Consider adding code examples" - fi - echo "| Code blocks | $CODE_BLOCKS | $CODE_STATUS | $CODE_REC |" >> $GITHUB_STEP_SUMMARY - - echo "" >> $GITHUB_STEP_SUMMARY - - # Check for key sections - echo "**Section Coverage:**" >> $GITHUB_STEP_SUMMARY - MISSING_COUNT=0 - grep -qi "install\|setup\|getting started" README.md && echo "- โœ… Installation/Setup instructions" >> $GITHUB_STEP_SUMMARY || { echo "- โš ๏ธ Missing: Installation/Setup" >> $GITHUB_STEP_SUMMARY; MISSING_COUNT=$((MISSING_COUNT + 1)); } - grep -qi "usage\|example\|how to" README.md && echo "- โœ… Usage examples" >> $GITHUB_STEP_SUMMARY || { echo "- โš ๏ธ Missing: Usage examples" >> $GITHUB_STEP_SUMMARY; MISSING_COUNT=$((MISSING_COUNT + 1)); } - grep -qi "license" README.md && echo "- โœ… License information" >> $GITHUB_STEP_SUMMARY || { echo "- โš ๏ธ Missing: License information" >> $GITHUB_STEP_SUMMARY; MISSING_COUNT=$((MISSING_COUNT + 1)); } - grep -qi "contribut" README.md && echo "- โœ… Contributing guidelines" >> $GITHUB_STEP_SUMMARY || echo "- โ„น๏ธ Optional: Contributing section" >> $GITHUB_STEP_SUMMARY - - if [ "$MISSING_COUNT" -gt 0 ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "**โš ๏ธ $MISSING_COUNT important sections missing**" >> $GITHUB_STEP_SUMMARY - fi - - - name: Validate CHANGELOG.md - run: | - set -x - echo "" >> $GITHUB_STEP_SUMMARY - echo "### CHANGELOG.md Analysis" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Locate changelog case-insensitively; accepted at root, src/, or docs/ - CHANGELOG_PATH=$(find . -maxdepth 3 \( -path ./.git -o -path ./node_modules \) -prune \ - -o -iname "changelog.md" -print | head -1 | sed 's|^\./||') - - if [ -z "$CHANGELOG_PATH" ]; then - echo "โŒ **Critical:** CHANGELOG.md not found (checked root, src/, docs/)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### โŒ Validation Failed: CHANGELOG.md Missing" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Error:** CHANGELOG.md is required for all MokoCLI-compliant repositories" >> $GITHUB_STEP_SUMMARY - echo "**Action Required:** Create CHANGELOG.md following [Keep a Changelog](https://keepachangelog.com/) format" >> $GITHUB_STEP_SUMMARY - echo "" - echo "โŒ ERROR: CHANGELOG.md not found - This is a critical requirement" - exit 1 - fi - - echo "๐Ÿ“„ Found: $CHANGELOG_PATH" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Analyze changelog structure - VERSIONS=$(grep -c "## \[" "$CHANGELOG_PATH" || echo 0) - UNRELEASED=$(grep -c "## \[Unreleased\]" "$CHANGELOG_PATH" || echo 0) - DATES=$(grep -c "[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}" "$CHANGELOG_PATH" || echo 0) - SIZE=$(wc -c < "$CHANGELOG_PATH") - - echo "| Metric | Value | Status | Notes |" >> $GITHUB_STEP_SUMMARY - echo "|--------|-------|--------|-------|" >> $GITHUB_STEP_SUMMARY - - # Check format - if grep -qi "## \[.*\]" "$CHANGELOG_PATH"; then - echo "| Format | Keep a Changelog | โœ… Pass | Standard format |" >> $GITHUB_STEP_SUMMARY - else - echo "| Format | Custom | โš ๏ธ Warning | Consider [Keep a Changelog](https://keepachangelog.com/) |" >> $GITHUB_STEP_SUMMARY - fi - - # Version count - VERSIONS_STATUS="โœ… Good" - VERSIONS_NOTE="Well maintained" - if [ "$VERSIONS" -lt 1 ]; then - VERSIONS_STATUS="โš ๏ธ Warning" - VERSIONS_NOTE="Add version entries" - fi - echo "| Versions | $VERSIONS | $VERSIONS_STATUS | $VERSIONS_NOTE |" >> $GITHUB_STEP_SUMMARY - - # Unreleased section - if [ "$UNRELEASED" -gt 0 ]; then - echo "| Unreleased | Yes | โœ… Good | Active development tracked |" >> $GITHUB_STEP_SUMMARY - else - echo "| Unreleased | No | โ„น๏ธ Info | Consider adding [Unreleased] section |" >> $GITHUB_STEP_SUMMARY - fi - - # Dates - DATES_STATUS="โœ… Good" - if [ "$DATES" -lt 1 ]; then - DATES_STATUS="โš ๏ธ Warning" - DATES_NOTE="Add release dates" - else - DATES_NOTE="Dates present" - fi - echo "| Release dates | $DATES | $DATES_STATUS | $DATES_NOTE |" >> $GITHUB_STEP_SUMMARY - - # Check for standard sections - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Changelog Sections:**" >> $GITHUB_STEP_SUMMARY - grep -qi "### Added" "$CHANGELOG_PATH" && echo "- โœ… Added section" >> $GITHUB_STEP_SUMMARY || echo "- โ„น๏ธ Added section (optional)" >> $GITHUB_STEP_SUMMARY - grep -qi "### Changed" "$CHANGELOG_PATH" && echo "- โœ… Changed section" >> $GITHUB_STEP_SUMMARY || echo "- โ„น๏ธ Changed section (optional)" >> $GITHUB_STEP_SUMMARY - grep -qi "### Fixed" "$CHANGELOG_PATH" && echo "- โœ… Fixed section" >> $GITHUB_STEP_SUMMARY || echo "- โ„น๏ธ Fixed section (optional)" >> $GITHUB_STEP_SUMMARY - - echo "" >> $GITHUB_STEP_SUMMARY - echo "๐Ÿ“š Reference: [Keep a Changelog](https://keepachangelog.com/)" >> $GITHUB_STEP_SUMMARY - - - name: Check Documentation Index - run: | - set -x - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Documentation Index" >> $GITHUB_STEP_SUMMARY - - if [ -f "docs/index.md" ] || [ -f "docs/README.md" ]; then - echo "โœ… Documentation index found" >> $GITHUB_STEP_SUMMARY - else - echo "โš ๏ธ No documentation index (docs/index.md or docs/README.md)" >> $GITHUB_STEP_SUMMARY - fi - - readme-completeness: - name: README Completeness Check - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Check README Sections - run: | - set -x - echo "## ๐Ÿ“„ README Completeness Check" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ ! -f "README.md" ]; then - echo "โŒ README.md not found" >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - # Required sections - REQUIRED_SECTIONS=("Installation" "Usage" "Contributing" "License") - MISSING=0 - PRESENT=0 - - echo "### Required Sections" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - for section in "${REQUIRED_SECTIONS[@]}"; do - if grep -qi "##.*$section" README.md; then - echo "โœ… $section" >> $GITHUB_STEP_SUMMARY - PRESENT=$((PRESENT + 1)) - else - echo "โŒ $section" >> $GITHUB_STEP_SUMMARY - MISSING=$((MISSING + 1)) - fi - done - - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Completeness**: $PRESENT/${#REQUIRED_SECTIONS[@]} required sections present" >> $GITHUB_STEP_SUMMARY - - if [ "$MISSING" -gt 0 ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Action Required**: Add missing sections to README.md" >> $GITHUB_STEP_SUMMARY - exit 1 - fi - - # ============================================================================ - # PHASE 3: Future Enhancements - # ============================================================================ - - git-hygiene: - name: Git Repository Hygiene - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - with: - fetch-depth: 0 - - - name: Check .gitignore - run: | - set -x - echo "### .gitignore Validation" >> $GITHUB_STEP_SUMMARY - - if [ ! -f ".gitignore" ]; then - echo "โš ๏ธ .gitignore file not found" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "### โš ๏ธ Warning: .gitignore Not Found" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Status:** .gitignore file is recommended but not required" >> $GITHUB_STEP_SUMMARY - echo "**Recommendation:** Add .gitignore to exclude build artifacts, dependencies, and temporary files" >> $GITHUB_STEP_SUMMARY - echo "" - echo "โš ๏ธ WARNING: .gitignore file not found - Continuing validation" - exit 0 - fi - - # Check for common exclusions - MISSING="" - grep -q "vendor/" .gitignore || MISSING="${MISSING}vendor/ " - grep -q "node_modules/" .gitignore || MISSING="${MISSING}node_modules/ " - - if [ -n "$MISSING" ]; then - echo "โš ๏ธ .gitignore may be missing common exclusions: $MISSING" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… .gitignore appears complete" >> $GITHUB_STEP_SUMMARY - fi - - - name: Check for Large Files - run: | - set -x - echo "" >> $GITHUB_STEP_SUMMARY - echo "### Large File Detection" >> $GITHUB_STEP_SUMMARY - - # Find files larger than 1MB - LARGE_FILES=$(find . -type f -size +1M ! -path "./.git/*" ! -path "./vendor/*" ! -path "./node_modules/*" | head -5) - - if [ -n "$LARGE_FILES" ]; then - echo "โš ๏ธ Large files detected (>1MB):" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "$LARGE_FILES" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "Consider using Git LFS for large binary files" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… No unusually large files detected" >> $GITHUB_STEP_SUMMARY - fi - - script-integrity: - name: Script Integrity Validation - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Set up Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: '3.x' - - - name: Validate Script Integrity - id: script_check - run: | - set -x - echo "## ๐Ÿ” Script Integrity Validation" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ -f "api/.script-registry.json" ]; then - echo "### Critical Scripts" >> $GITHUB_STEP_SUMMARY - php api/maintenance/update_sha_hashes.php \ - --dry-run --verbose | tee /tmp/script-validation.log - - EXIT_CODE=$? - - echo "" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - cat /tmp/script-validation.log >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - - if [ "$EXIT_CODE" -eq 0 ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "โœ… All critical scripts validated successfully!" >> $GITHUB_STEP_SUMMARY - exit 0 - else - echo "" >> $GITHUB_STEP_SUMMARY - echo "โŒ Script integrity violations detected" >> $GITHUB_STEP_SUMMARY - echo "**Action Required:** Review validation report and update registry" >> $GITHUB_STEP_SUMMARY - exit 1 - fi - else - echo "โ„น๏ธ Script registry not found - skipping integrity check" >> $GITHUB_STEP_SUMMARY - exit 0 - fi - - - # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - # TIER 3 โ€” QUALITY (code quality metrics) - # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - line-length-validation: - name: Line Length Check - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Check Line Lengths - run: | - set -x - echo "## ๐Ÿ“ Line Length Validation" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Line length standards: - # - General source code: 120 characters (hard limit) - # - YAML workflows: 180 characters (exception for GitHub Actions) - # - Markdown files: No limit (content-focused) - - echo "### Line Length Standards" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| File Type | Soft Limit | Hard Limit |" >> $GITHUB_STEP_SUMMARY - echo "|-----------|------------|------------|" >> $GITHUB_STEP_SUMMARY - echo "| General source code | 80 chars | 120 chars |" >> $GITHUB_STEP_SUMMARY - echo "| YAML workflows | 80 chars | 180 chars |" >> $GITHUB_STEP_SUMMARY - echo "| Markdown files | N/A | No limit |" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Check YAML files (using yamllint which is already configured) - echo "### YAML Files (180 char limit)" >> $GITHUB_STEP_SUMMARY - - YAML_VIOLATIONS=0 - if command -v yamllint >/dev/null 2>&1; then - # Install yamllint if not present - : - else - pip install yamllint >/dev/null 2>&1 || true - fi - - # Run yamllint and count line-length warnings - YAML_OUTPUT=$(yamllint .mokogitea/workflows/*.yml 2>&1 | grep "line too long" || true) - if [ -n "$YAML_OUTPUT" ]; then - YAML_VIOLATIONS=$(echo "$YAML_OUTPUT" | wc -l) - echo "โš ๏ธ Found $YAML_VIOLATIONS lines exceeding 180 characters in YAML files" >> $GITHUB_STEP_SUMMARY - echo "
View warnings (informational only)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "$YAML_OUTPUT" | head -20 >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… All YAML files comply with 180 character limit" >> $GITHUB_STEP_SUMMARY - fi - echo "" >> $GITHUB_STEP_SUMMARY - - # Check source code files (PHP, Python, JavaScript, etc.) for 120 char limit - echo "### Source Code Files (120 char limit)" >> $GITHUB_STEP_SUMMARY - - LONG_LINES=$(find . -type f \ - \( -name "*.php" -o -name "*.py" -o -name "*.js" -o -name "*.ts" \ - -o -name "*.go" -o -name "*.rs" -o -name "*.java" -o -name "*.c" \ - -o -name "*.cpp" -o -name "*.h" -o -name "*.sh" \) \ - ! -path "./vendor/*" \ - ! -path "./node_modules/*" \ - ! -path "./.git/*" \ - ! -path "./build/*" \ - ! -path "./dist/*" \ - -exec awk 'length > 120 { print FILENAME ":" NR ": " length " chars" }' {} \; 2>/dev/null | head -20 || true) - - if [ -n "$LONG_LINES" ]; then - LINE_COUNT=$(echo "$LONG_LINES" | wc -l) - echo "โš ๏ธ Found $LINE_COUNT source code lines exceeding 120 characters" >> $GITHUB_STEP_SUMMARY - echo "
View violations (informational)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "$LONG_LINES" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… All source code files comply with 120 character limit" >> $GITHUB_STEP_SUMMARY - fi - echo "" >> $GITHUB_STEP_SUMMARY - - # Confirm Markdown files are not checked - echo "### Markdown Files" >> $GITHUB_STEP_SUMMARY - echo "โœ… Markdown files have no line length limit per coding standards" >> $GITHUB_STEP_SUMMARY - echo "Rationale: Content-focused format, URLs, tables, and natural prose flow" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Summary - echo "### Summary" >> $GITHUB_STEP_SUMMARY - echo "This check is **informational only** and does not block merges." >> $GITHUB_STEP_SUMMARY - echo "Line length standards help maintain code readability." >> $GITHUB_STEP_SUMMARY - echo "Exceptions documented in: \`docs/policy/coding-style-guide.md\`" >> $GITHUB_STEP_SUMMARY - - file-naming-standards: - name: File Naming Standards - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Check File Naming - run: | - set -x - echo "## ๐Ÿ“ File Naming Standards" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - VIOLATIONS=0 - - # Check PHP files (should be PascalCase for classes) - INVALID_PHP=$(find . -name "*.php" ! -path "./vendor/*" ! -path "./.git/*" ! -regex ".*/[A-Z][a-zA-Z0-9]*\.php" ! -name "index.php" ! -name "functions.php" | wc -l || echo 0) - - # Check config files (should be kebab-case) - INVALID_CONFIG=$(find . -name "*.yml" -o -name "*.yaml" -o -name "*.json" ! -path "./vendor/*" ! -path "./.git/*" ! -path "./node_modules/*" | grep -E "[A-Z_]" | wc -l || echo 0) - - echo "### Naming Violations" >> $GITHUB_STEP_SUMMARY - echo "- **PHP files not PascalCase**: $INVALID_PHP" >> $GITHUB_STEP_SUMMARY - echo "- **Config files not kebab-case**: $INVALID_CONFIG" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - VIOLATIONS=$((INVALID_PHP + INVALID_CONFIG)) - - if [ "$VIOLATIONS" -gt 0 ]; then - echo "โš ๏ธ Found $VIOLATIONS naming convention violation(s)" >> $GITHUB_STEP_SUMMARY - echo "**Recommendation**: Follow naming conventions for consistency" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… File naming conventions followed" >> $GITHUB_STEP_SUMMARY - fi - - insecure-patterns: - name: Insecure Code Pattern Detection - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Scan for Insecure Patterns - run: | - set -x - echo "## ๐Ÿ”’ Insecure Code Pattern Detection" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - VIOLATIONS=0 - - # PHP: SQL injection patterns - if grep -r -n "\\$_\(GET\|POST\|REQUEST\).*mysql_query\|mysqli_query" . --include="*.php" ! -path "./vendor/*" 2>/dev/null > /tmp/sql_inject.txt; then - COUNT=$(wc -l < /tmp/sql_inject.txt) - echo "โš ๏ธ Found $COUNT potential SQL injection pattern(s)" >> $GITHUB_STEP_SUMMARY - VIOLATIONS=$((VIOLATIONS + COUNT)) - fi - - # PHP: eval/exec usage - if grep -r -n "eval\|exec\|system\|passthru\|shell_exec" . --include="*.php" ! -path "./vendor/*" 2>/dev/null > /tmp/exec.txt; then - COUNT=$(wc -l < /tmp/exec.txt) - echo "โš ๏ธ Found $COUNT dangerous function call(s)" >> $GITHUB_STEP_SUMMARY - VIOLATIONS=$((VIOLATIONS + COUNT)) - fi - - # Python: eval usage - if grep -r -n "eval(" . --include="*.py" 2>/dev/null > /tmp/py_eval.txt; then - COUNT=$(wc -l < /tmp/py_eval.txt) - echo "โš ๏ธ Found $COUNT Python eval() usage(s)" >> $GITHUB_STEP_SUMMARY - VIOLATIONS=$((VIOLATIONS + COUNT)) - fi - - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "$VIOLATIONS" -gt 0 ]; then - echo "**Total Violations**: $VIOLATIONS" >> $GITHUB_STEP_SUMMARY - echo "**Recommendation**: Review and secure flagged patterns" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… No insecure patterns detected" >> $GITHUB_STEP_SUMMARY - fi - - code-complexity: - name: Code Complexity Analysis - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.31.0 - with: - php-version: '8.1' - - - name: Analyze Complexity - run: | - set -x - echo "## ๐Ÿ“Š Code Complexity Analysis" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - PHP_COUNT=$(find . -name "*.php" ! -path "./vendor/*" ! -path "./.git/*" | wc -l) - - if [ "$PHP_COUNT" -gt 0 ]; then - # Install phploc - wget https://phar.phpunit.de/phploc.phar 2>/dev/null - chmod +x phploc.phar - - echo "### PHP Code Metrics" >> $GITHUB_STEP_SUMMARY - if ./phploc.phar --exclude vendor --exclude .git . 2>&1 | tee /tmp/phploc.txt; then - COMPLEXITY=$(grep "Cyclomatic Complexity" /tmp/phploc.txt | grep "Average" | awk '{print $NF}' || echo "N/A") - echo "**Average Cyclomatic Complexity**: $COMPLEXITY" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "$COMPLEXITY" != "N/A" ] && [ $(echo "$COMPLEXITY > 10" | bc -l) -eq 1 ]; then - echo "โš ๏ธ Average complexity exceeds recommended threshold (10)" >> $GITHUB_STEP_SUMMARY - echo "**Recommendation**: Refactor complex functions" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… Code complexity within acceptable limits" >> $GITHUB_STEP_SUMMARY - fi - fi - else - echo "โ„น๏ธ No PHP files found for complexity analysis" >> $GITHUB_STEP_SUMMARY - fi - - code-duplication: - name: Code Duplication Detection - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.31.0 - with: - php-version: '8.1' - - - name: Detect Duplicates - run: | - set -x - echo "## ๐Ÿ” Code Duplication Detection" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Check if PHP files exist - PHP_COUNT=$(find . -name "*.php" ! -path "./vendor/*" ! -path "./.git/*" | wc -l) - - if [ "$PHP_COUNT" -gt 0 ]; then - echo "### PHP Code Duplication" >> $GITHUB_STEP_SUMMARY - - # Install phpcpd - wget https://phar.phpunit.de/phpcpd.phar 2>/dev/null - chmod +x phpcpd.phar - - # Run duplication detection - if ./phpcpd.phar --exclude vendor --exclude .git . 2>&1 | tee /tmp/phpcpd.txt; then - DUPLICATION=$(grep "Found" /tmp/phpcpd.txt | grep -oE "[0-9]+\.[0-9]+%" | head -1 || echo "0.00%") - echo "๐Ÿ“Š **Duplication Rate**: $DUPLICATION" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - DUPLICATION_NUM=$(echo "$DUPLICATION" | sed 's/%//') - if [ $(echo "$DUPLICATION_NUM > 5.0" | bc -l) -eq 1 ]; then - echo "โš ๏ธ Code duplication exceeds 5% threshold" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "View duplication details" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - cat /tmp/phpcpd.txt >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… Code duplication within acceptable limits (<5%)" >> $GITHUB_STEP_SUMMARY - fi - else - echo "โœ… No significant code duplication detected" >> $GITHUB_STEP_SUMMARY - fi - else - echo "โ„น๏ธ No PHP files found for duplication analysis" >> $GITHUB_STEP_SUMMARY - fi - - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Note**: This is an informational check to encourage DRY principles." >> $GITHUB_STEP_SUMMARY - - dead-code-detection: - name: Dead Code Detection - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: '3.x' - - - name: Detect Dead Code - run: | - set -x - echo "## ๐Ÿ—‘๏ธ Dead Code Detection" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - PY_COUNT=$(find . -name "*.py" ! -path "./vendor/*" ! -path "./.git/*" ! -path "./venv/*" | wc -l) - - if [ "$PY_COUNT" -gt 0 ]; then - pip install vulture 2>/dev/null - echo "### Python Dead Code" >> $GITHUB_STEP_SUMMARY - - if vulture . --exclude vendor,venv,.git 2>&1 | tee /tmp/vulture.txt; then - DEAD_COUNT=$(wc -l < /tmp/vulture.txt || echo 0) - if [ "$DEAD_COUNT" -gt 0 ]; then - echo "โš ๏ธ Found $DEAD_COUNT potential dead code item(s)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "View dead code" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - head -50 /tmp/vulture.txt >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… No dead code detected" >> $GITHUB_STEP_SUMMARY - fi - fi - else - echo "โ„น๏ธ No Python files found for dead code analysis" >> $GITHUB_STEP_SUMMARY - fi - - - # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - # TIER 4 โ€” SUPPLEMENTARY (informational) - # โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• - file-size-limits: - name: File Size Limits - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Check File Sizes - run: | - set -x - echo "## ๐Ÿ“ฆ File Size Validation" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Exempt file types (allowed to be large) - EXEMPT="! -name *.mmdb ! -name *.woff2 ! -name *.woff ! -name *.ttf ! -name *.otf" - - # Find large files (>15MB warning, >20MB critical) - LARGE_FILES=$(find . -type f -size +15M $EXEMPT ! -path "./.git/*" ! -path "./vendor/*" ! -path "./node_modules/*" 2>/dev/null | wc -l) - HUGE_FILES=$(find . -type f -size +20M $EXEMPT ! -path "./.git/*" ! -path "./vendor/*" ! -path "./node_modules/*" 2>/dev/null | wc -l) - - echo "### Size Thresholds" >> $GITHUB_STEP_SUMMARY - echo "- **Warning**: Files >15MB" >> $GITHUB_STEP_SUMMARY - echo "- **Critical**: Files >20MB" >> $GITHUB_STEP_SUMMARY - echo "- **Exempt**: .mmdb, .woff2, .woff, .ttf, .otf" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "$HUGE_FILES" -gt 0 ]; then - echo "โŒ **Critical**: Found $HUGE_FILES file(s) exceeding 20MB" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "View files >20MB" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - find . -type f -size +20M $EXEMPT ! -path "./.git/*" ! -path "./vendor/*" ! -path "./node_modules/*" -exec ls -lh {} + 2>/dev/null | awk '{print $5, $9}' >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Action Required**: Remove or optimize files >20MB" >> $GITHUB_STEP_SUMMARY - exit 1 - elif [ "$LARGE_FILES" -gt 0 ]; then - echo "โš ๏ธ **Warning**: Found $LARGE_FILES file(s) between 15MB and 20MB" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "View files >15MB" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - find . -type f -size +15M $EXEMPT ! -path "./.git/*" ! -path "./vendor/*" ! -path "./node_modules/*" -exec ls -lh {} + 2>/dev/null | awk '{print $5, $9}' >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Recommendation**: Consider optimizing large files" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… All files within acceptable size limits" >> $GITHUB_STEP_SUMMARY - fi - - binary-file-detection: - name: Binary File Detection - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Detect Binary Files - run: | - set -x - echo "## ๐Ÿ” Binary File Detection" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Find binary files excluding allowed types - BINARIES=$(find . -type f ! -path "./.git/*" ! -path "./vendor/*" ! -path "./node_modules/*" \ - ! -name "*.png" ! -name "*.jpg" ! -name "*.jpeg" ! -name "*.gif" ! -name "*.svg" ! -name "*.ico" \ - ! -name "*.woff" ! -name "*.woff2" ! -name "*.ttf" ! -name "*.eot" \ - -exec file {} \; | grep -v "text" | grep -v "empty" | wc -l || echo 0) - - if [ "$BINARIES" -gt 0 ]; then - echo "โš ๏ธ Found $BINARIES non-image binary file(s)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "View binary files" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - find . -type f ! -path "./.git/*" ! -path "./vendor/*" ! -path "./node_modules/*" \ - ! -name "*.png" ! -name "*.jpg" ! -name "*.jpeg" ! -name "*.gif" ! -name "*.svg" ! -name "*.ico" \ - ! -name "*.woff" ! -name "*.woff2" ! -name "*.ttf" ! -name "*.eot" \ - -exec file {} \; | grep -v "text" | grep -v "empty" | cut -d: -f1 >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Recommendation**: Source control should primarily contain text files" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… No unexpected binary files detected" >> $GITHUB_STEP_SUMMARY - fi - - # ============================================================================ - # PHASE 4: Nice to Have Checks - # ============================================================================ - - todo-fixme-tracking: - name: TODO/FIXME Tracking - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Track Technical Debt - run: | - set -x - echo "## ๐Ÿ“ TODO/FIXME Tracking" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Tracking technical debt markers in source code." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Search for technical debt markers - PATTERNS="TODO|FIXME|HACK|XXX" - EXTENSIONS="*.php *.py *.js *.ts *.go *.rs *.java *.c *.cpp *.h *.hpp *.sh" - - echo "### Technical Debt Summary" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - TOTAL_COUNT=0 - for ext in $EXTENSIONS; do - COUNT=$(find . -type f -name "$ext" ! -path "./.git/*" ! -path "./vendor/*" ! -path "./node_modules/*" -exec grep -n -E "($PATTERNS)" {} + 2>/dev/null | wc -l || echo 0) - TOTAL_COUNT=$((TOTAL_COUNT + COUNT)) - done - - if [ "$TOTAL_COUNT" -gt 0 ]; then - echo "โš ๏ธ Found **$TOTAL_COUNT** technical debt item(s)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "View technical debt items" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - for ext in $EXTENSIONS; do - find . -type f -name "$ext" ! -path "./.git/*" ! -path "./vendor/*" ! -path "./node_modules/*" -exec grep -n -H -E "($PATTERNS)" {} + 2>/dev/null | head -100 || true - done >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… No technical debt markers found" >> $GITHUB_STEP_SUMMARY - fi - - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Note**: This is an informational check. Technical debt items don't block compliance." >> $GITHUB_STEP_SUMMARY - - dependency-vulnerabilities: - name: Dependency Vulnerability Scanning - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.31.0 - with: - php-version: '8.1' - - - name: Setup Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 - with: - python-version: '3.x' - - - name: Scan Dependencies - run: | - set -x - echo "## ๐Ÿ›ก๏ธ Dependency Vulnerability Scanning" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - VULNERABILITIES=0 - - # PHP Dependencies - if [ -f "composer.json" ]; then - echo "### PHP Dependencies (composer)" >> $GITHUB_STEP_SUMMARY - if composer audit --no-dev 2>&1 | tee /tmp/php_audit.txt; then - echo "โœ… No PHP vulnerabilities detected" >> $GITHUB_STEP_SUMMARY - else - VULN_COUNT=$(grep -c "vulnerability" /tmp/php_audit.txt || echo 0) - echo "โš ๏ธ Found $VULN_COUNT PHP vulnerability/vulnerabilities" >> $GITHUB_STEP_SUMMARY - VULNERABILITIES=$((VULNERABILITIES + VULN_COUNT)) - fi - echo "" >> $GITHUB_STEP_SUMMARY - fi - - # Python Dependencies - if [ -f "requirements.txt" ]; then - echo "### Python Dependencies" >> $GITHUB_STEP_SUMMARY - pip install pip-audit 2>&1 > /dev/null - if pip-audit -r requirements.txt 2>&1 | tee /tmp/py_audit.txt; then - echo "โœ… No Python vulnerabilities detected" >> $GITHUB_STEP_SUMMARY - else - VULN_COUNT=$(grep -c "vulnerability" /tmp/py_audit.txt || echo 0) - echo "โš ๏ธ Found $VULN_COUNT Python vulnerability/vulnerabilities" >> $GITHUB_STEP_SUMMARY - VULNERABILITIES=$((VULNERABILITIES + VULN_COUNT)) - fi - echo "" >> $GITHUB_STEP_SUMMARY - fi - - # NPM Dependencies - if [ -f "package.json" ]; then - echo "### NPM Dependencies" >> $GITHUB_STEP_SUMMARY - if npm audit --production 2>&1 | tee /tmp/npm_audit.txt; then - echo "โœ… No NPM vulnerabilities detected" >> $GITHUB_STEP_SUMMARY - else - VULN_COUNT=$(grep -c "vulnerability" /tmp/npm_audit.txt || echo 0) - echo "โš ๏ธ Found $VULN_COUNT NPM vulnerability/vulnerabilities" >> $GITHUB_STEP_SUMMARY - VULNERABILITIES=$((VULNERABILITIES + VULN_COUNT)) - fi - echo "" >> $GITHUB_STEP_SUMMARY - fi - - if [ "$VULNERABILITIES" -gt 0 ]; then - echo "**Total Vulnerabilities**: $VULNERABILITIES" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Action Required**: Update vulnerable dependencies" >> $GITHUB_STEP_SUMMARY - exit 1 - else - echo "โœ… No dependency vulnerabilities detected" >> $GITHUB_STEP_SUMMARY - fi - - unused-dependencies: - name: Unused Dependencies Check - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.31.0 - with: - php-version: '8.1' - - - name: Check Unused Dependencies - run: | - set -x - echo "## ๐Ÿ“ฆ Unused Dependencies Check" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ -f "composer.json" ]; then - echo "### PHP Dependencies" >> $GITHUB_STEP_SUMMARY - - # Install composer-unused - composer global require icanhazstring/composer-unused 2>/dev/null || true - - if composer global exec composer-unused 2>&1 | tee /tmp/unused.txt; then - UNUSED_COUNT=$(grep "unused" /tmp/unused.txt | wc -l || echo 0) - if [ "$UNUSED_COUNT" -gt 0 ]; then - echo "โš ๏ธ Found $UNUSED_COUNT unused dependency/dependencies" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "View unused dependencies" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - cat /tmp/unused.txt >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… No unused dependencies detected" >> $GITHUB_STEP_SUMMARY - fi - else - echo "โœ… All dependencies appear to be in use" >> $GITHUB_STEP_SUMMARY - fi - else - echo "โ„น๏ธ No composer.json found" >> $GITHUB_STEP_SUMMARY - fi - - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Recommendation**: Remove unused dependencies to reduce attack surface" >> $GITHUB_STEP_SUMMARY - - broken-link-detection: - name: Broken Link Detection - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Check Internal Links - run: | - set -x - echo "## ๐Ÿ”— Broken Link Detection" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Checking internal links in markdown files." >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - BROKEN_LINKS=0 - CHECKED_LINKS=0 - - # Find all markdown files - MD_FILES=$(find . -name "*.md" ! -path "./.git/*" ! -path "./vendor/*" ! -path "./node_modules/*") - - for file in $MD_FILES; do - # Extract markdown links [text](path) - while IFS= read -r line; do - # Extract path from [text](path) - link=$(echo "$line" | sed -n 's/.*\](\([^)]*\)).*/\1/p') - - # Skip external links (http/https) - if echo "$link" | grep -qE "^https?://"; then - continue - fi - - # Skip anchors only - if echo "$link" | grep -qE "^#"; then - continue - fi - - CHECKED_LINKS=$((CHECKED_LINKS + 1)) - - # Get directory of the markdown file - basedir=$(dirname "$file") - - # Resolve relative path - if [ -n "$link" ]; then - # Remove anchor if present - clean_link=$(echo "$link" | sed 's/#.*//') - - # Check if file exists - if [ ! -e "$basedir/$clean_link" ] && [ ! -e "$clean_link" ]; then - echo "Broken link in $file: $link" >> /tmp/broken_links.txt - BROKEN_LINKS=$((BROKEN_LINKS + 1)) - fi - fi - done < <(grep -o '\[.*\](.*)' "$file" 2>/dev/null || true) - done - - echo "### Link Validation Results" >> $GITHUB_STEP_SUMMARY - echo "- **Links Checked**: $CHECKED_LINKS" >> $GITHUB_STEP_SUMMARY - echo "- **Broken Links**: $BROKEN_LINKS" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "$BROKEN_LINKS" -gt 0 ]; then - echo "โš ๏ธ Found $BROKEN_LINKS broken internal link(s)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "View broken links" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - cat /tmp/broken_links.txt 2>/dev/null >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "
" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Recommendation**: Fix or remove broken links to maintain documentation quality" >> $GITHUB_STEP_SUMMARY - else - if [ "$CHECKED_LINKS" -gt 0 ]; then - echo "โœ… All internal links are valid" >> $GITHUB_STEP_SUMMARY - else - echo "โ„น๏ธ No internal links found to check" >> $GITHUB_STEP_SUMMARY - fi - fi - - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Note**: This check validates internal file references only. External URLs are not validated." >> $GITHUB_STEP_SUMMARY - - # ============================================================================ - # PHASE 2: Medium Priority Checks - # ============================================================================ - - api-documentation: - name: API Documentation Coverage - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Check Documentation - run: | - set -x - echo "## ๐Ÿ“š API Documentation Coverage" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Count public functions/classes - PUBLIC_METHODS=$(grep -r "public function" . --include="*.php" ! -path "./vendor/*" | wc -l || echo 0) - DOCUMENTED=$(grep -B5 -r "public function" . --include="*.php" ! -path "./vendor/*" | grep -c "/\*\*" || echo 0) - - if [ "$PUBLIC_METHODS" -gt 0 ]; then - COVERAGE=$((DOCUMENTED * 100 / PUBLIC_METHODS)) - echo "**Documentation Coverage**: $COVERAGE% ($DOCUMENTED/$PUBLIC_METHODS)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "$COVERAGE" -lt 80 ]; then - echo "โš ๏ธ Documentation coverage below 80% threshold" >> $GITHUB_STEP_SUMMARY - echo "**Recommendation**: Add PHPDoc blocks to public methods" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… Good documentation coverage" >> $GITHUB_STEP_SUMMARY - fi - else - echo "โ„น๏ธ No public methods found for documentation check" >> $GITHUB_STEP_SUMMARY - fi - - accessibility-check: - name: Accessibility Check - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Check Accessibility - run: | - set -x - echo "## โ™ฟ Accessibility Check" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - HTML_COUNT=$(find . -name "*.html" ! -path "./vendor/*" ! -path "./.git/*" ! -path "./node_modules/*" | wc -l || echo 0) - MD_IMG_COUNT=$(find . -name "*.md" ! -path "./vendor/*" ! -path "./.git/*" -exec grep -l "!\[" {} + 2>/dev/null | wc -l || echo 0) - - if [ "$HTML_COUNT" -gt 0 ] || [ "$MD_IMG_COUNT" -gt 0 ]; then - # Check for images without alt text - MISSING_ALT=0 - - if [ "$HTML_COUNT" -gt 0 ]; then - MISSING_ALT=$(grep -r "> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "$MISSING_ALT" -gt 0 ]; then - echo "โš ๏ธ Found images without alt text" >> $GITHUB_STEP_SUMMARY - echo "**Recommendation**: Add descriptive alt text for accessibility" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… All images have alt text" >> $GITHUB_STEP_SUMMARY - fi - else - echo "โ„น๏ธ No HTML files found for accessibility check" >> $GITHUB_STEP_SUMMARY - fi - - performance-metrics: - name: Performance Metrics - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Check Performance Metrics - run: | - set -x - echo "## โšก Performance Metrics" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Check if JavaScript bundles exist - if [ -f "package.json" ]; then - echo "### Bundle Analysis" >> $GITHUB_STEP_SUMMARY - - # Check for common bundle files - BUNDLE_SIZE=0 - if [ -d "dist" ]; then - BUNDLE_SIZE=$(du -sb dist/ 2>/dev/null | cut -f1 || echo 0) - elif [ -d "build" ]; then - BUNDLE_SIZE=$(du -sb build/ 2>/dev/null | cut -f1 || echo 0) - fi - - if [ "$BUNDLE_SIZE" -gt 0 ]; then - BUNDLE_MB=$((BUNDLE_SIZE / 1024 / 1024)) - echo "**Bundle Size**: ${BUNDLE_MB}MB" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "$BUNDLE_MB" -gt 5 ]; then - echo "โš ๏ธ Bundle size exceeds 5MB threshold" >> $GITHUB_STEP_SUMMARY - echo "**Recommendation**: Optimize bundle size" >> $GITHUB_STEP_SUMMARY - else - echo "โœ… Bundle size within acceptable limits" >> $GITHUB_STEP_SUMMARY - fi - else - echo "โ„น๏ธ No build artifacts found" >> $GITHUB_STEP_SUMMARY - fi - else - echo "โ„น๏ธ Not a JavaScript project" >> $GITHUB_STEP_SUMMARY - fi - - enterprise-readiness: - name: Enterprise Readiness Check - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Set up PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.31.0 - with: - php-version: '8.1' - extensions: json, mbstring - tools: composer - coverage: none - - - name: Install API Package - env: - GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' - run: | - if [ -f "composer.json" ]; then - composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader - else - echo "No composer.json โ€” pulling MokoCLI tools" - if [ ! -d "/tmp/mokostandards" ]; then - git clone --depth 1 --branch version/04 --quiet \ - "https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/mokocli.git" \ - /tmp/mokostandards 2>/dev/null || true - if [ -f "/tmp/mokostandards/composer.json" ]; then - cd /tmp/mokostandards && composer install --no-dev --no-interaction --quiet 2>/dev/null || true - cd - - fi - fi - fi - - - name: Check Enterprise Readiness - id: enterprise_check - run: | - echo "" >> $GITHUB_STEP_SUMMARY - - SCRIPT="" - if [ -f "api/validate/check_enterprise_readiness.php" ]; then - SCRIPT="api/validate/check_enterprise_readiness.php" - elif [ -f "/tmp/mokostandards/api/validate/check_enterprise_readiness.php" ]; then - SCRIPT="/tmp/mokostandards/api/validate/check_enterprise_readiness.php" - fi - - if [ -n "$SCRIPT" ]; then - php "$SCRIPT" --verbose | tee /tmp/enterprise-check.log - EXIT_CODE=$? - - echo "" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - cat /tmp/enterprise-check.log >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - - if [ "$EXIT_CODE" -eq 0 ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "โœ… Repository meets enterprise readiness criteria!" >> $GITHUB_STEP_SUMMARY - exit 0 - else - echo "" >> $GITHUB_STEP_SUMMARY - echo "โš ๏ธ Enterprise readiness issues detected" >> $GITHUB_STEP_SUMMARY - echo "**Note:** This is informational - review recommendations to improve" >> $GITHUB_STEP_SUMMARY - exit 0 # Non-blocking - fi - else - echo "โ„น๏ธ Enterprise readiness check script not found - skipping" >> $GITHUB_STEP_SUMMARY - exit 0 - fi - - repository-health: - name: Repository Health Check - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Set up PHP - uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.31.0 - with: - php-version: '8.1' - extensions: json, mbstring - tools: composer - coverage: none - - - name: Install API Package - env: - GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} - COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}' - run: | - if [ -f "composer.json" ]; then - composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader - else - echo "No composer.json โ€” pulling MokoCLI tools" - if [ ! -d "/tmp/mokostandards" ]; then - git clone --depth 1 --branch version/04 --quiet \ - "https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/mokocli.git" \ - /tmp/mokostandards 2>/dev/null || true - if [ -f "/tmp/mokostandards/composer.json" ]; then - cd /tmp/mokostandards && composer install --no-dev --no-interaction --quiet 2>/dev/null || true - cd - - fi - fi - fi - - - name: Check Repository Health - id: health_check - run: | - echo "" >> $GITHUB_STEP_SUMMARY - - SCRIPT="" - if [ -f "api/validate/check_repo_health.php" ]; then - SCRIPT="api/validate/check_repo_health.php" - elif [ -f "/tmp/mokostandards/api/validate/check_repo_health.php" ]; then - SCRIPT="/tmp/mokostandards/api/validate/check_repo_health.php" - fi - - if [ -n "$SCRIPT" ]; then - php "$SCRIPT" --verbose | tee /tmp/health-check.log - EXIT_CODE=$? - - echo "" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - cat /tmp/health-check.log >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - - if [ "$EXIT_CODE" -eq 0 ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "โœ… Repository health check passed!" >> $GITHUB_STEP_SUMMARY - exit 0 - else - echo "" >> $GITHUB_STEP_SUMMARY - echo "โš ๏ธ Repository health issues detected" >> $GITHUB_STEP_SUMMARY - echo "**Note:** This is informational - review recommendations to improve" >> $GITHUB_STEP_SUMMARY - exit 0 # Non-blocking - fi - else - echo "โ„น๏ธ Repository health check script not found - skipping" >> $GITHUB_STEP_SUMMARY - exit 0 - fi - - terraform-validation: - name: Terraform Configuration Validation - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup Terraform - uses: hashicorp/setup-terraform@5e8dbf3c6d9deaf4193ca7a8fb23f2ac83bb6c85 # v4.0.0 - with: - terraform_version: "1.0" - - - name: Validate Terraform Files - run: | - set -x - echo "## ๐Ÿ—๏ธ Terraform Configuration Validation" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Check if terraform files exist - TF_COUNT=$(find . -name "*.tf" -type f | wc -l || echo 0) - - if [ "$TF_COUNT" -eq 0 ]; then - echo "โ„น๏ธ No Terraform files found in repository" >> $GITHUB_STEP_SUMMARY - exit 0 - fi - - echo "**Terraform Files Found**: $TF_COUNT" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Validation Results - VALIDATION_PASSED=true - WARNINGS=0 - ERRORS=0 - - # 1. Check .mokogitea/config.tf location (not root override files) - echo "### Override Configuration Check" >> $GITHUB_STEP_SUMMARY - LEGACY_OVERRIDES=$(find . -maxdepth 1 -name "*override*.tf" -o -name "MokoCLI.override.tf" 2>/dev/null | wc -l || echo 0) - if [ "$LEGACY_OVERRIDES" -gt 0 ]; then - echo "โš ๏ธ Found legacy override files in root directory" >> $GITHUB_STEP_SUMMARY - echo "**Expected Location**: .mokogitea/config.tf" >> $GITHUB_STEP_SUMMARY - echo "**Legacy files found**: $LEGACY_OVERRIDES" >> $GITHUB_STEP_SUMMARY - WARNINGS=$((WARNINGS + 1)) - else - if [ -f ".mokogitea/config.tf" ]; then - echo "โœ… Override configuration in correct location (.mokogitea/config.tf)" >> $GITHUB_STEP_SUMMARY - else - echo "โ„น๏ธ No override configuration found" >> $GITHUB_STEP_SUMMARY - fi - fi - echo "" >> $GITHUB_STEP_SUMMARY - - # 2. Terraform Syntax Validation - echo "### Terraform Syntax Validation" >> $GITHUB_STEP_SUMMARY - SYNTAX_ERRORS=0 - - # Find all directories with terraform files - for dir in $(find . -name "*.tf" -type f -exec dirname {} \; | sort -u); do - cd "$dir" || continue - echo "Validating: $dir" >> $GITHUB_STEP_SUMMARY - - # Initialize without backend - terraform init -backend=false > /dev/null 2>&1 || true - - # Validate - if terraform validate -no-color > /tmp/tf_validate.txt 2>&1; then - echo " โœ… Syntax valid" >> $GITHUB_STEP_SUMMARY - else - echo " โŒ Syntax errors found" >> $GITHUB_STEP_SUMMARY - cat /tmp/tf_validate.txt >> $GITHUB_STEP_SUMMARY - SYNTAX_ERRORS=$((SYNTAX_ERRORS + 1)) - VALIDATION_PASSED=false - fi - cd - > /dev/null - done - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "$SYNTAX_ERRORS" -eq 0 ]; then - echo "โœ… All Terraform files have valid syntax" >> $GITHUB_STEP_SUMMARY - else - echo "โŒ Found $SYNTAX_ERRORS directories with syntax errors" >> $GITHUB_STEP_SUMMARY - ERRORS=$((ERRORS + SYNTAX_ERRORS)) - fi - echo "" >> $GITHUB_STEP_SUMMARY - - # 3. Terraform Formatting Check - echo "### Terraform Formatting Check" >> $GITHUB_STEP_SUMMARY - FORMAT_ISSUES=0 - - for tf_file in $(find . -name "*.tf" -type f); do - if ! terraform fmt -check=true -no-color "$tf_file" > /dev/null 2>&1; then - FORMAT_ISSUES=$((FORMAT_ISSUES + 1)) - fi - done - - if [ "$FORMAT_ISSUES" -eq 0 ]; then - echo "โœ… All Terraform files properly formatted" >> $GITHUB_STEP_SUMMARY - else - echo "โš ๏ธ Found $FORMAT_ISSUES files with formatting issues" >> $GITHUB_STEP_SUMMARY - echo "**Fix**: Run \`terraform fmt -recursive\`" >> $GITHUB_STEP_SUMMARY - WARNINGS=$((WARNINGS + 1)) - fi - echo "" >> $GITHUB_STEP_SUMMARY - - # 4. Check for file_metadata blocks - echo "### File Metadata Validation" >> $GITHUB_STEP_SUMMARY - MISSING_METADATA=0 - - for tf_file in $(find . -name "*.tf" -type f); do - if ! grep -q "file_metadata" "$tf_file"; then - MISSING_METADATA=$((MISSING_METADATA + 1)) - fi - done - - if [ "$MISSING_METADATA" -eq 0 ]; then - echo "โœ… All Terraform files contain file_metadata block" >> $GITHUB_STEP_SUMMARY - else - echo "โš ๏ธ Found $MISSING_METADATA files missing file_metadata block" >> $GITHUB_STEP_SUMMARY - echo "**Reference**: docs/policy/terraform-file-standards.md" >> $GITHUB_STEP_SUMMARY - WARNINGS=$((WARNINGS + 1)) - fi - echo "" >> $GITHUB_STEP_SUMMARY - - # 5. Version Consistency Check - echo "### Version Consistency Check" >> $GITHUB_STEP_SUMMARY - VERSION_MISMATCHES=0 - EXPECTED_VERSION="04.00.04" - - for tf_file in $(find . -name "*.tf" -type f); do - if grep -q "version.*=" "$tf_file"; then - if ! grep -q "version.*=.*\"$EXPECTED_VERSION\"" "$tf_file"; then - VERSION_MISMATCHES=$((VERSION_MISMATCHES + 1)) - fi - fi - done - - if [ "$VERSION_MISMATCHES" -eq 0 ]; then - echo "โœ… All Terraform file versions match $EXPECTED_VERSION" >> $GITHUB_STEP_SUMMARY - else - echo "โš ๏ธ Found $VERSION_MISMATCHES files with version mismatches" >> $GITHUB_STEP_SUMMARY - echo "**Expected Version**: $EXPECTED_VERSION" >> $GITHUB_STEP_SUMMARY - WARNINGS=$((WARNINGS + 1)) - fi - echo "" >> $GITHUB_STEP_SUMMARY - - # 6. Copyright Header Check - echo "### Copyright Header Check" >> $GITHUB_STEP_SUMMARY - MISSING_COPYRIGHT=0 - - for tf_file in $(find . -name "*.tf" -type f); do - if ! grep -q "Copyright (C)" "$tf_file"; then - MISSING_COPYRIGHT=$((MISSING_COPYRIGHT + 1)) - fi - done - - if [ "$MISSING_COPYRIGHT" -eq 0 ]; then - echo "โœ… All Terraform files have copyright headers" >> $GITHUB_STEP_SUMMARY - else - echo "โš ๏ธ Found $MISSING_COPYRIGHT files missing copyright headers" >> $GITHUB_STEP_SUMMARY - echo "**Reference**: docs/policy/terraform-file-standards.md" >> $GITHUB_STEP_SUMMARY - WARNINGS=$((WARNINGS + 1)) - fi - echo "" >> $GITHUB_STEP_SUMMARY - - # Summary - echo "---" >> $GITHUB_STEP_SUMMARY - echo "### Validation Summary" >> $GITHUB_STEP_SUMMARY - echo "**Total Files**: $TF_COUNT" >> $GITHUB_STEP_SUMMARY - echo "**Errors**: $ERRORS" >> $GITHUB_STEP_SUMMARY - echo "**Warnings**: $WARNINGS" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - if [ "$VALIDATION_PASSED" = true ] && [ "$ERRORS" -eq 0 ]; then - echo "โœ… **Terraform Validation: PASSED**" >> $GITHUB_STEP_SUMMARY - exit 0 - elif [ "$ERRORS" -gt 0 ]; then - echo "โŒ **Terraform Validation: FAILED**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Note**: This is an informational check and does not block merges" >> $GITHUB_STEP_SUMMARY - exit 0 # Informational only - else - echo "โš ๏ธ **Terraform Validation: PASSED WITH WARNINGS**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Note**: This is an informational check and does not block merges" >> $GITHUB_STEP_SUMMARY - exit 0 # Informational only - fi - - summary: - name: Compliance Summary - runs-on: ubuntu-latest - needs: [ - repository-structure, documentation-quality, coding-standards, line-length-validation, license-compliance, git-hygiene, workflow-validation, version-consistency, script-integrity, enterprise-readiness, repository-health, - todo-fixme-tracking, file-size-limits, secret-scanning, broken-link-detection, - dependency-vulnerabilities, code-duplication, unused-dependencies, readme-completeness, - code-complexity, api-documentation, insecure-patterns, binary-file-detection, - dead-code-detection, file-naming-standards, accessibility-check, performance-metrics, terraform-validation - ] - if: always() - - steps: - - name: Generate Compliance Report - run: | - set -x - echo "# ๐Ÿ“Š MokoCLI Compliance Report" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Calculate overall status - REPO_STATUS="${{ needs.repository-structure.result }}" - DOCS_STATUS="${{ needs.documentation-quality.result }}" - CODE_STATUS="${{ needs.coding-standards.result }}" - LINE_LENGTH_STATUS="${{ needs.line-length-validation.result }}" - LICENSE_STATUS="${{ needs.license-compliance.result }}" - GIT_STATUS="${{ needs.git-hygiene.result }}" - WORKFLOW_STATUS="${{ needs.workflow-validation.result }}" - VERSION_STATUS="${{ needs.version-consistency.result }}" - SCRIPT_STATUS="${{ needs.script-integrity.result }}" - ENTERPRISE_STATUS="${{ needs.enterprise-readiness.result }}" - HEALTH_STATUS="${{ needs.repository-health.result }}" - TERRAFORM_STATUS="${{ needs.terraform-validation.result }}" - - PASSED=0 - FAILED=0 - WARNINGS=0 - TOTAL=28 - - # Critical checks (must pass) - [ "$REPO_STATUS" = "success" ] && PASSED=$((PASSED + 1)) || FAILED=$((FAILED + 1)) - [ "$DOCS_STATUS" = "success" ] && PASSED=$((PASSED + 1)) || FAILED=$((FAILED + 1)) - [ "$CODE_STATUS" = "success" ] && PASSED=$((PASSED + 1)) || FAILED=$((FAILED + 1)) - [ "$LICENSE_STATUS" = "success" ] && PASSED=$((PASSED + 1)) || FAILED=$((FAILED + 1)) - [ "$GIT_STATUS" = "success" ] && PASSED=$((PASSED + 1)) || FAILED=$((FAILED + 1)) - [ "$WORKFLOW_STATUS" = "success" ] && PASSED=$((PASSED + 1)) || FAILED=$((FAILED + 1)) - [ "$VERSION_STATUS" = "success" ] && PASSED=$((PASSED + 1)) || FAILED=$((FAILED + 1)) - [ "$SCRIPT_STATUS" = "success" ] && PASSED=$((PASSED + 1)) || FAILED=$((FAILED + 1)) - - # Informational checks (don't fail build) - if [ "$ENTERPRISE_STATUS" = "success" ]; then - PASSED=$((PASSED + 1)) - else - WARNINGS=$((WARNINGS + 1)) - fi - - if [ "$HEALTH_STATUS" = "success" ]; then - PASSED=$((PASSED + 1)) - else - WARNINGS=$((WARNINGS + 1)) - fi - - if [ "$TERRAFORM_STATUS" = "success" ]; then - PASSED=$((PASSED + 1)) - else - WARNINGS=$((WARNINGS + 1)) - fi - - # Adjust total to only count critical checks for compliance percentage - CRITICAL_TOTAL=8 - CRITICAL_PASSED=$((PASSED - WARNINGS)) - COMPLIANCE_PERCENT=$((CRITICAL_PASSED * 100 / CRITICAL_TOTAL)) - - # Overall status badge - if [ "$COMPLIANCE_PERCENT" -eq 100 ]; then - echo "## โœ… Overall Status: **COMPLIANT** ($COMPLIANCE_PERCENT%)" >> $GITHUB_STEP_SUMMARY - elif [ "$COMPLIANCE_PERCENT" -ge 80 ]; then - echo "## โš ๏ธ Overall Status: **MOSTLY COMPLIANT** ($COMPLIANCE_PERCENT%)" >> $GITHUB_STEP_SUMMARY - elif [ "$COMPLIANCE_PERCENT" -ge 50 ]; then - echo "## โš ๏ธ Overall Status: **PARTIALLY COMPLIANT** ($COMPLIANCE_PERCENT%)" >> $GITHUB_STEP_SUMMARY - else - echo "## โŒ Overall Status: **NON-COMPLIANT** ($COMPLIANCE_PERCENT%)" >> $GITHUB_STEP_SUMMARY - fi - - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Critical Checks:** $CRITICAL_PASSED/$CRITICAL_TOTAL passed" >> $GITHUB_STEP_SUMMARY - echo "**Total Checks:** $PASSED/$TOTAL passed" >> $GITHUB_STEP_SUMMARY - if [ "$WARNINGS" -gt 0 ]; then - echo "**Informational:** $WARNINGS warning(s)" >> $GITHUB_STEP_SUMMARY - fi - echo "" >> $GITHUB_STEP_SUMMARY - - # Progress bar - FILLED=$((COMPLIANCE_PERCENT / 5)) - EMPTY=$((20 - FILLED)) - BAR="" - for i in $(seq 1 $FILLED); do BAR="${BAR}โ–ˆ"; done - for i in $(seq 1 $EMPTY); do BAR="${BAR}โ–‘"; done - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "$BAR $COMPLIANCE_PERCENT%" >> $GITHUB_STEP_SUMMARY - echo "\`\`\`" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - # Detailed breakdown - echo "## Validation Results" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "| Area | Status | Result | Priority |" >> $GITHUB_STEP_SUMMARY - echo "|------|--------|--------|----------|" >> $GITHUB_STEP_SUMMARY - - # Repository Structure - if [ "$REPO_STATUS" = "success" ]; then - echo "| ๐Ÿ“ Repository Structure | โœ… Pass | Compliant | - |" >> $GITHUB_STEP_SUMMARY - else - echo "| ๐Ÿ“ Repository Structure | โŒ Fail | **Action Required** | ๐Ÿ”ด Critical |" >> $GITHUB_STEP_SUMMARY - fi - - # Documentation Quality - if [ "$DOCS_STATUS" = "success" ]; then - echo "| ๐Ÿ“š Documentation Quality | โœ… Pass | Compliant | - |" >> $GITHUB_STEP_SUMMARY - else - echo "| ๐Ÿ“š Documentation Quality | โŒ Fail | **Action Required** | ๐Ÿ”ด Critical |" >> $GITHUB_STEP_SUMMARY - fi - - # Coding Standards - if [ "$CODE_STATUS" = "success" ]; then - echo "| ๐Ÿ’ป Coding Standards | โœ… Pass | Compliant | - |" >> $GITHUB_STEP_SUMMARY - else - echo "| ๐Ÿ’ป Coding Standards | โš ๏ธ Warning | Review Recommended | ๐ŸŸก Medium |" >> $GITHUB_STEP_SUMMARY - fi - - # License Compliance - if [ "$LICENSE_STATUS" = "success" ]; then - echo "| โš–๏ธ License Compliance | โœ… Pass | Compliant | - |" >> $GITHUB_STEP_SUMMARY - else - echo "| โš–๏ธ License Compliance | โŒ Fail | **Action Required** | ๐Ÿ”ด Critical |" >> $GITHUB_STEP_SUMMARY - fi - - # Git Hygiene - if [ "$GIT_STATUS" = "success" ]; then - echo "| ๐Ÿงน Git Repository Hygiene | โœ… Pass | Compliant | - |" >> $GITHUB_STEP_SUMMARY - else - echo "| ๐Ÿงน Git Repository Hygiene | โš ๏ธ Warning | Review Recommended | ๐ŸŸก Medium |" >> $GITHUB_STEP_SUMMARY - fi - - # Workflow Configuration - if [ "$WORKFLOW_STATUS" = "success" ]; then - echo "| โš™๏ธ Workflow Configuration | โœ… Pass | Compliant | - |" >> $GITHUB_STEP_SUMMARY - else - echo "| โš™๏ธ Workflow Configuration | โš ๏ธ Warning | Review Recommended | ๐ŸŸก Medium |" >> $GITHUB_STEP_SUMMARY - fi - - # Version Consistency - if [ "$VERSION_STATUS" = "success" ]; then - echo "| ๐Ÿ”ข Version Consistency | โœ… Pass | All versions match | - |" >> $GITHUB_STEP_SUMMARY - else - echo "| ๐Ÿ”ข Version Consistency | โŒ Fail | **Action Required** | ๐Ÿ”ด Critical |" >> $GITHUB_STEP_SUMMARY - fi - - # Script Integrity - if [ "$SCRIPT_STATUS" = "success" ]; then - echo "| ๐Ÿ” Script Integrity | โœ… Pass | SHA hashes validated | - |" >> $GITHUB_STEP_SUMMARY - else - echo "| ๐Ÿ” Script Integrity | โŒ Fail | **Action Required** | ๐Ÿ”ด Critical |" >> $GITHUB_STEP_SUMMARY - fi - - # Enterprise Readiness (Informational) - if [ "$ENTERPRISE_STATUS" = "success" ]; then - echo "| ๐Ÿข Enterprise Readiness | โœ… Pass | Ready for enterprise | โ„น๏ธ Info |" >> $GITHUB_STEP_SUMMARY - else - echo "| ๐Ÿข Enterprise Readiness | โ„น๏ธ Info | Review suggestions | โ„น๏ธ Info |" >> $GITHUB_STEP_SUMMARY - fi - - # Repository Health (Informational) - if [ "$HEALTH_STATUS" = "success" ]; then - echo "| ๐Ÿฅ Repository Health | โœ… Pass | Health check passed | โ„น๏ธ Info |" >> $GITHUB_STEP_SUMMARY - else - echo "| ๐Ÿฅ Repository Health | โ„น๏ธ Info | Review recommendations | โ„น๏ธ Info |" >> $GITHUB_STEP_SUMMARY - fi - - echo "" >> $GITHUB_STEP_SUMMARY - - # Action items summary - if [ "$FAILED" -gt 0 ]; then - echo "## โšก Action Items" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**$FAILED validation area(s) require attention:**" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - - [ "$REPO_STATUS" != "success" ] && echo "- ๐Ÿ”ด **Critical:** Fix repository structure issues" >> $GITHUB_STEP_SUMMARY - [ "$DOCS_STATUS" != "success" ] && echo "- ๐Ÿ”ด **Critical:** Improve documentation quality" >> $GITHUB_STEP_SUMMARY - [ "$LICENSE_STATUS" != "success" ] && echo "- ๐Ÿ”ด **Critical:** Resolve license compliance issues" >> $GITHUB_STEP_SUMMARY - [ "$CODE_STATUS" != "success" ] && echo "- ๐ŸŸก **Medium:** Review coding standards violations" >> $GITHUB_STEP_SUMMARY - [ "$GIT_STATUS" != "success" ] && echo "- ๐ŸŸก **Medium:** Address git repository hygiene items" >> $GITHUB_STEP_SUMMARY - [ "$WORKFLOW_STATUS" != "success" ] && echo "- ๐ŸŸก **Medium:** Review workflow configuration" >> $GITHUB_STEP_SUMMARY - - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Next Steps:**" >> $GITHUB_STEP_SUMMARY - echo "1. Review detailed results in individual job outputs above" >> $GITHUB_STEP_SUMMARY - echo "2. Follow remediation steps provided for each failure" >> $GITHUB_STEP_SUMMARY - echo "3. Re-run this workflow after making corrections" >> $GITHUB_STEP_SUMMARY - echo "4. Reach 100% compliance before merging" >> $GITHUB_STEP_SUMMARY - else - echo "## ๐ŸŽ‰ Excellent!" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "Your repository is **fully compliant** with MokoCLI!" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Achievements:**" >> $GITHUB_STEP_SUMMARY - echo "- โœ… All required directories and files present" >> $GITHUB_STEP_SUMMARY - echo "- โœ… Documentation meets quality standards" >> $GITHUB_STEP_SUMMARY - echo "- โœ… Coding standards followed" >> $GITHUB_STEP_SUMMARY - echo "- โœ… License compliance verified" >> $GITHUB_STEP_SUMMARY - echo "- โœ… Git repository well-maintained" >> $GITHUB_STEP_SUMMARY - echo "- โœ… Workflows properly configured" >> $GITHUB_STEP_SUMMARY - fi - - echo "" >> $GITHUB_STEP_SUMMARY - echo "---" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "๐Ÿ“š **Resources:**" >> $GITHUB_STEP_SUMMARY - echo "- [MokoCLI Documentation](https://github.com/mokoconsulting-tech/mokocli)" >> $GITHUB_STEP_SUMMARY - echo "- [Repository Structure Guide](https://github.com/mokoconsulting-tech/mokocli/tree/main/docs/policy/core-structure.md)" >> $GITHUB_STEP_SUMMARY - echo "- [Documentation Standards](https://github.com/mokoconsulting-tech/mokocli/tree/main/docs/policy/document-formatting.md)" >> $GITHUB_STEP_SUMMARY - echo "- [Coding Standards](https://github.com/mokoconsulting-tech/mokocli/tree/main/docs/policy/coding-style-guide.md)" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "_Generated by MokoCLI Compliance Workflow v${WORKFLOW_VERSION}_" >> $GITHUB_STEP_SUMMARY - - # Create tracking issue for non-compliance if on push - if [ "$COMPLIANCE_PERCENT" -lt 100 ] && [ "${{ github.event_name }}" = "push" ]; then - echo "Creating tracking issue for standards violations..." - fi - - # Exit with error if not fully compliant - if [ "$COMPLIANCE_PERCENT" -lt 100 ]; then - echo "" >> $GITHUB_STEP_SUMMARY - echo "### โŒ Standards Compliance Failed" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Overall Compliance:** $COMPLIANCE_PERCENT%" >> $GITHUB_STEP_SUMMARY - echo "**Status:** Repository does not meet 100% compliance requirement" >> $GITHUB_STEP_SUMMARY - echo "**Action Required:** Review and fix all validation failures above" >> $GITHUB_STEP_SUMMARY - echo "" - echo "โŒ ERROR: Standards compliance at $COMPLIANCE_PERCENT% - 100% required" - exit 1 - fi - - echo "" >> $GITHUB_STEP_SUMMARY - echo "### โœ… Full Standards Compliance Achieved" >> $GITHUB_STEP_SUMMARY - echo "" >> $GITHUB_STEP_SUMMARY - echo "**Overall Compliance:** 100%" >> $GITHUB_STEP_SUMMARY - echo "**Status:** Repository meets all MokoCLI requirements" >> $GITHUB_STEP_SUMMARY - echo "" - echo "โœ… SUCCESS: Repository is fully MokoCLI compliant" - - - name: Create or reopen tracking issue for standards violations - if: failure() - env: - GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }} - run: | - REPO="${{ github.repository }}" - RUN_URL="${{ github.server_url }}/${REPO}/actions/runs/${{ github.run_id }}" - DATE=$(date -u '+%Y-%m-%d') - SHA="${{ github.sha }}" - ACTOR="${{ github.actor }}" - BRANCH="${{ github.ref_name }}" - - # Collect failed checks - FAILED="" - [ "${{ needs.repository-structure.result }}" != "success" ] && FAILED="${FAILED}\n- Repository Structure" - [ "${{ needs.documentation-quality.result }}" != "success" ] && FAILED="${FAILED}\n- Documentation Quality" - [ "${{ needs.coding-standards.result }}" != "success" ] && FAILED="${FAILED}\n- Coding Standards" - [ "${{ needs.license-compliance.result }}" != "success" ] && FAILED="${FAILED}\n- License Compliance" - [ "${{ needs.git-hygiene.result }}" != "success" ] && FAILED="${FAILED}\n- Git Hygiene" - [ "${{ needs.workflow-validation.result }}" != "success" ] && FAILED="${FAILED}\n- Workflow Validation" - [ "${{ needs.version-consistency.result }}" != "success" ] && FAILED="${FAILED}\n- Version Consistency" - [ "${{ needs.script-integrity.result }}" != "success" ] && FAILED="${FAILED}\n- Script Integrity" - [ "${{ needs.secret-scanning.result }}" != "success" ] && FAILED="${FAILED}\n- Secret Scanning" - [ "${{ needs.line-length-validation.result }}" != "success" ] && FAILED="${FAILED}\n- Line Length" - [ "${{ needs.file-size-limits.result }}" != "success" ] && FAILED="${FAILED}\n- File Size Limits" - [ "${{ needs.readme-completeness.result }}" != "success" ] && FAILED="${FAILED}\n- README Completeness" - - if [ -z "$FAILED" ]; then - echo "No failed checks to report" - exit 0 - fi - - TITLE="[Standards] Compliance violations โ€” ${DATE}" - BODY="## Standards Compliance Violations - - | Field | Value | - |-------|-------| - | **Branch** | \`${BRANCH}\` | - | **Commit** | \`${SHA:0:7}\` | - | **Actor** | @${ACTOR} | - | **Run** | [View workflow](${RUN_URL}) | - - ### Failed Checks - $(printf '%b' "$FAILED") - - ### Required Actions - 1. Review the [workflow run](${RUN_URL}) for details - 2. Fix each failed check - 3. Push to trigger a new scan - - --- - *Auto-created by standards-compliance workflow*" - - BODY=$(echo "$BODY" | sed 's/^ //') - LABEL="standards-violation" - - gh label create "$LABEL" --repo "$REPO" --color "D73A4A" --description "Standards compliance failure" --force 2>/dev/null || true - - EXISTING=$(gh api "repos/${REPO}/issues?labels=${LABEL}&state=all&per_page=1&sort=created&direction=desc" \ - --jq '.[0].number' 2>/dev/null) - - if [ -n "$EXISTING" ] && [ "$EXISTING" != "null" ]; then - gh api "repos/${REPO}/issues/${EXISTING}" -X PATCH \ - -f title="$TITLE" -f body="$BODY" -f state="open" --silent - echo "Updated issue #${EXISTING}" - else - gh issue create --repo "$REPO" --title "$TITLE" --body "$BODY" \ - --label "$LABEL" --assignee "jmiller" - fi - -# CUSTOMIZATION: -# -# 1. Adjust severity of checks (convert warnings to errors or vice versa) -# 2. Add project-specific validation rules -# 3. Integrate with custom linting tools -# 4. Add notification steps for compliance failures -# 5. Customize required files/directories for your project type - diff --git a/.mokogitea/workflows/version-set.yml b/.mokogitea/workflows/version-set.yml deleted file mode 100644 index 172c085..0000000 --- a/.mokogitea/workflows/version-set.yml +++ /dev/null @@ -1,130 +0,0 @@ -# Copyright (C) 2026 Moko Consulting -# -# SPDX-License-Identifier: GPL-3.0-or-later -# -# FILE INFORMATION -# DEFGROUP: MokoGitea.Workflow.Template -# INGROUP: MokoCLI.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 "/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 '\K[^<]+' "$MANIFEST" | head -1) - sed -i "s|${OLD_VER}|${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