81 Commits

Author SHA1 Message Date
Jonathan Miller beed1e628a feat: add changelog:prune CLI + integrate into auto-release workflow
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 4s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 1m18s
- cli/changelog_prune.php: keeps [Unreleased] + last N versioned entries (default 5)
- Register changelog:promote and changelog:prune in bin/moko COMMAND_MAP
- auto-release.yml Step 4b: promote [Unreleased] to version, then prune old entries

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 00:37:33 -05:00
Jonathan Miller d7fdd99f68 fix: remove remaining DefinitionParser and definitions/ references
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 44s
- Remove generateRepositoryDefinition() method from RepositorySynchronizer
- Remove SYNC_DEFINITION_DIR constant
- Remove .tf version check from check_version_consistency.php
- Clean up stale comments referencing DefinitionParser

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-27 00:35:24 -05:00
Jonathan Miller a93794f1ba docs: update CHANGELOG with v09.02 session changes
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 23:50:16 -05:00
Jonathan Miller fe3644204a refactor: remove HCL definitions — Template repos are now canonical
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 2s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 45s
- Delete definitions/ directory (6 default + 48 sync = ~5000 lines of HCL)
- Delete lib/Enterprise/DefinitionParser.php
- Move manifest-schema.xsd to templates/schemas/
- Add governance.yml template for Template repos
- Update RepositorySynchronizer to remove DefinitionParser dependency
- Update push_files.php to detect platform from manifest.xml via API
- Update auto_detect_platform.php to gracefully handle missing schema dir
- Update archive_repo.php to remove sync def cleanup step

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 23:42:45 -05:00
Jonathan Miller 34ef05bd6e refactor: fix definition drift — migrate to .mokogitea, rename templates/gitea → templates/mokogitea
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 46s
- Replace all .github/workflows refs in definitions with .mokogitea/workflows
- Add all 13 universal workflows to every platform definition
- Fix ISSUE_TEMPLATE paths: .github/ → .mokogitea/
- Fix template source paths: templates/github/ → templates/mokogitea/
- Remove 60+ dead template references pointing to non-existent files
- Rename templates/gitea/ directory to templates/mokogitea/
- Add orphaned workflows (ci-platform, issue-branch) to definitions

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 23:21:42 -05:00
Jonathan Miller e177971462 chore: add .mokogitea/workflows to platform definitions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 46s
Adds branch-cleanup.yml and manifest.xml to the .mokogitea directory
definition across generic, joomla, dolibarr, and platform definitions.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 22:56:03 -05:00
Jonathan Miller 51e599acef feat: add branch-cleanup workflow — auto-delete merged feature branches
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 46s
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 22:48:14 -05:00
gitea-actions[bot] c73675234b feat(ci): add version branch creation on stable release [skip ci] 2026-05-27 02:19:40 +00:00
Jonathan Miller 16ff7e611e Merge branch 'dev'
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 2s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 45s
# Conflicts:
#	cli/version_bump.php
2026-05-26 20:39:51 -05:00
Jonathan Miller 030f057ab4 fix: preserve version suffix (-dev, -rc, etc.) during patch bump
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 1s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 44s
version_bump.php now detects and preserves the existing suffix from
manifest versions. The suffix should only change when the branch
changes, not on every patch bump.

version_read.php now returns the full version including suffix.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 18:52:20 -05:00
Jonathan Miller fec5464c17 chore(version): patch bump to 09.02.05 [skip ci]
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 18:43:53 -05:00
Jonathan Miller 2723fbf0e7 refactor: simplify version extraction — derive filename from manifest version (#191)
Version string (e.g. 01.02.14-dev) is now read once from the manifest
and flows through unchanged. Removes DISPLAY_VERSION/SUFFIX re-derivation
from branch names in workflows.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 18:43:53 -05:00
jmiller e9dcd48e44 Merge pull request 'chore: cascade main → dev (1bd170c) [skip ci]' (#190) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 22:47:23 +00:00
Jonathan Miller 1bd170c77f fix: workflow race conditions, pre-release CLI migration, auto-bump dev-only
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 48s
2026-05-26 17:47:19 -05:00
gitea-actions[bot] 1e18d6bcb8 chore(version): patch bump to 09.02.05 [skip ci] 2026-05-26 22:46:31 +00:00
Jonathan Miller 35befccf06 fix: resolve workflow race conditions and remaining gaps
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Version Bump (push) Successful in 3s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 45s
Race condition fixes:
- auto-bump.yml: dev-only, skips merge commits (avoids conflict with pre-release)
- auto-release.yml: minor bump restored inline (avoids conflict with auto-bump on main)

auto-release.yml:
- Step 8b: removed 26-line inline fallback, uses release_body_update.php only
- promote-rc: branch defaults to 'dev' when workflow_dispatch (no PR context)

pre-release.yml (328 → 223 lines):
- Replaced 53-line inline build with release_package.php
- Replaced 47-line inline release creation with release_create.php
- Standardized setup step to match auto-release pattern
- Fixed empty $MANIFEST variable output
- SHA256 now sourced from release_package.php output

auto-bump.yml:
- Dev-only (removed main), skips merge commits
- Added FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 env
- Fixed job name to "Version Bump"

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 17:46:25 -05:00
gitea-actions[bot] d012bb900b chore(version): minor bump to 09.03.00 [skip ci] 2026-05-26 22:34:23 +00:00
jmiller 2ab332161d Merge pull request 'chore: cascade main → dev (a9acb2d) [skip ci]' (#189) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 22:34:22 +00:00
Jonathan Miller a9acb2d27c Merge dev: SHA-256 in updates.xml, centralized auto-bump, Joomla package language fix
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Universal: Auto Version Bump / Patch Bump (push) Successful in 4s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 47s
2026-05-26 17:34:15 -05:00
gitea-actions[bot] e609a4e205 chore(version): patch bump to 09.02.04 [skip ci] 2026-05-26 22:31:57 +00:00
Jonathan Miller dbf4cdef9a fix: Joomla package builds now include language/ and other top-level dirs
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Auto Version Bump / Patch Bump (push) Successful in 4s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 45s
pre-release.yml package build was only copying *.xml and *.php from
src/ root, missing language/, media/, and other non-package directories.

Fixes #187

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 17:31:47 -05:00
gitea-actions[bot] 24d5238b64 chore(version): minor bump to 09.03.00 [skip ci] 2026-05-26 22:30:52 +00:00
jmiller 4ccefec2dc Merge pull request 'chore: cascade main → dev (5a8b18e) [skip ci]' (#188) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 22:30:50 +00:00
Jonathan Miller 5a8b18ea8d fix: SHA-256 checksums in updates.xml + centralized auto-bump
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 4s
Universal: Auto Version Bump / Patch Bump (push) Successful in 5s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 48s
2026-05-26 17:30:43 -05:00
gitea-actions[bot] 9dbf790e1a chore(version): patch bump to 09.02.03 [skip ci] 2026-05-26 22:30:20 +00:00
Jonathan Miller a07d93b6fc fix: pass SHA-256 checksum to updates.xml in both release workflows
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Auto Version Bump / Patch Bump (push) Successful in 4s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 47s
- release_package.php now outputs sha256_zip= to GITHUB_OUTPUT
- auto-release.yml: moved updates.xml write after package build so SHA is available
- pre-release.yml: passes --sha from zip step to updates_xml_build.php
- updates.xml now includes <sha256> tag for Joomla update integrity verification

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 17:30:12 -05:00
gitea-actions[bot] 415e58d06c chore(version): minor bump to 09.03.00 [skip ci] 2026-05-26 22:22:49 +00:00
jmiller d8ec7b5ba0 Merge pull request 'chore: cascade main → dev (e882425) [skip ci]' (#186) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 22:22:49 +00:00
Jonathan Miller e882425f04 feat: centralized auto-bump workflow for all version bumps
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Universal: Auto Version Bump / Patch Bump (push) Successful in 4s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 50s
2026-05-26 17:22:42 -05:00
gitea-actions[bot] 3171fb3ef0 chore(version): patch bump to 09.02.02 [skip ci] 2026-05-26 22:22:29 +00:00
Jonathan Miller 6cd46f0b7f feat: auto-bump.yml handles all version bumps, removed from release workflows
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Auto Version Bump / Patch Bump (push) Successful in 5s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 48s
auto-bump.yml: patch bump on push to dev, minor bump on push to main
auto-release.yml: removed Step 1b (minor bump) — handled by auto-bump
pre-release.yml: removed patch bump — handled by auto-bump

Version bump is now centralized in one workflow across all branches.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 17:21:55 -05:00
gitea-actions[bot] 48ae7c1e88 chore(version): patch bump to 09.02.01 [skip ci] 2026-05-26 22:19:23 +00:00
Jonathan Miller 63a2640254 fix: include top-level directories (language/) in Joomla package ZIP
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Generic: Repo Health / Release configuration (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Access control (push) Successful in 1s
Universal: PR Check / Branch Policy (pull_request) Successful in 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: PR Check / Validate PR (pull_request) Successful in 4s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 5s
Universal: Auto Version Bump / Patch Bump (push) Successful in 6s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 54s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 54s
release_package.php was only copying *.xml and *.php from the package
source root, missing the language/ directory. Joomla install fails with
"File does not exist" for package-level language files.

Now includes all top-level directories (except packages/) in the
package ZIP.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 17:19:15 -05:00
Jonathan Miller 6ec2202c6e fix: include language directory in Joomla package ZIP builds
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 2s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 54s
Package-type extensions reference language files in their manifest
(e.g. language/en-GB/pkg_*.sys.ini) but the build only copied
top-level XML/PHP files. Now also adds the language/ directory.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 17:19:09 -05:00
Jonathan Miller 8f7cce051b docs(changelog): add auto-bump entry [skip bump]
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Generic: Repo Health / Release configuration (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: Auto Version Bump / Patch Bump (push) Has been skipped
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 5s
Universal: PR Check / Validate PR (pull_request) Successful in 4s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 47s
2026-05-26 17:15:46 -05:00
Jonathan Miller f426f21f2e feat: auto patch-bump on every push to dev
New workflow auto-bump.yml: runs version_bump.php on every push to dev,
propagates via version_set_platform.php + version_check.php, commits
with [skip ci] to avoid retriggering.

Skips when commit message contains [skip ci] or [skip bump].

Removed redundant patch bump from pre-release.yml — version is already
bumped by the push workflow before pre-release runs.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 17:11:09 -05:00
Jonathan Miller 2ee5a55ec5 fix: preserve version suffix (-dev/-rc/etc) in version_bump.php
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 43s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Generic: Repo Health / Release configuration (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: Build & Release / Promote Pre-Release to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Universal: PR Check / Validate PR (pull_request) Successful in 4s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 5s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 44s
version_bump.php was stripping stability suffixes (e.g. -dev, -rc) from
Joomla extension manifest <version> tags during auto-bump. Now captures
the suffix from the source version and re-applies it after incrementing.

manifest.xml and README.md remain suffix-free (canonical bare version).
Only Joomla extension manifests preserve their suffix.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 17:07:07 -05:00
Jonathan Miller a04040533c docs(changelog): add v09.02.00 release notes
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 1s
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 16:59:25 -05:00
jmiller d5541abf22 Merge pull request 'chore: cascade main → dev (8ae829a) [skip ci]' (#183) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 21:55:38 +00:00
Jonathan Miller 8ae829ad89 feat: add live deploy target with multi-instance support to deploy-module.yml
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 44s
- Add 'live' and 'all' options to server selector
- Live deploy reads LIVE_TARGETS JSON secret for multiple production instances
- Move dev/demo host config from hardcoded env to vars.*
- Add summary step for deploy reporting

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 16:55:32 -05:00
jmiller 5815ad040f Merge pull request 'chore: cascade main → dev (96b6db7) [skip ci]' (#182) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 21:54:46 +00:00
Jonathan Miller 96b6db73a9 Merge dev: workflow CLI refactoring, version_bump package.json/pyproject.toml, v09.02.00
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 46s
2026-05-26 16:54:15 -05:00
jmiller 8eb3e310cf Merge pull request 'chore: cascade main → dev (eca475c) [skip ci]' (#181) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 21:32:24 +00:00
Jonathan Miller eca475c6e3 chore: remove legacy SFTP deploy steps from workflows
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 4s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 47s
- Remove SFTP deploy step from update-server.yml
- Delete deploy-manual.yml (manual SFTP deploy workflow)

All deployment now goes through updates.xml and Gitea releases.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 16:31:35 -05:00
Jonathan Miller 92822303ef feat: version_bump/read support package.json and pyproject.toml
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 44s
version_read.php now reads version from:
- .mokogitea/manifest.xml (authoritative)
- README.md, Joomla XML, composer.json (existing)
- package.json (Node.js / MCP repos) — new
- pyproject.toml (Python repos) — new

version_bump.php now writes bumped version to all of the above.

Also bumps moko-platform 09.01.00 → 09.02.00.

Closes #179

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 16:28:40 -05:00
Jonathan Miller 9649fb55cf refactor: replace all inline bash in workflows with CLI tool calls
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 46s
auto-release.yml (761 → 490 lines, -271 lines):
- Sanity checks: release_validate.php replaces 95-line inline bash (#180)
- Step 7b: release_create.php replaces 65-line curl+python3 block (#176)
- Step 8: release_package.php replaces 115-line build+upload block (#173)
- Step 9: release_mirror.php replaces 40-line GitHub mirror block (#175)
- Dolibarr reset: version_reset_dev.php replaces 20-line inline (#174)
- Added workflow_dispatch promote-rc fallback for MokoGitea#220 (#178)

pre-release.yml:
- Fixed updates.xml sync: checkout only updates.xml, not entire tree (#177)

Closes #173 #174 #175 #176 #177 #178 #180

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 16:21:02 -05:00
jmiller 8f39017b59 Merge pull request 'chore: cascade main → dev (bd18642) [skip ci]' (#171) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 20:14:09 +00:00
Jonathan Miller bd18642045 Merge dev: release promotion pipeline, manifest-aware CLI, workflow refactoring
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 46s
- 7 new CLI tools (manifest_element, release_create, release_package,
  release_promote, release_mirror, version_reset_dev, ManifestReader)
- Universal workflows: RC promotion, auto-dev-release, paths filter removed
- CLI tools now read .mokogitea/manifest.xml for platform-aware behavior
- updates_xml_build.php supports non-Joomla platforms
- Version 09.01.00
2026-05-26 15:13:34 -05:00
jmiller 820e968e1a Merge pull request 'chore: cascade main → dev (a5cd566) [skip ci]' (#170) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 20:12:40 +00:00
jmiller a5cd566dea fix: version_bump.php cascades version to all Joomla XML manifests
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 51s
Authored-by: Moko Consulting
2026-05-26 20:12:36 +00:00
jmiller b5599579a7 chore: sync .mokogitea/workflows/update-server.yml from moko-platform [skip ci] 2026-05-26 20:12:34 +00:00
Jonathan Miller 61a232dfc6 feat: CLI tools now read .mokogitea/manifest.xml for platform-aware behavior
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 56s
New: lib/Enterprise/ManifestReader.php — shared manifest.xml parser with
typed accessors for platform, package-type, entry-point, source-dir.

Updated CLI tools to read manifest.xml:
- updates_xml_build.php: supports non-Joomla platforms (dolibarr, generic,
  mcp) — builds generic updates.xml when no Joomla manifest found
- release_package.php: reads entry-point from manifest.xml for source dir
  resolution instead of hard-coded src/htdocs fallback
- pre-release.yml: replaced 75 lines of inline logic with CLI tool calls
  (manifest_read.php, manifest_element.php, updates_xml_build.php)

Closes #163

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 15:12:13 -05:00
jmiller a45bf42335 chore: sync .mokogitea/workflows/auto-release.yml from moko-platform [skip ci] 2026-05-26 20:09:07 +00:00
jmiller 77a1ae3977 Merge pull request 'chore: cascade main → dev (fb5461b) [skip ci]' (#169) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 20:08:09 +00:00
jmiller fb5461b661 Merge pull request 'feat: manifest.xml as canonical version source' (#168) from dev into main
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 48s
Merge PR #168: feat: manifest.xml as canonical version source
2026-05-26 20:08:03 +00:00
Jonathan Miller e15421699e feat: manifest.xml as canonical version source
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (pull_request) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Blocked by required conditions
Platform: moko-platform CI / CI Summary (pull_request) Blocked by required conditions
Universal: PR Check / Build RC Package (pull_request) Blocked by required conditions
Generic: Repo Health / Release configuration (pull_request) Blocked by required conditions
Generic: Repo Health / Scripts governance (pull_request) Blocked by required conditions
Generic: Repo Health / Repository health (pull_request) Blocked by required conditions
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Successful in 4s
Universal: Security Audit / Dependency Audit (pull_request) Successful in 4s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 50s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 48s
Universal: Build & Release / Promote Pre-Release to RC (pull_request) Has been skipped
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 12s
- version_bump.php: insert <version> tag into manifest.xml if missing
  (placed before <license> per schema order)
- version_read.php: backfill <version> into manifest.xml from fallback
  sources (README.md, Joomla XML) on first read
- manifest-schema.xsd: add version="09.01.00" attribute to xs:schema
- manifest.xml: add <version>, xsi:schemaLocation pointing to schema
  on moko-platform main branch

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 15:03:52 -05:00
Jonathan Miller 48d574e225 feat: release_mirror.php — mirror Gitea releases to GitHub
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 43s
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
New CLI tool that mirrors a Gitea release (with assets) to a GitHub
repository. Replaces the 40-line inline bash in auto-release.yml Step 9.

Supports create/update, asset download+upload, and proper GitHub API
headers (User-Agent, Accept).

Closes #160

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 14:46:59 -05:00
Jonathan Miller 1dba0c37b9 refactor: pre-release.yml uses CLI tools instead of inline logic
Replace 3 blocks of inline bash/PHP with existing CLI tool calls:
- Platform detection: manifest_read.php --github-output
- Element detection: manifest_element.php --github-output (28 lines → 4 lines)
- updates.xml update: updates_xml_build.php (25 lines → 3 lines)

Removes ~75 lines of duplicated inline logic. Workflow now delegates
all platform-aware logic to moko-platform CLI tools.

Closes #163

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 14:45:26 -05:00
Jonathan Miller 07ea171af9 feat: release promotion pipeline, 5 new CLI tools, workflow refactoring
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 43s
New CLI tools:
- manifest_element.php — extract element/type/prefix from any platform manifest
- release_create.php — create/overwrite Gitea releases with proper naming
- release_package.php — build ZIP+tar.gz, SHA-256, upload assets
- release_promote.php — promote releases between channels (dev→RC→stable)
- version_reset_dev.php — reset platform version on dev branch after release

Updated CLI tools:
- version_bump.php — now writes to manifests, Dolibarr mod, composer.json (not just README)
- release_cascade.php — added --version for version-aware deletion of stale releases
- release_validate.php — auto-detect platform, --github-output, source dir check

Workflow changes (auto-release.yml):
- Draft PR to main → auto-promote highest pre-release to RC
- Merged PR to main → promote RC to stable (skip rebuild when RC exists)
- Removed paths filter for Go/Node/generic repo compatibility
- Fixed cascade --api-base parameter bug

Workflow changes (pre-release.yml):
- Auto-trigger development pre-release on feature branch merge to dev
- Removed paths filter

Infrastructure:
- RepositorySynchronizer: fixed template repo names, .mokogitea/workflows path,
  universal workflow cascade (Template-Generic → other templates)
- bulk_sync.php: syncs universal workflows to templates before repo sync
- PHPDoc added to 4 classes missing class-level docs
- Version bump 09.00.00 → 09.01.00

Closes #152 #153 #154 #155 #156 #157 #158 #159 #161 #162

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-26 14:29:32 -05:00
jmiller 420b4f5f3c Merge pull request 'chore: cascade main → dev (f8c28f0) [skip ci]' (#166) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 19:28:01 +00:00
jmiller f8c28f055b feat: manifest-schema.xsd — XSD schema for .mokogitea/manifest.xml
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 47s
Authored-by: Moko Consulting
2026-05-26 19:27:57 +00:00
jmiller a7df4d49b9 feat: wiki_sync.php — sync standards wiki pages to template repos
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 50s
Authored-by: Moko Consulting
2026-05-26 19:27:57 +00:00
jmiller 320b2c57be Merge pull request 'chore: cascade main → dev (c5e4b41) [skip ci]' (#165) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 19:26:49 +00:00
jmiller d323ca52af feat: version_bump.php writes to .mokogitea/manifest.xml as canonical target
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Generic: Repo Health / Access control (push) Successful in 2s
Platform: moko-platform CI / Gate 1: Code Quality (push) Successful in 53s
Authored-by: Moko Consulting
2026-05-26 19:26:45 +00:00
jmiller c5e4b41100 feat: version_read.php uses .mokogitea/manifest.xml as canonical version source
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Universal: Cascade Main → Dev / Cascade main → branches (push) Failing after 2s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Platform: moko-platform CI / Gate 1: Code Quality (push) Successful in 53s
Authored-by: Moko Consulting
2026-05-26 19:26:45 +00:00
jmiller 335fcd0382 feat: manifest_read.php adds version field from identity block
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Platform: moko-platform CI / Gate 5: Template Integrity (push) Blocked by required conditions
Platform: moko-platform CI / CI Summary (push) Blocked by required conditions
Generic: Repo Health / Release configuration (push) Blocked by required conditions
Generic: Repo Health / Scripts governance (push) Blocked by required conditions
Generic: Repo Health / Repository health (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 4s
Platform: moko-platform CI / Gate 1: Code Quality (push) Successful in 52s
Authored-by: Moko Consulting
2026-05-26 19:26:44 +00:00
jmiller c1c820bb5c chore: add .mokogitea/workflows/update-server.yml from moko-platform [skip ci] 2026-05-26 19:03:58 +00:00
Jonathan Miller f441a8a51f fix(updates_xml): restore <client>site</client> for all extension types
Platform: moko-platform CI / Gate 1: Code Quality (push) Successful in 1m8s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 2s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Joomla requires the client tag to match updates to installed extensions.
Without it, extension_id=0 in #__updates and the update is invisible.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 23:40:54 -05:00
Jonathan Miller 005eb5cf39 fix(updates_xml): treat 'development' and 'dev' as same channel in preservation
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Platform: moko-platform CI / Gate 1: Code Quality (push) Successful in 43s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Legacy/manual entries used <tag>development</tag> while the CLI writes
<tag>dev</tag>. Without this alias, old entries survived preservation
and created duplicate dev channel entries.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 23:35:54 -05:00
jmiller 21acb19fed Merge pull request 'chore: cascade main → dev (1fe4f83) [skip ci]' (#141) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 04:33:44 +00:00
jmiller 1fe4f83e73 Merge pull request 'chore(release): v09.00.00' (#140) from dev into main
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 4s
Platform: moko-platform CI / Gate 1: Code Quality (push) Successful in 50s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
2026-05-26 04:33:40 +00:00
Jonathan Miller 7e5c322792 chore(release): bump to 09.00.00
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 4s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: PR Check / Branch Policy (pull_request) Successful in 5s
Generic: Repo Health / Access control (pull_request) Successful in 6s
Universal: PR Check / Validate PR (pull_request) Successful in 11s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 12s
Universal: Security Audit / Dependency Audit (pull_request) Successful in 9s
Platform: moko-platform CI / Gate 1: Code Quality (push) Successful in 1m21s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Successful in 59s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Has been cancelled
Platform: moko-platform CI / CI Summary (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Generic: Repo Health / Release configuration (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
PHPDoc standard, CI enforcement, updates_xml_build fixes.

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 23:32:42 -05:00
jmiller b010677d75 Merge pull request 'chore: cascade main → dev (9275e58) [skip ci]' (#139) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 04:29:33 +00:00
jmiller 9275e581c2 Merge pull request 'chore: PHPDoc Priority 1 + Coding Standards wiki' (#138) from dev into main
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Platform: moko-platform CI / Gate 1: Code Quality (push) Successful in 46s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
2026-05-26 04:29:29 +00:00
Jonathan Miller 3f3b1f79a0 chore: add PHPDoc to Priority 1 Enterprise classes
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Platform: moko-platform CI / Gate 1: Code Quality (push) Successful in 42s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Access control (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Successful in 4s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Successful in 45s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Generic: Repo Health / Release configuration (push) Has been cancelled
Generic: Repo Health / Scripts governance (push) Has been cancelled
Generic: Repo Health / Repository health (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Has been cancelled
Platform: moko-platform CI / CI Summary (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Generic: Repo Health / Release configuration (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Added @since, @param, @see tags to:
- CliFramework: class-level @since, 2 undocumented methods
- GitHubAdapter: class @since/@see, constructor @param, property docs
- MokoGiteaAdapter: class @since/@see, constructor @param, property docs
- ApiClient: class @since

Wiki: created Coding-Standards page with full PHPDoc standard,
PHPCS exclusion rationale, and file structure patterns.

Partial progress on #137

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 23:26:07 -05:00
Jonathan Miller 83842c50ad docs(changelog): add updates_xml_build fixes to Unreleased
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Scripts governance (push) Successful in 4s
Generic: Repo Health / Release configuration (push) Successful in 5s
Generic: Repo Health / Repository health (push) Successful in 10s
Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 23:12:12 -05:00
Jonathan Miller fbedd5966c fix(updates_xml): cascade entries down, fix Gitea release tag URLs, fix client tag
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Release configuration (push) Successful in 4s
Generic: Repo Health / Scripts governance (push) Successful in 4s
Generic: Repo Health / Repository health (push) Successful in 13s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 46s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
- Cascade: when stable releases, write all 5 channel entries pointing to stable
- Separate Joomla tags from Gitea release tags via releaseTagMap
- Only add client tag for templates and modules, not packages
- Preservation logic matches against Joomla tag names correctly

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 23:10:29 -05:00
jmiller eca2c13018 Merge pull request 'chore: cascade main → dev (48d0001) [skip ci]' (#136) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 04:09:19 +00:00
jmiller 48d000107d Merge pull request 'fix(ci): enforce PHPStan + PHPUnit in CI' (#135) from dev into main
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Blocked by required conditions
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Blocked by required conditions
Platform: moko-platform CI / Gate 4: Governance (push) Blocked by required conditions
Generic: Repo Health / Site Health (push) Has been skipped
Universal: Cascade Main → Dev / Cascade main → branches (push) Successful in 3s
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Release configuration (push) Successful in 5s
Generic: Repo Health / Scripts governance (push) Successful in 6s
Generic: Repo Health / Repository health (push) Successful in 11s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 49s
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been skipped
Platform: moko-platform CI / CI Summary (push) Has been cancelled
2026-05-26 04:09:14 +00:00
Jonathan Miller 7ceb9528cc fix(ci): enforce PHPStan + PHPUnit in CI gates
Generic: Repo Health / Site Health (push) Has been skipped
Generic: Repo Health / Access control (push) Successful in 1s
Generic: Repo Health / Release configuration (push) Successful in 4s
Generic: Repo Health / Scripts governance (push) Successful in 4s
Generic: Repo Health / Repository health (push) Successful in 10s
Universal: PR Check / Branch Policy (pull_request) Successful in 1s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 2s
Universal: Secret Scanning / Gitleaks Secret Scan (pull_request) Successful in 4s
Universal: PR Check / Validate PR (pull_request) Successful in 4s
Universal: PR Check / Build RC Package (pull_request) Successful in 2s
Generic: Repo Health / Release configuration (pull_request) Successful in 3s
Generic: Repo Health / Scripts governance (pull_request) Successful in 4s
Generic: Repo Health / Repository health (pull_request) Successful in 11s
Platform: moko-platform CI / Gate 1: Code Quality (push) Failing after 51s
Platform: moko-platform CI / Gate 1: Code Quality (pull_request) Failing after 51s
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (push) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (push) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (push) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (push) Has been cancelled
Platform: moko-platform CI / CI Summary (push) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.1) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.2) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 2: Unit Tests (8.3) (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 3: Self-Health Check (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 4: Governance (pull_request) Has been cancelled
Platform: moko-platform CI / Gate 5: Template Integrity (pull_request) Has been cancelled
Platform: moko-platform CI / CI Summary (pull_request) Has been cancelled
- PHPStan: remove continue-on-error, update label to Level 6,
  add --memory-limit=512M, fail on errors (was advisory)
- PHPUnit: add error handling — tests now block merges on failure
  (was silently passing even on test failures)

Authored-by: Moko Consulting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-25 23:08:33 -05:00
jmiller 5fabaec477 Merge pull request 'chore: cascade main → dev (e40b799) [skip ci]' (#134) from main into dev
chore: cascade main → dev [skip ci]
2026-05-26 03:55:23 +00:00
152 changed files with 6241 additions and 67241 deletions
+5 -1
View File
@@ -4,11 +4,15 @@
Auto-generated by cleanup script.
See: https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki/Home
-->
<moko-platform xmlns="https://standards.mokoconsulting.tech/moko-platform/1.0" schema-version="1.0">
<moko-platform xmlns="https://standards.mokoconsulting.tech/moko-platform/1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://standards.mokoconsulting.tech/moko-platform/1.0 https://git.mokoconsulting.tech/MokoConsulting/moko-platform/raw/branch/main/templates/schemas/manifest-schema.xsd"
schema-version="1.0">
<identity>
<name>moko-platform</name>
<org>MokoConsulting</org>
<description>Enterprise automation, validation, sync, and governance engine for all Moko Consulting repositories</description>
<version>09.02.05</version>
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
</identity>
<governance>
+84
View File
@@ -0,0 +1,84 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# 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
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
GITEA_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.GA_TOKEN }}
fetch-depth: 1
- name: Setup moko-platform 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/moko-platform/cli" ]; then
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
else
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
/tmp/moko-platform-api
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
fi
- name: Bump version
run: |
BUMP=$(php ${MOKO_CLI}/version_bump.php --path . 2>&1) || true
echo "$BUMP"
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null) || true
[ -z "$VERSION" ] && { echo "No version found — skipping"; exit 0; }
# Propagate to platform manifests
php ${MOKO_CLI}/version_set_platform.php \
--path . --version "$VERSION" --branch dev 2>/dev/null || true
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
# Commit if anything changed
if git diff --quiet && git diff --cached --quiet; then
echo "No version changes to commit"
exit 0
fi
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add -A
git commit -m "chore(version): patch bump to ${VERSION} [skip ci]" \
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
git push origin dev
echo "Bumped to ${VERSION}" >> $GITHUB_STEP_SUMMARY
+206 -349
View File
@@ -27,13 +27,19 @@ name: "Universal: Build & Release"
on:
pull_request:
types: [closed]
types: [opened, closed]
branches:
- main
paths:
- 'src/**'
- 'htdocs/**'
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
@@ -45,11 +51,66 @@ permissions:
contents: write
jobs:
# ── Draft PR → Promote highest pre-release to RC ─────────────────────────────
promote-rc:
name: Promote Pre-Release to RC
runs-on: release
if: >-
(github.event.action == 'opened' && github.event.pull_request.draft == true) ||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
token: ${{ secrets.GA_TOKEN }}
fetch-depth: 1
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
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
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
/tmp/moko-platform-api
cd /tmp/moko-platform-api
composer install --no-dev --no-interaction --quiet
- name: Promote to release-candidate
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/release_promote.php \
--from auto --to release-candidate \
--token "${{ secrets.GA_TOKEN }}" \
--api-base "${API_BASE}" \
--branch "${{ github.event.pull_request.head.ref || 'dev' }}"
- name: Cascade lesser channels
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/release_cascade.php \
--stability release-candidate \
--token "${{ secrets.GA_TOKEN }}" \
--api-base "${API_BASE}"
- name: Summary
if: always()
run: |
echo "## Promoted to Release Candidate" >> $GITHUB_STEP_SUMMARY
echo "Draft PR opened — promoted highest pre-release to RC" >> $GITHUB_STEP_SUMMARY
# ── Merged PR → Build & Release (or promote RC to stable) ────────────────────
release:
name: Build & Release Pipeline
runs-on: release
if: >-
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
github.event.pull_request.merged == true ||
(github.event_name == 'workflow_dispatch' && inputs.action != 'promote-rc')
steps:
- name: Checkout repository
@@ -100,14 +161,34 @@ jobs:
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "branch=main" >> "$GITHUB_OUTPUT"
- name: "Step 1b: Bump version"
id: bump
# -- CHECK FOR RC PROMOTION ------------------------------------------------
- name: "Check for RC release"
id: rc
if: steps.version.outputs.skip != 'true'
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
RC_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/tags/release-candidate" 2>/dev/null || echo "{}")
RC_ID=$(echo "$RC_JSON" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true)
if [ -n "$RC_ID" ] && [ "$RC_ID" != "None" ] && [ "$RC_ID" != "" ]; then
echo "promote=true" >> "$GITHUB_OUTPUT"
echo "release_id=${RC_ID}" >> "$GITHUB_OUTPUT"
echo "::notice::RC release found (id: ${RC_ID}) — will promote to stable"
else
echo "promote=false" >> "$GITHUB_OUTPUT"
echo "::notice::No RC release — full build pipeline"
fi
- name: "Step 1b: Minor bump version"
id: bump
if: >-
steps.version.outputs.skip != 'true' &&
steps.rc.outputs.promote != 'true'
run: |
MOKO_API="/tmp/moko-platform-api/cli"
BUMP=$(php ${MOKO_API}/version_bump.php --path . --minor)
VERSION=$(echo "$BUMP" | grep -oP '\d{2}\.\d{2}\.\d{2}$' || true)
[ -z "$VERSION" ] && VERSION=$(php ${MOKO_API}/version_read.php --path .)
php ${MOKO_API}/version_bump.php --path . --minor 2>&1 || true
VERSION=$(php ${MOKO_API}/version_read.php --path .)
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "Bumped to: ${VERSION}"
@@ -137,95 +218,8 @@ jobs:
steps.check.outputs.already_released != 'true'
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
ERRORS=0
PLATFORM="${{ steps.platform.outputs.platform }}"
MANIFEST="${{ steps.platform.outputs.manifest }}"
MOD_FILE="${{ steps.platform.outputs.mod_file }}"
echo "## Pre-Release Sanity Checks (${PLATFORM})" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# -- Version drift check (must pass before release) --------
README_VER=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' README.md 2>/dev/null | head -1)
if [ "$README_VER" != "$VERSION" ]; then
echo "- Version drift: README says \`${README_VER}\` but releasing \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
else
echo "- Version consistent: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
fi
# Check CHANGELOG version matches
CL_VER=$(sed -n 's/.*VERSION:[[:space:]]*\([0-9][0-9]\.[0-9][0-9]\.[0-9][0-9]\).*/\1/p' CHANGELOG.md 2>/dev/null | head -1)
if [ -n "$CL_VER" ] && [ "$CL_VER" != "$VERSION" ]; then
echo "- CHANGELOG drift: \`${CL_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
fi
# Check composer.json version if present
if [ -f "composer.json" ]; then
COMP_VER=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' composer.json 2>/dev/null | head -1)
if [ -n "$COMP_VER" ] && [ "$COMP_VER" != "$VERSION" ]; then
echo "- composer.json drift: \`${COMP_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
fi
fi
# Common checks
if [ ! -f "LICENSE" ]; then
echo "- Missing LICENSE file" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
else
echo "- LICENSE present" >> $GITHUB_STEP_SUMMARY
fi
if [ ! -d "src" ] && [ ! -d "htdocs" ]; then
echo "- Warning: No src/ or htdocs/ directory" >> $GITHUB_STEP_SUMMARY
else
echo "- Source directory present" >> $GITHUB_STEP_SUMMARY
fi
# -- Platform-specific checks --------
case "$PLATFORM" in
joomla)
if [ -n "$MANIFEST" ]; then
XML_VER=$(sed -n 's/.*<version>\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
if [ -n "$XML_VER" ] && [ "$XML_VER" != "$VERSION" ]; then
echo "- Manifest drift: \`${XML_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
else
echo "- Manifest version: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
fi
TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" 2>/dev/null)
echo "- Extension type: ${TYPE:-unknown}" >> $GITHUB_STEP_SUMMARY
else
echo "- No Joomla XML manifest (WaaS site)" >> $GITHUB_STEP_SUMMARY
fi ;;
dolibarr)
if [ -n "$MOD_FILE" ]; then
MOD_VER=$(sed -n "s/.*\\\$this->version = '\([^']*\)'.*/\1/p" "$MOD_FILE" 2>/dev/null | head -1)
if [ -n "$MOD_VER" ] && [ "$MOD_VER" != "$VERSION" ]; then
echo "- Module drift: \`${MOD_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
else
echo "- Module version: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
fi
else
echo "- No mod*.class.php found" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
fi
if [ ! -f "update.txt" ]; then
echo "- Missing update.txt" >> $GITHUB_STEP_SUMMARY
ERRORS=$((ERRORS+1))
fi ;;
*) echo "- Generic platform no manifest checks" >> $GITHUB_STEP_SUMMARY ;;
esac
echo "" >> $GITHUB_STEP_SUMMARY
if [ "$ERRORS" -gt 0 ]; then
echo "**${ERRORS} error(s) — release may be incomplete**" >> $GITHUB_STEP_SUMMARY
else
echo "**All sanity checks passed**" >> $GITHUB_STEP_SUMMARY
fi
php /tmp/moko-platform-api/cli/release_validate.php \
--path . --version "$VERSION" --output-summary --github-output || true
# -- STEP 2: Create or update version/XX.YY archive branch ---------------
# Always runs — every version change on main archives to version/XX.YY
@@ -265,25 +259,19 @@ jobs:
php /tmp/moko-platform-api/cli/badge_update.php --path . --version "${VERSION}" 2>/dev/null || true
php /tmp/moko-platform-api/cli/version_check.php --path . --fix 2>/dev/null || true
- name: "Step 5: Write update stream"
# Step 5 (updates.xml) moved after Step 8 to include SHA-256 checksum
- name: "Step 4b: Promote and prune CHANGELOG"
if: >-
steps.version.outputs.skip != 'true' &&
steps.platform.outputs.platform == 'joomla'
steps.check.outputs.already_released != 'true'
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
# Fetch latest updates.xml from main so preserve logic has all channels
GA_TOKEN="${{ secrets.GA_TOKEN }}"
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
curl -sf -H "Authorization: token ${GA_TOKEN}" \
"${API}/contents/updates.xml?ref=main" 2>/dev/null | \
python3 -c "import sys,json,base64; print(base64.b64decode(json.load(sys.stdin)['content']).decode())" \
> updates.xml 2>/dev/null || true
php /tmp/moko-platform-api/cli/updates_xml_build.php \
--path . --version "${VERSION}" --stability stable \
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
--github-output
MOKO_API="/tmp/moko-platform-api/cli"
if [ -f "CHANGELOG.md" ]; then
php ${MOKO_API}/changelog_promote.php --path . --version "$VERSION" 2>&1 || true
php ${MOKO_API}/changelog_prune.php --path . --keep 5 2>&1 || true
fi
- name: Commit release changes
if: >-
@@ -320,256 +308,116 @@ jobs:
fi
echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
# -- STEP 7: Create or update Gitea Release --------------------------------
- name: "Step 7: Gitea Release"
# -- STEP 7a: Promote RC to stable (skip build) ----------------------------
- name: "Step 7a: Promote RC to stable"
if: >-
steps.version.outputs.skip != 'true'
steps.version.outputs.skip != 'true' &&
steps.rc.outputs.promote == 'true'
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/release_promote.php \
--from release-candidate --to stable \
--token "${{ secrets.GA_TOKEN }}" \
--api-base "${API_BASE}" \
--path . --branch main
echo "Promoted RC → stable (${VERSION})" >> $GITHUB_STEP_SUMMARY
# -- STEP 7b: Create or update Gitea Release (full build path) -------------
- name: "Step 7b: Gitea Release"
if: >-
steps.version.outputs.skip != 'true' &&
steps.rc.outputs.promote != 'true'
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
BRANCH="${{ steps.version.outputs.branch }}"
MAJOR="${{ steps.version.outputs.major }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/release_create.php \
--path . --version "$VERSION" --tag "$RELEASE_TAG" \
--token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --branch main
echo "Release created: ${VERSION}" >> $GITHUB_STEP_SUMMARY
# Reuse metadata from Step 5 (single source of truth)
EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}"
EXT_NAME="${{ steps.updates.outputs.ext_name }}"
EXT_TYPE="${{ steps.updates.outputs.ext_type }}"
EXT_FOLDER="${{ steps.updates.outputs.ext_folder }}"
# Fallbacks if Step 5 was skipped
if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
fi
[ -z "$EXT_NAME" ] && EXT_NAME="${GITEA_REPO}"
NOTES=$(php /tmp/moko-platform-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
# Build release name: "Pretty Name VERSION (type_element-VERSION)"
# Strip existing type prefix to prevent duplication
EXT_ELEMENT=$(echo "$EXT_ELEMENT" | sed -E 's/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)//')
TYPE_PREFIX=""
case "${EXT_TYPE}" in
plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
module) TYPE_PREFIX="mod_" ;;
component) TYPE_PREFIX="com_" ;;
template) TYPE_PREFIX="tpl_" ;;
library) TYPE_PREFIX="lib_" ;;
package) TYPE_PREFIX="pkg_" ;;
esac
RELEASE_NAME="${EXT_NAME} ${VERSION} (${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION})"
# Delete existing release if present (overwrite, not append)
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
EXISTING_ID=$(echo "$EXISTING" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || true)
if [ -n "$EXISTING_ID" ]; then
curl -sS -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/${EXISTING_ID}" 2>/dev/null || true
curl -sS -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/tags/${RELEASE_TAG}" 2>/dev/null || true
echo "Deleted previous stable release (id: ${EXISTING_ID})"
fi
# Create fresh release
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
-H "Content-Type: application/json" \
"${API_BASE}/releases" \
-d "$(python3 -c "import json; print(json.dumps({
'tag_name': '${RELEASE_TAG}',
'name': '${RELEASE_NAME}',
'body': '''## ${VERSION} ($(date +%Y-%m-%d))\n${NOTES}''',
'target_commitish': '${BRANCH}'
}))")"
echo "Release created: ${RELEASE_NAME}" >> $GITHUB_STEP_SUMMARY
# -- STEP 8: Build Joomla install ZIP + SHA-256 checksum ------------------
- name: "Step 8: Build package and update checksum"
# -- STEP 8: Build packages and upload to release ----------------------------
- name: "Step 8: Build package and upload"
id: package
if: >-
steps.version.outputs.skip != 'true'
steps.version.outputs.skip != 'true' &&
steps.rc.outputs.promote != 'true'
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
REPO="${{ github.repository }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/release_package.php \
--path . --version "$VERSION" --tag "$RELEASE_TAG" \
--token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --output /tmp || true
# All ZIPs upload to the major release tag (vXX)
RELEASE_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -z "$RELEASE_ID" ]; then
echo "No release ${RELEASE_TAG} found — skipping ZIP upload"
exit 0
# -- STEP 5: Write update stream (after build so SHA-256 is available) -----
- name: "Step 5: Write update stream"
if: steps.version.outputs.skip != 'true'
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
SHA256="${{ steps.package.outputs.sha256_zip }}"
# Fetch latest updates.xml from main so preserve logic has all channels
GA_TOKEN="${{ secrets.GA_TOKEN }}"
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
curl -sf -H "Authorization: token ${GA_TOKEN}" \
"${API}/contents/updates.xml?ref=main" 2>/dev/null | \
python3 -c "import sys,json,base64; print(base64.b64decode(json.load(sys.stdin)['content']).decode())" \
> updates.xml 2>/dev/null || true
SHA_FLAG=""
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
php /tmp/moko-platform-api/cli/updates_xml_build.php \
--path . --version "${VERSION}" --stability stable \
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
${SHA_FLAG} --github-output
# Commit updates.xml if changed
if ! git diff --quiet updates.xml 2>/dev/null; then
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://jmiller:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
git add updates.xml
git commit -m "chore: update stable channel ${VERSION} [skip ci]" \
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
git push origin HEAD 2>&1 || true
fi
# Find extension element name from manifest
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)
[ -z "$MANIFEST" ] && exit 0
# Reuse element from Step 5, with same fallback chain
EXT_ELEMENT="${{ steps.updates.outputs.ext_element }}"
if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(sed -n 's/.*plugin="\([^"]*\)".*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
[ -z "$EXT_ELEMENT" ] && EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
fi
# ZIP name: type_folder_element-VERSION (e.g. plg_system_mokojgdpc-01.01.00.zip)
EXT_TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
EXT_FOLDER=$(sed -n 's/.*<extension[^>]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
# For packages, prefer <packagename> over filename-derived element
if [ "$EXT_TYPE" = "package" ]; then
PKG_NAME=$(sed -n 's/.*<packagename>\([^<]*\)<\/packagename>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
[ -n "$PKG_NAME" ] && EXT_ELEMENT="$PKG_NAME"
fi
# Strip existing type prefix to prevent duplication (e.g. pkg_mokowaas → mokowaas)
EXT_ELEMENT=$(echo "$EXT_ELEMENT" | sed -E 's/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)//')
TYPE_PREFIX=""
case "${EXT_TYPE}" in
plugin) TYPE_PREFIX="plg_${EXT_FOLDER}_" ;;
module) TYPE_PREFIX="mod_" ;;
component) TYPE_PREFIX="com_" ;;
template) TYPE_PREFIX="tpl_" ;;
library) TYPE_PREFIX="lib_" ;;
package) TYPE_PREFIX="pkg_" ;;
esac
ZIP_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip"
TAR_NAME="${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.tar.gz"
# -- Build install packages from src/ ----------------------------
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/"; exit 0; }
# ZIP package (type-aware via moko-platform PHP API)
php /tmp/moko-platform-api/cli/joomla_build.php --path . --version "${VERSION}" --output /tmp
# Match the expected ZIP_NAME for upload
BUILT_ZIP=$(ls /tmp/${TYPE_PREFIX}${EXT_ELEMENT}-${VERSION}.zip 2>/dev/null | head -1 || true)
if [ -n "$BUILT_ZIP" ] && [ "$BUILT_ZIP" != "/tmp/${ZIP_NAME}" ]; then
mv "$BUILT_ZIP" "/tmp/${ZIP_NAME}"
fi
# tar.gz package (flat source archive)
tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" --exclude='.ftpignore' --exclude='sftp-config*' --exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' .
ZIP_SIZE=$(stat -c%s "/tmp/${ZIP_NAME}" 2>/dev/null || stat -f%z "/tmp/${ZIP_NAME}" 2>/dev/null || echo "unknown")
TAR_SIZE=$(stat -c%s "/tmp/${TAR_NAME}" 2>/dev/null || stat -f%z "/tmp/${TAR_NAME}" 2>/dev/null || echo "unknown")
# -- Calculate SHA-256 for both ----------------------------------
SHA256_ZIP=$(sha256sum "/tmp/${ZIP_NAME}" | cut -d' ' -f1)
SHA256_TAR=$(sha256sum "/tmp/${TAR_NAME}" | cut -d' ' -f1)
# -- Get existing assets for cleanup --------------------------------
ASSETS=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/${RELEASE_ID}/assets" 2>/dev/null || echo "[]")
# -- Create per-file .sha256 checksum files -------------------------
echo "${SHA256_ZIP} ${ZIP_NAME}" > "/tmp/${ZIP_NAME}.sha256"
echo "${SHA256_TAR} ${TAR_NAME}" > "/tmp/${TAR_NAME}.sha256"
# -- Upload packages + checksums to release tag --------------------
for ASSET in "${ZIP_NAME}" "${TAR_NAME}" "${ZIP_NAME}.sha256" "${TAR_NAME}.sha256"; do
[ ! -f "/tmp/${ASSET}" ] && continue
# Delete existing asset with same name
ASSET_ID=$(echo "$ASSETS" | python3 -c "
import sys,json
assets = json.load(sys.stdin)
for a in assets:
if a['name'] == '${ASSET}':
print(a['id']); break
" 2>/dev/null || true)
[ -n "$ASSET_ID" ] && curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}" 2>/dev/null || true
# Upload
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"/tmp/${ASSET}" \
"${API_BASE}/releases/${RELEASE_ID}/assets?name=${ASSET}" > /dev/null 2>&1 || true
done
# updates.xml already handled by Step 5 (updates_xml_build.php with preserve logic)
echo "### Packages" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Package | Size | SHA-256 |" >> $GITHUB_STEP_SUMMARY
echo "|---------|------|---------|" >> $GITHUB_STEP_SUMMARY
echo "| \`${ZIP_NAME}\` | ${ZIP_SIZE} | \`${SHA256_ZIP}\` |" >> $GITHUB_STEP_SUMMARY
echo "| \`${TAR_NAME}\` | ${TAR_SIZE} | \`${SHA256_TAR}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Release | \`${RELEASE_TAG}\` | |" >> $GITHUB_STEP_SUMMARY
echo "| Download | [${ZIP_NAME}](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${ZIP_NAME}) |" >> $GITHUB_STEP_SUMMARY
# -- STEP 8b: Update release description with changelog ----------------------
- name: "Step 8b: Update release body"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
MOKO_CLI="/tmp/moko-platform-api/cli"
php ${MOKO_CLI}/release_body_update.php \
php /tmp/moko-platform-api/cli/release_body_update.php \
--path . --version "${VERSION}" --tag "${RELEASE_TAG}" \
--token "${{ secrets.GA_TOKEN }}" \
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
2>/dev/null || {
# Fallback: simple body update if CLI not available
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null | \
python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -n "$RELEASE_ID" ] && [ "$RELEASE_ID" != "None" ]; then
BODY="## ${VERSION} ($(date +%Y-%m-%d))\n\nChecksum files attached as \`*.sha256\` assets."
curl -sf -X PATCH -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
-H "Content-Type: application/json" \
"${API_BASE}/releases/${RELEASE_ID}" \
-d "{\"body\":\"${BODY}\"}" > /dev/null 2>&1
fi
}
2>&1 || true
echo "Release body updated" >> $GITHUB_STEP_SUMMARY
# -- STEP 9: Mirror to GitHub (stable only) --------------------------------
- name: "Step 9: Mirror release to GitHub"
if: >-
steps.version.outputs.skip != 'true' &&
steps.version.outputs.stability == 'stable' &&
secrets.GH_TOKEN != ''
continue-on-error: true
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
MAJOR="${{ steps.version.outputs.major }}"
BRANCH="${{ steps.version.outputs.branch }}"
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
NOTES=$(php /tmp/moko-platform-api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null || true)
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
echo "$NOTES" > /tmp/release_notes.md
EXISTING=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/tags/$RELEASE_TAG" 2>/dev/null | jq -r ".tag_name // empty" || true)
if [ -z "$EXISTING" ]; then
gh release create "$RELEASE_TAG" \
--repo "$GH_REPO" \
--title "v${MAJOR} (latest: ${VERSION})" \
--notes-file /tmp/release_notes.md \
--target "$BRANCH" || true
else
gh release edit "$RELEASE_TAG" \
--repo "$GH_REPO" \
--title "v${MAJOR} (latest: ${VERSION})" || true
fi
# Upload assets to GitHub mirror
for PKG in /tmp/${EXT_ELEMENT:-pkg}-${VERSION}.*; do
if [ -f "$PKG" ]; then
_RELID=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/tags/$RELEASE_TAG" 2>/dev/null | jq -r ".id // empty")
[ -n "$_RELID" ] && curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" -H "Content-Type: application/octet-stream" "${GITEA_URL:-https://git.mokoconsulting.tech}/api/v1/repos/${{ github.repository }}/releases/${_RELID}/assets?name=$(basename $PKG)" --data-binary "@$PKG" > /dev/null 2>&1 || true
fi
done
echo "GitHub mirror updated: ${GH_REPO} ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/release_mirror.php \
--version "$VERSION" --tag "$RELEASE_TAG" \
--token "${{ secrets.GA_TOKEN }}" --api-base "$API_BASE" \
--gh-token "${{ secrets.GH_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"
@@ -593,11 +441,13 @@ jobs:
- name: "Delete lesser pre-release channels"
continue-on-error: true
run: |
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/release_cascade.php \
--stability stable \
--version "${VERSION}" \
--token "${{ secrets.GA_TOKEN }}" \
--org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
--gitea-url "${GITEA_URL}" 2>/dev/null || true
--api-base "${API_BASE}" 2>/dev/null || true
- name: "Step 11: Delete and recreate dev branch from main"
if: steps.version.outputs.skip != 'true'
@@ -618,28 +468,35 @@ jobs:
echo "Dev branch reset from main (keeps dev ahead after release)" >> $GITHUB_STEP_SUMMARY
# -- Dolibarr post-release: Reset dev version -----------------------------
- name: "Dolibarr: Reset dev version"
if: >-
steps.version.outputs.skip != 'true' &&
steps.platform.outputs.platform == 'dolibarr' &&
steps.platform.outputs.mod_file != ''
- name: "Step 12: Create version branch from main"
if: steps.version.outputs.skip != 'true'
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.GA_TOKEN }}"
MOD_FILE="${{ steps.platform.outputs.mod_file }}"
ENCODED_PATH=$(echo "$MOD_FILE" | sed 's|^\./||' | python3 -c "import sys,urllib.parse; print(urllib.parse.quote(sys.stdin.read().strip()))")
FILE_RESP=$(curl -sf -H "Authorization: token ${TOKEN}" "${API_BASE}/contents/${ENCODED_PATH}?ref=dev" 2>/dev/null || true)
FILE_SHA=$(echo "$FILE_RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
FILE_CONTENT=$(echo "$FILE_RESP" | python3 -c "import sys,json,base64; print(base64.b64decode(json.load(sys.stdin).get('content','')).decode())" 2>/dev/null || true)
if [ -n "$FILE_SHA" ] && [ -n "$FILE_CONTENT" ]; then
UPDATED=$(echo "$FILE_CONTENT" | sed "s/\$this->version = '[^']*'/\$this->version = 'development'/")
ENCODED=$(echo "$UPDATED" | base64 -w0)
curl -sf -X PUT -H "Authorization: token ${TOKEN}" -H "Content-Type: application/json" "${API_BASE}/contents/${ENCODED_PATH}" \
-d "$(jq -n --arg content \"$ENCODED\" --arg sha \"$FILE_SHA\" --arg msg \"chore(version): reset dev version [skip ci]\" --arg branch \"dev\" '{content:$content,sha:$sha,message:$msg,branch:$branch}')" > /dev/null 2>&1 || true
fi
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="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
php /tmp/moko-platform-api/cli/version_reset_dev.php \
--token "${{ secrets.GA_TOKEN }}" --api-base "${API_BASE}" \
--branch dev --path . 2>&1 || true
# -- Summary --------------------------------------------------------------
- name: Pipeline Summary
+48
View File
@@ -0,0 +1,48 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# 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"
ENCODED=$(python3 -c "import urllib.parse; print(urllib.parse.quote('${BRANCH}', safe=''))")
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
-H "Authorization: token ${{ secrets.GA_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
+13 -10
View File
@@ -124,16 +124,16 @@ jobs:
echo "### PHPCS" >> $GITHUB_STEP_SUMMARY
echo "PSR-12 compliance: passed" >> $GITHUB_STEP_SUMMARY
- name: "PHPStan (Level 2)"
continue-on-error: true
- name: "PHPStan (Level 6)"
run: |
vendor/bin/phpstan analyse -c phpstan.neon --no-progress --error-format=github 2>&1 || {
echo "::warning::PHPStan found type errors (advisory)"
vendor/bin/phpstan analyse -c phpstan.neon --no-progress --memory-limit=512M --error-format=github 2>&1 || {
echo "::error::PHPStan found type errors"
echo "### PHPStan" >> $GITHUB_STEP_SUMMARY
echo "Static analysis errors detected. Run \`composer phpstan\` locally." >> $GITHUB_STEP_SUMMARY
exit 1
}
echo "### PHPStan" >> $GITHUB_STEP_SUMMARY
echo "Static analysis: advisory (level 0)" >> $GITHUB_STEP_SUMMARY
echo "Static analysis (level 6): passed" >> $GITHUB_STEP_SUMMARY
- name: "Psalm"
continue-on-error: true
@@ -177,11 +177,14 @@ jobs:
- name: "PHPUnit (PHP ${{ matrix.php }})"
run: |
vendor/bin/phpunit --testdox 2>&1
{
echo "### PHPUnit (PHP ${{ matrix.php }})"
echo "All tests passed."
} >> $GITHUB_STEP_SUMMARY
vendor/bin/phpunit --testdox 2>&1 || {
echo "::error::PHPUnit tests failed"
echo "### PHPUnit (PHP ${{ matrix.php }})" >> $GITHUB_STEP_SUMMARY
echo "Tests failed. Run \`vendor/bin/phpunit --testdox\` locally." >> $GITHUB_STEP_SUMMARY
exit 1
}
echo "### PHPUnit (PHP ${{ matrix.php }})" >> $GITHUB_STEP_SUMMARY
echo "All tests passed." >> $GITHUB_STEP_SUMMARY
# ═══════════════════════════════════════════════════════════════════════
# Gate 3 — Self-Health (Dogfood)
-139
View File
@@ -1,139 +0,0 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Deploy
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /templates/workflows/joomla/deploy-manual.yml.template
# VERSION: 04.07.00
# BRIEF: Manual SFTP deploy to dev server for Joomla repos
name: "Universal: Deploy to Dev (Manual)"
on:
workflow_dispatch:
inputs:
clear_remote:
description: 'Delete all remote files before uploading'
required: false
default: 'false'
type: boolean
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
permissions:
contents: read
jobs:
deploy:
name: SFTP Deploy to Dev
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup PHP
run: |
php -v && composer --version
- name: Setup moko-platform tools
env:
GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
run: |
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
/tmp/moko-platform-api 2>/dev/null || true
if [ -d "/tmp/moko-platform-api" ] && [ -f "/tmp/moko-platform-api/composer.json" ]; then
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
fi
- name: Check FTP configuration
id: check
env:
HOST: ${{ vars.DEV_FTP_HOST }}
PATH_VAR: ${{ vars.DEV_FTP_PATH }}
PORT: ${{ vars.DEV_FTP_PORT }}
run: |
if [ -z "$HOST" ] || [ -z "$PATH_VAR" ]; then
echo "DEV_FTP_HOST or DEV_FTP_PATH not configured -- cannot deploy"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "host=$HOST" >> "$GITHUB_OUTPUT"
REMOTE="${PATH_VAR%/}"
echo "remote=$REMOTE" >> "$GITHUB_OUTPUT"
[ -z "$PORT" ] && PORT="22"
echo "port=$PORT" >> "$GITHUB_OUTPUT"
- name: Deploy via SFTP
if: steps.check.outputs.skip != 'true'
env:
SFTP_KEY: ${{ secrets.DEV_FTP_KEY }}
SFTP_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
SFTP_USER: ${{ vars.DEV_FTP_USERNAME }}
run: |
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ -- nothing to deploy"; exit 0; }
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
"${{ steps.check.outputs.host }}" "${{ steps.check.outputs.port }}" "$SFTP_USER" "${{ steps.check.outputs.remote }}" \
> /tmp/sftp-config.json
if [ -n "$SFTP_KEY" ]; then
echo "$SFTP_KEY" > /tmp/deploy_key
chmod 600 /tmp/deploy_key
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
else
printf ',"password":"%s"}' "$SFTP_PASS" >> /tmp/sftp-config.json
fi
DEPLOY_ARGS=(--path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json)
[ "${{ inputs.clear_remote }}" = "true" ] && DEPLOY_ARGS+=(--clear-remote)
PLATFORM=$(php /tmp/moko-platform-api/cli/platform_detect.php --path . 2>/dev/null || true)
if [ "$PLATFORM" = "waas-component" ] && [ -f "/tmp/moko-platform-api/deploy/deploy-joomla.php" ]; then
php /tmp/moko-platform-api/deploy/deploy-joomla.php "${DEPLOY_ARGS[@]}"
else
php /tmp/moko-platform-api/deploy/deploy-sftp.php "${DEPLOY_ARGS[@]}"
fi
rm -f /tmp/deploy_key /tmp/sftp-config.json
- name: Post-deploy health check
if: success() && steps.check.outputs.skip != 'true'
run: |
if [ -f "deploy/health-check.php" ]; then
SITE_URL="${{ vars.DEV_SITE_URL }}"
if [ -n "$SITE_URL" ]; then
php deploy/health-check.php --url "$SITE_URL" --checks http --timeout 30 || echo "::warning::Health check failed after deploy"
else
echo "DEV_SITE_URL not configured, skipping health check"
fi
fi
- name: Summary
if: always()
run: |
if [ "${{ steps.check.outputs.skip }}" = "true" ]; then
echo "### Deploy Skipped -- FTP not configured" >> $GITHUB_STEP_SUMMARY
else
echo "### Manual Dev Deploy Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Host | \`${{ steps.check.outputs.host }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Remote | \`${{ steps.check.outputs.remote }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Clear | ${{ inputs.clear_remote }} |" >> $GITHUB_STEP_SUMMARY
fi
+223 -375
View File
@@ -1,375 +1,223 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.01.00
# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch
name: "Universal: Pre-Release"
on:
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 }})"
runs-on: release
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GA_TOKEN }}
- name: Setup tools
run: |
# Update moko-platform CLI tools if available; install PHP if missing
if command -v moko-platform-update &> /dev/null; then
moko-platform-update
elif [ -d "/opt/moko-platform" ]; then
cd /opt/moko-platform && git pull origin main --quiet 2>/dev/null || true
else
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 >/dev/null 2>&1
fi
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${{ secrets.GA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/moko-platform.git" \
/tmp/moko-platform-api
fi
# Set MOKO_CLI to whichever path exists
if [ -d "/opt/moko-platform/cli" ]; then
echo "MOKO_CLI=/opt/moko-platform/cli" >> "$GITHUB_ENV"
else
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
fi
- name: Detect platform
id: platform
run: |
PLATFORM=$(sed -n 's/.*<platform>\([^<]*\)<\/platform>.*/\1/p' .mokogitea/manifest.xml 2>/dev/null | head -1 | tr -d '[:space:]')
[ -z "$PLATFORM" ] && PLATFORM="generic"
echo "platform=$PLATFORM" >> "$GITHUB_OUTPUT"
MANIFEST=$(find ./src -maxdepth 1 -name "pkg_*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
[ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "*/packages/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
[ -z "$MANIFEST" ] && MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
MOD_FILE=$(find . -maxdepth 4 -name "mod*.class.php" ! -path "./.git/*" -exec grep -l 'extends DolibarrModules' {} \; 2>/dev/null | head -1)
echo "manifest=${MANIFEST}" >> "$GITHUB_OUTPUT"
echo "mod_file=${MOD_FILE}" >> "$GITHUB_OUTPUT"
- name: Resolve metadata and bump version
id: meta
run: |
STABILITY="${{ inputs.stability }}"
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
# Patch bump via CLI tool
php ${MOKO_CLI}/version_bump.php --path .
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null)
[ -z "$VERSION" ] && VERSION="00.00.01"
TODAY=$(date +%Y-%m-%d)
# Update platform-specific manifest
PLATFORM="${{ steps.platform.outputs.platform }}"
MANIFEST="${{ steps.platform.outputs.manifest }}"
MOD_FILE="${{ steps.platform.outputs.mod_file }}"
php ${MOKO_CLI}/version_set_platform.php \
--path . --version "$VERSION" --branch "${{ github.ref_name }}" 2>/dev/null || true
# Commit version bump
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://jmiller:${{ secrets.GA_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 (platform-aware)
EXT_ELEMENT=""
case "$PLATFORM" in
joomla)
if [ -n "$MANIFEST" ]; then
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" 2>/dev/null | head -1)
if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
case "$EXT_ELEMENT" in
templatedetails|manifest) EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
esac
fi
else
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
fi
;;
dolibarr)
if [ -n "$MOD_FILE" ]; then
MOD_BASENAME=$(basename "$MOD_FILE" .class.php)
EXT_ELEMENT=$(echo "$MOD_BASENAME" | sed 's/^mod//' | tr '[:upper:]' '[:lower:]')
else
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
fi
;;
*)
EXT_ELEMENT=$(echo "${GITEA_REPO}" | tr '[:upper:]' '[:lower:]' | tr -d ' -')
;;
esac
ZIP_NAME="${EXT_ELEMENT}-${VERSION}${SUFFIX}.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 "manifest=${MANIFEST}" >> "$GITHUB_OUTPUT"
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
- name: Build package
run: |
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
if [ ! -d "$SOURCE_DIR" ]; then
echo "::error::No src/ or htdocs/ directory"
exit 1
fi
MANIFEST="${{ steps.meta.outputs.manifest }}"
EXT_TYPE=""
if [ -n "$MANIFEST" ]; then
EXT_TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
fi
EXCLUDES="sftp-config* .ftpignore *.ppk *.pem *.key .env* *.local .build-trigger"
mkdir -p build/package
if [ "$EXT_TYPE" = "package" ] && [ -d "${SOURCE_DIR}/packages" ]; then
echo "=== Building Joomla PACKAGE (multi-extension) ==="
for ext_dir in "${SOURCE_DIR}"/packages/*/; do
[ ! -d "$ext_dir" ] && continue
EXT_NAME=$(basename "$ext_dir")
echo " Packaging sub-extension: ${EXT_NAME}"
cd "$ext_dir"
zip -r "../../build/package/${EXT_NAME}.zip" . -x $EXCLUDES
cd "$OLDPWD"
done
for f in "${SOURCE_DIR}"/*.xml "${SOURCE_DIR}"/*.php; do
[ -f "$f" ] && cp "$f" build/package/
done
else
echo "=== Building standard extension ==="
rsync -a \
--exclude='sftp-config*' \
--exclude='.ftpignore' \
--exclude='*.ppk' \
--exclude='*.pem' \
--exclude='*.key' \
--exclude='.env*' \
--exclude='*.local' \
--exclude='.build-trigger' \
"${SOURCE_DIR}/" build/package/
fi
- name: Create ZIP
id: zip
run: |
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
cd build/package
zip -r "../${ZIP_NAME}" .
cd ..
SHA256=$(sha256sum "${ZIP_NAME}" | cut -d' ' -f1)
echo "sha256=${SHA256}" >> "$GITHUB_OUTPUT"
echo "ZIP: ${ZIP_NAME} (SHA: ${SHA256:0:16}...)"
- name: Create or replace Gitea release
id: release
run: |
TAG="${{ steps.meta.outputs.tag }}"
VERSION="${{ steps.meta.outputs.version }}"
STABILITY="${{ steps.meta.outputs.stability }}"
SHA256="${{ steps.zip.outputs.sha256 }}"
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
EXT_ELEMENT="${{ steps.meta.outputs.ext_element }}"
TOKEN="${{ secrets.GA_TOKEN }}"
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
BRANCH=$(git branch --show-current)
BODY="## ${VERSION} ($(date +%Y-%m-%d))
**Channel:** ${STABILITY}
**SHA-256:** \`${SHA256}\`"
# Delete existing release
EXISTING_ID=$(curl -sS -H "Authorization: token ${TOKEN}" \
"${API}/releases/tags/${TAG}" | jq -r '.id // empty' 2>/dev/null)
if [ -n "$EXISTING_ID" ]; then
curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \
"${API}/releases/${EXISTING_ID}" 2>/dev/null || true
curl -sS -X DELETE -H "Authorization: token ${TOKEN}" \
"${API}/tags/${TAG}" 2>/dev/null || true
fi
# Create release
RELEASE_ID=$(curl -sS -X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/json" \
"${API}/releases" \
-d "$(jq -n \
--arg tag "$TAG" \
--arg target "$BRANCH" \
--arg name "${EXT_ELEMENT} ${VERSION} (${STABILITY})" \
--arg body "$BODY" \
'{tag_name: $tag, target_commitish: $target, name: $name, body: $body, prerelease: true}'
)" | jq -r '.id')
echo "release_id=${RELEASE_ID}" >> "$GITHUB_OUTPUT"
# Upload ZIP
curl -sS -X POST -H "Authorization: token ${TOKEN}" \
-H "Content-Type: application/octet-stream" \
"${API}/releases/${RELEASE_ID}/assets?name=${ZIP_NAME}" \
--data-binary "@build/${ZIP_NAME}"
echo "Released: ${EXT_ELEMENT} ${VERSION} (${STABILITY})"
- name: Update updates.xml
if: steps.platform.outputs.platform == 'joomla'
run: |
STABILITY="${{ steps.meta.outputs.stability }}"
VERSION="${{ steps.meta.outputs.version }}"
SHA256="${{ steps.zip.outputs.sha256 }}"
ZIP_NAME="${{ steps.meta.outputs.zip_name }}"
TAG="${{ steps.meta.outputs.tag }}"
if [ ! -f "updates.xml" ]; then
echo "No updates.xml -- skipping"
exit 0
fi
# Map stability to XML tag name
case "$STABILITY" in
development) XML_TAG="development" ;;
alpha) XML_TAG="alpha" ;;
beta) XML_TAG="beta" ;;
release-candidate) XML_TAG="rc" ;;
*) XML_TAG="$STABILITY" ;;
esac
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${TAG}/${ZIP_NAME}"
# Use PHP to update the channel in updates.xml
php -r '
$xml_tag = $argv[1];
$version = $argv[2];
$sha256 = $argv[3];
$url = $argv[4];
$date = date("Y-m-d");
$content = file_get_contents("updates.xml");
$pattern = "/(<update>(?:(?!<\/update>).)*?<tag>" . preg_quote($xml_tag) . "<\/tag>.*?<\/update>)/s";
$content = preg_replace_callback($pattern, function($m) use ($version, $sha256, $url, $date) {
$block = $m[0];
$block = preg_replace("/<version>[^<]*<\/version>/", "<version>{$version}</version>", $block);
if (strpos($block, "<sha256>") !== false) {
$block = preg_replace("/<sha256>[^<]*<\/sha256>/", "<sha256>{$sha256}</sha256>", $block);
} else {
$block = str_replace("</downloads>", "</downloads>\n <sha256>{$sha256}</sha256>", $block);
}
$block = preg_replace("/(<downloadurl[^>]*>)[^<]*(<\/downloadurl>)/", "\${1}{$url}\${2}", $block);
return $block;
}, $content);
file_put_contents("updates.xml", $content);
echo "Updated {$xml_tag} channel: version={$version}\n";
' "$XML_TAG" "$VERSION" "$SHA256" "$DOWNLOAD_URL"
# Commit and push
if ! git diff --quiet updates.xml 2>/dev/null; then
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git add updates.xml
git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
git push origin HEAD 2>&1 || echo "WARNING: push failed"
fi
- name: "Sync updates.xml to all branches"
if: steps.platform.outputs.platform == 'joomla'
run: |
CURRENT_BRANCH="${{ github.ref_name }}"
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
for BRANCH in main dev; do
[ "$BRANCH" = "$CURRENT_BRANCH" ] && continue
echo "Syncing updates.xml -> ${BRANCH}"
git fetch origin "${BRANCH}" 2>/dev/null || continue
git checkout "origin/${BRANCH}" -- . 2>/dev/null || continue
git checkout "${CURRENT_BRANCH}" -- updates.xml
if ! git diff --quiet updates.xml 2>/dev/null; then
git add updates.xml
git commit -m "chore: sync updates.xml from ${CURRENT_BRANCH} [skip ci]"
git push origin HEAD:refs/heads/${BRANCH} 2>&1 || echo "WARNING: push to ${BRANCH} failed"
fi
git checkout "${CURRENT_BRANCH}" 2>/dev/null
done
- name: "Delete lesser pre-release channels (cascade)"
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.GA_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.zip.outputs.sha256 }}"
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
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: moko-platform.Release
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /templates/workflows/universal/pre-release.yml.template
# VERSION: 05.01.00
# BRIEF: Manual pre-release -- builds dev/alpha/beta/rc packages from any branch
name: "Universal: Pre-Release"
on:
pull_request:
types: [closed]
branches:
- dev
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 || 'development' }})"
runs-on: release
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.pull_request.merged == true && github.event.pull_request.base.ref == 'dev')
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GA_TOKEN }}
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
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
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
/tmp/moko-platform-api
cd /tmp/moko-platform-api && composer install --no-dev --no-interaction --quiet
echo "MOKO_CLI=/tmp/moko-platform-api/cli" >> "$GITHUB_ENV"
- name: Detect platform
id: platform
run: |
php ${MOKO_CLI}/manifest_read.php --path . --github-output
- name: Resolve metadata and bump version
id: meta
run: |
STABILITY="${{ inputs.stability || 'development' }}"
# Map stability to Gitea release tag
case "$STABILITY" in
development) TAG="development" ;;
alpha) TAG="alpha" ;;
beta) TAG="beta" ;;
release-candidate) TAG="release-candidate" ;;
esac
# Read current version (includes suffix from manifest, e.g. 01.02.14-dev)
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null)
[ -z "$VERSION" ] && VERSION="00.00.01"
php ${MOKO_CLI}/version_set_platform.php \
--path . --version "$VERSION" --branch "${{ github.ref_name }}" 2>/dev/null || true
# Verify version consistency across all files
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
# Commit version bump
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git remote set-url origin "https://jmiller:${{ secrets.GA_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 "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "zip_name=${ZIP_NAME}" >> "$GITHUB_OUTPUT"
echo "ext_element=${EXT_ELEMENT}" >> "$GITHUB_OUTPUT"
echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION} ==="
- name: Create release
id: release
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.GA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --branch dev --prerelease
- name: Build package and upload
id: package
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.GA_TOKEN }}" --api-base "$API_BASE" \
--repo "${GITEA_REPO}" --output /tmp || true
- name: Update updates.xml
if: steps.platform.outputs.platform == 'joomla'
run: |
VERSION="${{ steps.meta.outputs.version }}"
STABILITY="${{ steps.meta.outputs.stability }}"
SHA256="${{ steps.package.outputs.sha256_zip }}"
if [ ! -f "updates.xml" ]; then
echo "No updates.xml -- skipping"
exit 0
fi
SHA_FLAG=""
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
php ${MOKO_CLI}/updates_xml_build.php \
--path . --version "${VERSION}" --stability "${STABILITY}" \
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
${SHA_FLAG}
# Commit and push
if ! git diff --quiet updates.xml 2>/dev/null; then
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git add updates.xml
git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
git push origin HEAD 2>&1 || echo "WARNING: push failed"
fi
- name: "Sync updates.xml to all branches"
if: steps.platform.outputs.platform == 'joomla'
run: |
CURRENT_BRANCH="${{ github.ref_name }}"
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
for BRANCH in main dev; do
[ "$BRANCH" = "$CURRENT_BRANCH" ] && continue
echo "Syncing updates.xml -> ${BRANCH}"
git fetch origin "${BRANCH}" 2>/dev/null || continue
git checkout "origin/${BRANCH}" -- updates.xml 2>/dev/null || continue
git checkout "${CURRENT_BRANCH}" -- updates.xml
if ! git diff --quiet updates.xml 2>/dev/null; then
git add updates.xml
git commit -m "chore: sync updates.xml from ${CURRENT_BRANCH} [skip ci]"
git push origin HEAD:refs/heads/${BRANCH} 2>&1 || echo "WARNING: push to ${BRANCH} failed"
fi
git checkout "${CURRENT_BRANCH}" 2>/dev/null
done
- name: "Delete lesser pre-release channels (cascade)"
continue-on-error: true
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
TOKEN="${{ secrets.GA_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
+599
View File
@@ -0,0 +1,599 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Universal
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
# PATH: /templates/workflows/update-server.yml
# VERSION: 04.07.00
# BRIEF: Update server XML feed with stable/rc/beta/alpha/dev entries (universal)
#
# Writes updates.xml with multiple <update> entries:
# - <tag>stable</tag> on push to main (from auto-release)
# - <tag>rc</tag> on push to rc/**
# - <tag>development</tag> on push to dev or dev/**
#
# Joomla filters by user's "Minimum Stability" setting.
name: "Update Server"
on:
push:
branches:
- 'dev'
- 'dev/**'
- 'alpha/**'
- 'beta/**'
- 'rc/**'
paths:
- 'src/**'
- 'htdocs/**'
pull_request:
types: [closed]
branches:
- 'dev'
- 'dev/**'
- 'alpha/**'
- 'beta/**'
- 'rc/**'
paths:
- 'src/**'
- 'htdocs/**'
workflow_dispatch:
inputs:
stability:
description: 'Stability tag'
required: true
default: 'development'
type: choice
options:
- development
- alpha
- beta
- rc
- stable
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
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 }}
permissions:
contents: write
jobs:
update-xml:
name: Update updates.xml
runs-on: release
if: >-
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GA_TOKEN }}
fetch-depth: 0
- name: Setup moko-platform tools
env:
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN }}
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
COMPOSER_AUTH: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.GA_TOKEN }}"}}}'
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 "/tmp/moko-platform" ]; then
echo "moko-platform already available — skipping clone"
else
git clone --depth 1 --branch main --quiet \
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
/tmp/moko-platform 2>/dev/null || true
fi
if [ -d "/tmp/moko-platform" ] && [ -f "/tmp/moko-platform/composer.json" ]; then
cd /tmp/moko-platform && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
fi
- name: Generate updates.xml entry
id: update
run: |
BRANCH="${{ github.ref_name }}"
REPO="${{ github.repository }}"
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
VERSION=$(php /tmp/moko-platform/cli/version_read.php --path . 2>/dev/null || echo "0.0.0")
# Auto-bump patch on all branches (dev, alpha, beta, rc)
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
BUMPED=$(php /tmp/moko-platform/cli/version_bump.php --path . 2>/dev/null || true)
if [ -n "$BUMPED" ]; then
VERSION=$(php /tmp/moko-platform/cli/version_read.php --path . 2>/dev/null || echo "$VERSION")
git add -A
git commit -m "chore(version): auto-bump patch ${VERSION} [skip ci]" \
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>" 2>/dev/null || true
git push 2>/dev/null || true
fi
# Determine stability from branch or input
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
STABILITY="${{ inputs.stability }}"
elif [[ "$BRANCH" == rc/* ]]; then
STABILITY="rc"
elif [[ "$BRANCH" == beta/* ]]; then
STABILITY="beta"
elif [[ "$BRANCH" == alpha/* ]]; then
STABILITY="alpha"
elif [[ "$BRANCH" == dev/* ]] || [[ "$BRANCH" == "dev" ]]; then
STABILITY="development"
else
STABILITY="stable"
fi
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
# Parse manifest (portable — no grep -P)
MANIFEST=$(find . -maxdepth 3 -name "*.xml" ! -path "./.git/*" ! -path "./build/*" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
if [ -z "$MANIFEST" ]; then
echo "No Joomla manifest found — skipping"
exit 0
fi
# Extract fields using sed (works on all runners)
EXT_NAME=$(sed -n 's/.*<name>\([^<]*\)<\/name>.*/\1/p' "$MANIFEST" | head -1)
EXT_TYPE=$(sed -n 's/.*<extension[^>]*type="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
EXT_ELEMENT=$(sed -n 's/.*<element>\([^<]*\)<\/element>.*/\1/p' "$MANIFEST" | head -1)
EXT_CLIENT=$(sed -n 's/.*<extension[^>]*client="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
EXT_FOLDER=$(sed -n 's/.*<extension[^>]*group="\([^"]*\)".*/\1/p' "$MANIFEST" | head -1)
EXT_VERSION=$(sed -n 's/.*<version>\([^<]*\)<\/version>.*/\1/p' "$MANIFEST" | head -1)
TARGET_PLATFORM=$(sed -n 's/.*\(<targetplatform[^/]*\/>\).*/\1/p' "$MANIFEST" | head -1)
PHP_MINIMUM=$(sed -n 's/.*<php_minimum>\([^<]*\)<\/php_minimum>.*/\1/p' "$MANIFEST" | head -1)
# Fallbacks
[ -z "$EXT_NAME" ] && EXT_NAME="${{ github.event.repository.name }}"
[ -z "$EXT_TYPE" ] && EXT_TYPE="component"
# Derive element if not in manifest: try XML filename, then repo name
if [ -z "$EXT_ELEMENT" ]; then
EXT_ELEMENT=$(basename "$MANIFEST" .xml | tr '[:upper:]' '[:lower:]')
case "$EXT_ELEMENT" in
templatedetails|manifest|*.xml) EXT_ELEMENT=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]' | tr -d ' -') ;;
esac
fi
# Use manifest version if README version is empty
[ "$VERSION" = "0.0.0" ] && [ -n "$EXT_VERSION" ] && VERSION="$EXT_VERSION"
[ -z "$TARGET_PLATFORM" ] && TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="((5.[0-9])|(6.[0-9]))" %s>' "/")
# Joomla requires <client> on ALL extension types for update matching
if [ -n "$EXT_CLIENT" ]; then
CLIENT_TAG="<client>${EXT_CLIENT}</client>"
else
CLIENT_TAG="<client>site</client>"
fi
FOLDER_TAG=""
[ -n "$EXT_FOLDER" ] && [ "$EXT_TYPE" = "plugin" ] && FOLDER_TAG="<folder>${EXT_FOLDER}</folder>"
PHP_TAG=""
[ -n "$PHP_MINIMUM" ] && PHP_TAG="<php_minimum>${PHP_MINIMUM}</php_minimum>"
# VERSION already includes suffix from manifest (e.g. 01.02.14-dev)
# No separate DISPLAY_VERSION needed
MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
# Each stability level has its own release tag
case "$STABILITY" in
development) RELEASE_TAG="development" ;;
alpha) RELEASE_TAG="alpha" ;;
beta) RELEASE_TAG="beta" ;;
rc) RELEASE_TAG="release-candidate" ;;
*) RELEASE_TAG="v${MAJOR}" ;;
esac
PACKAGE_NAME="${EXT_ELEMENT}-${VERSION}.zip"
DOWNLOAD_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
INFO_URL="${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}"
# -- Build install packages (ZIP + tar.gz) --------------------
SOURCE_DIR="src"
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
if [ -d "$SOURCE_DIR" ]; then
EXCLUDES=".ftpignore sftp-config* *.ppk *.pem *.key .env*"
TAR_NAME="${EXT_ELEMENT}-${VERSION}.tar.gz"
cd "$SOURCE_DIR"
zip -r "/tmp/${PACKAGE_NAME}" . -x $EXCLUDES
cd ..
tar -czf "/tmp/${TAR_NAME}" -C "$SOURCE_DIR" \
--exclude='.ftpignore' --exclude='sftp-config*' \
--exclude='*.ppk' --exclude='*.pem' --exclude='*.key' --exclude='.env*' .
SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1)
# Ensure release exists on Gitea
RELEASE_JSON=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/tags/${RELEASE_TAG}" 2>/dev/null || true)
RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
if [ -z "$RELEASE_ID" ]; then
# Create release
RELEASE_JSON=$(curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
-H "Content-Type: application/json" \
"${API_BASE}/releases" \
-d "$(python3 -c "import json; print(json.dumps({
'tag_name': '${RELEASE_TAG}',
'name': '${RELEASE_TAG} (${VERSION})',
'body': '${STABILITY} release',
'prerelease': True,
'target_commitish': 'main'
}))")" 2>/dev/null || true)
RELEASE_ID=$(echo "$RELEASE_JSON" | python3 -c "import sys,json; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
fi
if [ -n "$RELEASE_ID" ]; then
# Delete existing assets with same name before uploading
ASSETS=$(curl -sf -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/${RELEASE_ID}/assets" 2>/dev/null || echo "[]")
for ASSET_FILE in "$PACKAGE_NAME" "$TAR_NAME"; do
ASSET_ID=$(echo "$ASSETS" | python3 -c "
import sys,json
assets = json.load(sys.stdin)
for a in assets:
if a['name'] == '${ASSET_FILE}':
print(a['id']); break
" 2>/dev/null || true)
if [ -n "$ASSET_ID" ]; then
curl -sf -X DELETE -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
"${API_BASE}/releases/${RELEASE_ID}/assets/${ASSET_ID}" 2>/dev/null || true
fi
done
# Upload both formats
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"/tmp/${PACKAGE_NAME}" \
"${API_BASE}/releases/${RELEASE_ID}/assets?name=${PACKAGE_NAME}" > /dev/null 2>&1 || true
curl -sf -X POST -H "Authorization: token ${{ secrets.GA_TOKEN }}" \
-H "Content-Type: application/octet-stream" \
--data-binary @"/tmp/${TAR_NAME}" \
"${API_BASE}/releases/${RELEASE_ID}/assets?name=${TAR_NAME}" > /dev/null 2>&1 || true
fi
echo "Packages: ${PACKAGE_NAME} + ${TAR_NAME} (SHA: ${SHA256})" >> $GITHUB_STEP_SUMMARY
else
SHA256=""
fi
# -- Build the new entry (canonical format matching release.yml) --
NEW_ENTRY=""
NEW_ENTRY="${NEW_ENTRY} <update>\n"
NEW_ENTRY="${NEW_ENTRY} <name>${EXT_NAME}</name>\n"
NEW_ENTRY="${NEW_ENTRY} <description>${EXT_NAME} ${STABILITY} build.</description>\n"
NEW_ENTRY="${NEW_ENTRY} <element>${EXT_ELEMENT}</element>\n"
NEW_ENTRY="${NEW_ENTRY} <type>${EXT_TYPE}</type>\n"
[ -n "$CLIENT_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${CLIENT_TAG}\n"
[ -n "$FOLDER_TAG" ] && NEW_ENTRY="${NEW_ENTRY} ${FOLDER_TAG}\n"
NEW_ENTRY="${NEW_ENTRY} <version>${VERSION}</version>\n"
NEW_ENTRY="${NEW_ENTRY} <creationDate>$(date +%Y-%m-%d)</creationDate>\n"
NEW_ENTRY="${NEW_ENTRY} <infourl title='${EXT_NAME}'>https://git.mokoconsulting.tech/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${RELEASE_TAG}</infourl>\n"
NEW_ENTRY="${NEW_ENTRY} <downloads>\n"
NEW_ENTRY="${NEW_ENTRY} <downloadurl type='full' format='zip'>${DOWNLOAD_URL}</downloadurl>\n"
NEW_ENTRY="${NEW_ENTRY} </downloads>\n"
[ -n "$SHA256" ] && NEW_ENTRY="${NEW_ENTRY} <sha256>${SHA256}</sha256>\n"
NEW_ENTRY="${NEW_ENTRY} <tags><tag>${STABILITY}</tag></tags>\n"
NEW_ENTRY="${NEW_ENTRY} <maintainer>Moko Consulting</maintainer>\n"
NEW_ENTRY="${NEW_ENTRY} <maintainerurl>https://mokoconsulting.tech</maintainerurl>\n"
NEW_ENTRY="${NEW_ENTRY} <targetplatform name='joomla' version='(5|6).*'/>\n"
[ -n "$PHP_MINIMUM" ] && NEW_ENTRY="${NEW_ENTRY} <php_minimum>${PHP_MINIMUM}</php_minimum>\n"
NEW_ENTRY="${NEW_ENTRY} </update>"
# -- Write new entry to temp file --------------------------------
printf '%b' "$NEW_ENTRY" > /tmp/new_entry.xml
# -- Merge into updates.xml ----------------------------------------
# Cascade: stable→all | rc→rc+lower | beta→beta+lower | alpha→alpha+dev | dev→dev
CASCADE_MAP="stable:development,alpha,beta,rc,stable rc:development,alpha,beta,rc beta:development,alpha,beta alpha:development,alpha development:development"
TARGETS=""
for entry in $CASCADE_MAP; do
key="${entry%%:*}"
vals="${entry#*:}"
if [ "$key" = "${STABILITY}" ]; then
TARGETS="$vals"
break
fi
done
[ -z "$TARGETS" ] && TARGETS="${STABILITY}"
echo "Cascade: ${STABILITY} → ${TARGETS}"
# Create updates.xml if missing
if [ ! -f "updates.xml" ]; then
printf '%s\n' "<?xml version='1.0' encoding='UTF-8'?>" > updates.xml
printf '%s\n' "<!-- Copyright (C) $(date +%Y) Moko Consulting -->" >> updates.xml
printf '%s\n' "<updates>" >> updates.xml
printf '%s\n' "</updates>" >> updates.xml
fi
# Update existing blocks or create missing ones
export PY_TARGETS="$TARGETS" PY_VERSION="$VERSION" PY_DATE="$(date +%Y-%m-%d)"
python3 << 'PYEOF'
import re, os
targets = os.environ["PY_TARGETS"].split(",")
version = os.environ["PY_VERSION"]
date = os.environ["PY_DATE"]
with open("updates.xml") as f:
content = f.read()
with open("/tmp/new_entry.xml") as f:
new_entry_template = f.read()
for tag in targets:
tag = tag.strip()
# Build entry with this tag's name
new_entry = re.sub(r"<tag>[^<]*</tag>", f"<tag>{tag}</tag>", new_entry_template)
# Try to find existing block (handles both single-line and multi-line <tags>)
block_pattern = r"(<update>(?:(?!</update>).)*?<tag>" + re.escape(tag) + r"</tag>.*?</update>)"
match = re.search(block_pattern, content, re.DOTALL)
if match:
# Update in place — replace entire block
content = content.replace(match.group(1), new_entry.strip())
print(f" UPDATED: <tag>{tag}</tag> → {version}")
else:
# Create — insert before </updates>
content = content.replace("</updates>", "\n" + new_entry.strip() + "\n\n</updates>")
print(f" CREATED: <tag>{tag}</tag> → {version}")
# Clean up excessive blank lines
content = re.sub(r"\n{3,}", "\n\n", content)
with open("updates.xml", "w") as f:
f.write(content)
PYEOF
# Commit
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
git config --local user.name "gitea-actions[bot]"
git add updates.xml
git diff --cached --quiet || {
git commit -m "chore: update updates.xml (${STABILITY}: ${VERSION}) [skip ci]" \
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
git push
}
# -- Sync updates.xml to main (for non-main branches) ----------------------
- name: Sync updates.xml to main
if: github.ref_name != 'main'
run: |
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
GA_TOKEN="${{ secrets.GA_TOKEN }}"
FILE_SHA=$(curl -sf -H "Authorization: token ${GA_TOKEN}" \
"${API_BASE}/contents/updates.xml?ref=main" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
if [ -n "$FILE_SHA" ] && [ -f "updates.xml" ]; then
python3 -c "
import base64, json, urllib.request, sys
with open('updates.xml', 'rb') as f:
content = base64.b64encode(f.read()).decode()
payload = json.dumps({
'content': content,
'sha': '${FILE_SHA}',
'message': 'chore: sync updates.xml from ${STABILITY} [skip ci]',
'branch': 'main'
}).encode()
req = urllib.request.Request(
'${API_BASE}/contents/updates.xml',
data=payload, method='PUT',
headers={
'Authorization': 'token ${GA_TOKEN}',
'Content-Type': 'application/json'
})
try:
urllib.request.urlopen(req)
print('updates.xml synced to main')
except Exception as e:
print(f'ERROR: failed to sync updates.xml to main: {e}', file=sys.stderr)
sys.exit(1)
" \
&& echo "updates.xml synced to main (${STABILITY})" >> $GITHUB_STEP_SUMMARY \
|| echo "::error::failed to sync updates.xml to main" >> $GITHUB_STEP_SUMMARY
else
echo "::error::could not get updates.xml SHA from main — file may not exist on main yet" >> $GITHUB_STEP_SUMMARY
fi
- name: Validate updates.xml integrity
run: |
ERRORS=0
if [ ! -f "updates.xml" ]; then
echo "::error::updates.xml not found"
exit 1
fi
# Well-formed XML
if ! python3 -c "import xml.etree.ElementTree as ET; ET.parse('updates.xml')" 2>/dev/null; then
echo "::error::updates.xml is not valid XML"
ERRORS=$((ERRORS+1))
fi
python3 << 'PYEOF'
import xml.etree.ElementTree as ET, sys, re, os
tree = ET.parse("updates.xml")
root = tree.getroot()
updates = root.findall("update")
errors = 0
warnings = 0
seen_tags = set()
# All 5 channels MUST be present
REQUIRED_CHANNELS = {"stable", "rc", "beta", "alpha", "dev"}
VALID_TAGS = REQUIRED_CHANNELS | {"development"} # accept legacy alias
REPO = os.environ.get("GITEA_REPO", "")
ORG = os.environ.get("GITEA_ORG", "MokoConsulting")
REPO_BASE = f"https://git.mokoconsulting.tech/{ORG}/"
# Gitea release tag names per channel (Moko standard)
RELEASE_TAG_MAP = {
"stable": "stable",
"rc": "release-candidate",
"beta": "beta",
"alpha": "alpha",
"dev": "development",
"development": "development",
}
# Joomla update XML required fields per
# https://docs.joomla.org/Deploying_an_Update_Server
REQUIRED_FIELDS = ["name", "element", "type", "version", "infourl"]
for i, u in enumerate(updates):
tag_el = u.find("tags/tag")
tag = tag_el.text.strip() if tag_el is not None and tag_el.text else None
label = f"Entry {i+1} (<tag>{tag or '?'}</tag>)"
# -- Required Joomla fields --
for field in REQUIRED_FIELDS:
el = u.find(field)
if el is None or not (el.text or "").strip():
print(f"::error::{label}: missing required <{field}>")
errors += 1
# -- <downloads><downloadurl> --
dl = u.find("downloads/downloadurl")
if dl is None or not (dl.text or "").strip():
print(f"::error::{label}: missing <downloads><downloadurl>")
errors += 1
else:
dl_url = dl.text.strip()
# Must point to org repo
if REPO_BASE not in dl_url:
print(f"::error::{label}: download URL not under {REPO_BASE}: {dl_url}")
errors += 1
# Must end in .zip
if not dl_url.endswith(".zip"):
print(f"::error::{label}: download URL must end in .zip: {dl_url}")
errors += 1
# Must use correct Gitea release tag in path
if tag and tag in RELEASE_TAG_MAP:
expected_tag = RELEASE_TAG_MAP[tag]
if f"/download/{expected_tag}/" not in dl_url:
print(f"::error::{label}: download URL should contain /download/{expected_tag}/ but got: {dl_url}")
errors += 1
# -- <client> (required for Joomla to match update) --
client = u.find("client")
if client is None or not (client.text or "").strip():
print(f"::error::{label}: missing <client> (required for Joomla update matching)")
errors += 1
# -- <targetplatform> --
tp = u.find("targetplatform")
if tp is None:
print(f"::error::{label}: missing <targetplatform>")
errors += 1
else:
tp_name = tp.get("name", "")
tp_ver = tp.get("version", "")
if tp_name != "joomla":
print(f"::error::{label}: targetplatform name should be 'joomla', got '{tp_name}'")
errors += 1
if not tp_ver:
print(f"::error::{label}: targetplatform missing version regex")
errors += 1
elif "5" not in tp_ver or "6" not in tp_ver:
print(f"::warning::{label}: targetplatform version may not cover Joomla 5+6: {tp_ver}")
warnings += 1
# -- <type> must be valid Joomla type --
type_el = u.find("type")
if type_el is not None and type_el.text:
valid_types = {"component", "module", "plugin", "template", "library", "package", "file"}
if type_el.text.strip() not in valid_types:
print(f"::error::{label}: invalid type '{type_el.text}' (expected: {valid_types})")
errors += 1
# -- <version> format (XX.YY.ZZ with optional suffix) --
ver_el = u.find("version")
if ver_el is not None and ver_el.text:
if not re.match(r"^\d{2}\.\d{2}\.\d{2}(-\w+)?$", ver_el.text.strip()):
print(f"::warning::{label}: version '{ver_el.text}' does not match XX.YY.ZZ format")
warnings += 1
# -- <maintainer> and <maintainerurl> --
for field in ["maintainer", "maintainerurl"]:
el = u.find(field)
if el is None or not (el.text or "").strip():
print(f"::warning::{label}: missing <{field}>")
warnings += 1
# -- Valid stability tag --
if tag is None:
print(f"::error::{label}: missing <tags><tag>")
errors += 1
elif tag not in VALID_TAGS:
print(f"::error::{label}: invalid tag '{tag}' (expected: {VALID_TAGS})")
errors += 1
# -- Duplicate tag check --
norm_tag = "dev" if tag == "development" else tag
if norm_tag in seen_tags:
print(f"::error::{label}: duplicate channel '{tag}'")
errors += 1
if norm_tag:
seen_tags.add(norm_tag)
# -- All 5 channels must exist --
missing = REQUIRED_CHANNELS - seen_tags
if missing:
print(f"::error::Missing required update channels: {', '.join(sorted(missing))}")
errors += 1
# -- Version ordering: higher stability must not exceed dev version --
channel_versions = {}
for u in updates:
tag_el = u.find("tags/tag")
ver_el = u.find("version")
if tag_el is not None and ver_el is not None and tag_el.text and ver_el.text:
norm = "dev" if tag_el.text.strip() == "development" else tag_el.text.strip()
# Strip suffix for comparison (01.00.18-dev -> 01.00.18)
base_ver = re.sub(r"-\w+$", "", ver_el.text.strip())
channel_versions[norm] = base_ver
# Cascade check: dev >= alpha >= beta >= rc >= stable
ORDER = ["dev", "alpha", "beta", "rc", "stable"]
for j in range(1, len(ORDER)):
current = ORDER[j]
previous = ORDER[j - 1]
if current in channel_versions and previous in channel_versions:
if channel_versions[current] > channel_versions[previous]:
print(f"::error::{current} version ({channel_versions[current]}) is ahead of {previous} ({channel_versions[previous]})")
errors += 1
# -- Summary --
print(f"\nupdates.xml validation: {len(updates)} entries, {errors} error(s), {warnings} warning(s)")
if errors > 0:
sys.exit(1)
PYEOF
- name: Summary
if: always()
run: |
echo "## Joomla Update Server" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Element | \`${EXT_ELEMENT}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Download | [ZIP](${DOWNLOAD_URL}) |" >> $GITHUB_STEP_SUMMARY
+67
View File
@@ -18,6 +18,73 @@ Version format: `XX.YY.ZZ` (zero-padded semver).
## [Unreleased]
### Added
- `branch-cleanup.yml`: auto-delete merged feature branches after PR merge — synced to all 47 repos
- `governance.yml`: lightweight YAML schema replacing HCL definition files for repo governance config
- `auto-bump.yml`: auto patch-bump version on every push to dev
### Changed
- **Definitions removed**: deleted `definitions/` directory (63,602 lines of HCL) — Template repos are now the canonical source for platform-specific files
- **Template path migration**: `templates/gitea/``templates/mokogitea/`, all `.github/` references → `.mokogitea/` across definitions and sync tools
- `version_bump.php`: preserves version suffix (e.g. `-dev`) through bumps — moko manifest is now the single source of truth for the full version string (#191)
- `version_read.php`: accepts suffix from moko manifest (was stripping it)
- `update-server.yml`: removed `DISPLAY_VERSION` — derives filename directly from manifest version (#191)
- `pre-release.yml`: removed `SUFFIX` variable — version string already includes suffix
- `push_files.php`: detects platform from manifest.xml via API instead of local sync definition files
- `auto_detect_platform.php`: gracefully handles missing schema directory
- `DefinitionParser.php`: deleted — no longer needed
- `manifest-schema.xsd`: moved from `definitions/` to `templates/schemas/`
### Removed
- `definitions/default/` — 10 HCL definition files (generic, joomla, dolibarr, platform, standards, etc.)
- `definitions/sync/` — 48 auto-generated sync tracking files
- `lib/Enterprise/DefinitionParser.php` — HCL parser (replaced by Template repo sourcing)
- Redundant bump from pre-release.yml (handled by auto-bump)
- 47 merged feature branches cleaned up from remote
## [09.02.00] - 2026-05-26
### Added
- **Release promotion pipeline**: draft PR → RC promotion, merged PR → RC-to-stable (skip rebuild)
- **7 new CLI tools**: `manifest_element.php`, `release_create.php`, `release_package.php`, `release_promote.php`, `release_mirror.php`, `version_reset_dev.php`, `ManifestReader.php`
- `version_bump.php` / `version_read.php`: support for `package.json` (Node.js) and `pyproject.toml` (Python)
- `version_bump.php`: now writes bumped version to all sources (README, manifests, Dolibarr mod, composer.json, package.json, pyproject.toml)
- `release_cascade.php`: `--version` flag for version-aware deletion of stale releases
- `release_validate.php`: auto-detect platform from manifest.xml, `--github-output` flag, source dir check
- `updates_xml_build.php`: supports non-Joomla platforms via manifest.xml detection
- `release_package.php`: reads entry-point from manifest.xml for source dir resolution
- `auto-release.yml`: `workflow_dispatch` with `promote-rc` action as fallback for MokoGitea#220
- `update-server.yml`: now universal — pushed to all 69+ repos (Joomla, Dolibarr, generic, MCP)
- `ManifestReader.php`: shared typed accessor for `.mokogitea/manifest.xml`
- Universal workflow cascade: Template-Generic → other templates → all repos via `bulk_sync.php`
- Wiki: UPDATE_SERVER standard page on moko-platform and all template repos
- PHPDoc added to 4 classes missing class-level docs
### Changed
- `auto-release.yml`: 761 → 490 lines — replaced all inline bash with CLI tool calls
- `pre-release.yml`: 389 → 314 lines — replaced inline logic with `manifest_read.php`, `manifest_element.php`, `updates_xml_build.php`
- Removed `paths` filter from workflow triggers (enables Go, Node.js, generic repo compatibility)
- `RepositorySynchronizer.php`: fixed template repo names, `.mokogitea/workflows` path, universal workflow sync
- Template-Generic is now the single source of truth for universal workflows
### Fixed
- `release_cascade.php` in `auto-release.yml`: was using `--org`/`--repo` flags instead of `--api-base`
- `pre-release.yml`: updates.xml sync was checking out entire branch tree instead of just `updates.xml`
- MokoWaaS#48: Joomla 6 typed event API fix for `plg_webservices_mokowaas`
## [09.00.00] - 2026-05-26
### Added
- PHPDoc on Priority 1 Enterprise classes (CliFramework, adapters, ApiClient)
- Wiki: Coding-Standards page with PHPDoc standard, PHPCS exclusions, file patterns
- CI: PHPStan enforced at level 6 (was advisory), PHPUnit blocks on failure
### Fixed
- `updates_xml_build.php`: cascade entries down to lower channels — stable now writes all 5 entries instead of wiping them
- `updates_xml_build.php`: separate Joomla stability tags (`dev`, `rc`) from Gitea release tags (`development`, `release-candidate`) — download URLs now point to correct release assets
- `updates_xml_build.php`: only emit `<client>site</client>` for templates and modules, not packages or components
- `updates_xml_build.php`: preservation logic matches Joomla tag names when deciding which existing entries to keep
## [08.00.00] - 2026-05-26
### Changed
+2 -3
View File
@@ -11,7 +11,7 @@ This file provides guidance to Claude Code when working with this repository.
| **Language** | PHP 8.1+ |
| **Default branch** | main |
| **License** | GPL-3.0-or-later |
| **Version** | 06.00.00 |
| **Version** | 09.01.00 |
| **Wiki** | [moko-platform Wiki](https://git.mokoconsulting.tech/MokoConsulting/moko-platform/wiki) |
## Common Commands
@@ -44,8 +44,7 @@ composer check
| `lib/Enterprise/` | Core library — CliFramework, ApiClient, adapters, validators |
| `lib/Enterprise/Plugins/` | 11 platform plugins (Joomla, Dolibarr, Node.js, Python, etc.) |
| `deploy/` | SFTP deployment scripts (Joomla, Dolibarr, health checks) |
| `definitions/` | Repository structure definitions (HCL format) |
| `templates/` | Workflow templates, config templates, docs templates |
| `templates/` | Universal templates, configs, governance schema |
| `.mokogitea/workflows/` | CI/CD workflows (Gitea Actions) |
| `bin/moko` | Unified CLI dispatcher — runs any tool via `php bin/moko <command>` |
+3
View File
@@ -6,11 +6,14 @@ DEFGROUP: MokoStandards.Root
INGROUP: MokoStandards
REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
PATH: /README.md
VERSION: 09.02.05
BRIEF: Project overview and documentation
-->
# MokoStandards Enterprise API
![Version](https://img.shields.io/badge/version-09.01.00-blue) ![PHP](https://img.shields.io/badge/PHP-8.1%2B-777BB4) ![License](https://img.shields.io/badge/license-GPL--3.0--or--later-green)
PHP implementation of MokoStandards — enterprise standards, automation framework, workflow templates, and bulk sync tooling.
> **Primary platform**: [Gitea — git.mokoconsulting.tech](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API)
+5
View File
@@ -156,6 +156,11 @@ class BulkSync extends CliFramework
return 0;
}
// Sync universal workflows from Template-Generic → other templates first
$this->log("📋 Syncing universal workflows to template repos...", 'INFO');
$templateUpdates = $this->synchronizer->syncUniversalWorkflowsToTemplates($org);
$this->log("Template sync: {$templateUpdates} file(s) updated", 'INFO');
// Execute synchronization
$this->log("🔄 Starting synchronization...", 'INFO');
$results = $this->executeSynchronization($org, $repositories, $alreadyProcessed);
+25 -43
View File
@@ -26,7 +26,6 @@ use MokoEnterprise\{
AuditLogger,
CliFramework,
Config,
DefinitionParser,
GitPlatformAdapter,
MetricsCollector,
PlatformAdapterFactory,
@@ -59,7 +58,6 @@ class PushFiles extends CliFramework
private ApiClient $api;
private GitPlatformAdapter $adapter;
private AuditLogger $logger;
private DefinitionParser $defParser;
private ProjectTypeDetector $typeDetector;
/**
@@ -154,7 +152,6 @@ class PushFiles extends CliFramework
$this->adapter = PlatformAdapterFactory::create($config);
$this->api = $this->adapter->getApiClient();
$this->logger = new AuditLogger('push_files');
$this->defParser = new DefinitionParser();
$this->typeDetector = new ProjectTypeDetector($this->logger);
$platform = $this->adapter->getPlatformName();
@@ -198,43 +195,24 @@ class PushFiles extends CliFramework
$platform = $this->detectRepoPlatform($org, $repo);
$this->log(" {$repo}: platform = {$platform}", 'INFO');
// Build a destination→source lookup from the definition
$defEntries = $this->defParser->parseForPlatform($platform, $repoRoot);
$destToSource = [];
foreach ($defEntries as $entry) {
$destToSource[$entry['destination']] = $entry['source'];
}
$resolved = [];
foreach ($files as $fileSpec) {
if (str_contains($fileSpec, ':')) {
// Raw source:destination pair
[$src, $dest] = explode(':', $fileSpec, 2);
$srcAbs = rtrim($repoRoot, '/') . '/' . ltrim($src, '/');
if (!file_exists($srcAbs)) {
$this->log(" ⚠️ Source not found for {$repo}: {$src}", 'WARN');
continue;
}
$resolved[] = ['source' => $srcAbs, 'destination' => $dest];
$this->log("{$dest} (raw: {$src})", 'INFO');
} else {
// Destination path — look up in definition
$dest = ltrim($fileSpec, '/');
if (isset($destToSource[$dest])) {
$src = $destToSource[$dest];
$srcAbs = str_starts_with($src, '/')
? $src
: rtrim($repoRoot, '/') . '/' . ltrim($src, '/');
if (!file_exists($srcAbs)) {
$this->log(" ⚠️ Template not found for {$repo}: {$src}", 'WARN');
continue;
}
$resolved[] = ['source' => $srcAbs, 'destination' => $dest];
$this->log("{$dest}", 'INFO');
} else {
$this->log(" ⚠️ {$dest} not found in {$platform} definition for {$repo}", 'WARN');
}
// Same path as source and destination
$src = $fileSpec;
$dest = $fileSpec;
}
$dest = ltrim($dest, '/');
$srcAbs = rtrim($repoRoot, '/') . '/' . ltrim($src, '/');
if (!file_exists($srcAbs)) {
$this->log(" ⚠️ Source not found for {$repo}: {$src}", 'WARN');
continue;
}
$resolved[] = ['source' => $srcAbs, 'destination' => $dest];
$this->log("{$dest}", 'INFO');
}
if (!empty($resolved)) {
@@ -246,24 +224,28 @@ class PushFiles extends CliFramework
}
/**
* Detect platform for a repo by checking its sync def file, falling back
* to the live GitHub API detection used by bulk_sync.
* Detect platform for a repo via manifest or live detection.
*/
private function detectRepoPlatform(string $org, string $repo): string
{
// Check local sync def first — fastest path
$defDir = dirname(__DIR__) . '/definitions/sync';
$defFile = "{$defDir}/{$repo}.def.tf";
if (file_exists($defFile)) {
$content = file_get_contents($defFile) ?: '';
if (preg_match('/detected_platform\s*=\s*"([^"]+)"/', $content, $m)) {
return $m[1];
// Read platform from repo's .mokogitea/manifest.xml via API
try {
$manifestData = $this->adapter->getFileContent($org, $repo, '.mokogitea/manifest.xml', 'main');
if (!empty($manifestData)) {
$xml = @simplexml_load_string($manifestData);
if ($xml !== false) {
$platform = (string)($xml->governance->platform ?? '');
if (!empty($platform)) {
return $platform;
}
}
}
} catch (\Exception $e) {
// Fall through to local detection
}
// Fall back to live detection
try {
$repoData = $this->api->get("/repos/{$org}/{$repo}");
$result = $this->typeDetector->detect('.');
return $result['type'] ?? 'default';
} catch (\Exception $e) {
+12
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env php
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
@@ -123,14 +124,25 @@ const COMMAND_MAP = [
'release' => 'cli/release.php',
'release:notes' => 'cli/release_notes.php',
'release:validate' => 'cli/release_validate.php',
'manifest:element' => 'cli/manifest_element.php',
'release:cascade' => 'cli/release_cascade.php',
'release:promote' => 'cli/release_promote.php',
'release:create' => 'cli/release_create.php',
'release:manage' => 'cli/release_manage.php',
'release:mirror' => 'cli/release_mirror.php',
'release:package' => 'cli/release_package.php',
// Changelog
'changelog:promote' => 'cli/changelog_promote.php',
'changelog:prune' => 'cli/changelog_prune.php',
// Version management
'version:read' => 'cli/version_read.php',
'version:bump' => 'cli/version_bump.php',
'version:check' => 'cli/version_check.php',
'version:propagate' => 'maintenance/update_version_from_readme.php',
'version:set-platform' => 'cli/version_set_platform.php',
'version:reset-dev' => 'cli/version_reset_dev.php',
// Build & package
'build:package' => 'cli/package_build.php',
+1 -13
View File
@@ -118,19 +118,7 @@ if (!$dryRun) {
echo " (dry-run) would archive {$org}/{$repoName}\n";
}
// ── Step 5: Remove sync definition ──────────────────────────────────────
echo "Step 5: Removing sync definition...\n";
$defFile = "{$repoRoot}/definitions/sync/{$repoName}.def.tf";
if (file_exists($defFile)) {
if (!$dryRun) {
unlink($defFile);
echo " Removed: {$defFile}\n";
} else {
echo " (dry-run) would remove {$defFile}\n";
}
} else {
echo " No sync definition found\n";
}
// ── Step 5: (removed — sync definitions no longer used) ─────────────────
// ── Step 6: Create archival record ──────────────────────────────────────
echo "Step 6: Creating archival record...\n";
+134
View File
@@ -0,0 +1,134 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: moko-platform.CLI
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/changelog_prune.php
* BRIEF: Prune old CHANGELOG.md entries — keeps [Unreleased] + last N releases
*
* Usage:
* php changelog_prune.php --path /repo --keep 5
* php changelog_prune.php --path /repo --keep 3 --dry-run
*/
declare(strict_types=1);
$path = '.';
$keep = 5;
$dryRun = false;
foreach ($argv as $i => $arg) {
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
if ($arg === '--keep' && isset($argv[$i + 1])) $keep = (int)$argv[$i + 1];
if ($arg === '--dry-run') $dryRun = true;
if ($arg === '--help') {
echo "changelog_prune — Keep [Unreleased] + last N versioned entries\n\n";
echo "Usage: php changelog_prune.php --path . --keep 5 [--dry-run]\n\n";
echo "Options:\n";
echo " --path Repository path (default: .)\n";
echo " --keep Number of versioned releases to keep (default: 5)\n";
echo " --dry-run Preview without writing\n";
exit(0);
}
}
$changelog = realpath($path) . '/CHANGELOG.md';
if (!file_exists($changelog)) {
fwrite(STDERR, "No CHANGELOG.md found at {$path}\n");
exit(1);
}
$content = file_get_contents($changelog);
$lines = explode("\n", $content);
// Split into sections by ## headings
$sections = [];
$current = [];
$currentHeading = null;
foreach ($lines as $line) {
if (preg_match('/^## /', $line)) {
if ($currentHeading !== null) {
$sections[] = ['heading' => $currentHeading, 'lines' => $current];
}
$currentHeading = $line;
$current = [$line];
} else {
$current[] = $line;
}
}
if ($currentHeading !== null) {
$sections[] = ['heading' => $currentHeading, 'lines' => $current];
}
// Find the header (everything before the first ## section)
$header = [];
$contentLines = explode("\n", $content);
foreach ($contentLines as $line) {
if (preg_match('/^## /', $line)) {
break;
}
$header[] = $line;
}
// Separate [Unreleased] from versioned sections
$unreleased = null;
$versioned = [];
foreach ($sections as $section) {
if (preg_match('/\[Unreleased\]/i', $section['heading'])) {
$unreleased = $section;
} else {
$versioned[] = $section;
}
}
$totalVersioned = count($versioned);
$pruned = $totalVersioned - $keep;
if ($pruned <= 0) {
echo "CHANGELOG has {$totalVersioned} versioned entries — nothing to prune (keeping {$keep})\n";
exit(0);
}
// Keep only the first N versioned sections
$keptVersioned = array_slice($versioned, 0, $keep);
$droppedVersioned = array_slice($versioned, $keep);
// Report
echo "CHANGELOG: {$totalVersioned} versioned entries found\n";
echo " Keeping: {$keep} most recent\n";
echo " Pruning: {$pruned} old entries\n";
foreach ($droppedVersioned as $section) {
$heading = trim($section['heading']);
echo " - {$heading}\n";
}
if ($dryRun) {
echo "\n(dry-run) No changes written\n";
exit(0);
}
// Rebuild the file
$output = implode("\n", $header);
if ($unreleased !== null) {
$output .= implode("\n", $unreleased['lines']) . "\n";
}
foreach ($keptVersioned as $section) {
$output .= implode("\n", $section['lines']) . "\n";
}
// Clean up excessive blank lines at end
$output = rtrim($output) . "\n";
file_put_contents($changelog, $output);
echo "\nCHANGELOG pruned: removed {$pruned} old entries\n";
exit(0);
+188 -188
View File
@@ -1,188 +1,188 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: moko-platform.CLI
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/client_health_check.php
* BRIEF: Verify a client site's update server, installed version, and release availability
*
* Usage:
* php client_health_check.php --update-url URL
* php client_health_check.php --path /repo --github-output
*
* Options:
* --path Repository root (reads update server URL from manifest)
* --update-url Update server XML URL (overrides manifest)
* --site-url Live site URL for version checking via Joomla API (optional)
* --api-token Joomla API token for site-url (optional)
* --github-output Export results to $GITHUB_OUTPUT
*/
declare(strict_types=1);
$path = '.';
$updateUrl = null;
$siteUrl = null;
$apiToken = null;
$ghOutput = false;
foreach ($argv as $i => $arg) {
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
if ($arg === '--update-url' && isset($argv[$i + 1])) $updateUrl = $argv[$i + 1];
if ($arg === '--site-url' && isset($argv[$i + 1])) $siteUrl = $argv[$i + 1];
if ($arg === '--api-token' && isset($argv[$i + 1])) $apiToken = $argv[$i + 1];
if ($arg === '--github-output') $ghOutput = true;
}
$root = realpath($path) ?: $path;
$checks = [];
// ── Resolve update server URL from manifest ─────────────────────────────
if ($updateUrl === null) {
$searchDirs = ["{$root}/src", $root];
foreach ($searchDirs as $dir) {
if (!is_dir($dir)) continue;
foreach (glob("{$dir}/*.xml") ?: [] as $f) {
$xml = file_get_contents($f);
if (preg_match('/<server[^>]*>([^<]+)<\/server>/', $xml, $m)) {
$updateUrl = trim($m[1]);
break 2;
}
}
}
}
if ($updateUrl === null) {
fwrite(STDERR, "No update server URL found. Use --update-url or provide a manifest with <updateservers>.\n");
exit(1);
}
echo "Update server: {$updateUrl}\n\n";
// ── Check 1: Update server accessible ───────────────────────────────────
echo "--- Update Server ---\n";
$ch = curl_init($updateUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTPHEADER => ['User-Agent: MokoHealthCheck/1.0'],
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200 && !empty($response)) {
echo " PASS: HTTP {$httpCode}, " . strlen($response) . " bytes\n";
$checks['update_server'] = 'pass';
} else {
echo " FAIL: HTTP {$httpCode}\n";
$checks['update_server'] = 'fail';
}
// ── Check 2: Parse updates.xml for stable version ───────────────────────
$stableVersion = null;
$downloadUrl = null;
if (!empty($response)) {
$sections = preg_split('/<update>/', $response);
foreach ($sections as $section) {
if (strpos($section, '<tag>stable</tag>') !== false) {
if (preg_match('/<version>([^<]+)<\/version>/', $section, $m)) {
$stableVersion = $m[1];
}
if (preg_match('/<downloadurl[^>]*>([^<]+)<\/downloadurl>/', $section, $m)) {
$downloadUrl = trim($m[1]);
}
break;
}
}
if ($stableVersion === null && preg_match('/<version>([^<]+)<\/version>/', $response, $m)) {
$stableVersion = $m[1];
}
}
echo "\n--- Stable Release ---\n";
if ($stableVersion !== null) {
echo " Version: {$stableVersion}\n";
$checks['stable_version'] = $stableVersion;
} else {
echo " FAIL: Could not parse stable version\n";
$checks['stable_version'] = 'fail';
}
// ── Check 3: Download URL accessible ────────────────────────────────────
if ($downloadUrl !== null) {
echo "\n--- Download URL ---\n";
$ch = curl_init($downloadUrl);
curl_setopt_array($ch, [
CURLOPT_NOBODY => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_FOLLOWLOCATION => true,
]);
curl_exec($ch);
$dlCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$dlSize = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);
curl_close($ch);
if ($dlCode === 200) {
$sizeKb = $dlSize > 0 ? round($dlSize / 1024) . 'KB' : 'unknown size';
echo " PASS: HTTP {$dlCode}, {$sizeKb}\n";
$checks['download'] = 'pass';
} else {
echo " FAIL: HTTP {$dlCode}\n";
$checks['download'] = 'fail';
}
}
// ── Check 4: Site version (optional) ────────────────────────────────────
if ($siteUrl !== null && $apiToken !== null) {
echo "\n--- Site Version ---\n";
$apiUrl = rtrim($siteUrl, '/') . '/api/index.php/v1/extensions?filter[type]=file';
$ch = curl_init($apiUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_HTTPHEADER => [
"X-Joomla-Token: {$apiToken}",
'Accept: application/json',
],
]);
$siteResponse = curl_exec($ch);
$siteCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($siteCode === 200) {
echo " API accessible (HTTP {$siteCode})\n";
$checks['site_api'] = 'pass';
} else {
echo " WARN: Site API returned HTTP {$siteCode}\n";
$checks['site_api'] = 'warn';
}
}
// ── Summary ─────────────────────────────────────────────────────────────
echo "\n=== Health Check Summary ===\n";
$failed = 0;
foreach ($checks as $name => $result) {
$icon = ($result === 'fail') ? 'FAIL' : (($result === 'warn') ? 'WARN' : 'OK');
if ($result === 'fail') $failed++;
echo " {$icon}: {$name} = {$result}\n";
}
if ($ghOutput) {
$ghFile = getenv('GITHUB_OUTPUT');
if ($ghFile) {
file_put_contents($ghFile, "health_status=" . ($failed > 0 ? 'fail' : 'pass') . "\n", FILE_APPEND);
file_put_contents($ghFile, "health_version=" . ($stableVersion ?? 'unknown') . "\n", FILE_APPEND);
file_put_contents($ghFile, "health_failures={$failed}\n", FILE_APPEND);
}
}
exit($failed > 0 ? 1 : 0);
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: moko-platform.CLI
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/client_health_check.php
* BRIEF: Verify a client site's update server, installed version, and release availability
*
* Usage:
* php client_health_check.php --update-url URL
* php client_health_check.php --path /repo --github-output
*
* Options:
* --path Repository root (reads update server URL from manifest)
* --update-url Update server XML URL (overrides manifest)
* --site-url Live site URL for version checking via Joomla API (optional)
* --api-token Joomla API token for site-url (optional)
* --github-output Export results to $GITHUB_OUTPUT
*/
declare(strict_types=1);
$path = '.';
$updateUrl = null;
$siteUrl = null;
$apiToken = null;
$ghOutput = false;
foreach ($argv as $i => $arg) {
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
if ($arg === '--update-url' && isset($argv[$i + 1])) $updateUrl = $argv[$i + 1];
if ($arg === '--site-url' && isset($argv[$i + 1])) $siteUrl = $argv[$i + 1];
if ($arg === '--api-token' && isset($argv[$i + 1])) $apiToken = $argv[$i + 1];
if ($arg === '--github-output') $ghOutput = true;
}
$root = realpath($path) ?: $path;
$checks = [];
// ── Resolve update server URL from manifest ─────────────────────────────
if ($updateUrl === null) {
$searchDirs = ["{$root}/src", $root];
foreach ($searchDirs as $dir) {
if (!is_dir($dir)) continue;
foreach (glob("{$dir}/*.xml") ?: [] as $f) {
$xml = file_get_contents($f);
if (preg_match('/<server[^>]*>([^<]+)<\/server>/', $xml, $m)) {
$updateUrl = trim($m[1]);
break 2;
}
}
}
}
if ($updateUrl === null) {
fwrite(STDERR, "No update server URL found. Use --update-url or provide a manifest with <updateservers>.\n");
exit(1);
}
echo "Update server: {$updateUrl}\n\n";
// ── Check 1: Update server accessible ───────────────────────────────────
echo "--- Update Server ---\n";
$ch = curl_init($updateUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_HTTPHEADER => ['User-Agent: MokoHealthCheck/1.0'],
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200 && !empty($response)) {
echo " PASS: HTTP {$httpCode}, " . strlen($response) . " bytes\n";
$checks['update_server'] = 'pass';
} else {
echo " FAIL: HTTP {$httpCode}\n";
$checks['update_server'] = 'fail';
}
// ── Check 2: Parse updates.xml for stable version ───────────────────────
$stableVersion = null;
$downloadUrl = null;
if (!empty($response)) {
$sections = preg_split('/<update>/', $response);
foreach ($sections as $section) {
if (strpos($section, '<tag>stable</tag>') !== false) {
if (preg_match('/<version>([^<]+)<\/version>/', $section, $m)) {
$stableVersion = $m[1];
}
if (preg_match('/<downloadurl[^>]*>([^<]+)<\/downloadurl>/', $section, $m)) {
$downloadUrl = trim($m[1]);
}
break;
}
}
if ($stableVersion === null && preg_match('/<version>([^<]+)<\/version>/', $response, $m)) {
$stableVersion = $m[1];
}
}
echo "\n--- Stable Release ---\n";
if ($stableVersion !== null) {
echo " Version: {$stableVersion}\n";
$checks['stable_version'] = $stableVersion;
} else {
echo " FAIL: Could not parse stable version\n";
$checks['stable_version'] = 'fail';
}
// ── Check 3: Download URL accessible ────────────────────────────────────
if ($downloadUrl !== null) {
echo "\n--- Download URL ---\n";
$ch = curl_init($downloadUrl);
curl_setopt_array($ch, [
CURLOPT_NOBODY => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_FOLLOWLOCATION => true,
]);
curl_exec($ch);
$dlCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$dlSize = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);
curl_close($ch);
if ($dlCode === 200) {
$sizeKb = $dlSize > 0 ? round($dlSize / 1024) . 'KB' : 'unknown size';
echo " PASS: HTTP {$dlCode}, {$sizeKb}\n";
$checks['download'] = 'pass';
} else {
echo " FAIL: HTTP {$dlCode}\n";
$checks['download'] = 'fail';
}
}
// ── Check 4: Site version (optional) ────────────────────────────────────
if ($siteUrl !== null && $apiToken !== null) {
echo "\n--- Site Version ---\n";
$apiUrl = rtrim($siteUrl, '/') . '/api/index.php/v1/extensions?filter[type]=file';
$ch = curl_init($apiUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
CURLOPT_HTTPHEADER => [
"X-Joomla-Token: {$apiToken}",
'Accept: application/json',
],
]);
$siteResponse = curl_exec($ch);
$siteCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($siteCode === 200) {
echo " API accessible (HTTP {$siteCode})\n";
$checks['site_api'] = 'pass';
} else {
echo " WARN: Site API returned HTTP {$siteCode}\n";
$checks['site_api'] = 'warn';
}
}
// ── Summary ─────────────────────────────────────────────────────────────
echo "\n=== Health Check Summary ===\n";
$failed = 0;
foreach ($checks as $name => $result) {
$icon = ($result === 'fail') ? 'FAIL' : (($result === 'warn') ? 'WARN' : 'OK');
if ($result === 'fail') $failed++;
echo " {$icon}: {$name} = {$result}\n";
}
if ($ghOutput) {
$ghFile = getenv('GITHUB_OUTPUT');
if ($ghFile) {
file_put_contents($ghFile, "health_status=" . ($failed > 0 ? 'fail' : 'pass') . "\n", FILE_APPEND);
file_put_contents($ghFile, "health_version=" . ($stableVersion ?? 'unknown') . "\n", FILE_APPEND);
file_put_contents($ghFile, "health_failures={$failed}\n", FILE_APPEND);
}
}
exit($failed > 0 ? 1 : 0);
+136 -136
View File
@@ -1,136 +1,136 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: moko-platform.CLI
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/joomla_compat_check.php
* BRIEF: Check if extension targetplatform regex matches the latest Joomla version
*
* Usage:
* php joomla_compat_check.php --path /repo
* php joomla_compat_check.php --path /repo --github-output
*
* Options:
* --path Repository root (default: .)
* --github-output Export results to $GITHUB_OUTPUT
*/
declare(strict_types=1);
$path = '.';
$ghOutput = false;
foreach ($argv as $i => $arg) {
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
if ($arg === '--github-output') $ghOutput = true;
}
$root = realpath($path) ?: $path;
// ── Find manifest and extract targetplatform ────────────────────────────
$manifest = null;
$searchDirs = ["{$root}/src", $root];
foreach ($searchDirs as $dir) {
if (!is_dir($dir)) continue;
foreach (glob("{$dir}/*.xml") ?: [] as $f) {
$xml = file_get_contents($f);
if (strpos($xml, '<extension') !== false && strpos($xml, 'targetplatform') !== false) {
$manifest = $f;
break 2;
}
}
}
if ($manifest === null) {
fwrite(STDERR, "No manifest with targetplatform found\n");
exit(1);
}
$xml = file_get_contents($manifest);
$relManifest = str_replace($root . '/', '', $manifest);
// Extract targetplatform version regex
$targetRegex = '';
if (preg_match('/targetplatform[^>]*version="([^"]+)"/', $xml, $m)) {
$targetRegex = $m[1];
}
if (empty($targetRegex)) {
echo "No targetplatform version found in {$relManifest}\n";
exit(1);
}
echo "Manifest: {$relManifest}\n";
echo "Target regex: {$targetRegex}\n";
// ── Fetch latest Joomla version ─────────────────────────────────────────
$joomlaVersions = [];
$updateUrl = 'https://update.joomla.org/core/sts/list_sts.xml';
$updateXml = @file_get_contents($updateUrl);
if ($updateXml === false) {
// Fallback: try the LTS feed
$updateUrl = 'https://update.joomla.org/core/list.xml';
$updateXml = @file_get_contents($updateUrl);
}
if ($updateXml !== false) {
// Parse all version entries
preg_match_all('/<version>([^<]+)<\/version>/', $updateXml, $matches);
$joomlaVersions = $matches[1] ?? [];
}
if (empty($joomlaVersions)) {
echo "WARNING: Could not fetch Joomla versions from update server\n";
echo "Tested URL: {$updateUrl}\n";
exit(0);
}
// Sort and get latest
usort($joomlaVersions, 'version_compare');
$latestJoomla = end($joomlaVersions);
echo "Latest Joomla: {$latestJoomla}\n";
// ── Test compatibility ──────────────────────────────────────────────────
// The targetplatform regex uses Joomla's regex format
// Common patterns: "5\.[0-9]+" or "((5.[0-9])|(6.[0-9]))"
$compatible = @preg_match("/{$targetRegex}/", $latestJoomla);
if ($compatible === false) {
echo "ERROR: Invalid regex in targetplatform: {$targetRegex}\n";
$result = 'error';
} elseif ($compatible === 1) {
echo "PASS: Joomla {$latestJoomla} matches targetplatform regex\n";
$result = 'pass';
} else {
// Check which major versions are supported
$supported = [];
foreach (['5.0', '5.1', '5.2', '5.3', '5.4', '6.0', '6.1', '6.2', '7.0'] as $v) {
if (@preg_match("/{$targetRegex}/", $v)) {
$supported[] = $v;
}
}
echo "WARN: Joomla {$latestJoomla} does NOT match targetplatform regex\n";
echo "Supported versions: " . implode(', ', $supported) . "\n";
echo "Consider updating targetplatform to include Joomla {$latestJoomla}\n";
$result = 'warn';
}
// ── Export ───────────────────────────────────────────────────────────────
if ($ghOutput) {
$ghFile = getenv('GITHUB_OUTPUT');
if ($ghFile) {
file_put_contents($ghFile, "compat_result={$result}\n", FILE_APPEND);
file_put_contents($ghFile, "compat_joomla={$latestJoomla}\n", FILE_APPEND);
file_put_contents($ghFile, "compat_regex={$targetRegex}\n", FILE_APPEND);
}
}
exit($result === 'error' ? 1 : 0);
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: moko-platform.CLI
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/joomla_compat_check.php
* BRIEF: Check if extension targetplatform regex matches the latest Joomla version
*
* Usage:
* php joomla_compat_check.php --path /repo
* php joomla_compat_check.php --path /repo --github-output
*
* Options:
* --path Repository root (default: .)
* --github-output Export results to $GITHUB_OUTPUT
*/
declare(strict_types=1);
$path = '.';
$ghOutput = false;
foreach ($argv as $i => $arg) {
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
if ($arg === '--github-output') $ghOutput = true;
}
$root = realpath($path) ?: $path;
// ── Find manifest and extract targetplatform ────────────────────────────
$manifest = null;
$searchDirs = ["{$root}/src", $root];
foreach ($searchDirs as $dir) {
if (!is_dir($dir)) continue;
foreach (glob("{$dir}/*.xml") ?: [] as $f) {
$xml = file_get_contents($f);
if (strpos($xml, '<extension') !== false && strpos($xml, 'targetplatform') !== false) {
$manifest = $f;
break 2;
}
}
}
if ($manifest === null) {
fwrite(STDERR, "No manifest with targetplatform found\n");
exit(1);
}
$xml = file_get_contents($manifest);
$relManifest = str_replace($root . '/', '', $manifest);
// Extract targetplatform version regex
$targetRegex = '';
if (preg_match('/targetplatform[^>]*version="([^"]+)"/', $xml, $m)) {
$targetRegex = $m[1];
}
if (empty($targetRegex)) {
echo "No targetplatform version found in {$relManifest}\n";
exit(1);
}
echo "Manifest: {$relManifest}\n";
echo "Target regex: {$targetRegex}\n";
// ── Fetch latest Joomla version ─────────────────────────────────────────
$joomlaVersions = [];
$updateUrl = 'https://update.joomla.org/core/sts/list_sts.xml';
$updateXml = @file_get_contents($updateUrl);
if ($updateXml === false) {
// Fallback: try the LTS feed
$updateUrl = 'https://update.joomla.org/core/list.xml';
$updateXml = @file_get_contents($updateUrl);
}
if ($updateXml !== false) {
// Parse all version entries
preg_match_all('/<version>([^<]+)<\/version>/', $updateXml, $matches);
$joomlaVersions = $matches[1] ?? [];
}
if (empty($joomlaVersions)) {
echo "WARNING: Could not fetch Joomla versions from update server\n";
echo "Tested URL: {$updateUrl}\n";
exit(0);
}
// Sort and get latest
usort($joomlaVersions, 'version_compare');
$latestJoomla = end($joomlaVersions);
echo "Latest Joomla: {$latestJoomla}\n";
// ── Test compatibility ──────────────────────────────────────────────────
// The targetplatform regex uses Joomla's regex format
// Common patterns: "5\.[0-9]+" or "((5.[0-9])|(6.[0-9]))"
$compatible = @preg_match("/{$targetRegex}/", $latestJoomla);
if ($compatible === false) {
echo "ERROR: Invalid regex in targetplatform: {$targetRegex}\n";
$result = 'error';
} elseif ($compatible === 1) {
echo "PASS: Joomla {$latestJoomla} matches targetplatform regex\n";
$result = 'pass';
} else {
// Check which major versions are supported
$supported = [];
foreach (['5.0', '5.1', '5.2', '5.3', '5.4', '6.0', '6.1', '6.2', '7.0'] as $v) {
if (@preg_match("/{$targetRegex}/", $v)) {
$supported[] = $v;
}
}
echo "WARN: Joomla {$latestJoomla} does NOT match targetplatform regex\n";
echo "Supported versions: " . implode(', ', $supported) . "\n";
echo "Consider updating targetplatform to include Joomla {$latestJoomla}\n";
$result = 'warn';
}
// ── Export ───────────────────────────────────────────────────────────────
if ($ghOutput) {
$ghFile = getenv('GITHUB_OUTPUT');
if ($ghFile) {
file_put_contents($ghFile, "compat_result={$result}\n", FILE_APPEND);
file_put_contents($ghFile, "compat_joomla={$latestJoomla}\n", FILE_APPEND);
file_put_contents($ghFile, "compat_regex={$targetRegex}\n", FILE_APPEND);
}
}
exit($result === 'error' ? 1 : 0);
+8
View File
@@ -26,6 +26,14 @@ require_once __DIR__ . '/../vendor/autoload.php';
use MokoEnterprise\{ApiClient, AuditLogger, CliFramework, Config, PlatformAdapterFactory};
/**
* Joomla Release Manager
*
* Creates and manages Joomla extension releases on Gitea, including
* package building, asset upload, and update stream management.
*
* @since 04.06.00
*/
class JoomlaRelease extends CliFramework
{
private const VERSION = '04.06.00';
+235
View File
@@ -0,0 +1,235 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: moko-platform.CLI
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/manifest_element.php
* BRIEF: Extract element name, type, type prefix, and ZIP name from manifest
*
* Usage:
* php manifest_element.php --path .
* php manifest_element.php --path . --version 09.01.00 --stability dev --github-output
*
* Detects platform (joomla, dolibarr, generic) and resolves:
* ext_element — canonical element name (e.g. mokojgdpc)
* ext_type — extension type (plugin, module, component, package, etc.)
* ext_folder — group/folder for plugins (e.g. system)
* ext_name — human-readable name (e.g. "Moko JGDPC")
* type_prefix — Joomla type prefix (plg_system_, com_, mod_, etc.)
* zip_name — computed ZIP filename
*/
declare(strict_types=1);
$path = '.';
$version = null;
$stability = 'stable';
$githubOutput = false;
$repoName = '';
foreach ($argv as $i => $arg) {
if ($arg === '--path' && isset($argv[$i + 1])) {
$path = $argv[$i + 1];
}
if ($arg === '--version' && isset($argv[$i + 1])) {
$version = $argv[$i + 1];
}
if ($arg === '--stability' && isset($argv[$i + 1])) {
$stability = $argv[$i + 1];
}
if ($arg === '--repo' && isset($argv[$i + 1])) {
$repoName = $argv[$i + 1];
}
if ($arg === '--github-output') {
$githubOutput = true;
}
}
$root = realpath($path) ?: $path;
// ── Detect platform from manifest.xml ────────────────────────────────────────
$platform = 'generic';
$manifestXml = "{$root}/.mokogitea/manifest.xml";
if (file_exists($manifestXml)) {
$content = file_get_contents($manifestXml);
if (preg_match('/<platform>([^<]+)<\/platform>/', $content, $pm)) {
$platform = trim($pm[1]);
}
}
// ── Find extension manifest (Joomla XML) ─────────────────────────────────────
$extManifest = null;
$manifestFiles = array_merge(
glob("{$root}/src/pkg_*.xml") ?: [],
glob("{$root}/src/*.xml") ?: [],
glob("{$root}/*.xml") ?: []
);
foreach ($manifestFiles as $file) {
$c = file_get_contents($file);
if (strpos($c, '<extension') !== false) {
$extManifest = $file;
break;
}
}
// ── Find Dolibarr module file ────────────────────────────────────────────────
$modFile = null;
$modFiles = array_merge(
glob("{$root}/src/core/modules/mod*.class.php") ?: [],
glob("{$root}/htdocs/core/modules/mod*.class.php") ?: [],
glob("{$root}/core/modules/mod*.class.php") ?: []
);
foreach ($modFiles as $file) {
$c = file_get_contents($file);
if (strpos($c, 'extends DolibarrModules') !== false) {
$modFile = $file;
break;
}
}
// ── Extract metadata ─────────────────────────────────────────────────────────
$extElement = '';
$extType = '';
$extFolder = '';
$extName = '';
switch (true) {
// Joomla platforms
case in_array($platform, ['joomla', 'waas-component'], true) && $extManifest !== null:
$xml = file_get_contents($extManifest);
// Extension type and folder
if (preg_match('/type="([^"]*)"/', $xml, $tm)) {
$extType = $tm[1];
}
if (preg_match('/group="([^"]*)"/', $xml, $gm)) {
$extFolder = $gm[1];
}
// Element name: <element>, plugin= attribute, <packagename>, or filename
if (preg_match('/<element>([^<]+)<\/element>/', $xml, $em)) {
$extElement = $em[1];
}
if (empty($extElement) && preg_match('/plugin="([^"]*)"/', $xml, $pm)) {
$extElement = $pm[1];
}
if ($extType === 'package' && preg_match('/<packagename>([^<]+)<\/packagename>/', $xml, $pn)) {
$extElement = $pn[1];
}
if (empty($extElement)) {
$extElement = strtolower(basename($extManifest, '.xml'));
if (in_array($extElement, ['templatedetails', 'manifest'], true)) {
$extElement = strtolower(str_replace([' ', '-'], '', $repoName ?: basename($root)));
}
}
// Human-readable name
if (preg_match('/<name>([^<]+)<\/name>/', $xml, $nm)) {
$extName = trim($nm[1]);
}
break;
// Dolibarr platforms
case in_array($platform, ['dolibarr', 'crm-module'], true) && $modFile !== null:
$extType = 'dolibarr-module';
$modBasename = basename($modFile, '.class.php');
$extElement = strtolower(preg_replace('/^mod/', '', $modBasename));
$modContent = file_get_contents($modFile);
if (preg_match('/\$this->name\s*=\s*[\'"]([^\'"]+)[\'"]/', $modContent, $nm)) {
$extName = $nm[1];
}
break;
// Generic / fallback
default:
$extElement = strtolower(str_replace([' ', '-'], '', $repoName ?: basename($root)));
$extType = 'generic';
break;
}
// ── Strip existing type prefix from element to prevent duplication ────────────
$extElement = preg_replace('/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)/', '', $extElement);
// ── Compute type prefix ──────────────────────────────────────────────────────
$typePrefix = '';
switch ($extType) {
case 'plugin':
$typePrefix = "plg_{$extFolder}_";
break;
case 'module':
$typePrefix = 'mod_';
break;
case 'component':
$typePrefix = 'com_';
break;
case 'template':
$typePrefix = 'tpl_';
break;
case 'library':
$typePrefix = 'lib_';
break;
case 'package':
$typePrefix = 'pkg_';
break;
}
// ── Compute ZIP name ─────────────────────────────────────────────────────────
$suffixMap = [
'development' => '-dev',
'dev' => '-dev',
'alpha' => '-alpha',
'beta' => '-beta',
'rc' => '-rc',
'release-candidate' => '-rc',
'stable' => '',
];
$suffix = $suffixMap[$stability] ?? '';
$zipName = '';
if ($version !== null) {
$zipName = "{$typePrefix}{$extElement}-{$version}{$suffix}.zip";
}
// Fallback name
if (empty($extName)) {
$extName = $repoName ?: basename($root);
}
// ── Output ───────────────────────────────────────────────────────────────────
$outputs = [
'platform' => $platform,
'ext_element' => $extElement,
'ext_type' => $extType,
'ext_folder' => $extFolder,
'ext_name' => $extName,
'type_prefix' => $typePrefix,
'zip_name' => $zipName,
];
if ($githubOutput) {
$ghOutput = getenv('GITHUB_OUTPUT');
$lines = [];
foreach ($outputs as $key => $value) {
$lines[] = "{$key}={$value}";
}
if ($ghOutput) {
file_put_contents($ghOutput, implode("\n", $lines) . "\n", FILE_APPEND);
} else {
// Fallback: echo ::set-output (legacy)
foreach ($outputs as $key => $value) {
echo "::set-output name={$key}::{$value}\n";
}
}
} else {
foreach ($outputs as $key => $value) {
echo "{$key}={$value}\n";
}
}
exit(0);
+1
View File
@@ -103,6 +103,7 @@ if ($xml === false) {
'language' => (string)($xml->build->language ?? ''),
'package-type' => (string)($xml->build->{"package-type"} ?? ''),
'entry-point' => (string)($xml->build->{"entry-point"} ?? ''),
'version' => (string)($xml->identity->version ?? ''),
'source-dir' => (string)($xml->deploy->{"source-dir"} ?? ''),
'remote-subdir' => (string)($xml->deploy->{"remote-subdir"} ?? ''),
'excludes' => (string)($xml->deploy->excludes ?? ''),
+147 -56
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
@@ -14,12 +15,16 @@
* Usage:
* php release_cascade.php --stability stable --token TOKEN --api-base URL
* php release_cascade.php --stability rc --token TOKEN --api-base URL
* php release_cascade.php --stability stable --version 09.01.00 --token TOKEN --api-base URL
*
* Cascade rules:
* stable -> deletes development, alpha, beta, release-candidate
* rc -> deletes development, alpha, beta
* beta -> deletes development, alpha
* alpha -> deletes development
*
* When --version is given, also deletes releases on any channel whose version
* is lower than the specified version (prevents stale pre-releases lingering).
*/
declare(strict_types=1);
@@ -27,90 +32,176 @@ declare(strict_types=1);
$stability = null;
$token = null;
$apiBase = null;
$version = null;
foreach ($argv as $i => $arg) {
if ($arg === '--stability' && isset($argv[$i + 1])) $stability = $argv[$i + 1];
if ($arg === '--token' && isset($argv[$i + 1])) $token = $argv[$i + 1];
if ($arg === '--api-base' && isset($argv[$i + 1])) $apiBase = $argv[$i + 1];
if ($arg === '--stability' && isset($argv[$i + 1])) {
$stability = $argv[$i + 1];
}
if ($arg === '--token' && isset($argv[$i + 1])) {
$token = $argv[$i + 1];
}
if ($arg === '--api-base' && isset($argv[$i + 1])) {
$apiBase = $argv[$i + 1];
}
if ($arg === '--version' && isset($argv[$i + 1])) {
$version = $argv[$i + 1];
}
}
// Allow token from environment
if ($token === null) {
$token = getenv('GA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null;
$token = getenv('GA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null;
}
if ($stability === null || $token === null || $apiBase === null) {
fwrite(STDERR, "Usage: release_cascade.php --stability [stable|rc|beta|alpha] --token TOKEN --api-base URL\n");
fwrite(STDERR, " --api-base: e.g. https://git.mokoconsulting.tech/api/v1/repos/Org/Repo\n");
fwrite(STDERR, " Token can also be set via GA_TOKEN or GITEA_TOKEN env var\n");
exit(1);
fwrite(STDERR, "Usage: release_cascade.php --stability [stable|rc|beta|alpha] --token TOKEN --api-base URL\n");
fwrite(STDERR, " --api-base: e.g. https://git.mokoconsulting.tech/api/v1/repos/Org/Repo\n");
fwrite(STDERR, " Token can also be set via GA_TOKEN or GITEA_TOKEN env var\n");
exit(1);
}
// Define cascade hierarchy
$cascadeMap = [
'stable' => ['development', 'alpha', 'beta', 'release-candidate'],
'release-candidate' => ['development', 'alpha', 'beta'],
'rc' => ['development', 'alpha', 'beta'],
'beta' => ['development', 'alpha'],
'alpha' => ['development'],
'stable' => ['development', 'alpha', 'beta', 'release-candidate'],
'release-candidate' => ['development', 'alpha', 'beta'],
'rc' => ['development', 'alpha', 'beta'],
'beta' => ['development', 'alpha'],
'alpha' => ['development'],
];
if (!isset($cascadeMap[$stability])) {
fwrite(STDERR, "Unknown stability level: {$stability}\n");
fwrite(STDERR, "Valid options: stable, rc, beta, alpha\n");
exit(1);
fwrite(STDERR, "Unknown stability level: {$stability}\n");
fwrite(STDERR, "Valid options: stable, rc, beta, alpha\n");
exit(1);
}
$tagsToDelete = $cascadeMap[$stability];
$deleted = 0;
foreach ($tagsToDelete as $tag) {
// Get release by tag
$ch = curl_init("{$apiBase}/releases/tags/{$tag}");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
CURLOPT_TIMEOUT => 30,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Get release by tag
$ch = curl_init("{$apiBase}/releases/tags/{$tag}");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
CURLOPT_TIMEOUT => 30,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200 || empty($response)) {
continue;
}
if ($httpCode !== 200 || empty($response)) {
continue;
}
$data = json_decode($response, true);
$releaseId = $data['id'] ?? null;
$data = json_decode($response, true);
$releaseId = $data['id'] ?? null;
if ($releaseId === null) {
continue;
}
if ($releaseId === null) {
continue;
}
// Delete release
$ch = curl_init("{$apiBase}/releases/{$releaseId}");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
CURLOPT_TIMEOUT => 30,
]);
curl_exec($ch);
curl_close($ch);
// Delete release
$ch = curl_init("{$apiBase}/releases/{$releaseId}");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
CURLOPT_TIMEOUT => 30,
]);
curl_exec($ch);
curl_close($ch);
// Delete tag
$ch = curl_init("{$apiBase}/tags/{$tag}");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
CURLOPT_TIMEOUT => 30,
]);
curl_exec($ch);
curl_close($ch);
// Delete tag
$ch = curl_init("{$apiBase}/tags/{$tag}");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
CURLOPT_TIMEOUT => 30,
]);
curl_exec($ch);
curl_close($ch);
echo "Deleted: {$tag} (release id: {$releaseId})\n";
$deleted++;
echo "Deleted: {$tag} (release id: {$releaseId})\n";
$deleted++;
}
// ── Version-aware cleanup: delete releases with lesser version numbers ───────
if ($version !== null) {
// Normalize version for comparison (strip any suffix)
$baseVersion = preg_replace('/-[a-z]+$/', '', $version);
// Check all channels (including ones not in the cascade map for this stability)
$allChannels = ['development', 'alpha', 'beta', 'release-candidate', 'stable'];
foreach ($allChannels as $tag) {
// Skip the current stability channel
if ($tag === $stability) {
continue;
}
// Skip channels already deleted by cascade above
if (in_array($tag, $tagsToDelete, true)) {
continue;
}
$ch = curl_init("{$apiBase}/releases/tags/{$tag}");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
CURLOPT_TIMEOUT => 30,
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200 || empty($response)) {
continue;
}
$data = json_decode($response, true);
$releaseId = $data['id'] ?? null;
$releaseName = $data['name'] ?? '';
if ($releaseId === null) {
continue;
}
// Extract version from release name (e.g. "element 09.00.01 (development)")
$releaseVersion = null;
if (preg_match('/(\d{2}\.\d{2}\.\d{2})/', $releaseName, $vm)) {
$releaseVersion = $vm[1];
}
if ($releaseVersion === null) {
continue;
}
// Delete if release version is less than the promoted version
if (version_compare($releaseVersion, $baseVersion, '<')) {
$delCh = curl_init("{$apiBase}/releases/{$releaseId}");
curl_setopt_array($delCh, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
CURLOPT_TIMEOUT => 30,
]);
curl_exec($delCh);
curl_close($delCh);
$tagCh = curl_init("{$apiBase}/tags/{$tag}");
curl_setopt_array($tagCh, [
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
CURLOPT_TIMEOUT => 30,
]);
curl_exec($tagCh);
curl_close($tagCh);
echo "Deleted: {$tag} — version {$releaseVersion} < {$baseVersion}\n";
$deleted++;
}
}
}
echo "Cleaned up {$deleted} pre-release channel(s)\n";
+328
View File
@@ -0,0 +1,328 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: moko-platform.CLI
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/release_create.php
* BRIEF: Create or overwrite a Gitea release with proper naming
*
* Usage:
* php release_create.php --version 09.01.00 --tag stable --token TOKEN --api-base URL
* php release_create.php --version 09.01.00 --tag development --token TOKEN --api-base URL --prerelease
* php release_create.php --version 09.01.00 --tag stable --token TOKEN --api-base URL --path . --repo MyRepo
*
* Replaces the inline bash in auto-release.yml Step 7b.
* Detects extension metadata from manifest, builds a proper release name,
* generates release notes, and creates (or overwrites) a Gitea release.
*/
declare(strict_types=1);
// ── Argument parsing ────────────────────────────────────────────────────────
$path = '.';
$version = null;
$tag = null;
$token = null;
$apiBase = null;
$branch = 'main';
$repoName = '';
$prerelease = false;
foreach ($argv as $i => $arg) {
if ($arg === '--path' && isset($argv[$i + 1])) {
$path = $argv[$i + 1];
}
if ($arg === '--version' && isset($argv[$i + 1])) {
$version = $argv[$i + 1];
}
if ($arg === '--tag' && isset($argv[$i + 1])) {
$tag = $argv[$i + 1];
}
if ($arg === '--token' && isset($argv[$i + 1])) {
$token = $argv[$i + 1];
}
if ($arg === '--api-base' && isset($argv[$i + 1])) {
$apiBase = $argv[$i + 1];
}
if ($arg === '--branch' && isset($argv[$i + 1])) {
$branch = $argv[$i + 1];
}
if ($arg === '--repo' && isset($argv[$i + 1])) {
$repoName = $argv[$i + 1];
}
if ($arg === '--prerelease') {
$prerelease = true;
}
}
// Allow token from environment
if ($token === null) {
$envToken = getenv('GA_TOKEN');
if ($envToken === false || $envToken === '') {
$envToken = getenv('GITEA_TOKEN');
}
if ($envToken !== false && $envToken !== '') {
$token = $envToken;
}
}
if ($version === null || $tag === null || $token === null || $apiBase === null) {
fwrite(STDERR, "Usage: release_create.php --version VER --tag TAG --token TOKEN --api-base URL [options]\n");
fwrite(STDERR, " --path . Repo root for manifest detection (default: .)\n");
fwrite(STDERR, " --branch main Target commitish (default: main)\n");
fwrite(STDERR, " --repo REPO Repo name for fallback element detection\n");
fwrite(STDERR, " --prerelease Mark release as prerelease\n");
fwrite(STDERR, " Token can also be set via GA_TOKEN or GITEA_TOKEN env var\n");
exit(1);
}
// ── Helper: Gitea API request ───────────────────────────────────────────────
/**
* Send a request to the Gitea API.
*
* @param string $url Full API URL
* @param string $token Authorization token
* @param string $method HTTP method (GET, POST, DELETE, etc.)
* @param string|null $body JSON request body
*
* @return array<string, mixed>|null Decoded response or null on failure
*/
function giteaApi(string $url, string $token, string $method = 'GET', ?string $body = null): ?array
{
$ch = curl_init($url);
if ($ch === false) {
return null;
}
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: token {$token}",
'Content-Type: application/json',
],
CURLOPT_TIMEOUT => 30,
CURLOPT_CUSTOMREQUEST => $method,
]);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300 || empty($response) || !is_string($response)) {
return null;
}
$decoded = json_decode($response, true);
return is_array($decoded) ? $decoded : null;
}
// ── Detect element metadata ─────────────────────────────────────────────────
$root = realpath($path) ?: $path;
$extElement = '';
$extType = '';
$extFolder = '';
$extName = '';
$typePrefix = '';
// Detect platform from manifest.xml
$platform = 'generic';
$manifestXml = "{$root}/.mokogitea/manifest.xml";
if (file_exists($manifestXml)) {
$content = file_get_contents($manifestXml);
if ($content !== false && preg_match('/<platform>([^<]+)<\/platform>/', $content, $pm)) {
$platform = trim($pm[1]);
}
}
// Find extension manifest (Joomla XML)
$extManifest = null;
$manifestFiles = array_merge(
glob("{$root}/src/pkg_*.xml") ?: [],
glob("{$root}/src/*.xml") ?: [],
glob("{$root}/*.xml") ?: []
);
foreach ($manifestFiles as $file) {
$c = file_get_contents($file);
if ($c !== false && strpos($c, '<extension') !== false) {
$extManifest = $file;
break;
}
}
// Find Dolibarr module file
$modFile = null;
$modFiles = array_merge(
glob("{$root}/src/core/modules/mod*.class.php") ?: [],
glob("{$root}/htdocs/core/modules/mod*.class.php") ?: [],
glob("{$root}/core/modules/mod*.class.php") ?: []
);
foreach ($modFiles as $file) {
$c = file_get_contents($file);
if ($c !== false && strpos($c, 'extends DolibarrModules') !== false) {
$modFile = $file;
break;
}
}
// Extract metadata based on platform
switch (true) {
case in_array($platform, ['joomla', 'waas-component'], true) && $extManifest !== null:
$xml = file_get_contents($extManifest);
if ($xml === false) {
break;
}
if (preg_match('/type="([^"]*)"/', $xml, $tm)) {
$extType = $tm[1];
}
if (preg_match('/group="([^"]*)"/', $xml, $gm)) {
$extFolder = $gm[1];
}
// Element name: <element>, plugin= attribute, <packagename>, or filename
if (preg_match('/<element>([^<]+)<\/element>/', $xml, $em)) {
$extElement = $em[1];
}
if (empty($extElement) && preg_match('/plugin="([^"]*)"/', $xml, $pm2)) {
$extElement = $pm2[1];
}
if ($extType === 'package' && preg_match('/<packagename>([^<]+)<\/packagename>/', $xml, $pn)) {
$extElement = $pn[1];
}
if (empty($extElement)) {
$extElement = strtolower(basename($extManifest, '.xml'));
if (in_array($extElement, ['templatedetails', 'manifest'], true)) {
$extElement = strtolower(str_replace([' ', '-'], '', $repoName !== '' ? $repoName : basename($root)));
}
}
// Human-readable name
if (preg_match('/<name>([^<]+)<\/name>/', $xml, $nm)) {
$extName = trim($nm[1]);
}
break;
case in_array($platform, ['dolibarr', 'crm-module'], true) && $modFile !== null:
$extType = 'dolibarr-module';
$modBasename = basename($modFile, '.class.php');
$extElement = strtolower(preg_replace('/^mod/', '', $modBasename) ?? $modBasename);
$modContent = file_get_contents($modFile);
if ($modContent !== false && preg_match('/\$this->name\s*=\s*[\'"]([^\'"]+)[\'"]/', $modContent, $nm2)) {
$extName = $nm2[1];
}
break;
default:
$extElement = strtolower(str_replace([' ', '-'], '', $repoName !== '' ? $repoName : basename($root)));
$extType = 'generic';
break;
}
// Strip existing type prefix from element to prevent duplication
$extElement = preg_replace('/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)/', '', $extElement) ?? $extElement;
// Compute type prefix
switch ($extType) {
case 'plugin':
$typePrefix = "plg_{$extFolder}_";
break;
case 'module':
$typePrefix = 'mod_';
break;
case 'component':
$typePrefix = 'com_';
break;
case 'template':
$typePrefix = 'tpl_';
break;
case 'library':
$typePrefix = 'lib_';
break;
case 'package':
$typePrefix = 'pkg_';
break;
}
// Fallback name
if (empty($extName)) {
$extName = $repoName !== '' ? $repoName : basename($root);
}
echo "Element: {$extElement}, Type: {$extType}, Prefix: {$typePrefix}, Name: {$extName}\n";
// ── Build release name ──────────────────────────────────────────────────────
$releaseName = "{$extName} {$version} ({$typePrefix}{$extElement}-{$version})";
echo "Release name: {$releaseName}\n";
// ── Generate release notes ──────────────────────────────────────────────────
$releaseNotes = "Release {$version}";
$releaseNotesScript = dirname(__DIR__) . '/cli/release_notes.php';
if (file_exists($releaseNotesScript)) {
$cmd = sprintf(
'php %s --path %s --version %s',
escapeshellarg($releaseNotesScript),
escapeshellarg($root),
escapeshellarg($version)
);
$output = [];
$exitCode = 0;
exec($cmd, $output, $exitCode);
if ($exitCode === 0 && count($output) > 0) {
$notes = implode("\n", $output);
if (trim($notes) !== '') {
$releaseNotes = $notes;
echo "Release notes: generated from CHANGELOG.md\n";
}
}
}
// ── Delete existing release at tag (if present) ─────────────────────────────
$existing = giteaApi("{$apiBase}/releases/tags/{$tag}", $token);
if ($existing !== null && !empty($existing['id'])) {
$existingId = $existing['id'];
echo "Deleting existing release: {$tag} (id: {$existingId})\n";
// Delete release
giteaApi("{$apiBase}/releases/{$existingId}", $token, 'DELETE');
// Delete tag
giteaApi("{$apiBase}/tags/{$tag}", $token, 'DELETE');
}
// ── Create new release ──────────────────────────────────────────────────────
$payload = json_encode([
'tag_name' => $tag,
'target_commitish' => $branch,
'name' => $releaseName,
'body' => $releaseNotes,
'prerelease' => $prerelease,
]);
$newRelease = giteaApi("{$apiBase}/releases", $token, 'POST', $payload !== false ? $payload : '{}');
if ($newRelease === null || empty($newRelease['id'])) {
fwrite(STDERR, "Failed to create release at tag: {$tag}\n");
exit(1);
}
$releaseId = $newRelease['id'];
echo "Created release: {$tag} (id: {$releaseId})\n";
// Output release_id to stdout for CI consumption
echo "release_id={$releaseId}\n";
exit(0);
+300
View File
@@ -0,0 +1,300 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: moko-platform.CLI
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/release_mirror.php
* BRIEF: Mirror a Gitea release (with assets) to a GitHub repository
*
* Usage:
* php release_mirror.php --version 09.01.00 --tag stable --token TOKEN --api-base URL \
* --gh-token GH_TOKEN --gh-repo MokoConsulting/MokoWaaS
*
* Mirrors a Gitea release (title, body, assets) to a corresponding GitHub release.
* If the GitHub release already exists at the same tag, its title is updated via PATCH.
* All assets from the Gitea release are downloaded and uploaded to the GitHub release.
*/
declare(strict_types=1);
// ── Argument parsing ─────────────────────────────────────────────────────────
$version = null;
$tag = null;
$token = null;
$apiBase = null;
$ghToken = null;
$ghRepo = null;
$branch = 'main';
foreach ($argv as $i => $arg) {
if ($arg === '--version' && isset($argv[$i + 1])) {
$version = $argv[$i + 1];
}
if ($arg === '--tag' && isset($argv[$i + 1])) {
$tag = $argv[$i + 1];
}
if ($arg === '--token' && isset($argv[$i + 1])) {
$token = $argv[$i + 1];
}
if ($arg === '--api-base' && isset($argv[$i + 1])) {
$apiBase = $argv[$i + 1];
}
if ($arg === '--gh-token' && isset($argv[$i + 1])) {
$ghToken = $argv[$i + 1];
}
if ($arg === '--gh-repo' && isset($argv[$i + 1])) {
$ghRepo = $argv[$i + 1];
}
if ($arg === '--branch' && isset($argv[$i + 1])) {
$branch = $argv[$i + 1];
}
}
// Allow tokens from environment
$token = $token ?: (getenv('GA_TOKEN') ?: (getenv('GITEA_TOKEN') ?: null));
$ghToken = $ghToken ?: (getenv('GH_TOKEN') ?: null);
if (
$version === null || $tag === null || $token === null || $apiBase === null
|| $ghToken === null || $ghRepo === null
) {
fwrite(STDERR, "Usage: release_mirror.php --version VER --tag TAG --token TOKEN " .
"--api-base URL --gh-token GH_TOKEN --gh-repo org/repo [--branch main]\n");
fwrite(STDERR, " --token: Gitea token (or GA_TOKEN / GITEA_TOKEN env)\n");
fwrite(STDERR, " --gh-token: GitHub token (or GH_TOKEN env)\n");
exit(1);
}
// ── Helper: Gitea API request ────────────────────────────────────────────────
/**
* Send a request to the Gitea API.
*
* @param string $url Full Gitea API URL
* @param string $token Gitea API token
* @param string $method HTTP method (GET, POST, PATCH, DELETE)
* @param string|null $body JSON request body or null
*
* @return array<string, mixed>|null Decoded response or null on failure
*/
function giteaApi(string $url, string $token, string $method = 'GET', ?string $body = null): ?array
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: token {$token}",
'Content-Type: application/json',
],
CURLOPT_TIMEOUT => 30,
CURLOPT_CUSTOMREQUEST => $method,
]);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300 || empty($response)) {
return null;
}
return json_decode($response, true) ?: null;
}
/**
* Download a file from Gitea to a local path.
*
* @param string $url Download URL
* @param string $token Gitea API token
* @param string $dest Local destination path
*
* @return bool True on success
*/
function giteaDownload(string $url, string $token, string $dest): bool
{
$ch = curl_init($url);
$fp = fopen($dest, 'wb');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
CURLOPT_FILE => $fp,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 120,
]);
curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
fclose($fp);
return $httpCode >= 200 && $httpCode < 300;
}
/**
* Send a request to the GitHub API.
*
* @param string $url Full GitHub API URL
* @param string $token GitHub personal access token
* @param string $method HTTP method (GET, POST, PATCH, DELETE)
* @param string|null $body JSON request body or null
*
* @return array<string, mixed>|null Decoded response or null on failure
*/
function githubApi(string $url, string $token, string $method = 'GET', ?string $body = null): ?array
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: token {$token}",
'Accept: application/vnd.github+json',
'User-Agent: moko-platform',
'Content-Type: application/json',
],
CURLOPT_TIMEOUT => 30,
CURLOPT_CUSTOMREQUEST => $method,
]);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300 || empty($response)) {
return null;
}
return json_decode($response, true) ?: null;
}
/**
* Upload a binary asset to a GitHub release.
*
* @param string $uploadUrl GitHub upload URL (uploads.github.com)
* @param string $token GitHub personal access token
* @param string $filePath Local file path to upload
* @param string $name Asset filename for GitHub
*
* @return int HTTP status code
*/
function githubUploadAsset(string $uploadUrl, string $token, string $filePath, string $name): int
{
$url = $uploadUrl . '?name=' . urlencode($name);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: token {$token}",
'Accept: application/vnd.github+json',
'User-Agent: moko-platform',
'Content-Type: application/octet-stream',
],
CURLOPT_POSTFIELDS => file_get_contents($filePath),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 120,
]);
curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $httpCode;
}
// ── Step 1: Get Gitea release by tag ─────────────────────────────────────────
echo "Fetching Gitea release: {$tag}\n";
$giteaRelease = giteaApi("{$apiBase}/releases/tags/{$tag}", $token);
if (!$giteaRelease || empty($giteaRelease['id'])) {
fwrite(STDERR, "No Gitea release found with tag: {$tag}\n");
exit(1);
}
$giteaId = $giteaRelease['id'];
$releaseName = $giteaRelease['name'] ?? "{$version}";
$releaseBody = $giteaRelease['body'] ?? '';
$assets = $giteaRelease['assets'] ?? [];
echo " Name: {$releaseName}\n";
echo " Assets: " . count($assets) . " file(s)\n";
// ── Step 2: Check / create GitHub release ────────────────────────────────────
$ghApiBase = "https://api.github.com/repos/{$ghRepo}";
$ghUploadBase = "https://uploads.github.com/repos/{$ghRepo}";
echo "Checking GitHub release: {$tag}\n";
$ghRelease = githubApi("{$ghApiBase}/releases/tags/{$tag}", $ghToken);
if ($ghRelease && !empty($ghRelease['id'])) {
// Update existing release title
$ghReleaseId = $ghRelease['id'];
echo " GitHub release exists (id: {$ghReleaseId}), updating title\n";
$patchPayload = json_encode([
'name' => $releaseName,
'body' => $releaseBody,
]);
githubApi("{$ghApiBase}/releases/{$ghReleaseId}", $ghToken, 'PATCH', $patchPayload);
} else {
// Create new release
echo " Creating GitHub release\n";
$createPayload = json_encode([
'tag_name' => $tag,
'target_commitish' => $branch,
'name' => $releaseName,
'body' => $releaseBody,
'draft' => false,
'prerelease' => ($tag !== 'stable'),
]);
$ghRelease = githubApi("{$ghApiBase}/releases", $ghToken, 'POST', $createPayload);
if (!$ghRelease || empty($ghRelease['id'])) {
fwrite(STDERR, "Failed to create GitHub release\n");
exit(1);
}
$ghReleaseId = $ghRelease['id'];
echo " Created GitHub release (id: {$ghReleaseId})\n";
}
// ── Step 3: Download assets from Gitea ───────────────────────────────────────
$tmpDir = sys_get_temp_dir() . '/moko-mirror-' . getmypid();
@mkdir($tmpDir, 0755, true);
$uploadUrl = "{$ghUploadBase}/releases/{$ghReleaseId}/assets";
foreach ($assets as $asset) {
$name = $asset['name'] ?? '';
$downloadUrl = $asset['browser_download_url'] ?? '';
if ($name === '' || $downloadUrl === '') {
continue;
}
$localPath = "{$tmpDir}/{$name}";
echo " Downloading: {$name}\n";
if (!giteaDownload($downloadUrl, $token, $localPath)) {
fwrite(STDERR, " Failed to download: {$name}\n");
continue;
}
// ── Step 4: Upload asset to GitHub ───────────────────────────────────────
echo " Uploading: {$name}\n";
$code = githubUploadAsset($uploadUrl, $ghToken, $localPath, $name);
$status = ($code >= 200 && $code < 300) ? 'OK' : "FAILED ({$code})";
echo " {$status}\n";
}
// ── Cleanup ──────────────────────────────────────────────────────────────────
array_map('unlink', glob("{$tmpDir}/*") ?: []);
@rmdir($tmpDir);
// ── Summary ──────────────────────────────────────────────────────────────────
echo "\nMirror complete: {$tag} -> github.com/{$ghRepo}\n";
echo " Version: {$version}\n";
echo " Assets: " . count($assets) . " file(s)\n";
exit(0);
+567
View File
@@ -0,0 +1,567 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: moko-platform.CLI
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/release_package.php
* BRIEF: Build packages (ZIP + tar.gz) with SHA-256 and upload to Gitea release
*
* Usage:
* php release_package.php --path . --version 09.01.00 --tag stable --token TOKEN --api-base URL
* php release_package.php --path . --version 09.01.00 --tag development --token TOKEN --api-base URL --repo myrepo
*
* Builds ZIP and tar.gz packages from src/ or htdocs/, computes SHA-256 checksums,
* creates .sha256 sidecar files, and uploads all assets to an existing Gitea release.
*
* For Joomla packages (type=package with packages/ subdir):
* - ZIPs each sub-extension directory
* - Copies top-level XML/PHP to package root before archiving
*
* For standard extensions:
* - Builds ZIP and tar.gz from source dir
* - Excludes: sftp-config*, .ftpignore, *.ppk, *.pem, *.key, .env*, *.local, .build-trigger
*/
declare(strict_types=1);
// ── Argument parsing ─────────────────────────────────────────────────────────
$path = '.';
$version = null;
$tag = null;
$token = null;
$apiBase = null;
$repoName = '';
$outputDir = sys_get_temp_dir();
foreach ($argv as $i => $arg) {
if ($arg === '--path' && isset($argv[$i + 1])) {
$path = $argv[$i + 1];
}
if ($arg === '--version' && isset($argv[$i + 1])) {
$version = $argv[$i + 1];
}
if ($arg === '--tag' && isset($argv[$i + 1])) {
$tag = $argv[$i + 1];
}
if ($arg === '--token' && isset($argv[$i + 1])) {
$token = $argv[$i + 1];
}
if ($arg === '--api-base' && isset($argv[$i + 1])) {
$apiBase = $argv[$i + 1];
}
if ($arg === '--repo' && isset($argv[$i + 1])) {
$repoName = $argv[$i + 1];
}
if ($arg === '--output' && isset($argv[$i + 1])) {
$outputDir = $argv[$i + 1];
}
}
// Allow token from environment
if ($token === null) {
$token = getenv('GA_TOKEN') ?: (getenv('GITEA_TOKEN') ?: null);
}
if ($version === null || $tag === null || $token === null || $apiBase === null) {
fwrite(STDERR, "Usage: release_package.php --path . --version VER --tag TAG --token TOKEN --api-base URL\n");
fwrite(STDERR, " --repo REPO Repo name for element detection fallback\n");
fwrite(STDERR, " --output DIR Output directory for built packages (default: sys_get_temp_dir())\n");
fwrite(STDERR, " Token can also be set via GA_TOKEN or GITEA_TOKEN env var\n");
exit(1);
}
$root = realpath($path) ?: $path;
// ── Helper: Gitea API request ────────────────────────────────────────────────
/**
* Perform a Gitea API request.
*
* @param string $url Full API URL
* @param string $token API token
* @param string $method HTTP method
* @param string|null $body Request body (JSON)
*
* @return array{data: array<string, mixed>|null, code: int}
*/
function giteaApiRequest(string $url, string $token, string $method = 'GET', ?string $body = null): array
{
$ch = curl_init($url);
if ($ch === false) {
return ['data' => null, 'code' => 0];
}
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: token {$token}",
'Content-Type: application/json',
],
CURLOPT_TIMEOUT => 30,
CURLOPT_CUSTOMREQUEST => $method,
]);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$response = curl_exec($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300 || !is_string($response) || $response === '') {
return ['data' => null, 'code' => $httpCode];
}
$decoded = json_decode($response, true);
return ['data' => is_array($decoded) ? $decoded : null, 'code' => $httpCode];
}
/**
* Upload a file as a release asset.
*
* @param string $url Upload endpoint URL
* @param string $token API token
* @param string $filePath Local file path
*
* @return int HTTP status code
*/
function giteaUploadAsset(string $url, string $token, string $filePath): int
{
$ch = curl_init($url);
if ($ch === false) {
return 0;
}
$fileContent = file_get_contents($filePath);
if ($fileContent === false) {
return 0;
}
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: token {$token}",
'Content-Type: application/octet-stream',
],
CURLOPT_POSTFIELDS => $fileContent,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 120,
]);
curl_exec($ch);
$httpCode = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
return $httpCode;
}
// ── Read platform from .mokogitea/manifest.xml ───────────────────────────────
$detectedPlatform = 'generic';
$detectedEntryPoint = '';
$mokoManifest = "{$root}/.mokogitea/manifest.xml";
if (file_exists($mokoManifest)) {
$mokoXml = @simplexml_load_file($mokoManifest);
if ($mokoXml !== false) {
$rawPlatform = (string)($mokoXml->governance->platform ?? '');
if ($rawPlatform !== '') {
$detectedPlatform = match ($rawPlatform) {
'waas-component' => 'joomla',
'crm-module' => 'dolibarr',
default => $rawPlatform,
};
}
$detectedEntryPoint = (string)($mokoXml->build->{"entry-point"} ?? '');
}
}
// ── Detect element metadata from manifest XML ────────────────────────────────
$extElement = '';
$extType = '';
$extFolder = '';
$typePrefix = '';
$manifestFiles = array_merge(
glob("{$root}/src/pkg_*.xml") ?: [],
glob("{$root}/src/*.xml") ?: [],
glob("{$root}/*.xml") ?: []
);
$extManifest = null;
foreach ($manifestFiles as $file) {
$content = file_get_contents($file);
if ($content !== false && strpos($content, '<extension') !== false) {
$extManifest = $file;
break;
}
}
if ($extManifest !== null) {
$xml = file_get_contents($extManifest);
if ($xml === false) {
$xml = '';
}
// Extension type and folder
if (preg_match('/type="([^"]*)"/', $xml, $tm)) {
$extType = $tm[1];
}
if (preg_match('/group="([^"]*)"/', $xml, $gm)) {
$extFolder = $gm[1];
}
// Element name: <element>, plugin= attribute, <packagename>, or filename
if (preg_match('/<element>([^<]+)<\/element>/', $xml, $em)) {
$extElement = $em[1];
}
if ($extElement === '' && preg_match('/plugin="([^"]*)"/', $xml, $pm)) {
$extElement = $pm[1];
}
// For packages: prefer <packagename> over filename
if ($extType === 'package' && preg_match('/<packagename>([^<]+)<\/packagename>/', $xml, $pn)) {
$extElement = $pn[1];
}
if ($extElement === '') {
$extElement = strtolower(basename($extManifest, '.xml'));
if (in_array($extElement, ['templatedetails', 'manifest'], true)) {
$extElement = strtolower(str_replace([' ', '-'], '', $repoName !== '' ? $repoName : basename($root)));
}
}
}
// Fallback to repo name
if ($extElement === '') {
$extElement = strtolower(str_replace([' ', '-'], '', $repoName !== '' ? $repoName : basename($root)));
}
// Strip existing type prefix to prevent duplication
$extElement = (string) preg_replace('/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)/', '', $extElement);
// Compute type prefix
switch ($extType) {
case 'plugin':
$typePrefix = "plg_{$extFolder}_";
break;
case 'module':
$typePrefix = 'mod_';
break;
case 'component':
$typePrefix = 'com_';
break;
case 'template':
$typePrefix = 'tpl_';
break;
case 'library':
$typePrefix = 'lib_';
break;
case 'package':
$typePrefix = 'pkg_';
break;
}
echo "Element: {$typePrefix}{$extElement}\n";
echo "Type: {$extType}\n";
// ── Compute filenames ────────────────────────────────────────────────────────
$baseName = "{$typePrefix}{$extElement}-{$version}";
$zipFile = "{$outputDir}/{$baseName}.zip";
$tarFile = "{$outputDir}/{$baseName}.tar.gz";
echo "ZIP: {$baseName}.zip\n";
echo "TAR: {$baseName}.tar.gz\n";
// ── Find source directory ────────────────────────────────────────────────────
$sourceDir = null;
// Use entry-point from manifest.xml if available
if ($detectedEntryPoint !== '') {
$entryDir = rtrim(dirname($detectedEntryPoint) === '.' ? $detectedEntryPoint : dirname($detectedEntryPoint), '/');
if (is_dir("{$root}/{$entryDir}")) {
$sourceDir = "{$root}/{$entryDir}";
}
}
// Fallback to common directories
if ($sourceDir === null && is_dir("{$root}/src")) {
$sourceDir = "{$root}/src";
} elseif ($sourceDir === null && is_dir("{$root}/htdocs")) {
$sourceDir = "{$root}/htdocs";
}
if ($sourceDir === null) {
echo "No src/ or htdocs/ directory found — skipping package build\n";
exit(0);
}
echo "Source: {$sourceDir}\n";
// ── File exclusion patterns ──────────────────────────────────────────────────
/** @var array<int, string> */
$excludePatterns = [
'sftp-config*',
'.ftpignore',
'*.ppk',
'*.pem',
'*.key',
'.env*',
'*.local',
'.build-trigger',
];
/**
* Check if a filename matches any exclusion pattern.
*
* @param string $filename Filename to check
* @param array<int,string> $patterns Glob patterns to exclude
*
* @return bool True if the file should be excluded
*/
function isExcluded(string $filename, array $patterns): bool
{
$basename = basename($filename);
foreach ($patterns as $pattern) {
if (fnmatch($pattern, $basename)) {
return true;
}
}
return false;
}
/**
* Recursively add files from a directory to a ZipArchive.
*
* @param ZipArchive $zip ZipArchive instance
* @param string $sourceDir Source directory path
* @param string $prefix Path prefix inside the archive
* @param array<int,string> $excludes Exclusion patterns
*/
function addDirToZip(ZipArchive $zip, string $sourceDir, string $prefix, array $excludes): void
{
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($sourceDir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($iterator as $file) {
if (!$file instanceof SplFileInfo || !$file->isFile()) {
continue;
}
$realPath = $file->getRealPath();
if ($realPath === false) {
continue;
}
if (isExcluded($file->getFilename(), $excludes)) {
continue;
}
$relativePath = substr($realPath, strlen($sourceDir) + 1);
// Normalise to forward slashes for ZIP compatibility
$relativePath = str_replace('\\', '/', $relativePath);
$archivePath = $prefix !== '' ? "{$prefix}/{$relativePath}" : $relativePath;
$zip->addFile($realPath, $archivePath);
}
}
// ── Build packages ───────────────────────────────────────────────────────────
$isJoomlaPackage = ($extType === 'package' && is_dir("{$sourceDir}/packages"));
if ($isJoomlaPackage) {
// ── Joomla package: ZIP each sub-extension, then combine ─────────────────
echo "Building Joomla package (sub-extensions)...\n";
$zip = new ZipArchive();
if ($zip->open($zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
fwrite(STDERR, "Failed to create ZIP: {$zipFile}\n");
exit(1);
}
// ZIP each sub-extension directory
$packageDirs = glob("{$sourceDir}/packages/*", GLOB_ONLYDIR) ?: [];
foreach ($packageDirs as $pkgDir) {
$subName = basename($pkgDir);
$subZipPath = "{$outputDir}/{$subName}.zip";
$subZip = new ZipArchive();
if ($subZip->open($subZipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
fwrite(STDERR, "Failed to create sub-package ZIP: {$subZipPath}\n");
continue;
}
addDirToZip($subZip, $pkgDir, '', $excludePatterns);
$subZip->close();
$zip->addFile($subZipPath, "packages/{$subName}.zip");
echo " Sub-package: {$subName}.zip\n";
}
// Copy top-level XML and PHP files into the package root
$topLevelFiles = array_merge(
glob("{$sourceDir}/*.xml") ?: [],
glob("{$sourceDir}/*.php") ?: []
);
foreach ($topLevelFiles as $tlFile) {
if (!isExcluded(basename($tlFile), $excludePatterns)) {
$zip->addFile($tlFile, basename($tlFile));
}
}
// Include top-level directories (e.g. language/) that aren't packages/
$topLevelDirs = glob("{$sourceDir}/*", GLOB_ONLYDIR) ?: [];
foreach ($topLevelDirs as $tlDir) {
$dirName = basename($tlDir);
if ($dirName === 'packages') {
continue;
}
addDirToZip($zip, $tlDir, $dirName, $excludePatterns);
echo " Included dir: {$dirName}/\n";
}
$zip->close();
echo "ZIP created: {$zipFile}\n";
} else {
// ── Standard extension: ZIP from source dir ──────────────────────────────
echo "Building standard extension ZIP...\n";
$zip = new ZipArchive();
if ($zip->open($zipFile, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
fwrite(STDERR, "Failed to create ZIP: {$zipFile}\n");
exit(1);
}
addDirToZip($zip, $sourceDir, '', $excludePatterns);
$zip->close();
echo "ZIP created: {$zipFile}\n";
}
// ── Build tar.gz ─────────────────────────────────────────────────────────────
$tarExcludeArgs = [];
foreach ($excludePatterns as $pattern) {
$tarExcludeArgs[] = '--exclude=' . escapeshellarg($pattern);
}
$tarCommand = sprintf(
'tar -czf %s -C %s %s .',
escapeshellarg($tarFile),
escapeshellarg($sourceDir),
implode(' ', $tarExcludeArgs)
);
$tarReturnCode = 0;
$tarOutputLines = [];
exec($tarCommand . ' 2>&1', $tarOutputLines, $tarReturnCode);
if (!file_exists($tarFile)) {
fwrite(STDERR, "Failed to create tar.gz: {$tarFile}\n");
if ($tarOutputLines !== []) {
fwrite(STDERR, implode("\n", $tarOutputLines) . "\n");
}
exit(1);
}
echo "TAR created: {$tarFile}\n";
// ── Compute SHA-256 checksums ────────────────────────────────────────────────
$zipHash = hash_file('sha256', $zipFile);
$tarHash = hash_file('sha256', $tarFile);
if ($zipHash === false || $tarHash === false) {
fwrite(STDERR, "Failed to compute SHA-256 checksums\n");
exit(1);
}
$zipSha = "{$zipFile}.sha256";
$tarSha = "{$tarFile}.sha256";
file_put_contents($zipSha, "{$zipHash} {$baseName}.zip\n");
file_put_contents($tarSha, "{$tarHash} {$baseName}.tar.gz\n");
echo "SHA-256 (ZIP): {$zipHash}\n";
echo "SHA-256 (TAR): {$tarHash}\n";
echo "sha256_zip={$zipHash}\n";
echo "zip_name={$baseName}.zip\n";
// Write to GITHUB_OUTPUT if available
$ghOutput = getenv('GITHUB_OUTPUT');
if ($ghOutput) {
file_put_contents($ghOutput, "sha256_zip={$zipHash}\nzip_name={$baseName}.zip\n", FILE_APPEND);
}
// ── Get release ID from tag ──────────────────────────────────────────────────
$result = giteaApiRequest("{$apiBase}/releases/tags/{$tag}", $token);
if ($result['data'] === null || !isset($result['data']['id'])) {
fwrite(STDERR, "No release found for tag: {$tag} (HTTP {$result['code']})\n");
exit(1);
}
$releaseId = (int) $result['data']['id'];
echo "Release ID: {$releaseId} (tag: {$tag})\n";
// ── Delete existing assets with same names ───────────────────────────────────
$assetsResult = giteaApiRequest("{$apiBase}/releases/{$releaseId}/assets", $token);
$existingAssets = $assetsResult['data'] ?? [];
$uploadNames = [
"{$baseName}.zip",
"{$baseName}.tar.gz",
"{$baseName}.zip.sha256",
"{$baseName}.tar.gz.sha256",
];
foreach ($existingAssets as $asset) {
if (!is_array($asset)) {
continue;
}
$assetName = $asset['name'] ?? '';
$assetId = $asset['id'] ?? 0;
if (in_array($assetName, $uploadNames, true) && $assetId > 0) {
giteaApiRequest("{$apiBase}/releases/{$releaseId}/assets/{$assetId}", $token, 'DELETE');
echo "Deleted existing asset: {$assetName}\n";
}
}
// ── Upload assets ────────────────────────────────────────────────────────────
$filesToUpload = [
"{$baseName}.zip" => $zipFile,
"{$baseName}.tar.gz" => $tarFile,
"{$baseName}.zip.sha256" => $zipSha,
"{$baseName}.tar.gz.sha256" => $tarSha,
];
$uploaded = 0;
foreach ($filesToUpload as $name => $localPath) {
if (!file_exists($localPath)) {
fwrite(STDERR, "File not found, skipping: {$localPath}\n");
continue;
}
$uploadUrl = "{$apiBase}/releases/{$releaseId}/assets?name=" . urlencode($name);
$httpCode = giteaUploadAsset($uploadUrl, $token, $localPath);
$status = ($httpCode >= 200 && $httpCode < 300) ? 'OK' : "FAILED ({$httpCode})";
echo "Upload: {$name}{$status}\n";
if ($httpCode >= 200 && $httpCode < 300) {
$uploaded++;
}
}
// ── Summary ──────────────────────────────────────────────────────────────────
echo "\n";
echo "Package build complete\n";
echo " Element: {$typePrefix}{$extElement}\n";
echo " Version: {$version}\n";
echo " Tag: {$tag}\n";
echo " Uploaded: {$uploaded}/" . count($filesToUpload) . " asset(s)\n";
exit($uploaded === count($filesToUpload) ? 0 : 1);
+316
View File
@@ -0,0 +1,316 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: moko-platform.CLI
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/release_promote.php
* BRIEF: Promote a Gitea release from one channel to another (rename release, tag, assets)
*
* Usage:
* php release_promote.php --from development --to release-candidate --token TOKEN --api-base URL
* php release_promote.php --from release-candidate --to stable --token TOKEN --api-base URL --path .
*
* When promoting to stable, --path detects extension type prefix for asset renaming.
* When --from is "auto", checks beta > alpha > development and uses the first found.
*/
declare(strict_types=1);
$from = null;
$to = null;
$token = null;
$apiBase = null;
$path = '.';
$branch = 'main';
foreach ($argv as $i => $arg) {
if ($arg === '--from' && isset($argv[$i + 1])) {
$from = $argv[$i + 1];
}
if ($arg === '--to' && isset($argv[$i + 1])) {
$to = $argv[$i + 1];
}
if ($arg === '--token' && isset($argv[$i + 1])) {
$token = $argv[$i + 1];
}
if ($arg === '--api-base' && isset($argv[$i + 1])) {
$apiBase = $argv[$i + 1];
}
if ($arg === '--path' && isset($argv[$i + 1])) {
$path = $argv[$i + 1];
}
if ($arg === '--branch' && isset($argv[$i + 1])) {
$branch = $argv[$i + 1];
}
}
$token = $token ?: (getenv('GA_TOKEN') ?: (getenv('GITEA_TOKEN') ?: null));
if ($to === null || $token === null || $apiBase === null) {
fwrite(STDERR, "Usage: release_promote.php --from <channel|auto> --to <channel> --token TOKEN --api-base URL [--path .]\n");
fwrite(STDERR, " --from auto: checks beta > alpha > development\n");
exit(1);
}
// ── Suffix maps ──────────────────────────────────────────────────────────────
$suffixMap = [
'development' => '-dev',
'alpha' => '-alpha',
'beta' => '-beta',
'release-candidate' => '-rc',
'stable' => '',
];
// ── Channel hierarchy (highest first) ────────────────────────────────────────
$channelOrder = ['beta', 'alpha', 'development'];
// ── Helper: Gitea API request ────────────────────────────────────────────────
/** @return array<string, mixed>|null */
function giteaApi(string $url, string $token, string $method = 'GET', ?string $body = null): ?array
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: token {$token}",
'Content-Type: application/json',
],
CURLOPT_TIMEOUT => 30,
CURLOPT_CUSTOMREQUEST => $method,
]);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300 || empty($response)) {
return null;
}
return json_decode($response, true) ?: null;
}
function giteaDownload(string $url, string $token, string $dest): bool
{
$ch = curl_init($url);
$fp = fopen($dest, 'wb');
curl_setopt_array($ch, [
CURLOPT_HTTPHEADER => ["Authorization: token {$token}"],
CURLOPT_FILE => $fp,
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_TIMEOUT => 120,
]);
curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
fclose($fp);
return $httpCode >= 200 && $httpCode < 300;
}
// ── Resolve --from auto ──────────────────────────────────────────────────────
if ($from === 'auto') {
foreach ($channelOrder as $candidate) {
$data = giteaApi("{$apiBase}/releases/tags/{$candidate}", $token);
if ($data && !empty($data['id'])) {
$from = $candidate;
echo "Auto-detected source channel: {$from}\n";
break;
}
}
if ($from === 'auto') {
echo "No pre-release found to promote\n";
exit(0);
}
}
// ── Find source release ──────────────────────────────────────────────────────
$sourceRelease = giteaApi("{$apiBase}/releases/tags/{$from}", $token);
if (!$sourceRelease || empty($sourceRelease['id'])) {
fwrite(STDERR, "No release found with tag: {$from}\n");
exit(1);
}
$sourceId = $sourceRelease['id'];
$sourceName = $sourceRelease['name'] ?? '';
$sourceBody = $sourceRelease['body'] ?? '';
echo "Source: {$from} (id: {$sourceId}) — {$sourceName}\n";
// ── Get source assets ────────────────────────────────────────────────────────
$assets = giteaApi("{$apiBase}/releases/{$sourceId}/assets", $token) ?: [];
echo "Assets: " . count($assets) . " file(s)\n";
// ── Download assets to temp ──────────────────────────────────────────────────
$tmpDir = sys_get_temp_dir() . '/moko-promote-' . getmypid();
@mkdir($tmpDir, 0755, true);
foreach ($assets as $asset) {
$name = $asset['name'];
$downloadUrl = $asset['browser_download_url'];
echo " Downloading: {$name}\n";
giteaDownload($downloadUrl, $token, "{$tmpDir}/{$name}");
}
// ── Detect type prefix for stable promotion ──────────────────────────────────
$typePrefix = '';
if ($to === 'stable') {
$root = realpath($path) ?: $path;
$manifestFiles = array_merge(
glob("{$root}/src/pkg_*.xml") ?: [],
glob("{$root}/src/*.xml") ?: [],
glob("{$root}/*.xml") ?: []
);
foreach ($manifestFiles as $xmlFile) {
$xmlContent = file_get_contents($xmlFile);
if (strpos($xmlContent, '<extension') === false) {
continue;
}
$extType = '';
$extFolder = '';
if (preg_match('/type="([^"]*)"/', $xmlContent, $tm)) {
$extType = $tm[1];
}
if (preg_match('/group="([^"]*)"/', $xmlContent, $gm)) {
$extFolder = $gm[1];
}
switch ($extType) {
case 'plugin':
$typePrefix = "plg_{$extFolder}_";
break;
case 'module':
$typePrefix = 'mod_';
break;
case 'component':
$typePrefix = 'com_';
break;
case 'template':
$typePrefix = 'tpl_';
break;
case 'library':
$typePrefix = 'lib_';
break;
case 'package':
$typePrefix = 'pkg_';
break;
}
if ($typePrefix !== '') {
break;
}
}
}
// ── Rename assets ────────────────────────────────────────────────────────────
$oldSuffix = $suffixMap[$from] ?? '';
$newSuffix = $suffixMap[$to] ?? '';
$renamedAssets = [];
foreach ($assets as $asset) {
$oldName = $asset['name'];
$newName = $oldName;
// Strip old suffix
if ($oldSuffix !== '') {
$newName = str_replace($oldSuffix, '', $newName);
}
// Add type prefix for stable (if not already prefixed)
if ($to === 'stable' && $typePrefix !== '' && strpos($newName, $typePrefix) !== 0) {
// Strip any existing type prefix to prevent duplication
$newName = preg_replace('/^(pkg_|com_|mod_|plg_[a-z]+_|tpl_|lib_)/', '', $newName);
$newName = $typePrefix . $newName;
}
// Add new suffix (for non-stable targets)
if ($newSuffix !== '' && strpos($newName, $newSuffix) === false) {
// Insert before extension
$newName = preg_replace('/(\.(zip|tar\.gz|sha256))$/', $newSuffix . '$1', $newName);
}
$renamedAssets[] = ['old' => $oldName, 'new' => $newName];
if ($oldName !== $newName) {
echo " Rename: {$oldName}{$newName}\n";
}
}
// ── Delete source release + tag ──────────────────────────────────────────────
giteaApi("{$apiBase}/releases/{$sourceId}", $token, 'DELETE');
giteaApi("{$apiBase}/tags/{$from}", $token, 'DELETE');
echo "Deleted source: {$from} release + tag\n";
// ── Delete existing target release + tag (if any) ────────────────────────────
$existingTarget = giteaApi("{$apiBase}/releases/tags/{$to}", $token);
if ($existingTarget && !empty($existingTarget['id'])) {
giteaApi("{$apiBase}/releases/{$existingTarget['id']}", $token, 'DELETE');
giteaApi("{$apiBase}/tags/{$to}", $token, 'DELETE');
echo "Deleted existing target: {$to} release + tag\n";
}
// ── Create target release ────────────────────────────────────────────────────
$isPrerelease = ($to !== 'stable');
$newName = preg_replace('/\(' . preg_quote($from, '/') . '\)/', "({$to})", $sourceName);
if ($newName === $sourceName) {
$newName = str_ireplace($from, $to, $sourceName);
}
$newBody = str_ireplace($from, $to, $sourceBody);
$payload = json_encode([
'tag_name' => $to,
'target_commitish' => $branch,
'name' => $newName,
'body' => $newBody,
'prerelease' => $isPrerelease,
]);
$newRelease = giteaApi("{$apiBase}/releases", $token, 'POST', $payload);
if (!$newRelease || empty($newRelease['id'])) {
fwrite(STDERR, "Failed to create {$to} release\n");
exit(1);
}
$newId = $newRelease['id'];
echo "Created: {$to} release (id: {$newId})\n";
// ── Upload renamed assets ────────────────────────────────────────────────────
foreach ($renamedAssets as $entry) {
$localFile = "{$tmpDir}/{$entry['old']}";
if (!file_exists($localFile)) {
continue;
}
$uploadName = urlencode($entry['new']);
$url = "{$apiBase}/releases/{$newId}/assets?name={$uploadName}";
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
"Authorization: token {$token}",
'Content-Type: application/octet-stream',
],
CURLOPT_POSTFIELDS => file_get_contents($localFile),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 120,
]);
curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$status = ($code >= 200 && $code < 300) ? 'OK' : "FAILED ({$code})";
echo " Upload: {$entry['new']}{$status}\n";
}
// ── Cleanup temp ─────────────────────────────────────────────────────────────
array_map('unlink', glob("{$tmpDir}/*") ?: []);
@rmdir($tmpDir);
echo "Promoted: {$from}{$to}\n";
exit(0);
+175 -96
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
@@ -26,153 +27,231 @@ declare(strict_types=1);
$path = '.';
$version = null;
$platform = 'joomla';
$platform = null;
$outputSummary = false;
$githubOutput = false;
foreach ($argv as $i => $arg) {
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
if ($arg === '--version' && isset($argv[$i + 1])) $version = $argv[$i + 1];
if ($arg === '--platform' && isset($argv[$i + 1])) $platform = $argv[$i + 1];
if ($arg === '--output-summary') $outputSummary = true;
if ($arg === '--path' && isset($argv[$i + 1])) {
$path = $argv[$i + 1];
}
if ($arg === '--version' && isset($argv[$i + 1])) {
$version = $argv[$i + 1];
}
if ($arg === '--platform' && isset($argv[$i + 1])) {
$platform = $argv[$i + 1];
}
if ($arg === '--output-summary') {
$outputSummary = true;
}
if ($arg === '--github-output') {
$githubOutput = true;
}
}
if ($version === null) {
fwrite(STDERR, "Usage: release_validate.php --path . --version XX.YY.ZZ [--platform joomla]\n");
exit(1);
fwrite(STDERR, "Usage: release_validate.php --path . --version XX.YY.ZZ [--platform joomla]\n");
exit(1);
}
$root = realpath($path) ?: $path;
// Auto-detect platform from manifest.xml if not specified
if ($platform === null) {
$manifestXml = "{$root}/.mokogitea/manifest.xml";
if (file_exists($manifestXml)) {
$mContent = file_get_contents($manifestXml);
if (preg_match('/<platform>([^<]+)<\/platform>/', $mContent, $pm)) {
$platform = trim($pm[1]);
}
}
// Normalize platform aliases
if (in_array($platform, ['waas-component'], true)) {
$platform = 'joomla';
}
if (in_array($platform, ['crm-module'], true)) {
$platform = 'dolibarr';
}
if ($platform === null) {
$platform = 'generic';
}
}
$pass = 0;
$fail = 0;
$warn = 0;
/** @var array<int, array{check: string, status: string, details: string}> */
$results = [];
function addResult(string $check, string $status, string $details): void {
global $pass, $fail, $warn, $results;
$results[] = ['check' => $check, 'status' => $status, 'details' => $details];
if ($status === 'PASS') $pass++;
elseif ($status === 'FAIL') $fail++;
elseif ($status === 'WARN') $warn++;
/**
* Record a validation result.
*
* @param string $check Check name
* @param string $status PASS, FAIL, or WARN
* @param string $details Human-readable details
*/
function addResult(string $check, string $status, string $details): void
{
global $pass, $fail, $warn, $results;
$results[] = ['check' => $check, 'status' => $status, 'details' => $details];
if ($status === 'PASS') {
$pass++;
} elseif ($status === 'FAIL') {
$fail++;
} elseif ($status === 'WARN') {
$warn++;
}
}
// 0. Source directory check
$hasSource = is_dir("{$root}/src") || is_dir("{$root}/htdocs");
if ($hasSource) {
addResult('Source directory', 'PASS', 'src/ or htdocs/ found');
} else {
addResult('Source directory', 'WARN', 'No src/ or htdocs/ directory');
}
// 1. README.md exists and contains VERSION
if (!file_exists("{$root}/README.md")) {
addResult('README.md', 'FAIL', 'Not found');
addResult('README.md', 'FAIL', 'Not found');
} else {
$readme = file_get_contents("{$root}/README.md");
if (preg_match('/VERSION:\s*' . preg_quote($version, '/') . '/', $readme) ||
strpos($readme, $version) !== false) {
addResult('README.md version', 'PASS', "`{$version}` found");
} else {
addResult('README.md version', 'FAIL', "`{$version}` not found in README.md");
}
$readme = file_get_contents("{$root}/README.md");
if (
preg_match('/VERSION:\s*' . preg_quote($version, '/') . '/', $readme) ||
strpos($readme, $version) !== false
) {
addResult('README.md version', 'PASS', "`{$version}` found");
} else {
addResult('README.md version', 'FAIL', "`{$version}` not found in README.md");
}
}
// 2. CHANGELOG.md exists with matching section
if (!file_exists("{$root}/CHANGELOG.md")) {
addResult('CHANGELOG.md', 'WARN', 'Not found');
addResult('CHANGELOG.md', 'WARN', 'Not found');
} else {
$cl = file_get_contents("{$root}/CHANGELOG.md");
if (preg_match('/^##\s.*' . preg_quote($version, '/') . '/m', $cl)) {
addResult('CHANGELOG.md version', 'PASS', "Section for `{$version}` found");
} else {
addResult('CHANGELOG.md version', 'WARN', "No section header for `{$version}`");
}
$cl = file_get_contents("{$root}/CHANGELOG.md");
if (preg_match('/^##\s.*' . preg_quote($version, '/') . '/m', $cl)) {
addResult('CHANGELOG.md version', 'PASS', "Section for `{$version}` found");
} else {
addResult('CHANGELOG.md version', 'WARN', "No section header for `{$version}`");
}
}
// 3. LICENSE file exists
$licenseFound = false;
foreach (['LICENSE', 'LICENSE.md', 'LICENSE.txt', 'COPYING'] as $lf) {
if (file_exists("{$root}/{$lf}")) { $licenseFound = true; break; }
if (file_exists("{$root}/{$lf}")) {
$licenseFound = true;
break;
}
}
addResult('LICENSE', $licenseFound ? 'PASS' : 'FAIL', $licenseFound ? 'Found' : 'Not found');
// 4. Platform-specific checks
if ($platform === 'joomla') {
// Find XML manifest
$manifest = null;
$searchDirs = ["{$root}/src", $root];
foreach ($searchDirs as $dir) {
if (!is_dir($dir)) continue;
foreach (glob("{$dir}/*.xml") as $xmlFile) {
$content = file_get_contents($xmlFile);
if (strpos($content, '<extension') !== false) {
$manifest = $xmlFile;
break 2;
}
}
}
if ($manifest === null) {
addResult('XML manifest', 'FAIL', 'No Joomla manifest found');
} else {
if (preg_match('/<version>([^<]+)<\/version>/', file_get_contents($manifest), $m)) {
$mVer = trim($m[1]);
if ($mVer === $version) {
addResult('Manifest version', 'PASS', "`{$mVer}` matches");
} else {
addResult('Manifest version', 'FAIL', "`{$mVer}` != `{$version}`");
}
} else {
addResult('Manifest version', 'FAIL', 'No <version> tag in manifest');
}
}
// Find XML manifest
$manifest = null;
$searchDirs = ["{$root}/src", $root];
foreach ($searchDirs as $dir) {
if (!is_dir($dir)) {
continue;
}
foreach (glob("{$dir}/*.xml") as $xmlFile) {
$content = file_get_contents($xmlFile);
if (strpos($content, '<extension') !== false) {
$manifest = $xmlFile;
break 2;
}
}
}
if ($manifest === null) {
addResult('XML manifest', 'FAIL', 'No Joomla manifest found');
} else {
if (preg_match('/<version>([^<]+)<\/version>/', file_get_contents($manifest), $m)) {
$mVer = trim($m[1]);
if ($mVer === $version) {
addResult('Manifest version', 'PASS', "`{$mVer}` matches");
} else {
addResult('Manifest version', 'FAIL', "`{$mVer}` != `{$version}`");
}
} else {
addResult('Manifest version', 'FAIL', 'No <version> tag in manifest');
}
}
// updates.xml
if (!file_exists("{$root}/updates.xml")) {
addResult('updates.xml', 'WARN', 'Not found');
} else {
$ux = file_get_contents("{$root}/updates.xml");
if (preg_match('/<version>' . preg_quote($version, '/') . '<\/version>/', $ux)) {
addResult('updates.xml version', 'PASS', "`{$version}` found");
} else {
addResult('updates.xml version', 'FAIL', "`{$version}` not in updates.xml");
}
}
// updates.xml
if (!file_exists("{$root}/updates.xml")) {
addResult('updates.xml', 'WARN', 'Not found');
} else {
$ux = file_get_contents("{$root}/updates.xml");
if (preg_match('/<version>' . preg_quote($version, '/') . '<\/version>/', $ux)) {
addResult('updates.xml version', 'PASS', "`{$version}` found");
} else {
addResult('updates.xml version', 'FAIL', "`{$version}` not in updates.xml");
}
}
} elseif ($platform === 'dolibarr') {
$modFile = null;
foreach (['src', 'htdocs'] as $sd) {
$pattern = "{$root}/{$sd}/mod*.class.php";
$matches = glob($pattern);
if (!empty($matches)) { $modFile = $matches[0]; break; }
}
if ($modFile === null) {
addResult('Dolibarr mod file', 'FAIL', 'No mod*.class.php found');
} else {
$mc = file_get_contents($modFile);
if (preg_match("/\\\$this->version\s*=\s*'" . preg_quote($version, '/') . "'/", $mc)) {
addResult('Dolibarr version', 'PASS', "`{$version}` matches");
} else {
addResult('Dolibarr version', 'FAIL', "`{$version}` not found in " . basename($modFile));
}
}
$modFile = null;
foreach (['src', 'htdocs'] as $sd) {
$pattern = "{$root}/{$sd}/mod*.class.php";
$matches = glob($pattern);
if (!empty($matches)) {
$modFile = $matches[0];
break;
}
}
if ($modFile === null) {
addResult('Dolibarr mod file', 'FAIL', 'No mod*.class.php found');
} else {
$mc = file_get_contents($modFile);
if (preg_match("/\\\$this->version\s*=\s*'" . preg_quote($version, '/') . "'/", $mc)) {
addResult('Dolibarr version', 'PASS', "`{$version}` matches");
} else {
addResult('Dolibarr version', 'FAIL', "`{$version}` not found in " . basename($modFile));
}
}
}
// 5. composer.json version (if present)
if (file_exists("{$root}/composer.json")) {
$composer = json_decode(file_get_contents("{$root}/composer.json"), true);
if (isset($composer['version'])) {
if ($composer['version'] === $version) {
addResult('composer.json version', 'PASS', "`{$version}` matches");
} else {
addResult('composer.json version', 'WARN', "`{$composer['version']}` != `{$version}`");
}
}
$composer = json_decode(file_get_contents("{$root}/composer.json"), true);
if (isset($composer['version'])) {
if ($composer['version'] === $version) {
addResult('composer.json version', 'PASS', "`{$version}` matches");
} else {
addResult('composer.json version', 'WARN', "`{$composer['version']}` != `{$version}`");
}
}
}
// Output
$table = "| Check | Result | Details |\n|-------|--------|--------|\n";
foreach ($results as $r) {
$table .= "| {$r['check']} | {$r['status']} | {$r['details']} |\n";
$table .= "| {$r['check']} | {$r['status']} | {$r['details']} |\n";
}
$table .= "\n**Validation: {$pass} passed, {$fail} failed, {$warn} warnings**\n";
echo $table;
if ($outputSummary) {
$summaryFile = getenv('GITHUB_STEP_SUMMARY');
if ($summaryFile) {
file_put_contents($summaryFile, "### Pre-Release Validation\n\n{$table}\n", FILE_APPEND);
}
$summaryFile = getenv('GITHUB_STEP_SUMMARY');
if ($summaryFile) {
file_put_contents($summaryFile, "## Pre-Release Sanity Checks ({$platform})\n\n{$table}\n", FILE_APPEND);
}
}
if ($githubOutput) {
$ghOutput = getenv('GITHUB_OUTPUT');
$lines = [
"validation_pass={$pass}",
"validation_fail={$fail}",
"validation_warn={$warn}",
"validation_platform={$platform}",
];
if ($ghOutput) {
file_put_contents($ghOutput, implode("\n", $lines) . "\n", FILE_APPEND);
}
}
exit($fail > 0 ? 1 : 0);
+209 -209
View File
@@ -1,209 +1,209 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: moko-platform.CLI
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/theme_lint.php
* BRIEF: Lint theme files — CSS syntax, image sizes, hardcoded URLs
*
* Usage:
* php theme_lint.php --path /repo
* php theme_lint.php --path /repo --max-image-kb 500
* php theme_lint.php --path /repo --github-output
*
* Options:
* --path Repository root (default: .)
* --max-image-kb Maximum image file size in KB (default: 500)
* --github-output Export results to $GITHUB_OUTPUT
* --strict Exit 1 on any warning (default: only on errors)
*/
declare(strict_types=1);
$path = '.';
$maxImageKb = 500;
$ghOutput = false;
$strict = false;
foreach ($argv as $i => $arg) {
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
if ($arg === '--max-image-kb' && isset($argv[$i + 1])) $maxImageKb = (int)$argv[$i + 1];
if ($arg === '--github-output') $ghOutput = true;
if ($arg === '--strict') $strict = true;
}
$root = realpath($path) ?: $path;
$errors = 0;
$warnings = 0;
// ── Find source directory ───────────────────────────────────────────────
$srcDir = null;
foreach (['src', 'htdocs'] as $d) {
if (is_dir("{$root}/{$d}")) { $srcDir = "{$root}/{$d}"; break; }
}
if ($srcDir === null) {
fwrite(STDERR, "No src/ or htdocs/ directory in {$root}\n");
exit(1);
}
echo "Theme Lint: {$srcDir}\n\n";
// ── Check 1: CSS syntax validation ──────────────────────────────────────
echo "--- CSS Syntax ---\n";
$cssFiles = findFiles($srcDir, '*.css');
$cssMinFiles = findFiles($srcDir, '*.min.css');
$cssToCheck = array_diff($cssFiles, $cssMinFiles);
if (empty($cssToCheck)) {
echo " No CSS files to check\n";
} else {
foreach ($cssToCheck as $file) {
$content = file_get_contents($file);
$relPath = str_replace($root . '/', '', $file);
// Check for unmatched braces
$openBraces = substr_count($content, '{');
$closeBraces = substr_count($content, '}');
if ($openBraces !== $closeBraces) {
echo " ERROR: {$relPath}: unmatched braces (open={$openBraces}, close={$closeBraces})\n";
$errors++;
}
// Check for empty rules
if (preg_match_all('/\{[\s]*\}/', $content, $m)) {
$count = count($m[0]);
echo " WARN: {$relPath}: {$count} empty rule(s)\n";
$warnings++;
}
// Check for !important abuse (more than 10 in one file)
$importantCount = substr_count($content, '!important');
if ($importantCount > 10) {
echo " WARN: {$relPath}: {$importantCount} !important declarations (consider refactoring)\n";
$warnings++;
}
}
if ($errors === 0) {
echo " OK: " . count($cssToCheck) . " CSS file(s) checked\n";
}
}
// ── Check 2: Image file sizes ───────────────────────────────────────────
echo "\n--- Image Sizes (max {$maxImageKb}KB) ---\n";
$imageExts = ['*.jpg', '*.jpeg', '*.png', '*.gif', '*.webp', '*.svg', '*.bmp'];
$images = [];
foreach ($imageExts as $ext) {
$images = array_merge($images, findFiles($srcDir, $ext));
}
// Also check root images/ directory
if (is_dir("{$root}/images")) {
foreach ($imageExts as $ext) {
$images = array_merge($images, findFiles("{$root}/images", $ext));
}
}
$oversized = 0;
$totalSize = 0;
foreach ($images as $file) {
$size = filesize($file);
$totalSize += $size;
$relPath = str_replace($root . '/', '', $file);
$sizeKb = round($size / 1024);
if ($sizeKb > $maxImageKb) {
echo " WARN: {$relPath}: {$sizeKb}KB (exceeds {$maxImageKb}KB limit)\n";
$oversized++;
$warnings++;
}
}
$totalMb = round($totalSize / 1024 / 1024, 1);
echo " " . count($images) . " image(s), {$totalMb}MB total";
if ($oversized > 0) {
echo ", {$oversized} oversized";
}
echo "\n";
// ── Check 3: Hardcoded URLs in CSS/JS ───────────────────────────────────
echo "\n--- Hardcoded URLs ---\n";
$codeFiles = array_merge(
findFiles($srcDir, '*.css'),
findFiles($srcDir, '*.js')
);
// Exclude minified files
$codeFiles = array_filter($codeFiles, function($f) {
return !preg_match('/\.min\.(css|js)$/', $f);
});
$urlPatterns = [
'/https?:\/\/clarksvillefurs\.com/' => 'hardcoded production URL',
'/https?:\/\/[a-z]+\.dev\.mokoconsulting\.tech/' => 'hardcoded dev URL',
'/https?:\/\/localhost/' => 'localhost reference',
];
$urlIssues = 0;
foreach ($codeFiles as $file) {
$content = file_get_contents($file);
$relPath = str_replace($root . '/', '', $file);
foreach ($urlPatterns as $pattern => $desc) {
if (preg_match_all($pattern, $content, $matches)) {
$count = count($matches[0]);
echo " WARN: {$relPath}: {$count} {$desc}\n";
$urlIssues++;
$warnings++;
}
}
}
if ($urlIssues === 0) {
echo " OK: No hardcoded URLs found\n";
}
// ── Summary ─────────────────────────────────────────────────────────────
echo "\n=== Summary ===\n";
echo "Errors: {$errors}\n";
echo "Warnings: {$warnings}\n";
if ($ghOutput) {
$ghFile = getenv('GITHUB_OUTPUT');
if ($ghFile) {
file_put_contents($ghFile, "lint_errors={$errors}\n", FILE_APPEND);
file_put_contents($ghFile, "lint_warnings={$warnings}\n", FILE_APPEND);
file_put_contents($ghFile, "lint_images=" . count($images) . "\n", FILE_APPEND);
file_put_contents($ghFile, "lint_css=" . count($cssToCheck) . "\n", FILE_APPEND);
}
}
if ($errors > 0) {
exit(1);
}
if ($strict && $warnings > 0) {
exit(1);
}
exit(0);
// ── Helper: recursively find files matching a glob pattern ──────────────
function findFiles(string $dir, string $pattern): array
{
$results = [];
if (!is_dir($dir)) return $results;
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if (fnmatch($pattern, $file->getFilename())) {
$results[] = $file->getPathname();
}
}
return $results;
}
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: moko-platform.CLI
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/theme_lint.php
* BRIEF: Lint theme files — CSS syntax, image sizes, hardcoded URLs
*
* Usage:
* php theme_lint.php --path /repo
* php theme_lint.php --path /repo --max-image-kb 500
* php theme_lint.php --path /repo --github-output
*
* Options:
* --path Repository root (default: .)
* --max-image-kb Maximum image file size in KB (default: 500)
* --github-output Export results to $GITHUB_OUTPUT
* --strict Exit 1 on any warning (default: only on errors)
*/
declare(strict_types=1);
$path = '.';
$maxImageKb = 500;
$ghOutput = false;
$strict = false;
foreach ($argv as $i => $arg) {
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
if ($arg === '--max-image-kb' && isset($argv[$i + 1])) $maxImageKb = (int)$argv[$i + 1];
if ($arg === '--github-output') $ghOutput = true;
if ($arg === '--strict') $strict = true;
}
$root = realpath($path) ?: $path;
$errors = 0;
$warnings = 0;
// ── Find source directory ───────────────────────────────────────────────
$srcDir = null;
foreach (['src', 'htdocs'] as $d) {
if (is_dir("{$root}/{$d}")) { $srcDir = "{$root}/{$d}"; break; }
}
if ($srcDir === null) {
fwrite(STDERR, "No src/ or htdocs/ directory in {$root}\n");
exit(1);
}
echo "Theme Lint: {$srcDir}\n\n";
// ── Check 1: CSS syntax validation ──────────────────────────────────────
echo "--- CSS Syntax ---\n";
$cssFiles = findFiles($srcDir, '*.css');
$cssMinFiles = findFiles($srcDir, '*.min.css');
$cssToCheck = array_diff($cssFiles, $cssMinFiles);
if (empty($cssToCheck)) {
echo " No CSS files to check\n";
} else {
foreach ($cssToCheck as $file) {
$content = file_get_contents($file);
$relPath = str_replace($root . '/', '', $file);
// Check for unmatched braces
$openBraces = substr_count($content, '{');
$closeBraces = substr_count($content, '}');
if ($openBraces !== $closeBraces) {
echo " ERROR: {$relPath}: unmatched braces (open={$openBraces}, close={$closeBraces})\n";
$errors++;
}
// Check for empty rules
if (preg_match_all('/\{[\s]*\}/', $content, $m)) {
$count = count($m[0]);
echo " WARN: {$relPath}: {$count} empty rule(s)\n";
$warnings++;
}
// Check for !important abuse (more than 10 in one file)
$importantCount = substr_count($content, '!important');
if ($importantCount > 10) {
echo " WARN: {$relPath}: {$importantCount} !important declarations (consider refactoring)\n";
$warnings++;
}
}
if ($errors === 0) {
echo " OK: " . count($cssToCheck) . " CSS file(s) checked\n";
}
}
// ── Check 2: Image file sizes ───────────────────────────────────────────
echo "\n--- Image Sizes (max {$maxImageKb}KB) ---\n";
$imageExts = ['*.jpg', '*.jpeg', '*.png', '*.gif', '*.webp', '*.svg', '*.bmp'];
$images = [];
foreach ($imageExts as $ext) {
$images = array_merge($images, findFiles($srcDir, $ext));
}
// Also check root images/ directory
if (is_dir("{$root}/images")) {
foreach ($imageExts as $ext) {
$images = array_merge($images, findFiles("{$root}/images", $ext));
}
}
$oversized = 0;
$totalSize = 0;
foreach ($images as $file) {
$size = filesize($file);
$totalSize += $size;
$relPath = str_replace($root . '/', '', $file);
$sizeKb = round($size / 1024);
if ($sizeKb > $maxImageKb) {
echo " WARN: {$relPath}: {$sizeKb}KB (exceeds {$maxImageKb}KB limit)\n";
$oversized++;
$warnings++;
}
}
$totalMb = round($totalSize / 1024 / 1024, 1);
echo " " . count($images) . " image(s), {$totalMb}MB total";
if ($oversized > 0) {
echo ", {$oversized} oversized";
}
echo "\n";
// ── Check 3: Hardcoded URLs in CSS/JS ───────────────────────────────────
echo "\n--- Hardcoded URLs ---\n";
$codeFiles = array_merge(
findFiles($srcDir, '*.css'),
findFiles($srcDir, '*.js')
);
// Exclude minified files
$codeFiles = array_filter($codeFiles, function($f) {
return !preg_match('/\.min\.(css|js)$/', $f);
});
$urlPatterns = [
'/https?:\/\/clarksvillefurs\.com/' => 'hardcoded production URL',
'/https?:\/\/[a-z]+\.dev\.mokoconsulting\.tech/' => 'hardcoded dev URL',
'/https?:\/\/localhost/' => 'localhost reference',
];
$urlIssues = 0;
foreach ($codeFiles as $file) {
$content = file_get_contents($file);
$relPath = str_replace($root . '/', '', $file);
foreach ($urlPatterns as $pattern => $desc) {
if (preg_match_all($pattern, $content, $matches)) {
$count = count($matches[0]);
echo " WARN: {$relPath}: {$count} {$desc}\n";
$urlIssues++;
$warnings++;
}
}
}
if ($urlIssues === 0) {
echo " OK: No hardcoded URLs found\n";
}
// ── Summary ─────────────────────────────────────────────────────────────
echo "\n=== Summary ===\n";
echo "Errors: {$errors}\n";
echo "Warnings: {$warnings}\n";
if ($ghOutput) {
$ghFile = getenv('GITHUB_OUTPUT');
if ($ghFile) {
file_put_contents($ghFile, "lint_errors={$errors}\n", FILE_APPEND);
file_put_contents($ghFile, "lint_warnings={$warnings}\n", FILE_APPEND);
file_put_contents($ghFile, "lint_images=" . count($images) . "\n", FILE_APPEND);
file_put_contents($ghFile, "lint_css=" . count($cssToCheck) . "\n", FILE_APPEND);
}
}
if ($errors > 0) {
exit(1);
}
if ($strict && $warnings > 0) {
exit(1);
}
exit(0);
// ── Helper: recursively find files matching a glob pattern ──────────────
function findFiles(string $dir, string $pattern): array
{
$results = [];
if (!is_dir($dir)) return $results;
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if (fnmatch($pattern, $file->getFilename())) {
$results[] = $file->getPathname();
}
}
return $results;
}
+307 -208
View File
@@ -1,5 +1,6 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
@@ -42,310 +43,408 @@ $outputFile = null;
$githubOutput = false;
foreach ($argv as $i => $arg) {
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
if ($arg === '--version' && isset($argv[$i + 1])) $version = $argv[$i + 1];
if ($arg === '--stability' && isset($argv[$i + 1])) $stability = $argv[$i + 1];
if ($arg === '--sha' && isset($argv[$i + 1])) $sha = $argv[$i + 1];
if ($arg === '--gitea-url' && isset($argv[$i + 1])) $giteaUrl = $argv[$i + 1];
if ($arg === '--org' && isset($argv[$i + 1])) $org = $argv[$i + 1];
if ($arg === '--repo' && isset($argv[$i + 1])) $repo = $argv[$i + 1];
if ($arg === '--output' && isset($argv[$i + 1])) $outputFile = $argv[$i + 1];
if ($arg === '--github-output') $githubOutput = true;
if ($arg === '--path' && isset($argv[$i + 1])) {
$path = $argv[$i + 1];
}
if ($arg === '--version' && isset($argv[$i + 1])) {
$version = $argv[$i + 1];
}
if ($arg === '--stability' && isset($argv[$i + 1])) {
$stability = $argv[$i + 1];
}
if ($arg === '--sha' && isset($argv[$i + 1])) {
$sha = $argv[$i + 1];
}
if ($arg === '--gitea-url' && isset($argv[$i + 1])) {
$giteaUrl = $argv[$i + 1];
}
if ($arg === '--org' && isset($argv[$i + 1])) {
$org = $argv[$i + 1];
}
if ($arg === '--repo' && isset($argv[$i + 1])) {
$repo = $argv[$i + 1];
}
if ($arg === '--output' && isset($argv[$i + 1])) {
$outputFile = $argv[$i + 1];
}
if ($arg === '--github-output') {
$githubOutput = true;
}
}
if ($version === null) {
fwrite(STDERR, "Usage: updates_xml_build.php --path . --version XX.YY.ZZ [--stability stable] [--sha SHA]\n");
exit(1);
fwrite(STDERR, "Usage: updates_xml_build.php --path . --version XX.YY.ZZ [--stability stable] [--sha SHA]\n");
exit(1);
}
$root = realpath($path) ?: $path;
// -- Read platform from .mokogitea/manifest.xml --------------------------------
$detectedPlatform = 'joomla'; // default for backward compat
$detectedName = $repo;
$detectedPackageType = '';
$mokoManifest = "{$root}/.mokogitea/manifest.xml";
if (file_exists($mokoManifest)) {
$mokoXml = @simplexml_load_file($mokoManifest);
if ($mokoXml !== false) {
$rawPlatform = (string)($mokoXml->governance->platform ?? '');
if ($rawPlatform !== '') {
$detectedPlatform = match ($rawPlatform) {
'waas-component' => 'joomla',
'crm-module' => 'dolibarr',
default => $rawPlatform,
};
}
$detectedName = (string)($mokoXml->identity->name ?? $repo);
$detectedPackageType = (string)($mokoXml->build->{"package-type"} ?? '');
}
}
// -- Locate Joomla manifest ---------------------------------------------------
$manifest = null;
// Priority: pkg_*.xml in src/ > any extension XML in src/ > any in root
$candidates = glob("{$root}/src/pkg_*.xml") ?: [];
foreach ($candidates as $f) {
if (strpos(file_get_contents($f), '<extension') !== false) {
$manifest = $f;
break;
}
if (strpos(file_get_contents($f), '<extension') !== false) {
$manifest = $f;
break;
}
}
if ($manifest === null) {
$searchDirs = ["{$root}/src", "{$root}"];
foreach ($searchDirs as $dir) {
if (!is_dir($dir)) continue;
foreach (glob("{$dir}/*.xml") ?: [] as $f) {
if (strpos(file_get_contents($f), '<extension') !== false) {
$manifest = $f;
break 2;
}
}
}
$searchDirs = ["{$root}/src", "{$root}"];
foreach ($searchDirs as $dir) {
if (!is_dir($dir)) {
continue;
}
foreach (glob("{$dir}/*.xml") ?: [] as $f) {
if (strpos(file_get_contents($f), '<extension') !== false) {
$manifest = $f;
break 2;
}
}
}
}
if ($manifest === null) {
fwrite(STDERR, "No Joomla XML manifest found in {$root}\n");
exit(1);
if ($manifest === null && $detectedPlatform === 'joomla') {
fwrite(STDERR, "No Joomla XML manifest found in {$root}\n");
exit(1);
}
// -- Parse extension metadata -------------------------------------------------
$xml = file_get_contents($manifest);
// Extract fields via regex (more portable than SimpleXML for malformed manifests)
$extName = '';
if (preg_match('/<name>([^<]+)<\/name>/', $xml, $m)) $extName = $m[1];
$extType = '';
if (preg_match('/<extension[^>]*type="([^"]+)"/', $xml, $m)) $extType = $m[1];
$extElement = '';
if (preg_match('/<element>([^<]+)<\/element>/', $xml, $m)) $extElement = $m[1];
// For packages, prefer <packagename> to avoid pkg_pkg_ duplication
if (empty($extElement) && preg_match('/<packagename>([^<]+)<\/packagename>/', $xml, $m)) $extElement = $m[1];
if (empty($extElement) && preg_match('/plugin="([^"]+)"/', $xml, $m)) $extElement = $m[1];
if (empty($extElement) && preg_match('/module="([^"]+)"/', $xml, $m)) $extElement = $m[1];
if (empty($extElement)) {
$fname = strtolower(pathinfo($manifest, PATHINFO_FILENAME));
if (in_array($fname, ['templatedetails', 'manifest'])) {
$extElement = strtolower(str_replace([' ', '-'], '', $repo ?: basename($root)));
} else {
$extElement = $fname;
}
}
// Strip existing type prefix to prevent duplication (e.g. pkg_mokowaas → mokowaas)
$extElement = preg_replace('/^(pkg_|com_|mod_|plg_\w+_|tpl_|lib_)/', '', $extElement);
$extClient = '';
if (preg_match('/<extension[^>]*client="([^"]+)"/', $xml, $m)) $extClient = $m[1];
$extFolder = '';
if (preg_match('/<extension[^>]*group="([^"]+)"/', $xml, $m)) $extFolder = $m[1];
$targetPlatform = '';
if (preg_match('/(<targetplatform[^\/]*\/>)/', $xml, $m)) $targetPlatform = $m[1];
if (empty($targetPlatform)) {
$targetPlatform = '<targetplatform name="joomla" version="(5|6)\..*" />';
}
$phpMinimum = '';
if (preg_match('/<php_minimum>([^<]+)<\/php_minimum>/', $xml, $m)) $phpMinimum = $m[1];
if ($manifest !== null) {
// Joomla manifest found — parse extension metadata from it
$xml = file_get_contents($manifest);
if (preg_match('/<name>([^<]+)<\/name>/', $xml, $m)) {
$extName = $m[1];
}
if (preg_match('/<extension[^>]*type="([^"]+)"/', $xml, $m)) {
$extType = $m[1];
}
if (preg_match('/<element>([^<]+)<\/element>/', $xml, $m)) {
$extElement = $m[1];
}
if (empty($extElement) && preg_match('/<packagename>([^<]+)<\/packagename>/', $xml, $m)) {
$extElement = $m[1];
}
if (empty($extElement) && preg_match('/plugin="([^"]+)"/', $xml, $m)) {
$extElement = $m[1];
}
if (empty($extElement) && preg_match('/module="([^"]+)"/', $xml, $m)) {
$extElement = $m[1];
}
if (empty($extElement)) {
$fname = strtolower(pathinfo($manifest, PATHINFO_FILENAME));
if (in_array($fname, ['templatedetails', 'manifest'])) {
$extElement = strtolower(str_replace([' ', '-'], '', $repo ?: basename($root)));
} else {
$extElement = $fname;
}
}
$extElement = preg_replace('/^(pkg_|com_|mod_|plg_\w+_|tpl_|lib_)/', '', $extElement);
if (preg_match('/<extension[^>]*client="([^"]+)"/', $xml, $m)) {
$extClient = $m[1];
}
if (preg_match('/<extension[^>]*group="([^"]+)"/', $xml, $m)) {
$extFolder = $m[1];
}
if (preg_match('/(<targetplatform[^\/]*\/>)/', $xml, $m)) {
$targetPlatform = $m[1];
}
if (empty($targetPlatform)) {
$targetPlatform = '<targetplatform name="joomla" version="(5|6)\..*" />';
}
if (preg_match('/<php_minimum>([^<]+)<\/php_minimum>/', $xml, $m)) {
$phpMinimum = $m[1];
}
} else {
// Non-Joomla platform — derive metadata from .mokogitea/manifest.xml
$extName = $detectedName ?: ($repo ?: basename($root));
$extElement = strtolower(str_replace([' ', '-'], '', $extName));
$extType = $detectedPackageType ?: 'generic';
$targetPlatform = "<targetplatform name=\"{$detectedPlatform}\" version=\".*\" />";
}
// Resolve language key names (e.g. PLG_SYSTEM_MOKOJOOMTOS)
if (preg_match('/^[A-Z_]+$/', $extName)) {
$iniFiles = [];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($root, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if (preg_match('/\.sys\.ini$/i', $file->getFilename())) {
$iniFiles[] = $file->getPathname();
}
}
foreach ($iniFiles as $ini) {
$content = file_get_contents($ini);
if (preg_match('/^' . preg_quote($extName, '/') . '="([^"]+)"/m', $content, $m)) {
$extName = $m[1];
break;
}
}
$iniFiles = [];
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($root, RecursiveDirectoryIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if (preg_match('/\.sys\.ini$/i', $file->getFilename())) {
$iniFiles[] = $file->getPathname();
}
}
foreach ($iniFiles as $ini) {
$content = file_get_contents($ini);
if (preg_match('/^' . preg_quote($extName, '/') . '="([^"]+)"/m', $content, $m)) {
$extName = $m[1];
break;
}
}
}
// Fallbacks
if (empty($extName)) $extName = $repo ?: basename($root);
if (empty($extType)) $extType = 'component';
if (empty($extName)) {
$extName = $repo ?: basename($root);
}
if (empty($extType)) {
$extType = 'component';
}
// -- Build type prefix --------------------------------------------------------
$typePrefix = '';
switch ($extType) {
case 'plugin': $typePrefix = "plg_{$extFolder}_"; break;
case 'module': $typePrefix = 'mod_'; break;
case 'component': $typePrefix = 'com_'; break;
case 'template': $typePrefix = 'tpl_'; break;
case 'library': $typePrefix = 'lib_'; break;
case 'package': $typePrefix = 'pkg_'; break;
case 'plugin':
$typePrefix = "plg_{$extFolder}_";
break;
case 'module':
$typePrefix = 'mod_';
break;
case 'component':
$typePrefix = 'com_';
break;
case 'template':
$typePrefix = 'tpl_';
break;
case 'library':
$typePrefix = 'lib_';
break;
case 'package':
$typePrefix = 'pkg_';
break;
}
// -- Export to GITHUB_OUTPUT if requested -------------------------------------
if ($githubOutput) {
$ghOutput = getenv('GITHUB_OUTPUT');
$lines = [
"ext_element={$extElement}",
"ext_name={$extName}",
"ext_type={$extType}",
"ext_folder={$extFolder}",
"type_prefix={$typePrefix}",
];
if ($ghOutput) {
file_put_contents($ghOutput, implode("\n", $lines) . "\n", FILE_APPEND);
fwrite(STDERR, "Exported " . count($lines) . " fields to GITHUB_OUTPUT\n");
} else {
foreach ($lines as $line) echo "{$line}\n";
}
$ghOutput = getenv('GITHUB_OUTPUT');
$lines = [
"ext_element={$extElement}",
"ext_name={$extName}",
"ext_type={$extType}",
"ext_folder={$extFolder}",
"type_prefix={$typePrefix}",
];
if ($ghOutput) {
file_put_contents($ghOutput, implode("\n", $lines) . "\n", FILE_APPEND);
fwrite(STDERR, "Exported " . count($lines) . " fields to GITHUB_OUTPUT\n");
} else {
foreach ($lines as $line) {
echo "{$line}\n";
}
}
}
// -- Stability suffix map -----------------------------------------------------
$stabilitySuffixMap = [
'stable' => '',
'rc' => '-rc',
'beta' => '-beta',
'alpha' => '-alpha',
'development' => '-dev',
'stable' => '',
'rc' => '-rc',
'beta' => '-beta',
'alpha' => '-alpha',
'development' => '-dev',
];
// Joomla's stabilityTagToInteger() maps these to STABILITY_* constants.
// MUST use 'dev' not 'development' — STABILITY_DEVELOPMENT does not exist.
// Joomla <tags><tag> values — maps to Joomla's stabilityTagToInteger()
$stabilityTagMap = [
'stable' => 'stable',
'rc' => 'rc',
'beta' => 'beta',
'alpha' => 'alpha',
'development' => 'dev',
'stable' => 'stable',
'rc' => 'rc',
'beta' => 'beta',
'alpha' => 'alpha',
'development' => 'dev',
];
// Gitea release tag names (used in download/info URLs)
$releaseTagMap = [
'stable' => 'stable',
'rc' => 'release-candidate',
'beta' => 'beta',
'alpha' => 'alpha',
'development' => 'development',
];
// -- Build update entries -----------------------------------------------------
$releaseTag = $stabilityTagMap[$stability] ?? $stability;
// For the primary entry: apply suffix if not stable
$primarySuffix = $stabilitySuffixMap[$stability] ?? '';
$primaryVersion = $version . $primarySuffix;
$downloadUrl = "{$giteaUrl}/{$org}/{$repo}/releases/download/{$releaseTag}/{$typePrefix}{$extElement}-{$primaryVersion}.zip";
$infoUrl = "{$giteaUrl}/{$org}/{$repo}/releases/tag/{$releaseTag}";
// Build client tag — Joomla defaults to client_id=1 (administrator) when missing.
// Packages install with client_id=0 (site), so we MUST include <client>site</client>
// for all types to prevent a mismatch that causes extension_id=0 in #__updates.
// Build client tag — Joomla requires <client>site</client> to match updates
// to installed extensions. Without it, extension_id=0 in #__updates.
$clientTag = '';
if (!empty($extClient)) {
$clientTag = " <client>{$extClient}</client>";
$clientTag = " <client>{$extClient}</client>";
} else {
$clientTag = ' <client>site</client>';
$clientTag = ' <client>site</client>';
}
// Build folder tag
$folderTag = '';
if (!empty($extFolder) && $extType === 'plugin') {
$folderTag = " <folder>{$extFolder}</folder>";
$folderTag = " <folder>{$extFolder}</folder>";
}
// PHP minimum tag
$phpTag = '';
if (!empty($phpMinimum)) {
$phpTag = " <php_minimum>{$phpMinimum}</php_minimum>";
$phpTag = " <php_minimum>{$phpMinimum}</php_minimum>";
}
// SHA tag
$shaTag = '';
if (!empty($sha)) {
$shaTag = " <sha256>{$sha}</sha256>";
$shaTag = " <sha256>{$sha}</sha256>";
}
/**
* Build a single <update> entry for a given stability tag
*/
function buildEntry(
string $tagName,
string $entryVersion,
string $entryDownloadUrl,
string $extName,
string $extElement,
string $extType,
string $clientTag,
string $folderTag,
string $infoUrl,
string $targetPlatform,
string $phpTag,
string $shaTag
string $tagName,
string $entryVersion,
string $entryDownloadUrl,
string $extName,
string $extElement,
string $extType,
string $clientTag,
string $folderTag,
string $infoUrl,
string $targetPlatform,
string $phpTag,
string $shaTag
): string {
$lines = [];
$lines[] = ' <update>';
$lines[] = " <name>{$extName}</name>";
$lines[] = " <description>{$extName} update</description>";
// Element in updates.xml must match what Joomla stores in #__extensions
// For packages: pkg_elementname. For plugins: elementname (folder handles grouping).
$dbElement = ($extType === 'package') ? "pkg_{$extElement}" : $extElement;
$lines[] = " <element>{$dbElement}</element>";
$lines[] = " <type>{$extType}</type>";
$lines[] = " <version>{$entryVersion}</version>";
if (!empty($clientTag)) $lines[] = $clientTag;
if (!empty($folderTag)) $lines[] = $folderTag;
$lines[] = " <tags><tag>{$tagName}</tag></tags>";
$lines[] = " <infourl title=\"{$extName}\">{$infoUrl}</infourl>";
$lines[] = ' <downloads>';
$lines[] = " <downloadurl type=\"full\" format=\"zip\">{$entryDownloadUrl}</downloadurl>";
$lines[] = ' </downloads>';
if (!empty($shaTag)) $lines[] = $shaTag;
$lines[] = " {$targetPlatform}";
if (!empty($phpTag)) $lines[] = $phpTag;
$lines[] = ' <maintainer>Moko Consulting</maintainer>';
$lines[] = ' <maintainerurl>https://mokoconsulting.tech</maintainerurl>';
$lines[] = ' </update>';
return implode("\n", $lines);
$lines = [];
$lines[] = ' <update>';
$lines[] = " <name>{$extName}</name>";
$lines[] = " <description>{$extName} update</description>";
// Element in updates.xml must match what Joomla stores in #__extensions
// For packages: pkg_elementname. For plugins: elementname (folder handles grouping).
$dbElement = ($extType === 'package') ? "pkg_{$extElement}" : $extElement;
$lines[] = " <element>{$dbElement}</element>";
$lines[] = " <type>{$extType}</type>";
$lines[] = " <version>{$entryVersion}</version>";
if (!empty($clientTag)) {
$lines[] = $clientTag;
}
if (!empty($folderTag)) {
$lines[] = $folderTag;
}
$lines[] = " <tags><tag>{$tagName}</tag></tags>";
$lines[] = " <infourl title=\"{$extName}\">{$infoUrl}</infourl>";
$lines[] = ' <downloads>';
$lines[] = " <downloadurl type=\"full\" format=\"zip\">{$entryDownloadUrl}</downloadurl>";
$lines[] = ' </downloads>';
if (!empty($shaTag)) {
$lines[] = $shaTag;
}
$lines[] = " {$targetPlatform}";
if (!empty($phpTag)) {
$lines[] = $phpTag;
}
$lines[] = ' <maintainer>Moko Consulting</maintainer>';
$lines[] = ' <maintainerurl>https://mokoconsulting.tech</maintainerurl>';
$lines[] = ' </update>';
return implode("\n", $lines);
}
// -- Determine which channels to write ----------------------------------------
// Stable cascades to all channels; pre-releases only write their level and below
// Each channel gets its own suffixed version:
// development -> 04.01.00-dev
// alpha -> 04.01.00-alpha
// beta -> 04.01.00-beta
// rc -> 04.01.00-rc
// stable -> 04.01.00
// Stable cascades to all channels; pre-releases cascade down to lower channels.
// Each channel entry represents "latest release available at this stability or higher".
// When stable releases, ALL channels point to stable (it's the newest for everyone).
// When RC releases, rc/beta/alpha/dev point to RC; stable is preserved.
// When dev releases, only dev is updated; everything else is preserved.
$allChannels = ['development', 'alpha', 'beta', 'rc', 'stable'];
$stabilityIndex = array_search($stability === 'development' ? 'development' : $stability, $allChannels);
if ($stabilityIndex === false) $stabilityIndex = 4; // default to stable
if ($stabilityIndex === false) {
$stabilityIndex = 4; // default to stable
}
// Write only the current channel entry (not cascade)
// Each channel release only creates its own entry; preserved entries handle other channels
// Write entries for the current channel AND all lower channels (cascade down)
// All cascaded entries point to the CURRENT release (the highest stability being built)
$entries = [];
$channelName = $allChannels[$stabilityIndex];
$channelSuffix = $stabilitySuffixMap[$channelName] ?? '';
$channelVersion = $version . $channelSuffix;
$channelTag = $stabilityTagMap[$channelName] ?? $channelName;
$channelDownloadUrl = "{$giteaUrl}/{$org}/{$repo}/releases/download/{$channelTag}/{$typePrefix}{$extElement}-{$channelVersion}.zip";
$channelInfoUrl = "{$giteaUrl}/{$org}/{$repo}/releases/tag/{$channelTag}";
$giteaTag = $releaseTagMap[$stability] ?? $stability;
$channelVersion = $version . ($stabilitySuffixMap[$stability] ?? '');
$channelDownloadUrl = "{$giteaUrl}/{$org}/{$repo}/releases/download/{$giteaTag}/{$typePrefix}{$extElement}-{$channelVersion}.zip";
$channelInfoUrl = "{$giteaUrl}/{$org}/{$repo}/releases/tag/{$giteaTag}";
$entries[] = buildEntry(
$channelName,
$channelVersion,
$channelDownloadUrl,
$extName,
$extElement,
$extType,
$clientTag,
$folderTag,
$channelInfoUrl,
$targetPlatform,
$phpTag,
$shaTag
);
for ($i = 0; $i <= $stabilityIndex; $i++) {
$channelName = $allChannels[$i];
$joomlaTag = $stabilityTagMap[$channelName] ?? $channelName;
// Only attach SHA to the primary channel entry
$entrySha = ($i === $stabilityIndex) ? $shaTag : '';
$entries[] = buildEntry(
$joomlaTag,
$channelVersion,
$channelDownloadUrl,
$extName,
$extElement,
$extType,
$clientTag,
$folderTag,
$channelInfoUrl,
$targetPlatform,
$phpTag,
$entrySha
);
}
// -- Preserve existing entries for channels not being updated -----------------
$dest = $outputFile ?? "{$root}/updates.xml";
$preservedEntries = [];
if (file_exists($dest)) {
$existingXml = @simplexml_load_file($dest);
if ($existingXml) {
// Channels we're writing — don't preserve these
$writtenChannels = [];
for ($i = 0; $i <= $stabilityIndex; $i++) {
$writtenChannels[] = $allChannels[$i];
}
$existingXml = @simplexml_load_file($dest);
if ($existingXml) {
// Joomla tags we're writing — don't preserve these
$writtenChannels = [];
for ($i = 0; $i <= $stabilityIndex; $i++) {
$writtenChannels[] = $stabilityTagMap[$allChannels[$i]] ?? $allChannels[$i];
}
// Also match legacy/alternate tag names (e.g. 'development' = 'dev')
$writtenChannels[] = 'development'; // alias for 'dev'
foreach ($existingXml->update as $existingUpdate) {
$existingTag = '';
if (isset($existingUpdate->tags->tag)) {
$existingTag = (string) $existingUpdate->tags->tag;
}
// Keep entries for channels we're NOT overwriting
if (!empty($existingTag) && !in_array($existingTag, $writtenChannels, true)) {
$preservedEntries[] = ' ' . trim($existingUpdate->asXML());
}
}
}
foreach ($existingXml->update as $existingUpdate) {
$existingTag = '';
if (isset($existingUpdate->tags->tag)) {
$existingTag = (string) $existingUpdate->tags->tag;
}
// Keep entries for channels we're NOT overwriting
if (!empty($existingTag) && !in_array($existingTag, $writtenChannels, true)) {
$preservedEntries[] = ' ' . trim($existingUpdate->asXML());
}
}
}
}
// -- Write updates.xml --------------------------------------------------------
+110 -20
View File
@@ -9,7 +9,7 @@
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/version_bump.php
* BRIEF: Auto-increment patch version — checks both README.md and manifest XML, uses the higher version as base
* BRIEF: Auto-increment version — manifest.xml is canonical, cascades to all XML and MD files
*/
declare(strict_types=1);
@@ -24,7 +24,20 @@ foreach ($argv as $i => $arg) {
$root = realpath($path) ?: $path;
// ── Read version from README.md ──────────────────────────────────────────────
// -- 1. Read version from .mokogitea/manifest.xml (canonical) --
$mokoVersion = null;
$mokoSuffix = '';
$mokoManifest = "{$root}/.mokogitea/manifest.xml";
$mokoContent = '';
if (file_exists($mokoManifest)) {
$mokoContent = file_get_contents($mokoManifest);
if (preg_match('|<version>(\d{2}\.\d{2}\.\d{2})(?:-([a-z]+))?</version>|', $mokoContent, $m)) {
$mokoVersion = $m[1];
$mokoSuffix = isset($m[2]) ? $m[2] : '';
}
}
// -- 2. Fallback: README.md --
$readmeVersion = null;
$readme = "{$root}/README.md";
$readmeContent = '';
@@ -35,10 +48,9 @@ if (file_exists($readme)) {
}
}
// ── Read version from Joomla manifest XML ────────────────────────────────────
// -- 3. Fallback: Joomla manifest XML --
$manifestVersion = null;
// Check package manifest first (pkg_*.xml), then sub-extension manifests
$manifestSuffix = '';
$manifestFiles = array_merge(
glob("{$root}/src/pkg_*.xml") ?: [],
glob("{$root}/src/*.xml") ?: [],
@@ -52,31 +64,31 @@ foreach ($manifestFiles as $xmlFile) {
if (strpos($xmlContent, '<extension') === false && strpos($xmlContent, '<version>') === false) {
continue;
}
if (preg_match('|<version>(\d{2}\.\d{2}\.\d{2})(?:-[a-z]+)?</version>|', $xmlContent, $xm)) {
if (preg_match('|<version>(\d{2}\.\d{2}\.\d{2})(-[a-z]+)?</version>|', $xmlContent, $xm)) {
$candidate = $xm[1];
if ($manifestVersion === null || version_compare($candidate, $manifestVersion, '>')) {
$manifestVersion = $candidate;
// Preserve the suffix from the manifest (e.g. dev, rc) — strip leading dash
$manifestSuffix = ltrim($xm[2] ?? '', '-');
}
}
}
// ── Use the higher version as base ───────────────────────────────────────────
// -- Use the highest version as base --
$baseVersion = null;
if ($readmeVersion !== null && $manifestVersion !== null) {
$baseVersion = version_compare($manifestVersion, $readmeVersion, '>') ? $manifestVersion : $readmeVersion;
} elseif ($manifestVersion !== null) {
$baseVersion = $manifestVersion;
} elseif ($readmeVersion !== null) {
$baseVersion = $readmeVersion;
$candidates = array_filter([$mokoVersion, $readmeVersion, $manifestVersion]);
foreach ($candidates as $v) {
if ($baseVersion === null || version_compare($v, $baseVersion, '>')) {
$baseVersion = $v;
}
}
if ($baseVersion === null) {
fwrite(STDERR, "No version found in README.md or manifest XML\n");
fwrite(STDERR, "No version found in manifest.xml, README.md, or Joomla XML\n");
exit(1);
}
// ── Parse and bump ───────────────────────────────────────────────────────────
// -- Parse and bump --
if (!preg_match('/^(\d{2})\.(\d{2})\.(\d{2})$/', $baseVersion, $parts)) {
fwrite(STDERR, "Invalid version format: {$baseVersion}\n");
exit(1);
@@ -99,16 +111,94 @@ switch ($type) {
$new = sprintf('%02d.%02d.%02d', $major, $minor, $patch);
// ── Update README.md ─────────────────────────────────────────────────────────
// -- Determine suffix to preserve (moko manifest takes priority, then Joomla) --
$suffix = !empty($mokoSuffix) ? $mokoSuffix : (!empty($manifestSuffix) ? $manifestSuffix : '');
$newFull = $suffix !== '' ? "{$new}-{$suffix}" : $new;
// -- Update .mokogitea/manifest.xml (canonical — preserves suffix) --
if (file_exists($mokoManifest) && !empty($mokoContent)) {
$updated = preg_replace(
'|<version>\d{2}\.\d{2}\.\d{2}(?:-[a-z]+)?</version>|',
"<version>{$newFull}</version>",
$mokoContent,
1
);
file_put_contents($mokoManifest, $updated);
}
// -- Update README.md --
if (file_exists($readme) && !empty($readmeContent)) {
$updated = preg_replace(
'/(VERSION:\s*)\d{2}\.\d{2}\.\d{2}/m',
'${1}' . $new,
'/(VERSION:\s*)\d{2}\.\d{2}\.\d{2}(?:-[a-z]+)?/m',
'${1}' . $newFull,
$readmeContent,
1
);
file_put_contents($readme, $updated);
}
echo "{$old}{$new}\n";
// -- Cascade to ALL Joomla extension XML manifests --
$xmlPatterns = [
"{$root}/src/pkg_*.xml",
"{$root}/src/*.xml",
"{$root}/src/packages/*/*.xml",
"{$root}/*.xml",
];
$updatedFiles = [];
foreach ($xmlPatterns as $pattern) {
foreach (glob($pattern) ?: [] as $xmlFile) {
$content = file_get_contents($xmlFile);
// Only update files that have an <extension> tag (Joomla manifests)
if (strpos($content, '<extension') === false) {
continue;
}
$newContent = preg_replace(
'|<version>\d{2}\.\d{2}\.\d{2}(?:-[a-z]+)?</version>|',
"<version>{$newFull}</version>",
$content
);
if ($newContent !== $content) {
file_put_contents($xmlFile, $newContent);
$updatedFiles[] = substr($xmlFile, strlen($root) + 1);
}
}
}
if (!empty($updatedFiles)) {
fwrite(STDERR, "Updated " . count($updatedFiles) . " Joomla manifest(s): " . implode(', ', $updatedFiles) . "\n");
}
// -- Update package.json (Node.js / MCP) --
$packageJsonFile = "{$root}/package.json";
if (file_exists($packageJsonFile)) {
$pkgContent = file_get_contents($packageJsonFile);
$updatedPkg = preg_replace(
'/("version"\s*:\s*")\d{2}\.\d{2}\.\d{2}(?:-[a-z]+)?(")/m',
'${1}' . $newFull . '${2}',
$pkgContent
);
if ($updatedPkg !== $pkgContent) {
file_put_contents($packageJsonFile, $updatedPkg);
fwrite(STDERR, "Updated package.json\n");
}
}
// -- Update pyproject.toml (Python) --
$pyprojectFile = "{$root}/pyproject.toml";
if (file_exists($pyprojectFile)) {
$pyContent = file_get_contents($pyprojectFile);
$updatedPy = preg_replace(
'/^(version\s*=\s*")\d{2}\.\d{2}\.\d{2}(?:-[a-z]+)?(")/m',
'${1}' . $newFull . '${2}',
$pyContent
);
if ($updatedPy !== $pyContent) {
file_put_contents($pyprojectFile, $updatedPy);
fwrite(STDERR, "Updated pyproject.toml\n");
}
}
$oldFull = $suffix !== '' ? "{$old}-{$suffix}" : $old;
echo "{$oldFull} -> {$newFull}\n";
exit(0);
+233 -233
View File
@@ -1,233 +1,233 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: moko-platform.CLI
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/version_bump_remote.php
* BRIEF: Bump version in manifest XML and CHANGELOG.md on a remote branch via Gitea API
*
* Usage:
* php version_bump_remote.php --path . --branch dev --bump minor --token TOKEN --api-base URL
* php version_bump_remote.php --path . --branch dev --bump patch --token TOKEN --api-base URL
* php version_bump_remote.php --path . --branch dev --bump minor --no-changelog --token TOKEN --api-base URL
*
* Options:
* --path Repository root (reads current version from local manifest)
* --branch Target branch to bump (required, e.g. dev)
* --bump Bump type: patch | minor | major (default: minor)
* --token Gitea API token (or GA_TOKEN env var)
* --api-base Gitea API base URL for the repo
* --no-changelog Skip CHANGELOG.md bump
* --repo Repository path (owner/repo) for API base construction
* --gitea-url Gitea instance URL (default: env GITEA_URL)
*/
declare(strict_types=1);
$path = '.';
$branch = null;
$bumpType = 'minor';
$token = null;
$apiBase = null;
$noChangelog = false;
$repo = null;
$giteaUrl = null;
foreach ($argv as $i => $arg) {
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
if ($arg === '--branch' && isset($argv[$i + 1])) $branch = $argv[$i + 1];
if ($arg === '--bump' && isset($argv[$i + 1])) $bumpType = $argv[$i + 1];
if ($arg === '--token' && isset($argv[$i + 1])) $token = $argv[$i + 1];
if ($arg === '--api-base' && isset($argv[$i + 1])) $apiBase = $argv[$i + 1];
if ($arg === '--no-changelog') $noChangelog = true;
if ($arg === '--repo' && isset($argv[$i + 1])) $repo = $argv[$i + 1];
if ($arg === '--gitea-url' && isset($argv[$i + 1])) $giteaUrl = $argv[$i + 1];
}
if ($token === null) $token = getenv('GA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null;
if ($giteaUrl === null) $giteaUrl = getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech';
if ($apiBase === null && $repo !== null) {
$apiBase = rtrim($giteaUrl, '/') . '/api/v1/repos/' . $repo;
}
if ($branch === null || $token === null || $apiBase === null) {
fwrite(STDERR, "Usage: version_bump_remote.php --branch BRANCH --token TOKEN --api-base URL [--bump minor|patch|major]\n");
fwrite(STDERR, " or: version_bump_remote.php --branch BRANCH --token TOKEN --repo owner/repo\n");
exit(1);
}
$root = realpath($path) ?: $path;
// ── Read current version from local manifest ────────────────────────────
$version = null;
$manifestFile = null;
$searchDirs = ["{$root}/src", $root];
foreach ($searchDirs as $dir) {
if (!is_dir($dir)) continue;
foreach (glob("{$dir}/*.xml") ?: [] as $f) {
$xml = file_get_contents($f);
if (strpos($xml, '<extension') !== false || strpos($xml, '<version>') !== false) {
if (preg_match('|<version>(\d{2}\.\d{2}\.\d{2})</version>|', $xml, $m)) {
if ($version === null || version_compare($m[1], $version, '>')) {
$version = $m[1];
$manifestFile = basename($f);
}
}
}
}
}
if ($version === null) {
fwrite(STDERR, "No version found in manifest XML\n");
exit(1);
}
// ── Compute next version ────────────────────────────────────────────────
if (!preg_match('/^(\d{2})\.(\d{2})\.(\d{2})$/', $version, $parts)) {
fwrite(STDERR, "Invalid version format: {$version}\n");
exit(1);
}
$major = (int)$parts[1];
$minor = (int)$parts[2];
$patch = (int)$parts[3];
switch ($bumpType) {
case 'major': $major++; $minor = 0; $patch = 0; break;
case 'minor': $minor++; $patch = 0; break;
default: $patch++; break;
}
$nextVersion = sprintf('%02d.%02d.%02d', $major, $minor, $patch);
echo "{$version} -> {$nextVersion} ({$branch})\n";
// ── Helper: Gitea API request ───────────────────────────────────────────
function giteaApi(string $method, string $url, string $token, ?string $body = null): ?array
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: token {$token}",
'Content-Type: application/json',
],
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_TIMEOUT => 30,
]);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 400 || $response === false) {
return null;
}
return json_decode($response, true) ?: [];
}
// ── Helper: Update a file on a remote branch ────────────────────────────
function updateRemoteFile(
string $apiBase,
string $token,
string $filePath,
string $branch,
callable $transform,
string $commitMessage
): bool {
$url = "{$apiBase}/contents/{$filePath}?ref={$branch}";
$file = giteaApi('GET', $url, $token);
if ($file === null || !isset($file['sha']) || !isset($file['content'])) {
return false;
}
$content = base64_decode($file['content']);
$newContent = $transform($content);
if ($newContent === $content) {
fwrite(STDERR, " {$filePath}: no changes needed\n");
return true;
}
$payload = json_encode([
'content' => base64_encode($newContent),
'sha' => $file['sha'],
'message' => $commitMessage,
'branch' => $branch,
]);
$result = giteaApi('PUT', "{$apiBase}/contents/{$filePath}", $token, $payload);
if ($result === null) {
fwrite(STDERR, " {$filePath}: failed to update\n");
return false;
}
echo " {$filePath}: updated on {$branch}\n";
return true;
}
// ── Update manifest XML on the remote branch ────────────────────────────
$manifestPaths = [];
if ($manifestFile !== null) {
$manifestPaths[] = "src/{$manifestFile}";
}
$manifestPaths = array_merge($manifestPaths, [
'src/templateDetails.xml',
'src/manifest.xml',
]);
$manifestUpdated = false;
foreach ($manifestPaths as $mPath) {
$result = updateRemoteFile(
$apiBase, $token, $mPath, $branch,
function (string $content) use ($version, $nextVersion): string {
return str_replace(
"<version>{$version}</version>",
"<version>{$nextVersion}</version>",
$content
);
},
"chore(version): bump {$version} -> {$nextVersion} [skip ci]"
);
if ($result) {
$manifestUpdated = true;
break;
}
}
if (!$manifestUpdated) {
fwrite(STDERR, "WARNING: could not update manifest on {$branch}\n");
}
// ── Update CHANGELOG.md on the remote branch ────────────────────────────
if (!$noChangelog) {
updateRemoteFile(
$apiBase, $token, 'CHANGELOG.md', $branch,
function (string $content) use ($version, $nextVersion): string {
$content = str_replace("VERSION: {$version}", "VERSION: {$nextVersion}", $content);
if (strpos($content, '[Unreleased]') === false
&& strpos($content, "## [{$nextVersion}]") === false
) {
$marker = "## [{$version}]";
if (strpos($content, $marker) !== false) {
$unreleased = "## [{$nextVersion}] - Unreleased\n\n### Added\n\n### Changed\n\n### Fixed\n\n";
$content = str_replace($marker, $unreleased . $marker, $content);
}
}
return $content;
},
"chore(version): bump CHANGELOG {$version} -> {$nextVersion} [skip ci]"
);
}
exit(0);
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: moko-platform.CLI
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/version_bump_remote.php
* BRIEF: Bump version in manifest XML and CHANGELOG.md on a remote branch via Gitea API
*
* Usage:
* php version_bump_remote.php --path . --branch dev --bump minor --token TOKEN --api-base URL
* php version_bump_remote.php --path . --branch dev --bump patch --token TOKEN --api-base URL
* php version_bump_remote.php --path . --branch dev --bump minor --no-changelog --token TOKEN --api-base URL
*
* Options:
* --path Repository root (reads current version from local manifest)
* --branch Target branch to bump (required, e.g. dev)
* --bump Bump type: patch | minor | major (default: minor)
* --token Gitea API token (or GA_TOKEN env var)
* --api-base Gitea API base URL for the repo
* --no-changelog Skip CHANGELOG.md bump
* --repo Repository path (owner/repo) for API base construction
* --gitea-url Gitea instance URL (default: env GITEA_URL)
*/
declare(strict_types=1);
$path = '.';
$branch = null;
$bumpType = 'minor';
$token = null;
$apiBase = null;
$noChangelog = false;
$repo = null;
$giteaUrl = null;
foreach ($argv as $i => $arg) {
if ($arg === '--path' && isset($argv[$i + 1])) $path = $argv[$i + 1];
if ($arg === '--branch' && isset($argv[$i + 1])) $branch = $argv[$i + 1];
if ($arg === '--bump' && isset($argv[$i + 1])) $bumpType = $argv[$i + 1];
if ($arg === '--token' && isset($argv[$i + 1])) $token = $argv[$i + 1];
if ($arg === '--api-base' && isset($argv[$i + 1])) $apiBase = $argv[$i + 1];
if ($arg === '--no-changelog') $noChangelog = true;
if ($arg === '--repo' && isset($argv[$i + 1])) $repo = $argv[$i + 1];
if ($arg === '--gitea-url' && isset($argv[$i + 1])) $giteaUrl = $argv[$i + 1];
}
if ($token === null) $token = getenv('GA_TOKEN') ?: getenv('GITEA_TOKEN') ?: null;
if ($giteaUrl === null) $giteaUrl = getenv('GITEA_URL') ?: 'https://git.mokoconsulting.tech';
if ($apiBase === null && $repo !== null) {
$apiBase = rtrim($giteaUrl, '/') . '/api/v1/repos/' . $repo;
}
if ($branch === null || $token === null || $apiBase === null) {
fwrite(STDERR, "Usage: version_bump_remote.php --branch BRANCH --token TOKEN --api-base URL [--bump minor|patch|major]\n");
fwrite(STDERR, " or: version_bump_remote.php --branch BRANCH --token TOKEN --repo owner/repo\n");
exit(1);
}
$root = realpath($path) ?: $path;
// ── Read current version from local manifest ────────────────────────────
$version = null;
$manifestFile = null;
$searchDirs = ["{$root}/src", $root];
foreach ($searchDirs as $dir) {
if (!is_dir($dir)) continue;
foreach (glob("{$dir}/*.xml") ?: [] as $f) {
$xml = file_get_contents($f);
if (strpos($xml, '<extension') !== false || strpos($xml, '<version>') !== false) {
if (preg_match('|<version>(\d{2}\.\d{2}\.\d{2})</version>|', $xml, $m)) {
if ($version === null || version_compare($m[1], $version, '>')) {
$version = $m[1];
$manifestFile = basename($f);
}
}
}
}
}
if ($version === null) {
fwrite(STDERR, "No version found in manifest XML\n");
exit(1);
}
// ── Compute next version ────────────────────────────────────────────────
if (!preg_match('/^(\d{2})\.(\d{2})\.(\d{2})$/', $version, $parts)) {
fwrite(STDERR, "Invalid version format: {$version}\n");
exit(1);
}
$major = (int)$parts[1];
$minor = (int)$parts[2];
$patch = (int)$parts[3];
switch ($bumpType) {
case 'major': $major++; $minor = 0; $patch = 0; break;
case 'minor': $minor++; $patch = 0; break;
default: $patch++; break;
}
$nextVersion = sprintf('%02d.%02d.%02d', $major, $minor, $patch);
echo "{$version} -> {$nextVersion} ({$branch})\n";
// ── Helper: Gitea API request ───────────────────────────────────────────
function giteaApi(string $method, string $url, string $token, ?string $body = null): ?array
{
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: token {$token}",
'Content-Type: application/json',
],
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_TIMEOUT => 30,
]);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode >= 400 || $response === false) {
return null;
}
return json_decode($response, true) ?: [];
}
// ── Helper: Update a file on a remote branch ────────────────────────────
function updateRemoteFile(
string $apiBase,
string $token,
string $filePath,
string $branch,
callable $transform,
string $commitMessage
): bool {
$url = "{$apiBase}/contents/{$filePath}?ref={$branch}";
$file = giteaApi('GET', $url, $token);
if ($file === null || !isset($file['sha']) || !isset($file['content'])) {
return false;
}
$content = base64_decode($file['content']);
$newContent = $transform($content);
if ($newContent === $content) {
fwrite(STDERR, " {$filePath}: no changes needed\n");
return true;
}
$payload = json_encode([
'content' => base64_encode($newContent),
'sha' => $file['sha'],
'message' => $commitMessage,
'branch' => $branch,
]);
$result = giteaApi('PUT', "{$apiBase}/contents/{$filePath}", $token, $payload);
if ($result === null) {
fwrite(STDERR, " {$filePath}: failed to update\n");
return false;
}
echo " {$filePath}: updated on {$branch}\n";
return true;
}
// ── Update manifest XML on the remote branch ────────────────────────────
$manifestPaths = [];
if ($manifestFile !== null) {
$manifestPaths[] = "src/{$manifestFile}";
}
$manifestPaths = array_merge($manifestPaths, [
'src/templateDetails.xml',
'src/manifest.xml',
]);
$manifestUpdated = false;
foreach ($manifestPaths as $mPath) {
$result = updateRemoteFile(
$apiBase, $token, $mPath, $branch,
function (string $content) use ($version, $nextVersion): string {
return str_replace(
"<version>{$version}</version>",
"<version>{$nextVersion}</version>",
$content
);
},
"chore(version): bump {$version} -> {$nextVersion} [skip ci]"
);
if ($result) {
$manifestUpdated = true;
break;
}
}
if (!$manifestUpdated) {
fwrite(STDERR, "WARNING: could not update manifest on {$branch}\n");
}
// ── Update CHANGELOG.md on the remote branch ────────────────────────────
if (!$noChangelog) {
updateRemoteFile(
$apiBase, $token, 'CHANGELOG.md', $branch,
function (string $content) use ($version, $nextVersion): string {
$content = str_replace("VERSION: {$version}", "VERSION: {$nextVersion}", $content);
if (strpos($content, '[Unreleased]') === false
&& strpos($content, "## [{$nextVersion}]") === false
) {
$marker = "## [{$version}]";
if (strpos($content, $marker) !== false) {
$unreleased = "## [{$nextVersion}] - Unreleased\n\n### Added\n\n### Changed\n\n### Fixed\n\n";
$content = str_replace($marker, $unreleased . $marker, $content);
}
}
return $content;
},
"chore(version): bump CHANGELOG {$version} -> {$nextVersion} [skip ci]"
);
}
exit(0);
+83 -13
View File
@@ -9,7 +9,7 @@
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/version_read.php
* BRIEF: Read version from README.md or manifest XML — outputs the higher of the two
* BRIEF: Read version — manifest.xml is canonical, falls back to README.md and Joomla XML
*/
declare(strict_types=1);
@@ -23,7 +23,26 @@ foreach ($argv as $i => $arg) {
$root = realpath($path) ?: $path;
// ── Read from README.md ──────────────────────────────────────────────────────
// -- 1. Read from .mokogitea/manifest.xml (canonical source) --
$mokoVersion = null;
$mokoManifest = "{$root}/.mokogitea/manifest.xml";
if (file_exists($mokoManifest)) {
$xml = @simplexml_load_file($mokoManifest);
if ($xml !== false) {
$v = (string)($xml->identity->version ?? '');
if (preg_match('/^\d{2}\.\d{2}\.\d{2}(-[a-z]+)?$/', $v)) {
$mokoVersion = $v;
}
}
}
// If manifest.xml has a version, that is authoritative
if ($mokoVersion !== null) {
echo $mokoVersion . "\n";
exit(0);
}
// -- 2. Fallback: README.md --
$readmeVersion = null;
$readme = "{$root}/README.md";
if (file_exists($readme)) {
@@ -33,7 +52,7 @@ if (file_exists($readme)) {
}
}
// ── Read from Joomla manifest XML ────────────────────────────────────────────
// -- 3. Fallback: Joomla manifest XML --
$manifestVersion = null;
$manifestFiles = array_merge(
glob("{$root}/src/pkg_*.xml") ?: [],
@@ -47,28 +66,79 @@ foreach ($manifestFiles as $xmlFile) {
if (strpos($xmlContent, '<extension') === false && strpos($xmlContent, '<version>') === false) {
continue;
}
if (preg_match('|<version>(\d{2}\.\d{2}\.\d{2})(?:-[a-z]+)?</version>|', $xmlContent, $xm)) {
if (preg_match('|<version>(\d{2}\.\d{2}\.\d{2}(?:-[a-z]+)?)</version>|', $xmlContent, $xm)) {
$candidate = $xm[1];
if ($manifestVersion === null || version_compare($candidate, $manifestVersion, '>')) {
$candidateBase = preg_replace('/-[a-z]+$/', '', $candidate);
$currentBase = $manifestVersion ? preg_replace('/-[a-z]+$/', '', $manifestVersion) : null;
if ($currentBase === null || version_compare($candidateBase, $currentBase, '>')) {
$manifestVersion = $candidate;
}
}
}
// ── Output the higher version ────────────────────────────────────────────────
// -- 4. Fallback: package.json (Node.js / MCP) --
$packageJsonVersion = null;
$packageJsonFile = "{$root}/package.json";
if (file_exists($packageJsonFile)) {
$pkgData = json_decode(file_get_contents($packageJsonFile), true);
if (isset($pkgData['version']) && preg_match('/^\d{2}\.\d{2}\.\d{2}$/', $pkgData['version'])) {
$packageJsonVersion = $pkgData['version'];
}
}
// -- 5. Fallback: pyproject.toml (Python) --
$pyprojectVersion = null;
$pyprojectFile = "{$root}/pyproject.toml";
if (file_exists($pyprojectFile)) {
$pyContent = file_get_contents($pyprojectFile);
if (preg_match('/^version\s*=\s*"(\d{2}\.\d{2}\.\d{2})"/m', $pyContent, $pm)) {
$pyprojectVersion = $pm[1];
}
}
// -- Output the higher version --
$candidates = array_filter([
$readmeVersion,
$manifestVersion,
$packageJsonVersion,
$pyprojectVersion,
]);
$version = null;
if ($readmeVersion !== null && $manifestVersion !== null) {
$version = version_compare($manifestVersion, $readmeVersion, '>') ? $manifestVersion : $readmeVersion;
} elseif ($manifestVersion !== null) {
$version = $manifestVersion;
} elseif ($readmeVersion !== null) {
$version = $readmeVersion;
foreach ($candidates as $candidate) {
if ($version === null || version_compare($candidate, $version, '>')) {
$version = $candidate;
}
}
if ($version === null) {
fwrite(STDERR, "No version found in README.md or manifest XML\n");
fwrite(STDERR, "No version found in manifest.xml, README.md, Joomla XML, package.json, or pyproject.toml\n");
exit(1);
}
// -- Backfill: if manifest.xml exists but lacks <version>, insert it --
if (file_exists($mokoManifest)) {
$content = file_get_contents($mokoManifest);
if (!preg_match('|<version>\d{2}\.\d{2}\.\d{2}(-[a-z]+)?</version>|', $content)) {
if (strpos($content, '<license') !== false) {
$content = preg_replace(
'|(\s*<license)|',
"\n <version>{$version}</version>\$1",
$content,
1
);
} elseif (strpos($content, '</identity>') !== false) {
$content = preg_replace(
'|(</identity>)|',
" <version>{$version}</version>\n \$1",
$content,
1
);
}
file_put_contents($mokoManifest, $content);
fwrite(STDERR, "Backfilled manifest.xml with version {$version}\n");
}
}
echo $version . "\n";
exit(0);
+319
View File
@@ -0,0 +1,319 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: moko-platform.CLI
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/version_reset_dev.php
* BRIEF: Reset platform version to 'development' on a branch via Gitea API
*
* Usage:
* php version_reset_dev.php --token TOKEN --api-base URL
* php version_reset_dev.php --token TOKEN --api-base URL --branch dev
* php version_reset_dev.php --token TOKEN --api-base URL --platform dolibarr
* php version_reset_dev.php --token TOKEN --api-base URL --path /repo/root
*
* This replaces the inline curl+python3+sed block previously used in
* auto-release.yml to reset Dolibarr's $this->version on the dev branch
* after a stable release.
*/
declare(strict_types=1);
// ── Argument parsing ─────────────────────────────────────────────────────────
$token = null;
$apiBase = null;
$branch = 'dev';
$platform = null;
$path = null;
foreach ($argv as $i => $arg) {
if ($arg === '--token' && isset($argv[$i + 1])) {
$token = $argv[$i + 1];
}
if ($arg === '--api-base' && isset($argv[$i + 1])) {
$apiBase = rtrim($argv[$i + 1], '/');
}
if ($arg === '--branch' && isset($argv[$i + 1])) {
$branch = $argv[$i + 1];
}
if ($arg === '--platform' && isset($argv[$i + 1])) {
$platform = $argv[$i + 1];
}
if ($arg === '--path' && isset($argv[$i + 1])) {
$path = $argv[$i + 1];
}
if ($arg === '--help' || $arg === '-h') {
printUsage();
exit(0);
}
}
// Allow token from environment
if ($token === null) {
$envToken = getenv('GA_TOKEN');
if ($envToken !== false && $envToken !== '') {
$token = $envToken;
}
}
if ($token === null) {
$envToken = getenv('GITEA_TOKEN');
if ($envToken !== false && $envToken !== '') {
$token = $envToken;
}
}
if ($token === null || $apiBase === null) {
fwrite(STDERR, "Error: --token and --api-base are required.\n\n");
printUsage();
exit(1);
}
// ── Platform detection ───────────────────────────────────────────────────────
if ($platform === null && $path !== null) {
$platform = detectPlatform($path);
if ($platform !== null) {
echo "Detected platform: {$platform}\n";
}
}
if ($platform === null) {
fwrite(STDERR, "Error: could not determine platform. Use --platform or --path.\n");
exit(1);
}
// ── Dispatch by platform ─────────────────────────────────────────────────────
$changed = 0;
if (in_array($platform, ['dolibarr', 'crm-module'], true)) {
$changed = resetDolibarrVersion($apiBase, $token, $branch);
} elseif (in_array($platform, ['joomla', 'waas-component'], true)) {
echo "Joomla version reset is not yet implemented — skipping.\n";
} else {
echo "Platform '{$platform}' has no version-reset logic — skipping.\n";
}
echo "Reset {$changed} file(s) to 'development' on branch '{$branch}'.\n";
exit(0);
// ══════════════════════════════════════════════════════════════════════════════
// Helper functions
// ══════════════════════════════════════════════════════════════════════════════
/**
* Print usage information to stdout.
*
* @return void
*/
function printUsage(): void
{
echo <<<'USAGE'
Reset platform version to 'development' on a branch via Gitea API.
Usage:
php version_reset_dev.php --token TOKEN --api-base URL [options]
Required:
--token TOKEN Gitea API token (also reads GA_TOKEN / GITEA_TOKEN env)
--api-base URL Gitea API base URL for the repo
e.g. https://git.mokoconsulting.tech/api/v1/repos/Org/Repo
Options:
--branch BRANCH Target branch (default: dev)
--platform TYPE Platform type: dolibarr, crm-module, joomla, waas-component
--path DIR Repo root for auto-detecting platform from manifest.xml
--help Show this help
USAGE;
}
/**
* Detect the platform type from a repo's .mokogitea/manifest.xml file.
*
* @param string $repoPath Path to the repository root
* @return string|null The detected platform, or null if detection fails
*/
function detectPlatform(string $repoPath): ?string
{
$root = realpath($repoPath) ?: $repoPath;
$manifestXml = "{$root}/.mokogitea/manifest.xml";
if (!file_exists($manifestXml)) {
return null;
}
$xml = @simplexml_load_file($manifestXml);
if ($xml === false) {
return null;
}
if (isset($xml->governance->platform)) {
$platform = (string) $xml->governance->platform;
if ($platform !== '') {
return $platform;
}
}
return null;
}
/**
* Make a Gitea API call and return the decoded JSON response.
*
* @param string $url Full API URL
* @param string $token Gitea API token
* @param string $method HTTP method (GET, PUT, POST, DELETE)
* @param string|null $body JSON request body, or null for bodiless requests
* @return array<string, mixed>|null Decoded JSON response, or null on failure
*/
function giteaApiCall(string $url, string $token, string $method = 'GET', ?string $body = null): ?array
{
$ch = curl_init($url);
if ($ch === false) {
fwrite(STDERR, "Error: curl_init() failed for {$url}\n");
return null;
}
$headers = [
"Authorization: token {$token}",
'Accept: application/json',
];
if ($body !== null) {
$headers[] = 'Content-Type: application/json';
}
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_TIMEOUT => 30,
CURLOPT_CUSTOMREQUEST => $method,
]);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
}
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode < 200 || $httpCode >= 300 || !is_string($response) || $response === '') {
return null;
}
$data = json_decode($response, true);
if (!is_array($data)) {
return null;
}
return $data;
}
/**
* Reset Dolibarr module version to 'development' on the target branch.
*
* Searches the repository tree for mod*.class.php files that contain
* `extends DolibarrModules`, then replaces `$this->version = '...'`
* with `$this->version = 'development'` via the Gitea file contents API.
*
* @param string $apiBase Gitea API base URL for the repo
* @param string $token Gitea API token
* @param string $branch Target branch name
* @return int Number of files modified
*/
function resetDolibarrVersion(string $apiBase, string $token, string $branch): int
{
// Search the repo tree for mod*.class.php files
$treeUrl = "{$apiBase}/git/trees/{$branch}?recursive=true";
$tree = giteaApiCall($treeUrl, $token);
if ($tree === null || !isset($tree['tree']) || !is_array($tree['tree'])) {
fwrite(STDERR, "Error: could not read repository tree for branch '{$branch}'.\n");
return 0;
}
// Find candidate files: mod*.class.php anywhere in the tree
$candidates = [];
foreach ($tree['tree'] as $entry) {
if (!isset($entry['path']) || !is_string($entry['path'])) {
continue;
}
$basename = basename($entry['path']);
if (preg_match('/^mod[A-Za-z0-9_]+\.class\.php$/', $basename)) {
$candidates[] = $entry['path'];
}
}
if (empty($candidates)) {
echo "No mod*.class.php files found on branch '{$branch}'.\n";
return 0;
}
$changed = 0;
foreach ($candidates as $filePath) {
// GET file contents via API
$encodedPath = implode('/', array_map('rawurlencode', explode('/', $filePath)));
$fileUrl = "{$apiBase}/contents/{$encodedPath}?ref={$branch}";
$fileData = giteaApiCall($fileUrl, $token);
if ($fileData === null || !isset($fileData['content'])) {
echo "Skipping {$filePath}: could not fetch contents.\n";
continue;
}
// Decode base64 content
$rawContent = is_string($fileData['content']) ? $fileData['content'] : '';
$content = base64_decode($rawContent, true);
if ($content === false) {
echo "Skipping {$filePath}: could not decode content.\n";
continue;
}
// Verify this file extends DolibarrModules
if (!str_contains($content, 'extends DolibarrModules')) {
continue;
}
// Replace $this->version = '...' with $this->version = 'development'
$updated = preg_replace(
'/(\$this->version\s*=\s*)[\'"][^\'"]*[\'"]/',
"\${1}'development'",
$content
);
if ($updated === null || $updated === $content) {
echo "Skipping {$filePath}: no version change needed.\n";
continue;
}
// PUT updated content back via API
$sha = $fileData['sha'] ?? '';
$putBody = json_encode([
'content' => base64_encode($updated),
'message' => 'chore(version): reset dev version [skip ci]',
'branch' => $branch,
'sha' => $sha,
]);
$putUrl = "{$apiBase}/contents/{$encodedPath}";
$result = giteaApiCall($putUrl, $token, 'PUT', $putBody);
if ($result !== null) {
echo "Reset: {$filePath} -> \$this->version = 'development'\n";
$changed++;
} else {
fwrite(STDERR, "Error: failed to update {$filePath} on branch '{$branch}'.\n");
}
}
return $changed;
}
+282
View File
@@ -0,0 +1,282 @@
#!/usr/bin/env php
<?php
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: moko-platform.CLI
* INGROUP: moko-platform
* REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
* PATH: /cli/wiki_sync.php
* VERSION: 01.00.00
* BRIEF: Sync select wiki pages from moko-platform to all template repos
*/
declare(strict_types=1);
final class WikiSync
{
private string $giteaUrl = 'https://git.mokoconsulting.tech';
private string $token = '';
private string $org = 'MokoConsulting';
private string $sourceRepo = 'moko-platform';
private array $targetRepos = [];
private array $pages = [];
private bool $dryRun = false;
private bool $allTemplates = false;
private int $synced = 0;
private int $created = 0;
private int $skipped = 0;
private int $errors = 0;
public function run(): int
{
$this->parseArgs();
if ($this->token === '') {
$this->log('ERROR: --token is required.');
$this->printUsage();
return 1;
}
if (empty($this->pages) && !$this->allTemplates) {
$this->log('ERROR: --page or --all-standards is required.');
$this->printUsage();
return 1;
}
// Discover template repos if --all-templates
if ($this->allTemplates || empty($this->targetRepos)) {
$this->targetRepos = $this->discoverTemplateRepos();
}
if (empty($this->targetRepos)) {
$this->log('No target repos found.');
return 0;
}
// If --all-standards, get all pages that start with uppercase
if (empty($this->pages)) {
$this->pages = $this->getStandardsPages();
}
$this->log("Syncing " . count($this->pages) . " page(s) to " . count($this->targetRepos) . " repo(s)");
if ($this->dryRun) {
$this->log("[DRY RUN] No changes will be made.\n");
}
foreach ($this->pages as $pageName) {
$this->log("\n--- Page: {$pageName} ---");
$sourceContent = $this->getWikiPage($this->sourceRepo, $pageName);
if ($sourceContent === null) {
$this->log(" WARNING: page not found in {$this->sourceRepo}");
$this->errors++;
continue;
}
foreach ($this->targetRepos as $repo) {
$existing = $this->getWikiPage($repo, $pageName);
if ($existing !== null && $existing === $sourceContent) {
$this->log(" {$repo}: IDENTICAL (skipped)");
$this->skipped++;
continue;
}
if ($this->dryRun) {
$action = $existing !== null ? 'WOULD UPDATE' : 'WOULD CREATE';
$this->log(" {$repo}: {$action}");
continue;
}
if ($existing !== null) {
$ok = $this->updateWikiPage($repo, $pageName, $sourceContent);
$this->log(" {$repo}: " . ($ok ? 'UPDATED' : 'ERROR'));
$ok ? $this->synced++ : $this->errors++;
} else {
$ok = $this->createWikiPage($repo, $pageName, $sourceContent);
$this->log(" {$repo}: " . ($ok ? 'CREATED' : 'ERROR'));
$ok ? $this->created++ : $this->errors++;
}
}
}
$this->log("\nDone: {$this->synced} updated, {$this->created} created, {$this->skipped} skipped, {$this->errors} error(s)");
return $this->errors > 0 ? 1 : 0;
}
private function discoverTemplateRepos(): array
{
$repos = $this->apiGet("/orgs/{$this->org}/repos?limit=100");
$templates = [];
foreach ($repos as $repo) {
if (str_starts_with($repo['name'], 'Template-') && !($repo['archived'] ?? false)) {
$templates[] = $repo['name'];
}
}
sort($templates);
$this->log("Found template repos: " . implode(', ', $templates));
return $templates;
}
private function getStandardsPages(): array
{
$pages = $this->apiGet("/repos/{$this->org}/{$this->sourceRepo}/wiki/pages");
$standards = [];
foreach ($pages as $page) {
$title = $page['title'] ?? '';
// Sync pages that are all-caps with underscores (standards pages)
if (preg_match('/^[A-Z][A-Z0-9_-]+$/', $title)) {
$standards[] = $title;
}
}
sort($standards);
$this->log("Found " . count($standards) . " standards pages: " . implode(', ', $standards));
return $standards;
}
private function getWikiPage(string $repo, string $pageName): ?string
{
$data = $this->apiGet("/repos/{$this->org}/{$repo}/wiki/page/{$pageName}");
if ($data === null || !isset($data['content_base64'])) {
return null;
}
return base64_decode($data['content_base64']);
}
private function createWikiPage(string $repo, string $pageName, string $content): bool
{
$payload = json_encode([
'title' => $pageName,
'content_base64' => base64_encode($content),
]);
return $this->apiPost("/repos/{$this->org}/{$repo}/wiki/new", $payload) !== null;
}
private function updateWikiPage(string $repo, string $pageName, string $content): bool
{
$payload = json_encode([
'title' => $pageName,
'content_base64' => base64_encode($content),
]);
return $this->apiPatch("/repos/{$this->org}/{$repo}/wiki/page/{$pageName}", $payload) !== null;
}
private function apiGet(string $endpoint): ?array
{
$url = "{$this->giteaUrl}/api/v1{$endpoint}";
$opts = [
'http' => [
'method' => 'GET',
'header' => "Authorization: token {$this->token}\r\nAccept: application/json\r\n",
'ignore_errors' => true,
],
];
$ctx = stream_context_create($opts);
$result = @file_get_contents($url, false, $ctx);
if ($result === false) return null;
$data = json_decode($result, true);
return is_array($data) ? $data : null;
}
private function apiPost(string $endpoint, string $payload): ?array
{
return $this->apiWrite('POST', $endpoint, $payload);
}
private function apiPatch(string $endpoint, string $payload): ?array
{
return $this->apiWrite('PATCH', $endpoint, $payload);
}
private function apiWrite(string $method, string $endpoint, string $payload): ?array
{
$url = "{$this->giteaUrl}/api/v1{$endpoint}";
$opts = [
'http' => [
'method' => $method,
'header' => "Authorization: token {$this->token}\r\nContent-Type: application/json\r\nAccept: application/json\r\n",
'content' => $payload,
'ignore_errors' => true,
],
];
$ctx = stream_context_create($opts);
$result = @file_get_contents($url, false, $ctx);
if ($result === false) return null;
$data = json_decode($result, true);
return is_array($data) ? $data : null;
}
private function parseArgs(): void
{
global $argv;
$args = $argv;
for ($i = 1; $i < count($args); $i++) {
switch ($args[$i]) {
case '--token':
$this->token = $args[++$i] ?? '';
break;
case '--org':
$this->org = $args[++$i] ?? '';
break;
case '--source':
$this->sourceRepo = $args[++$i] ?? '';
break;
case '--target':
$this->targetRepos[] = $args[++$i] ?? '';
break;
case '--page':
$this->pages[] = $args[++$i] ?? '';
break;
case '--all-standards':
$this->pages = []; // will be populated from source wiki
$this->allTemplates = true;
break;
case '--all-templates':
$this->allTemplates = true;
break;
case '--dry-run':
$this->dryRun = true;
break;
case '--help':
case '-h':
$this->printUsage();
exit(0);
default:
$this->log("WARNING: Unknown argument: {$args[$i]}");
break;
}
}
}
private function printUsage(): void
{
$this->log('Usage: wiki_sync.php --token <token> [options]');
$this->log('');
$this->log('Sync wiki pages from moko-platform to template repos.');
$this->log('');
$this->log('Options:');
$this->log(' --token <token> Gitea API token (required)');
$this->log(' --org <org> Organization (default: MokoConsulting)');
$this->log(' --source <repo> Source repo (default: moko-platform)');
$this->log(' --target <repo> Target repo (can repeat; default: all Template-* repos)');
$this->log(' --page <name> Page to sync (can repeat)');
$this->log(' --all-standards Sync all UPPERCASE standards pages');
$this->log(' --all-templates Target all Template-* repos');
$this->log(' --dry-run Show what would be done');
$this->log(' --help, -h Show this help');
$this->log('');
$this->log('Examples:');
$this->log(' php wiki_sync.php --token xxx --page MANIFEST_STANDARD --all-templates');
$this->log(' php wiki_sync.php --token xxx --all-standards --all-templates --dry-run');
$this->log(' php wiki_sync.php --token xxx --page WORKFLOW_STANDARDS --target Template-Joomla');
}
private function log(string $msg): void
{
fwrite(STDERR, $msg . "\n");
}
}
(new WikiSync())->run();
+1 -1
View File
@@ -2,7 +2,7 @@
"name": "mokoconsulting-tech/enterprise",
"description": "MokoStandards Enterprise API \u2014 PHP implementation",
"type": "library",
"version": "08.00.00",
"version": "09.01.00",
"license": "GPL-3.0-or-later",
"authors": [
{
-206
View File
@@ -1,206 +0,0 @@
/**
* Client Repository Structure Definition
* Standard repository structure for managed Joomla client sites (WaaS)
*
* This is NOT a Joomla extension — it's a full managed client site with
* deployment configs, monitoring, SFTP settings, and sync workflows.
* The src/ directory mirrors the Joomla site's public_html.
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Client Site"
description = "Managed Joomla client site — full site structure, not an extension"
repository_type = "client"
platform = "client"
last_updated = "2026-05-09T00:00:00Z"
maintainer = "Moko Consulting"
version = "01.00.00"
schema_version = "1.0"
}
detection_hints = [
"scripts/sftp-config/",
"scripts/sync-dev-to-live.sh",
"monitoring/grafana/",
"src/administrator/",
"src/components/",
"src/plugins/",
"src/templates/",
"src/media/templates/site/mokoonyx/"
]
root_files = [
{
name = "README.md"
extension = "md"
description = "Client site overview and deployment info"
required = true
always_overwrite = false
protected = true
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Release history"
required = true
always_overwrite = false
},
{
name = "LICENSE"
extension = ""
description = "GPL-3.0-or-later license file"
required = true
always_overwrite = true
template = "templates/docs/required/LICENSE"
},
{
name = "Makefile"
extension = ""
description = "Build and deployment targets (includes minify)"
required = true
always_overwrite = false
},
{
name = "composer.json"
extension = "json"
description = "PHP dependencies"
required = true
always_overwrite = false
},
{
name = ".gitignore"
extension = ""
description = "Git ignore rules (must include *.min.css, *.min.js, TODO.md)"
required = true
always_overwrite = false
}
]
directories = [
{
name = "src"
path = "src"
description = "Joomla site public_html mirror — deployed via SFTP"
required = true
purpose = "Contains the full Joomla site directory structure"
subdirectories = [
{ name = "administrator", path = "src/administrator", description = "Joomla admin", required = true },
{ name = "components", path = "src/components", description = "Frontend components", required = true },
{ name = "plugins", path = "src/plugins", description = "Plugins", required = true },
{ name = "modules", path = "src/modules", description = "Modules", required = true },
{ name = "templates", path = "src/templates", description = "Templates", required = true },
{ name = "media", path = "src/media", description = "Media assets", required = true },
{ name = "images", path = "src/images", description = "Site images", required = false },
{ name = "language", path = "src/language", description = "Language files", required = false },
{ name = "libraries", path = "src/libraries", description = "Libraries", required = false },
{ name = "layouts", path = "src/layouts", description = "Layouts", required = false }
]
},
{
name = "scripts"
path = "scripts"
description = "Deployment, sync, and monitoring scripts"
required = true
purpose = "Contains SFTP configs, sync scripts, and monitoring"
subdirectories = [
{
name = "sftp-config"
path = "scripts/sftp-config"
description = "SFTP connection configs (dev + live)"
required = true
files = [
{
name = "sftp-config.dev.json"
extension = "json"
description = "Dev server SFTP connection"
required = true
always_overwrite = false
},
{
name = "sftp-config.rs.json"
extension = "json"
description = "Live/release server SFTP connection"
required = true
always_overwrite = false
}
]
}
]
},
{
name = "monitoring"
path = "monitoring"
description = "Grafana dashboard templates"
required = true
purpose = "Contains Panopticon-style Grafana dashboard JSON"
subdirectories = [
{
name = "grafana"
path = "monitoring/grafana"
description = "Grafana dashboard JSON templates"
required = true
files = [
{
name = "client-joomla-dashboard.json"
extension = "json"
description = "Panopticon-style Grafana dashboard template"
required = true
always_overwrite = true
template = "templates/monitoring/client-joomla-dashboard.json"
}
]
}
]
},
{
name = ".gitea"
path = ".gitea"
description = "Gitea configuration"
required = true
purpose = "Contains Gitea Actions workflows"
subdirectories = [
{
name = "workflows"
path = ".gitea/workflows"
description = "Gitea Actions CI/CD workflows"
required = true
files = [
{
name = "auto-release.yml"
extension = "yml"
description = "Auto-release on merge to main"
required = true
always_overwrite = true
},
{
name = "deploy.yml"
extension = "yml"
description = "Deploy src/ to servers via SFTP"
required = true
always_overwrite = true
},
{
name = "add-endpoint.yml"
extension = "yml"
description = "Add monitoring endpoint to sites.json"
required = true
always_overwrite = true
}
]
}
]
}
]
}
}
output "client_structure" {
description = "Client site repository structure definition"
value = local.repository_structure
}
-207
View File
@@ -1,207 +0,0 @@
{
"schemaVersion": "1.0",
"metadata": {
"name": "Default Repository Structure",
"description": "Default repository structure applicable to all repository types with minimal requirements",
"repositoryType": "library",
"platform": "multi-platform",
"lastUpdated": "2026-01-16T00:00:00Z",
"maintainer": "Moko Consulting"
},
"structure": {
"rootFiles": [
{
"name": "README.md",
"extension": "md",
"description": "Project overview and documentation",
"requirementStatus": "required",
"audience": "general",
"template": "templates/docs/required/template-README.md"
},
{
"name": "LICENSE",
"extension": "",
"description": "License file (GPL-3.0-or-later)",
"requirementStatus": "required",
"audience": "general",
"template": "templates/licenses/GPL-3.0"
},
{
"name": "CHANGELOG.md",
"extension": "md",
"description": "Version history and changes",
"requirementStatus": "required",
"audience": "general",
"template": "templates/docs/required/template-CHANGELOG.md"
},
{
"name": "CONTRIBUTING.md",
"extension": "md",
"description": "Contribution guidelines",
"requirementStatus": "required",
"audience": "contributor",
"template": "templates/docs/required/template-CONTRIBUTING.md"
},
{
"name": "SECURITY.md",
"extension": "md",
"description": "Security policy and vulnerability reporting",
"requirementStatus": "required",
"audience": "general",
"template": "templates/docs/required/template-SECURITY.md"
},
{
"name": "CODE_OF_CONDUCT.md",
"extension": "md",
"description": "Community code of conduct",
"requirementStatus": "suggested",
"audience": "contributor",
"template": "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
"name": ".gitignore",
"extension": "gitignore",
"description": "Git ignore patterns",
"requirementStatus": "required",
"alwaysOverwrite": false,
"audience": "developer"
},
{
"name": ".gitattributes",
"extension": "gitattributes",
"description": "Git attributes configuration",
"requirementStatus": "required",
"audience": "developer"
},
{
"name": ".editorconfig",
"extension": "editorconfig",
"description": "Editor configuration for consistent coding style",
"requirementStatus": "required",
"alwaysOverwrite": false,
"audience": "developer"
},
{
"name": "Makefile",
"description": "Build automation",
"requirementStatus": "suggested",
"audience": "developer"
},
{
"name": "renovate.json",
"extension": "json",
"description": "Renovate dependency management configuration",
"requirementStatus": "required",
"alwaysOverwrite": false,
"audience": "developer",
"template": "templates/configs/renovate.json"
}
],
"directories": [
{
"name": "docs",
"path": "docs",
"description": "Documentation directory",
"requirementStatus": "required",
"purpose": "Contains comprehensive project documentation",
"files": [
{
"name": "index.md",
"extension": "md",
"description": "Documentation index",
"requirementStatus": "suggested"
}
]
},
{
"name": "scripts",
"path": "scripts",
"description": "Build and automation scripts",
"requirementStatus": "required",
"purpose": "Contains scripts for building, testing, and deploying"
},
{
"name": "src",
"path": "src",
"description": "Source code directory",
"requirementStatus": "required",
"purpose": "Contains application source code"
},
{
"name": "tests",
"path": "tests",
"description": "Test files",
"requirementStatus": "suggested",
"purpose": "Contains unit tests, integration tests, and test fixtures",
"subdirectories": [
{
"name": "unit",
"path": "tests/unit",
"description": "Unit tests",
"requirementStatus": "suggested"
},
{
"name": "integration",
"path": "tests/integration",
"description": "Integration tests",
"requirementStatus": "optional"
}
]
},
{
"name": ".github",
"path": ".github",
"description": "Gitea/GitHub Actions configuration (Gitea reads .github/workflows natively)",
"requirementStatus": "required",
"purpose": "Contains CI/CD workflows and repository configuration. Gitea is the primary platform; GitHub is backup only.",
"subdirectories": [
{
"name": "workflows",
"path": ".github/workflows",
"description": "CI/CD workflows (Gitea-primary, GitHub-compatible)",
"requirementStatus": "required",
"requiredFiles": [
"auto-assign.yml",
"auto-dev-issue.yml",
"auto-release.yml",
"branch-freeze.yml",
"changelog-validation.yml",
"repository-cleanup.yml",
"sync-version-on-merge.yml",
"cascade-dev.yml",
"gitleaks.yml"
]
}
]
},
{
"name": "node_modules",
"path": "node_modules",
"description": "Node.js dependencies (generated)",
"requirementStatus": "not-allowed",
"purpose": "Generated directory that should not be committed"
},
{
"name": "vendor",
"path": "vendor",
"description": "PHP dependencies (generated)",
"requirementStatus": "not-allowed",
"purpose": "Generated directory that should not be committed"
},
{
"name": "build",
"path": "build",
"description": "Build artifacts (generated)",
"requirementStatus": "not-allowed",
"purpose": "Generated directory that should not be committed"
},
{
"name": "dist",
"path": "dist",
"description": "Distribution files (generated)",
"requirementStatus": "not-allowed",
"purpose": "Generated directory that should not be committed"
}
]
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-686
View File
@@ -1,686 +0,0 @@
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "05.00.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
name = "composer.json"
extension = "json"
description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
required = true
always_overwrite = false
audience = "developer"
template = "templates/configs/composer.generic.json"
},
{
name = "renovate.json"
extension = "json"
description = "Renovate dependency management configuration"
requirement_status = "required"
always_overwrite = false
audience = "developer"
template = "templates/configs/renovate.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
name = "scripts"
path = "scripts"
description = "Repo-specific scripts — not managed by MokoStandards sync"
required = false
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
files = [
{
name = "MokoStandards.override.xml"
extension = "xml"
description = "MokoStandards sync override configuration"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = true
template = ".github/workflows/standards-compliance.yml"
},
{
name = "enterprise-firewall-setup.yml"
extension = "yml"
description = "Enterprise firewall configuration for trusted domain access"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment of src/ to the development server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment of src/ to the demo server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment of src/ to the release staging server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-rs.yml.template"
},
{
name = "sync-version-on-merge.yml"
extension = "yml"
description = "Auto-bump patch version on merge and propagate to all file headers"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
},
{
name = "auto-release.yml"
extension = "yml"
description = "Auto-create GitHub Release on push to main with version from README.md"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/repository-cleanup.yml.template"
},
{
name = "auto-dev-issue.yml"
extension = "yml"
description = "Auto-create tracking issue when a dev/** branch is pushed"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
},
{
name = "cascade-dev.yml"
extension = "yml"
description = "Forward-merge main to all open branches (dev, rc/*, beta/*, alpha/*) on push to main"
requirement_status = "required"
always_overwrite = true
template = "workflows/cascade-dev.yml"
},
{
name = "gitleaks.yml"
extension = "yml"
description = "Secret scanning — detect leaked credentials, API keys, and tokens using Gitleaks"
requirement_status = "required"
always_overwrite = true
template = "workflows/gitleaks.yml"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates synced from MokoStandards"
requirement_status = "required"
files = [
{
name = "config.yml"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/config.yml"
},
{
name = "adr.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/adr.md"
},
{
name = "bug_report.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
},
{
name = "documentation.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
},
{
name = "enterprise_support.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
},
{
name = "feature_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
},
{
name = "firewall-request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
},
{
name = "question.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/question.md"
},
{
name = "request-license.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
},
{
name = "rfc.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
},
{
name = "security.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/security.md"
}
]
}
]
},
{
name = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT — configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 0
dismiss_stale_reviews = true
block_on_rejected_reviews = true
restrict_pushes = true
push_whitelist = ["jmiller"]
enable_force_push = true
force_push_whitelist = ["jmiller"]
enforce_admins = false
},
{
branch_pattern = "dev"
require_pull_request = false
required_approvals = 0
restrict_pushes = false
enable_force_push = true
force_push_whitelist = ["jmiller"]
},
{
branch_pattern = "rc/*"
require_pull_request = false
required_approvals = 0
restrict_pushes = false
enable_force_push = true
force_push_whitelist = ["jmiller"]
},
{
branch_pattern = "beta/*"
require_pull_request = false
required_approvals = 0
restrict_pushes = false
enable_force_push = true
force_push_whitelist = ["jmiller"]
},
{
branch_pattern = "alpha/*"
require_pull_request = false
required_approvals = 0
restrict_pushes = false
enable_force_push = true
force_push_whitelist = ["jmiller"]
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
@@ -1,331 +0,0 @@
/**
* .github-private Repository Structure Definition
* Org-level private repository containing universal GitHub Actions workflows,
* helper scripts, and default issue templates for all governed repositories.
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*
* NOTES
* ─────
* • GitHub reads ISSUE_TEMPLATE/ from this repo as org-wide defaults for any
* governed repo that does not supply its own templates.
* • Workflows in .github/workflows/ support both standalone execution and
* workflow_call so governed repos can invoke them as reusable workflows via
* `uses: mokoconsulting-tech/.github-private/.github/workflows/<name>.yml@main`.
* • This repo is EXCLUDED from bulk-repo-sync — it manages its own content
* independently as GitHub's org-level defaults repo.
*/
locals {
github_private_repository_structure = {
metadata = {
name = ".github-private"
description = "Private GitHub org defaults — universal workflows, issue templates, and helper scripts"
repository_type = "github-private"
platform = "github-private"
last_updated = "2026-03-12T00:00:00Z"
maintainer = "Moko Consulting"
version = "05.00.00"
schema_version = "1.0"
visibility = "private"
sync_priority = -1
exclude_from_sync = true
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Repository overview — purpose, contents, and how governed repos use this repo"
required = true
always_overwrite = false
protected = true
audience = "general"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
required = true
audience = "general"
template = "templates/licenses/GPL-3.0"
license_type = "GPL-3.0-or-later"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
required = true
audience = "general"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and private vulnerability reporting"
required = true
audience = "general"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
required = true
always_overwrite = true
audience = "contributor"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
required = true
audience = "contributor"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Governance policy and decision-making process"
required = true
always_overwrite = true
audience = "general"
template = "templates/docs/required/GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
required = true
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
required = true
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
required = true
always_overwrite = false
audience = "developer"
},
{
name = ".gitea/.mokostandards"
extension = "xml"
description = "MokoStandards XML manifest — generated programmatically by RepositorySynchronizer::migrateMokoStandards()"
required = true
always_overwrite = false
template = "managed-by-sync"
source_type = "programmatic"
}
]
directories = [
{
name = "ISSUE_TEMPLATE"
path = "ISSUE_TEMPLATE"
description = "Org-default issue templates — applied to all governed repos without their own templates"
requirement_status = "required"
purpose = "GitHub reads ISSUE_TEMPLATE/ from this repo as org-wide defaults"
files = [
{
name = "config.yml"
extension = "yml"
description = "Issue template chooser — disables blank issues and lists contact links"
requirement_status = "required"
always_overwrite = true
template = "templates/github-private/ISSUE_TEMPLATE/config.yml.template"
},
{
name = "bug_report.md"
extension = "md"
description = "Bug report issue template"
requirement_status = "required"
always_overwrite = false
template = "templates/github-private/ISSUE_TEMPLATE/bug_report.md.template"
},
{
name = "feature_request.md"
extension = "md"
description = "Feature request issue template"
requirement_status = "required"
always_overwrite = false
template = "templates/github-private/ISSUE_TEMPLATE/feature_request.md.template"
}
]
},
{
name = "scripts"
path = "scripts"
description = "Helper scripts used by universal workflows and available as git hooks"
requirement_status = "required"
purpose = "Reusable Bash utilities for commit-message and PR-title validation"
files = [
{
name = "check-pr-title.sh"
extension = "sh"
description = "Validates PR title follows conventional-commit format"
requirement_status = "required"
always_overwrite = true
template = "templates/github-private/scripts/check-pr-title.sh.template"
},
{
name = "check-commit-msg.sh"
extension = "sh"
description = "Validates individual commit messages follow conventional-commit format; usable as a git commit-msg hook"
requirement_status = "required"
always_overwrite = true
template = "templates/github-private/scripts/check-commit-msg.sh.template"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration for .github-private itself"
requirement_status = "required"
purpose = "Contains CI workflows for this repo and reusable workflows callable org-wide"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "CI + universal reusable workflows; callable via uses: mokoconsulting-tech/.github-private/.github/workflows/<name>.yml@main"
requirement_status = "required"
files = [
{
name = "stale.yml"
extension = "yml"
description = "Marks stale issues and pull requests; standalone (schedule) and reusable (workflow_call)"
requirement_status = "required"
always_overwrite = true
template = "templates/github-private/workflows/stale.yml.template"
},
{
name = "auto-assign.yml"
extension = "yml"
description = "Auto-assigns PR author and logs CODEOWNERS status; standalone and reusable"
requirement_status = "required"
always_overwrite = true
template = "templates/github-private/workflows/auto-assign.yml.template"
},
{
name = "pr-labeler.yml"
extension = "yml"
description = "Labels PRs from branch name and validates PR title format; standalone and reusable"
requirement_status = "required"
always_overwrite = true
template = "templates/github-private/workflows/pr-labeler.yml.template"
},
{
name = "welcome.yml"
extension = "yml"
description = "Posts welcome message on first-time contributor PRs and issues; standalone and reusable"
requirement_status = "required"
always_overwrite = true
template = "templates/github-private/workflows/welcome.yml.template"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
template = ".github/workflows/standards-compliance.yml"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment of src/ to the development server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "sync-version-on-merge.yml"
extension = "yml"
description = "Auto-bump patch version on merge and propagate to all file headers"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
}
]
}
]
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT for automation — required for bulk sync and workflow execution"
required = true
scope = "org"
},
{
name = "DEV_FTP_KEY"
description = "SSH private key for SFTP dev deployment (preferred); if DEV_FTP_PASSWORD is also set it is used as the key passphrase, with password-only as fallback"
required = false
scope = "org"
},
{
name = "DEV_FTP_PASSWORD"
description = "SFTP password for dev deployment; used as SSH key passphrase when DEV_FTP_KEY is also set, and as standalone fallback if key auth fails"
required = false
scope = "org"
note = "At least one of DEV_FTP_KEY or DEV_FTP_PASSWORD must be configured"
}
]
variables = [
{
name = "DEV_FTP_HOST"
description = "Dev server hostname; may include port suffix (e.g. dev.example.com or dev.example.com:2222)"
required = true
scope = "org"
},
{
name = "DEV_FTP_PATH"
description = "Base remote path for SFTP deployment (e.g. /var/www/html)"
required = true
scope = "org"
},
{
name = "DEV_FTP_USERNAME"
description = "SFTP username for dev server authentication"
required = true
scope = "org"
},
{
name = "DEV_FTP_PORT"
description = "Explicit SFTP port override; if omitted the port is parsed from DEV_FTP_HOST or defaults to 22"
required = false
scope = "org"
},
{
name = "DEV_FTP_PATH_SUFFIX"
description = "Per-repo path suffix appended to DEV_FTP_PATH (e.g. /.github-private)"
required = false
scope = "repo"
}
]
repository_settings = {
visibility = "private"
has_issues = true
has_projects = false
has_wiki = false
has_discussions = false
allow_squash_merge = true
allow_merge_commit = false
allow_rebase_merge = true
delete_branch_on_merge = true
}
}
}
}
File diff suppressed because it is too large Load Diff
-484
View File
@@ -1,484 +0,0 @@
/**
* MCP Server Repository Structure Definition
* Standard repository structure for Model Context Protocol (MCP) server projects
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "MCP Server"
description = "Standard repository structure for Model Context Protocol (MCP) server projects — TypeScript/Node.js MCP servers that expose external APIs as AI assistant tools"
repository_type = "mcp-server"
platform = "mcp-server"
last_updated = "2026-05-07T00:00:00Z"
maintainer = "Moko Consulting"
version = "04.06.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview with tool reference table, install, and configuration"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "package.json"
extension = "json"
description = "Node.js project manifest — @mokoconsulting scoped, MCP SDK + Zod dependencies"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "developer"
},
{
name = "tsconfig.json"
extension = "json"
description = "TypeScript configuration — ES2022 target, Node16 module, strict mode"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "config.example.json"
extension = "json"
description = "Example multi-connection configuration file"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".gitmessage"
extension = "gitmessage"
description = "Conventional commit message template"
requirement_status = "required"
always_overwrite = true
audience = "developer"
},
{
name = "Makefile"
description = "Build automation — install, build, dev, clean, setup, start targets"
requirement_status = "required"
always_overwrite = false
audience = "developer"
}
]
directories = [
{
name = "src"
path = "src"
description = "TypeScript source code"
requirement_status = "required"
purpose = "Contains MCP server entry point, API client, config loader, and type definitions"
files = [
{
name = "index.ts"
extension = "ts"
description = "MCP server entry point — registers all API tools with McpServer"
requirement_status = "required"
},
{
name = "client.ts"
extension = "ts"
description = "HTTP client wrapper for the target API (GET/POST/PUT/DELETE)"
requirement_status = "required"
},
{
name = "config.ts"
extension = "ts"
description = "Configuration loader — reads ~/.{project}.json with multi-connection support"
requirement_status = "required"
},
{
name = "types.ts"
extension = "ts"
description = "TypeScript interfaces for connection, config, and API response types"
requirement_status = "required"
}
]
},
{
name = "scripts"
path = "scripts"
description = "Setup and utility scripts"
requirement_status = "required"
purpose = "Contains interactive setup wizard and repo-specific helpers"
files = [
{
name = "setup.mjs"
extension = "mjs"
description = "Interactive setup wizard — prompts for API connection details and writes config"
requirement_status = "required"
always_overwrite = false
protected = true
}
]
},
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
}
]
},
{
name = ".gitea"
path = ".gitea"
description = "Gitea-specific configuration"
requirement_status = "required"
purpose = "Contains Gitea Actions workflows and platform configuration"
files = [
{
name = ".mokostandards"
description = "MokoStandards platform declaration — must contain 'platform: mcp-server'"
requirement_status = "required"
always_overwrite = false
}
]
subdirectories = [
{
name = "workflows"
path = ".gitea/workflows"
description = "Gitea Actions workflows"
requirement_status = "required"
files = [
{
name = "auto-release.yml"
extension = "yml"
description = "Auto-create release on push to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "auto-dev-issue.yml"
extension = "yml"
description = "Auto-create tracking issue when a dev/** branch is pushed"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
},
{
name = "auto-assign.yml"
extension = "yml"
description = "Auto-assign issues and PRs"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-assign.yml.template"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/standards-compliance.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/codeql-analysis.yml.template"
},
{
name = "changelog-validation.yml"
extension = "yml"
description = "CHANGELOG validation on PR"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/changelog-validation.yml.template"
},
{
name = "sync-version-on-merge.yml"
extension = "yml"
description = "Auto-bump patch version on merge"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup of stale branches and workflow runs"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/repository-cleanup.yml.template"
},
{
name = "enterprise-firewall-setup.yml"
extension = "yml"
description = "Enterprise firewall configuration for trusted domain access"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "Deployment to development server"
requirement_status = "suggested"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "Deployment to demo server on merge to main"
requirement_status = "suggested"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "copilot-agent.yml"
extension = "yml"
description = "Copilot agent workflow for automated code review"
requirement_status = "optional"
always_overwrite = true
template = "templates/workflows/shared/copilot-agent.yml.template"
},
{
name = "mcp-build-test.yml"
extension = "yml"
description = "MCP server build validation — TypeScript compile, dist verification, tool count"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/mcp/mcp-build-test.yml.template"
},
{
name = "mcp-sdk-check.yml"
extension = "yml"
description = "Weekly check for MCP SDK and Zod updates — creates issue when new version available"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/mcp/mcp-sdk-check.yml.template"
},
{
name = "mcp-tool-inventory.yml"
extension = "yml"
description = "Generate tool inventory report on push to main"
requirement_status = "suggested"
always_overwrite = true
template = "templates/workflows/mcp/mcp-tool-inventory.yml.template"
}
]
}
]
},
{
name = "dist"
path = "dist"
description = "Compiled JavaScript output (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level Gitea PAT — configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "Gitea Actions workflows"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "20"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
},
{
name = "new-tool"
color = "5319e7"
description = "New MCP tool/endpoint to add"
},
{
name = "api-change"
color = "fbca04"
description = "Upstream API changed — tool needs update"
}
]
}
}
}
-331
View File
@@ -1,331 +0,0 @@
/**
* Dolibarr Platform Structure Definition
* Standard repository structure for the full Dolibarr ERP/CRM installation
*
* This is distinct from dolibarr — it defines the ENTIRE Dolibarr platform
* (htdocs/, not src/). It does NOT have a module descriptor, numero, or
* publish-to-mokodolimods workflow.
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Dolibarr Platform"
description = "Full Dolibarr ERP/CRM installation — htdocs/ root, not a module"
repository_type = "platform"
platform = "dolibarr"
last_updated = "2026-03-31T00:00:00Z"
maintainer = "Moko Consulting"
version = "05.00.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Developer-focused documentation"
required = true
always_overwrite = false
protected = true
audience = "developer"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
required = true
always_overwrite = true
template = "templates/docs/required/template-CONTRIBUTING.md"
audience = "contributor"
},
{
name = "LICENSE"
extension = ""
description = "GPL-3.0-or-later license file"
required = true
always_overwrite = true
template = "templates/docs/required/LICENSE"
},
{
name = "composer.json"
extension = "json"
description = "Composer package definition"
required = true
always_overwrite = false
},
{
name = "phpstan.neon"
extension = "neon"
description = "PHPStan static analysis configuration"
required = true
always_overwrite = true
template = "templates/configs/phpstan.neon"
},
{
name = "Makefile"
extension = ""
description = "Build automation targets"
required = true
always_overwrite = true
template = "templates/configs/Makefile"
},
{
name = "src/.ftpignore"
extension = ""
description = "Files excluded from SFTP deployment"
required = true
always_overwrite = true
template = "templates/configs/ftp_ignore"
},
{
name = ".gitea/.mokostandards"
extension = "xml"
description = "MokoStandards XML manifest — generated programmatically by RepositorySynchronizer::migrateMokoStandards()"
required = true
always_overwrite = false
template = "managed-by-sync"
source_type = "programmatic"
},
{
name = "renovate.json"
extension = "json"
description = "Renovate dependency management configuration"
required = true
always_overwrite = false
audience = "developer"
template = "templates/configs/renovate.json"
}
]
directories = [
{
name = "htdocs"
path = "htdocs"
description = "Dolibarr web root — entire platform"
required = true
purpose = "Contains the full Dolibarr installation including core, custom modules, and themes"
},
{
name = "docs"
path = "docs"
description = "Developer and technical documentation"
required = true
purpose = "Contains technical documentation"
files = [
{
name = "update-server.md"
extension = "md"
description = "Dolibarr update server (update.txt) documentation"
required = true
always_overwrite = true
template = "templates/docs/required/template-update-server-dolibarr.md"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub configuration"
required = true
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
template = ".github/workflows/standards-compliance.yml"
},
{
name = "enterprise-firewall-setup.yml"
extension = "yml"
description = "Enterprise firewall configuration"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment to dev server (htdocs/)"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment to demo server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment to release staging server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-rs.yml.template"
},
{
name = "sync-version-on-merge.yml"
extension = "yml"
description = "Auto-bump patch version on merge"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
},
{
name = "auto-release.yml"
extension = "yml"
description = "Auto-create GitHub Release on minor version"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup: retired workflows, stale branches"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/repository-cleanup.yml.template"
},
{
name = "auto-dev-issue.yml"
extension = "yml"
description = "Auto-create tracking issue on dev/rc branch creation"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
},
{
name = "repo_health.yml"
extension = "yml"
description = "Dolibarr platform health checks (shared guardrails, no module-specific checks)"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/dolibarr/repo_health.yml.template"
},
{
name = "cascade-dev.yml"
extension = "yml"
description = "Forward-merge main to all open branches (dev, rc/*, beta/*, alpha/*) on push to main"
requirement_status = "required"
always_overwrite = true
template = "workflows/cascade-dev.yml"
},
{
name = "gitleaks.yml"
extension = "yml"
description = "Secret scanning — detect leaked credentials, API keys, and tokens using Gitleaks"
requirement_status = "required"
always_overwrite = true
template = "workflows/gitleaks.yml"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates"
requirement_status = "required"
files = [
{
name = "config.yml"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/config.yml"
},
{
name = "adr.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/adr.md"
},
{
name = "bug_report.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
},
{
name = "documentation.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
},
{
name = "enterprise_support.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
},
{
name = "feature_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
},
{
name = "firewall-request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
},
{
name = "question.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/question.md"
},
{
name = "request-license.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
},
{
name = "rfc.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
},
{
name = "security.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/security.md"
},
{
name = "dolibarr_issue.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/dolibarr_issue.md"
},
{
name = "dolibarr_module_id_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/dolibarr_module_id_request.md"
}
]
}
]
}
]
}
}
output "crm_platform_structure" {
description = "Dolibarr Platform repository structure definition"
value = local.repository_structure
}
-763
View File
@@ -1,763 +0,0 @@
/**
* MokoStandards Repository Structure Definition
* Repository structure definition for the MokoStandards standards and templates repository
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "MokoStandards Repository"
description = "Repository structure definition for MokoStandards - organizational standards, templates, and automation"
repository_type = "standards"
platform = "standards"
last_updated = "2026-03-03T00:00:00Z"
maintainer = "Moko Consulting"
version = "05.00.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Repository overview and documentation"
required = true
always_overwrite = false
protected = true
audience = "general"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
required = true
audience = "general"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
required = true
always_overwrite = false
protected = true
audience = "general"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
required = true
always_overwrite = true
template = "templates/docs/required/template-SECURITY.md"
always_overwrite = false
protected = true
audience = "general"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
required = true
always_overwrite = true
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
always_overwrite = false
protected = true
audience = "contributor"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
required = true
always_overwrite = false
protected = true
audience = "general"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
required = true
always_overwrite = true
template = "templates/docs/required/template-CONTRIBUTING.md"
always_overwrite = false
protected = true
audience = "contributor"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
required = true
always_overwrite = false
protected = true
audience = "general"
},
{
name = "CITATION.cff"
extension = "cff"
description = "Citation file format for academic references"
required = true
audience = "general"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
required = true
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
required = true
audience = "developer"
},
{
name = ".gitmessage"
extension = "gitmessage"
description = "Git commit message template"
required = true
audience = "developer"
},
{
name = ".git-blame-ignore-revs"
extension = "git-blame-ignore-revs"
description = "Git blame ignore revisions"
requirement_status = "suggested"
audience = "developer"
},
{
name = ".mailmap"
extension = "mailmap"
description = "Git mailmap for contributor attribution"
requirement_status = "suggested"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
required = true
always_overwrite = false
audience = "developer"
},
{
name = ".eslintrc.json"
extension = "json"
description = "ESLint configuration for JavaScript"
requirement_status = "suggested"
audience = "developer"
},
{
name = ".prettierrc.json"
extension = "json"
description = "Prettier configuration for code formatting"
requirement_status = "suggested"
audience = "developer"
},
{
name = ".markdownlint.json"
extension = "json"
description = "Markdown linting configuration"
requirement_status = "suggested"
audience = "developer"
},
{
name = ".yamllint"
extension = "yamllint"
description = "YAML linting configuration"
requirement_status = "suggested"
audience = "developer"
},
{
name = ".pylintrc"
extension = "pylintrc"
description = "Python linting configuration"
requirement_status = "suggested"
audience = "developer"
},
{
name = ".htmlhintrc"
extension = "htmlhintrc"
description = "HTML linting configuration"
requirement_status = "suggested"
audience = "developer"
},
{
name = "composer.json"
extension = "json"
description = "PHP dependency management"
requirement_status = "suggested"
audience = "developer"
},
{
name = ".gitea/.mokostandards"
extension = "xml"
description = "MokoStandards XML manifest — generated programmatically by RepositorySynchronizer::migrateMokoStandards()"
requirement_status = "required"
always_overwrite = false
audience = "developer"
template = "managed-by-sync"
source_type = "programmatic"
},
{
name = "renovate.json"
extension = "json"
description = "Renovate dependency management configuration"
required = true
always_overwrite = false
audience = "developer"
template = "templates/configs/renovate.json"
}
]
directories = [
{
name = "api"
path = "api"
description = "API scripts and automation"
required = true
purpose = "Contains all operational scripts - validation, automation, build, release, etc."
subdirectories = [
{
name = "validate"
path = "api/validate"
description = "Validation scripts"
required = true
purpose = "Scripts for validating repository structure, health, and compliance"
},
{
name = "automation"
path = "api/automation"
description = "Automation scripts"
required = true
purpose = "Scripts for bulk operations and repository synchronization"
},
{
name = "build"
path = "api/build"
description = "Build scripts"
requirement_status = "suggested"
purpose = "Scripts for building and packaging"
},
{
name = "release"
path = "api/release"
description = "Release scripts"
requirement_status = "suggested"
purpose = "Scripts for release management"
},
{
name = "tests"
path = "api/tests"
description = "Test scripts"
requirement_status = "suggested"
purpose = "Test scripts and test data"
},
{
name = "maintenance"
path = "api/maintenance"
description = "Maintenance scripts"
requirement_status = "suggested"
purpose = "Scripts for repository maintenance tasks"
},
{
name = "definitions"
path = "api/definitions"
description = "Repository structure definitions"
required = true
purpose = "HCL/Terraform definition files for different repository types"
},
{
name = "lib"
path = "api/lib"
description = "Shared libraries"
requirement_status = "suggested"
purpose = "Shared code libraries and utilities"
}
]
},
{
name = "docs"
path = "docs"
description = "Documentation"
required = true
purpose = "Comprehensive documentation for standards, guides, policies, and references"
subdirectories = [
{
name = "guide"
path = "docs/guide"
description = "User guides"
requirement_status = "suggested"
},
{
name = "reference"
path = "docs/reference"
description = "Reference documentation"
requirement_status = "suggested"
},
{
name = "policy"
path = "docs/policy"
description = "Policies and standards"
requirement_status = "suggested"
},
{
name = "workflows"
path = "docs/workflows"
description = "Workflow documentation"
requirement_status = "suggested"
},
{
name = "security"
path = "docs/security"
description = "Security documentation"
requirement_status = "suggested"
},
{
name = "development"
path = "docs/development"
description = "Development documentation"
requirement_status = "suggested"
}
]
},
{
name = "templates"
path = "templates"
description = "Template files"
required = true
purpose = "Template files for workflows, configs, documentation, and projects"
subdirectories = [
{
name = "workflows"
path = "templates/workflows"
description = "GitHub Actions workflow templates"
required = true
},
{
name = "github"
path = "templates/github"
description = "GitHub configuration templates"
required = true
},
{
name = "docs"
path = "templates/docs"
description = "Documentation templates"
requirement_status = "suggested"
},
{
name = "configs"
path = "templates/configs"
description = "Configuration file templates"
requirement_status = "suggested"
},
{
name = "licenses"
path = "templates/licenses"
description = "License templates"
requirement_status = "suggested"
},
{
name = "projects"
path = "templates/projects"
description = "Project definition templates"
requirement_status = "suggested"
},
{
name = "terraform"
path = "templates/terraform"
description = "Terraform configuration templates"
requirement_status = "suggested"
},
{
name = "scripts"
path = "templates/scripts"
description = "Script templates"
requirement_status = "suggested"
}
]
},
{
name = "logs"
path = "logs"
description = "Log files"
requirement_status = "suggested"
purpose = "Storage for operation logs, audit trails, and metrics"
subdirectories = [
{
name = "audit"
path = "logs/audit"
description = "Audit logs"
requirement_status = "suggested"
},
{
name = "automation"
path = "logs/automation"
description = "Automation logs"
requirement_status = "suggested"
},
{
name = "validation"
path = "logs/validation"
description = "Validation logs"
requirement_status = "suggested"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
required = true
purpose = "GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
required = true
files = [
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment of src/ to the development server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment of src/ to the demo server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment of src/ to the release staging server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-rs.yml.template"
},
{
name = "sync-version-on-merge.yml"
extension = "yml"
description = "Auto-bump patch version on merge and propagate to all file headers"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
},,
},
{
name = "auto-release.yml"
extension = "yml"
description = "Auto-create GitHub Release on push to main with version from README.md"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "One-time cleanup: reset labels, strip issue template headers, delete old branches — self-deletes after run"
requirement_status = "suggested"
always_overwrite = true
template = "templates/workflows/shared/repository-cleanup.yml.template"
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards self-compliance validation"
requirement_status = "suggested"
always_overwrite = true
template = ".github/workflows/standards-compliance.yml"
},
{
name = "enterprise-firewall-setup.yml"
extension = "yml"
description = "Enterprise firewall rules setup workflow"
requirement_status = "suggested"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "auto-dev-issue.yml"
extension = "yml"
description = "Auto-create tracking issue when a dev/** branch is pushed"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
},
{
name = "cascade-dev.yml"
extension = "yml"
description = "Forward-merge main to all open branches (dev, rc/*, beta/*, alpha/*) on push to main"
requirement_status = "required"
always_overwrite = true
template = "workflows/cascade-dev.yml"
},
{
name = "gitleaks.yml"
extension = "yml"
description = "Secret scanning — detect leaked credentials, API keys, and tokens using Gitleaks"
requirement_status = "required"
always_overwrite = true
template = "workflows/gitleaks.yml"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates"
requirement_status = "required"
files = [
{
name = "config.yml"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/config.yml"
},
{
name = "adr.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/adr.md"
},
{
name = "bug_report.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
},
{
name = "documentation.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
},
{
name = "enterprise_support.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
},
{
name = "feature_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
},
{
name = "firewall-request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
},
{
name = "question.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/question.md"
},
{
name = "request-license.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
},
{
name = "rfc.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
},
{
name = "security.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/security.md"
}
]
}
]
files = [
{
name = "config.tf"
extension = "tf"
description = "Repository override configuration for bulk sync"
requirement_status = "suggested"
always_overwrite = false
audience = "developer"
},
{
name = "copilot.yml"
extension = "yml"
description = "GitHub Copilot configuration — topic list and repo metadata"
requirement_status = "required"
always_overwrite = true
audience = "developer"
},
{
name = "copilot-instructions.md"
extension = "md"
description = "GitHub Copilot custom instructions for this repository"
requirement_status = "suggested"
always_overwrite = false
audience = "developer"
},
{
name = "CLAUDE.md"
extension = "md"
description = "Claude Code context and instructions for this repository"
requirement_status = "suggested"
always_overwrite = false
audience = "developer"
}
]
},
{
name = ".checkpoints"
path = ".checkpoints"
description = "Checkpoint files for long-running operations"
requirement_status = "optional"
purpose = "Stores checkpoint data for resumable operations"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT for automation — configure in org Actions secrets"
required = true
},
{
name = "DEV_FTP_KEY"
description = "SSH private key for SFTP dev deployment (preferred); if DEV_FTP_PASSWORD is also set it is used as the key passphrase, with password-only as fallback"
required = false
scope = "org"
},
{
name = "DEV_FTP_PASSWORD"
description = "SFTP password for dev deployment; used as SSH key passphrase when DEV_FTP_KEY is also set, and as standalone fallback if key auth fails"
required = false
scope = "org"
note = "At least one of DEV_FTP_KEY or DEV_FTP_PASSWORD must be configured"
}
]
variables = [
{
name = "STANDARDS_VERSION"
description = "Current MokoStandards version"
required = false
},
{
name = "DEV_FTP_HOST"
description = "Dev server hostname; may include port suffix (e.g. dev.example.com or dev.example.com:2222)"
required = true
scope = "org"
},
{
name = "DEV_FTP_PATH"
description = "Base remote path for SFTP deployment (e.g. /var/www/html)"
required = true
scope = "org"
},
{
name = "DEV_FTP_USERNAME"
description = "SFTP username for dev server authentication"
required = true
scope = "org"
},
{
name = "DEV_FTP_PORT"
description = "Explicit SFTP port override; if omitted the port is parsed from DEV_FTP_HOST or defaults to 22"
required = false
scope = "org"
},
{
name = "DEV_FTP_SUFFIX"
description = "Per-repo path suffix appended to DEV_FTP_PATH (e.g. /mokostandards)"
required = false
scope = "repo"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 0
dismiss_stale_reviews = true
block_on_rejected_reviews = true
restrict_pushes = true
push_whitelist = ["jmiller"]
enable_force_push = true
force_push_whitelist = ["jmiller"]
enforce_admins = false
},
{
branch_pattern = "dev"
require_pull_request = false
required_approvals = 0
restrict_pushes = false
enable_force_push = true
force_push_whitelist = ["jmiller"]
},
{
branch_pattern = "rc/*"
require_pull_request = false
required_approvals = 0
restrict_pushes = false
enable_force_push = true
force_push_whitelist = ["jmiller"]
},
{
branch_pattern = "beta/*"
require_pull_request = false
required_approvals = 0
restrict_pushes = false
enable_force_push = true
force_push_whitelist = ["jmiller"]
},
{
branch_pattern = "alpha/*"
require_pull_request = false
required_approvals = 0
restrict_pushes = false
enable_force_push = true
force_push_whitelist = ["jmiller"]
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = true
allow_squash_merge = true
allow_merge_commit = false
allow_rebase_merge = true
delete_branch_on_merge = true
}
labels = [
{ name = "bulk-sync-success", color = "0e8a16", description = "Bulk sync completed successfully" },
{ name = "bulk-sync-failure", color = "d73a4a", description = "Bulk sync failed" },
{ name = "standards-update", color = "fbca04", description = "Standards update" },
{ name = "template-update", color = "d4c5f9", description = "Template file update" },
{ name = "documentation", color = "0075ca", description = "Documentation changes" },
{ name = "automation", color = "5319e7", description = "Automation scripts" }
]
}
}
}
-31
View File
@@ -1,31 +0,0 @@
<!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
SPDX-License-Identifier: GPL-3.0-or-later
FILE INFORMATION
DEFGROUP: MokoStandards.Index
INGROUP: MokoStandards.Definitions
REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
PATH: /definitions/index.md
BRIEF: Definitions directory index
-->
# Docs Index: /api/definitions
## Purpose
This index provides navigation to documentation within this folder.
## Documents
- [README](./README.md)
## Metadata
- **Document Type:** index
- **Auto-generated:** This file is automatically generated by rebuild_indexes.py
## Revision History
| Date | Author | Change | Notes |
| ---------- | ------------------ | ----------------- | ------------------------------------------ |
| Auto | rebuild_indexes.py | Automated update | Generated by documentation index automation |
-682
View File
@@ -1,682 +0,0 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/.github-private
*
* Auto-generated by MokoStandards bulk sync on 2026-03-24T19:30:16+00:00
* Platform : default-repository
* Description: This is the private organization-level configuration repository for mokoconsulting-tech. It provides centralized community health files, templates, and standards that apply across all private repositories in the organization.
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/.github-private"
default_branch = "main"
detected_platform = "default-repository"
description = "This is the private organization-level configuration repository for mokoconsulting-tech. It provides centralized community health files, templates, and standards that apply across all private repositories in the organization."
sync_timestamp = "2026-03-24T19:30:16+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 39
created_files = 0
updated_files = 28
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/ci.yml" action = "updated" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/workflows/ci.yml" action = "updated" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/repo-health.yml" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/repo-health.yml" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "05.00.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
name = "composer.json"
extension = "json"
description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
required = true
always_overwrite = false
audience = "developer"
template = "templates/configs/composer.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
name = "scripts"
path = "scripts"
description = "Repo-specific scripts — not managed by MokoStandards sync"
required = false
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
files = [
{
name = "MokoStandards.override.xml"
extension = "xml"
description = "MokoStandards sync override configuration"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "ci.yml"
extension = "yml"
description = "Continuous integration workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "ci.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "ci.yml"
create_path = true
template = "templates/workflows/generic/ci.yml.template"
},
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "repo-health.yml"
extension = "yml"
description = "Repository health monitoring"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "repo_health.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "repo-health.yml"
create_path = true
template = "templates/workflows/generic/repo_health.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = true
template = ".github/workflows/standards-compliance.yml"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates synced from MokoStandards"
requirement_status = "required"
files = [
{
name = "config.yml"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/config.yml"
},
{
name = "adr.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/adr.md"
},
{
name = "bug_report.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
},
{
name = "documentation.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
},
{
name = "enterprise_support.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
},
{
name = "feature_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
},
{
name = "firewall-request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
},
{
name = "question.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/question.md"
},
{
name = "request-license.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
},
{
name = "rfc.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
},
{
name = "security.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/security.md"
}
]
}
]
},
{
name = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT — configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
-733
View File
@@ -1,733 +0,0 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/.github
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T21:05:55+00:00
* Platform : default-repository
* Description:
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/.github"
default_branch = "main"
detected_platform = "default-repository"
description = ""
sync_timestamp = "2026-04-02T21:05:55+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 42
updated_files = 5
skipped_files = 6
}
synced_files = [
{ path = "LICENSE" action = "created" },
{ path = "CONTRIBUTING.md" action = "created" },
{ path = "SECURITY.md" action = "created" },
{ path = "CODE_OF_CONDUCT.md" action = "created" },
{ path = "ROADMAP.md" action = "created" },
{ path = "Makefile" action = "created" },
{ path = "composer.json" action = "created" },
{ path = "docs/index.md" action = "created" },
{ path = "docs/INSTALLATION.md" action = "created" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "created" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "created" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/workflows/deploy-dev.yml" action = "created" },
{ path = ".github/workflows/deploy-demo.yml" action = "created" },
{ path = ".github/workflows/deploy-rs.yml" action = "created" },
{ path = ".github/workflows/sync-version-on-merge.yml" action = "created" },
{ path = ".github/workflows/auto-release.yml" action = "created" },
{ path = ".github/workflows/repository-cleanup.yml" action = "created" },
{ path = ".github/workflows/auto-dev-issue.yml" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "created" },
{ path = ".github/CODEOWNERS" action = "created" },
{ path = "composer.json" action = "enterprise dependency added" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README — never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "05.00.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
name = "composer.json"
extension = "json"
description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
required = true
always_overwrite = false
audience = "developer"
template = "templates/configs/composer.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
name = "scripts"
path = "scripts"
description = "Repo-specific scripts — not managed by MokoStandards sync"
required = false
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
files = [
{
name = "MokoStandards.override.xml"
extension = "xml"
description = "MokoStandards sync override configuration"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = true
template = ".github/workflows/standards-compliance.yml"
},
{
name = "enterprise-firewall-setup.yml"
extension = "yml"
description = "Enterprise firewall configuration for trusted domain access"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment of src/ to the development server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment of src/ to the demo server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment of src/ to the release staging server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-rs.yml.template"
},
{
name = "sync-version-on-merge.yml"
extension = "yml"
description = "Auto-bump patch version on merge and propagate to all file headers"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
},
{
name = "auto-release.yml"
extension = "yml"
description = "Auto-create GitHub Release on push to main with version from README.md"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/repository-cleanup.yml.template"
},
{
name = "auto-dev-issue.yml"
extension = "yml"
description = "Auto-create tracking issue when a dev/** branch is pushed"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates synced from MokoStandards"
requirement_status = "required"
files = [
{
name = "config.yml"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/config.yml"
},
{
name = "adr.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/adr.md"
},
{
name = "bug_report.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
},
{
name = "documentation.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
},
{
name = "enterprise_support.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
},
{
name = "feature_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
},
{
name = "firewall-request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
},
{
name = "question.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/question.md"
},
{
name = "request-license.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
},
{
name = "rfc.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
},
{
name = "security.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/security.md"
}
]
}
]
},
{
name = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT — configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
View File
-734
View File
@@ -1,734 +0,0 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/Copy-PortablePath
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:20:12+00:00
* Platform : default-repository
* Description: Copy Portable Path is a lightweight PowerShell utility that adds two context menu items to Windows Explorer — Copy Relative Path and Copy Absolute Path — both using forward slashes for cross-platform compatibility. Supports multiple selections, optional MSYS/WSL /c/... style, and installs per-user without admin rights.
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/Copy-PortablePath"
default_branch = "main"
detected_platform = "default-repository"
description = "Copy Portable Path is a lightweight PowerShell utility that adds two context menu items to Windows Explorer — Copy Relative Path and Copy Absolute Path — both using forward slashes for cross-platform compatibility. Supports multiple selections, optional MSYS/WSL /c/... style, and installs per-user without admin rights."
sync_timestamp = "2026-04-02T15:20:12+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 11
updated_files = 31
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
{ path = ".github/workflows/deploy-dev.yml" action = "updated" },
{ path = ".github/workflows/deploy-demo.yml" action = "updated" },
{ path = ".github/workflows/deploy-rs.yml" action = "updated" },
{ path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
{ path = ".github/workflows/auto-release.yml" action = "updated" },
{ path = ".github/workflows/repository-cleanup.yml" action = "updated" },
{ path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
{ path = ".github/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/Copy-PortablePath/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "05.00.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
name = "composer.json"
extension = "json"
description = "Composer manifest requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
required = true
always_overwrite = false
audience = "developer"
template = "templates/configs/composer.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
name = "scripts"
path = "scripts"
description = "Repo-specific scripts not managed by MokoStandards sync"
required = false
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
files = [
{
name = "MokoStandards.override.xml"
extension = "xml"
description = "MokoStandards sync override configuration"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = true
template = ".github/workflows/standards-compliance.yml"
},
{
name = "enterprise-firewall-setup.yml"
extension = "yml"
description = "Enterprise firewall configuration for trusted domain access"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment of src/ to the development server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment of src/ to the demo server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment of src/ to the release staging server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-rs.yml.template"
},
{
name = "sync-version-on-merge.yml"
extension = "yml"
description = "Auto-bump patch version on merge and propagate to all file headers"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
},
{
name = "auto-release.yml"
extension = "yml"
description = "Auto-create GitHub Release on push to main with version from README.md"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/repository-cleanup.yml.template"
},
{
name = "auto-dev-issue.yml"
extension = "yml"
description = "Auto-create tracking issue when a dev/** branch is pushed"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates synced from MokoStandards"
requirement_status = "required"
files = [
{
name = "config.yml"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/config.yml"
},
{
name = "adr.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/adr.md"
},
{
name = "bug_report.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
},
{
name = "documentation.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
},
{
name = "enterprise_support.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
},
{
name = "feature_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
},
{
name = "firewall-request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
},
{
name = "question.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/question.md"
},
{
name = "request-license.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
},
{
name = "rfc.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
},
{
name = "security.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/security.md"
}
]
}
]
},
{
name = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-400
View File
@@ -1,400 +0,0 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoDoliMods
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:19:29+00:00
* Platform : crm-platform
* Description: The DoliMods is the repository of the Dolibarr ERP CRM modules, developed by the DoliCloud.com team.
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/crm-platform.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/MokoDoliMods"
default_branch = "main"
detected_platform = "crm-platform"
description = "The DoliMods is the repository of the Dolibarr ERP CRM modules, developed by the DoliCloud.com team."
sync_timestamp = "2026-04-02T15:19:29+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/crm-platform.tf"
}
sync_stats = {
total_files = 53
created_files = 3
updated_files = 40
skipped_files = 10
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "CONTRIBUTING.md" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "updated" },
{ path = ".github/deploy-dev.yml" action = "updated" },
{ path = ".github/deploy-demo.yml" action = "updated" },
{ path = ".github/deploy-rs.yml" action = "updated" },
{ path = ".github/sync-version-on-merge.yml" action = "updated" },
{ path = ".github/auto-release.yml" action = "updated" },
{ path = ".github/repository-cleanup.yml" action = "updated" },
{ path = ".github/auto-dev-issue.yml" action = "updated" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
{ path = ".github/workflows/deploy-dev.yml" action = "updated" },
{ path = ".github/workflows/deploy-demo.yml" action = "updated" },
{ path = ".github/workflows/deploy-rs.yml" action = "updated" },
{ path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
{ path = ".github/workflows/auto-release.yml" action = "updated" },
{ path = ".github/workflows/repository-cleanup.yml" action = "updated" },
{ path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
{ path = ".github/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoDoliMods/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Dolibarr Platform Structure Definition
* Standard repository structure for the full Dolibarr ERP/CRM installation
*
* This is distinct from crm-module — it defines the ENTIRE Dolibarr platform
* (htdocs/, not src/). It does NOT have a module descriptor, numero, or
* publish-to-mokodolimods workflow.
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Dolibarr Platform"
description = "Full Dolibarr ERP/CRM installation htdocs/ root, not a module"
repository_type = "crm-platform"
platform = "dolibarr"
last_updated = "2026-03-31T00:00:00Z"
maintainer = "Moko Consulting"
version = "05.00.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Developer-focused documentation"
required = true
always_overwrite = false
protected = true
audience = "developer"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
required = true
always_overwrite = true
template = "templates/docs/required/template-CONTRIBUTING.md"
audience = "contributor"
},
{
name = "LICENSE"
extension = ""
description = "GPL-3.0-or-later license file"
required = true
always_overwrite = true
template = "templates/docs/required/LICENSE"
},
{
name = "composer.json"
extension = "json"
description = "Composer package definition"
required = true
always_overwrite = false
},
{
name = "phpstan.neon"
extension = "neon"
description = "PHPStan static analysis configuration"
required = true
always_overwrite = true
template = "templates/configs/phpstan.neon"
},
{
name = "Makefile"
extension = ""
description = "Build automation targets"
required = true
always_overwrite = true
template = "templates/configs/Makefile"
},
{
name = "src/.ftpignore"
extension = ""
description = "Files excluded from SFTP deployment"
required = true
always_overwrite = true
template = "templates/configs/ftp_ignore"
},
{
name = ".mokostandards"
extension = ""
description = "MokoStandards platform identifier"
required = true
always_overwrite = true
template = "templates/configs/mokostandards.yml.template"
}
]
directories = [
{
name = "htdocs"
path = "htdocs"
description = "Dolibarr web root entire platform"
required = true
purpose = "Contains the full Dolibarr installation including core, custom modules, and themes"
},
{
name = "docs"
path = "docs"
description = "Developer and technical documentation"
required = true
purpose = "Contains technical documentation"
files = [
{
name = "update-server.md"
extension = "md"
description = "Dolibarr update server (update.txt) documentation"
required = true
always_overwrite = true
template = "templates/docs/required/template-update-server-dolibarr.md"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub configuration"
required = true
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
template = ".github/workflows/standards-compliance.yml"
},
{
name = "enterprise-firewall-setup.yml"
extension = "yml"
description = "Enterprise firewall configuration"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment to dev server (htdocs/)"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment to demo server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment to release staging server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-rs.yml.template"
},
{
name = "sync-version-on-merge.yml"
extension = "yml"
description = "Auto-bump patch version on merge"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
},
{
name = "auto-release.yml"
extension = "yml"
description = "Auto-create GitHub Release on minor version"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup: retired workflows, stale branches"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/repository-cleanup.yml.template"
},
{
name = "auto-dev-issue.yml"
extension = "yml"
description = "Auto-create tracking issue on dev/rc branch creation"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
},
{
name = "repo_health.yml"
extension = "yml"
description = "Dolibarr platform health checks (shared guardrails, no module-specific checks)"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/dolibarr/repo_health.yml.template"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates"
requirement_status = "required"
files = [
{
name = "config.yml"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/config.yml"
},
{
name = "adr.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/adr.md"
},
{
name = "bug_report.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
},
{
name = "documentation.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
},
{
name = "enterprise_support.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
},
{
name = "feature_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
},
{
name = "firewall-request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
},
{
name = "question.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/question.md"
},
{
name = "request-license.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
},
{
name = "rfc.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
},
{
name = "security.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/security.md"
},
{
name = "dolibarr_issue.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/dolibarr_issue.md"
},
{
name = "dolibarr_module_id_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/dolibarr_module_id_request.md"
}
]
}
]
}
]
}
}
output "crm_platform_structure" {
description = "Dolibarr Platform repository structure definition"
value = local.repository_structure
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-400
View File
@@ -1,400 +0,0 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoDolibarr
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:42:39+00:00
* Platform : crm-platform
* Description: Dolibarr ERP CRM is a modern software package to manage your company or foundation\'s activity (contacts, suppliers, invoices, orders, stocks, agenda, accounting, ...). it\'s an open source Web application (written in PHP) designed for businesses of any sizes, foundations and freelancers.
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/crm-platform.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/MokoDolibarr"
default_branch = "main"
detected_platform = "crm-platform"
description = "Dolibarr ERP CRM is a modern software package to manage your company or foundation\'s activity (contacts, suppliers, invoices, orders, stocks, agenda, accounting, ...). it\'s an open source Web application (written in PHP) designed for businesses of any sizes, foundations and freelancers."
sync_timestamp = "2026-04-02T15:42:39+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/crm-platform.tf"
}
sync_stats = {
total_files = 53
created_files = 16
updated_files = 29
skipped_files = 8
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "CONTRIBUTING.md" action = "created" },
{ path = "CODE_OF_CONDUCT.md" action = "created" },
{ path = "ROADMAP.md" action = "created" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "created" },
{ path = "docs/INSTALLATION.md" action = "created" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
{ path = ".github/workflows/deploy-dev.yml" action = "updated" },
{ path = ".github/workflows/deploy-demo.yml" action = "updated" },
{ path = ".github/workflows/deploy-rs.yml" action = "updated" },
{ path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
{ path = ".github/workflows/auto-release.yml" action = "updated" },
{ path = ".github/workflows/repository-cleanup.yml" action = "updated" },
{ path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
{ path = ".github/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoDolibarr/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Dolibarr Platform Structure Definition
* Standard repository structure for the full Dolibarr ERP/CRM installation
*
* This is distinct from crm-module — it defines the ENTIRE Dolibarr platform
* (htdocs/, not src/). It does NOT have a module descriptor, numero, or
* publish-to-mokodolimods workflow.
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Dolibarr Platform"
description = "Full Dolibarr ERP/CRM installation htdocs/ root, not a module"
repository_type = "crm-platform"
platform = "dolibarr"
last_updated = "2026-03-31T00:00:00Z"
maintainer = "Moko Consulting"
version = "05.00.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Developer-focused documentation"
required = true
always_overwrite = false
protected = true
audience = "developer"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
required = true
always_overwrite = true
template = "templates/docs/required/template-CONTRIBUTING.md"
audience = "contributor"
},
{
name = "LICENSE"
extension = ""
description = "GPL-3.0-or-later license file"
required = true
always_overwrite = true
template = "templates/docs/required/LICENSE"
},
{
name = "composer.json"
extension = "json"
description = "Composer package definition"
required = true
always_overwrite = false
},
{
name = "phpstan.neon"
extension = "neon"
description = "PHPStan static analysis configuration"
required = true
always_overwrite = true
template = "templates/configs/phpstan.neon"
},
{
name = "Makefile"
extension = ""
description = "Build automation targets"
required = true
always_overwrite = true
template = "templates/configs/Makefile"
},
{
name = "src/.ftpignore"
extension = ""
description = "Files excluded from SFTP deployment"
required = true
always_overwrite = true
template = "templates/configs/ftp_ignore"
},
{
name = ".mokostandards"
extension = ""
description = "MokoStandards platform identifier"
required = true
always_overwrite = true
template = "templates/configs/mokostandards.yml.template"
}
]
directories = [
{
name = "htdocs"
path = "htdocs"
description = "Dolibarr web root entire platform"
required = true
purpose = "Contains the full Dolibarr installation including core, custom modules, and themes"
},
{
name = "docs"
path = "docs"
description = "Developer and technical documentation"
required = true
purpose = "Contains technical documentation"
files = [
{
name = "update-server.md"
extension = "md"
description = "Dolibarr update server (update.txt) documentation"
required = true
always_overwrite = true
template = "templates/docs/required/template-update-server-dolibarr.md"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub configuration"
required = true
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
template = ".github/workflows/standards-compliance.yml"
},
{
name = "enterprise-firewall-setup.yml"
extension = "yml"
description = "Enterprise firewall configuration"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment to dev server (htdocs/)"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment to demo server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment to release staging server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-rs.yml.template"
},
{
name = "sync-version-on-merge.yml"
extension = "yml"
description = "Auto-bump patch version on merge"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
},
{
name = "auto-release.yml"
extension = "yml"
description = "Auto-create GitHub Release on minor version"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup: retired workflows, stale branches"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/repository-cleanup.yml.template"
},
{
name = "auto-dev-issue.yml"
extension = "yml"
description = "Auto-create tracking issue on dev/rc branch creation"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
},
{
name = "repo_health.yml"
extension = "yml"
description = "Dolibarr platform health checks (shared guardrails, no module-specific checks)"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/dolibarr/repo_health.yml.template"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates"
requirement_status = "required"
files = [
{
name = "config.yml"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/config.yml"
},
{
name = "adr.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/adr.md"
},
{
name = "bug_report.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
},
{
name = "documentation.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
},
{
name = "enterprise_support.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
},
{
name = "feature_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
},
{
name = "firewall-request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
},
{
name = "question.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/question.md"
},
{
name = "request-license.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
},
{
name = "rfc.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
},
{
name = "security.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/security.md"
},
{
name = "dolibarr_issue.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/dolibarr_issue.md"
},
{
name = "dolibarr_module_id_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/dolibarr_module_id_request.md"
}
]
}
]
}
]
}
}
output "crm_platform_structure" {
description = "Dolibarr Platform repository structure definition"
value = local.repository_structure
}
@@ -1,734 +0,0 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoISOUpdatePortable
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:33:45+00:00
* Platform : default-repository
* Description: A PortableApp that keeps ISOs of selected systems up to date.
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/MokoISOUpdatePortable"
default_branch = "main"
detected_platform = "default-repository"
description = "A PortableApp that keeps ISOs of selected systems up to date."
sync_timestamp = "2026-04-02T15:33:45+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 11
updated_files = 31
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
{ path = ".github/workflows/deploy-dev.yml" action = "updated" },
{ path = ".github/workflows/deploy-demo.yml" action = "updated" },
{ path = ".github/workflows/deploy-rs.yml" action = "updated" },
{ path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
{ path = ".github/workflows/auto-release.yml" action = "updated" },
{ path = ".github/workflows/repository-cleanup.yml" action = "updated" },
{ path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
{ path = ".github/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoISOUpdatePortable/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "05.00.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
name = "composer.json"
extension = "json"
description = "Composer manifest requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
required = true
always_overwrite = false
audience = "developer"
template = "templates/configs/composer.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
name = "scripts"
path = "scripts"
description = "Repo-specific scripts not managed by MokoStandards sync"
required = false
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
files = [
{
name = "MokoStandards.override.xml"
extension = "xml"
description = "MokoStandards sync override configuration"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = true
template = ".github/workflows/standards-compliance.yml"
},
{
name = "enterprise-firewall-setup.yml"
extension = "yml"
description = "Enterprise firewall configuration for trusted domain access"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment of src/ to the development server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment of src/ to the demo server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment of src/ to the release staging server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-rs.yml.template"
},
{
name = "sync-version-on-merge.yml"
extension = "yml"
description = "Auto-bump patch version on merge and propagate to all file headers"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
},
{
name = "auto-release.yml"
extension = "yml"
description = "Auto-create GitHub Release on push to main with version from README.md"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/repository-cleanup.yml.template"
},
{
name = "auto-dev-issue.yml"
extension = "yml"
description = "Auto-create tracking issue when a dev/** branch is pushed"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates synced from MokoStandards"
requirement_status = "required"
files = [
{
name = "config.yml"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/config.yml"
},
{
name = "adr.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/adr.md"
},
{
name = "bug_report.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
},
{
name = "documentation.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
},
{
name = "enterprise_support.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
},
{
name = "feature_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
},
{
name = "firewall-request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
},
{
name = "question.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/question.md"
},
{
name = "request-license.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
},
{
name = "rfc.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
},
{
name = "security.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/security.md"
}
]
}
]
},
{
name = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,734 +0,0 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoPerfectPublisher-Discord
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T21:05:10+00:00
* Platform : default-repository
* Description: A Perfect Publisher plugin to post to Discord
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/MokoPerfectPublisher-Discord"
default_branch = "main"
detected_platform = "default-repository"
description = "A Perfect Publisher plugin to post to Discord"
sync_timestamp = "2026-04-02T21:05:10+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 11
updated_files = 31
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
{ path = ".github/workflows/deploy-dev.yml" action = "updated" },
{ path = ".github/workflows/deploy-demo.yml" action = "updated" },
{ path = ".github/workflows/deploy-rs.yml" action = "updated" },
{ path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
{ path = ".github/workflows/auto-release.yml" action = "updated" },
{ path = ".github/workflows/repository-cleanup.yml" action = "updated" },
{ path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
{ path = ".github/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoPerfectPublisher-Discord/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "05.00.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
name = "composer.json"
extension = "json"
description = "Composer manifest requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
required = true
always_overwrite = false
audience = "developer"
template = "templates/configs/composer.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
name = "scripts"
path = "scripts"
description = "Repo-specific scripts not managed by MokoStandards sync"
required = false
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
files = [
{
name = "MokoStandards.override.xml"
extension = "xml"
description = "MokoStandards sync override configuration"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = true
template = ".github/workflows/standards-compliance.yml"
},
{
name = "enterprise-firewall-setup.yml"
extension = "yml"
description = "Enterprise firewall configuration for trusted domain access"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment of src/ to the development server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment of src/ to the demo server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment of src/ to the release staging server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-rs.yml.template"
},
{
name = "sync-version-on-merge.yml"
extension = "yml"
description = "Auto-bump patch version on merge and propagate to all file headers"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
},
{
name = "auto-release.yml"
extension = "yml"
description = "Auto-create GitHub Release on push to main with version from README.md"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/repository-cleanup.yml.template"
},
{
name = "auto-dev-issue.yml"
extension = "yml"
description = "Auto-create tracking issue when a dev/** branch is pushed"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates synced from MokoStandards"
requirement_status = "required"
files = [
{
name = "config.yml"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/config.yml"
},
{
name = "adr.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/adr.md"
},
{
name = "bug_report.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
},
{
name = "documentation.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
},
{
name = "enterprise_support.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
},
{
name = "feature_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
},
{
name = "firewall-request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
},
{
name = "question.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/question.md"
},
{
name = "request-license.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
},
{
name = "rfc.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
},
{
name = "security.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/security.md"
}
]
}
]
},
{
name = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
@@ -1,734 +0,0 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoStandards-Template-Client
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:40:35+00:00
* Platform : default-repository
* Description: A template repo for clients of Moko Consulting
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/MokoStandards-Template-Client"
default_branch = "main"
detected_platform = "default-repository"
description = "A template repo for clients of Moko Consulting"
sync_timestamp = "2026-04-02T15:40:35+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 11
updated_files = 31
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
{ path = ".github/workflows/deploy-dev.yml" action = "updated" },
{ path = ".github/workflows/deploy-demo.yml" action = "updated" },
{ path = ".github/workflows/deploy-rs.yml" action = "updated" },
{ path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
{ path = ".github/workflows/auto-release.yml" action = "updated" },
{ path = ".github/workflows/repository-cleanup.yml" action = "updated" },
{ path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
{ path = ".github/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoStandards-Template-Client/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "05.00.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
name = "composer.json"
extension = "json"
description = "Composer manifest requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
required = true
always_overwrite = false
audience = "developer"
template = "templates/configs/composer.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
name = "scripts"
path = "scripts"
description = "Repo-specific scripts not managed by MokoStandards sync"
required = false
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
files = [
{
name = "MokoStandards.override.xml"
extension = "xml"
description = "MokoStandards sync override configuration"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = true
template = ".github/workflows/standards-compliance.yml"
},
{
name = "enterprise-firewall-setup.yml"
extension = "yml"
description = "Enterprise firewall configuration for trusted domain access"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment of src/ to the development server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment of src/ to the demo server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment of src/ to the release staging server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-rs.yml.template"
},
{
name = "sync-version-on-merge.yml"
extension = "yml"
description = "Auto-bump patch version on merge and propagate to all file headers"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
},
{
name = "auto-release.yml"
extension = "yml"
description = "Auto-create GitHub Release on push to main with version from README.md"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/repository-cleanup.yml.template"
},
{
name = "auto-dev-issue.yml"
extension = "yml"
description = "Auto-create tracking issue when a dev/** branch is pushed"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates synced from MokoStandards"
requirement_status = "required"
files = [
{
name = "config.yml"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/config.yml"
},
{
name = "adr.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/adr.md"
},
{
name = "bug_report.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
},
{
name = "documentation.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
},
{
name = "enterprise_support.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
},
{
name = "feature_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
},
{
name = "firewall-request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
},
{
name = "question.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/question.md"
},
{
name = "request-license.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
},
{
name = "rfc.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
},
{
name = "security.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/security.md"
}
]
}
]
},
{
name = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,734 +0,0 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoStandards-Template-Generic
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:27:41+00:00
* Platform : default-repository
* Description: A repo template for a generic coding project according to MokoStandards
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/MokoStandards-Template-Generic"
default_branch = "main"
detected_platform = "default-repository"
description = "A repo template for a generic coding project according to MokoStandards"
sync_timestamp = "2026-04-02T15:27:41+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 11
updated_files = 31
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
{ path = ".github/workflows/deploy-dev.yml" action = "updated" },
{ path = ".github/workflows/deploy-demo.yml" action = "updated" },
{ path = ".github/workflows/deploy-rs.yml" action = "updated" },
{ path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
{ path = ".github/workflows/auto-release.yml" action = "updated" },
{ path = ".github/workflows/repository-cleanup.yml" action = "updated" },
{ path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
{ path = ".github/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoStandards-Template-Generic/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "05.00.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
name = "composer.json"
extension = "json"
description = "Composer manifest requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
required = true
always_overwrite = false
audience = "developer"
template = "templates/configs/composer.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
name = "scripts"
path = "scripts"
description = "Repo-specific scripts not managed by MokoStandards sync"
required = false
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
files = [
{
name = "MokoStandards.override.xml"
extension = "xml"
description = "MokoStandards sync override configuration"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = true
template = ".github/workflows/standards-compliance.yml"
},
{
name = "enterprise-firewall-setup.yml"
extension = "yml"
description = "Enterprise firewall configuration for trusted domain access"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment of src/ to the development server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment of src/ to the demo server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment of src/ to the release staging server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-rs.yml.template"
},
{
name = "sync-version-on-merge.yml"
extension = "yml"
description = "Auto-bump patch version on merge and propagate to all file headers"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
},
{
name = "auto-release.yml"
extension = "yml"
description = "Auto-create GitHub Release on push to main with version from README.md"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/repository-cleanup.yml.template"
},
{
name = "auto-dev-issue.yml"
extension = "yml"
description = "Auto-create tracking issue when a dev/** branch is pushed"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates synced from MokoStandards"
requirement_status = "required"
files = [
{
name = "config.yml"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/config.yml"
},
{
name = "adr.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/adr.md"
},
{
name = "bug_report.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
},
{
name = "documentation.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
},
{
name = "enterprise_support.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
},
{
name = "feature_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
},
{
name = "firewall-request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
},
{
name = "question.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/question.md"
},
{
name = "request-license.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
},
{
name = "rfc.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
},
{
name = "security.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/security.md"
}
]
}
]
},
{
name = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-734
View File
@@ -1,734 +0,0 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoTesting
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:23:26+00:00
* Platform : default-repository
* Description: Testign grond for Moko Consulting
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/MokoTesting"
default_branch = "main"
detected_platform = "default-repository"
description = "Testign grond for Moko Consulting"
sync_timestamp = "2026-04-02T15:23:26+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 11
updated_files = 31
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
{ path = ".github/workflows/deploy-dev.yml" action = "updated" },
{ path = ".github/workflows/deploy-demo.yml" action = "updated" },
{ path = ".github/workflows/deploy-rs.yml" action = "updated" },
{ path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
{ path = ".github/workflows/auto-release.yml" action = "updated" },
{ path = ".github/workflows/repository-cleanup.yml" action = "updated" },
{ path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
{ path = ".github/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoTesting/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "05.00.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
name = "composer.json"
extension = "json"
description = "Composer manifest requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
required = true
always_overwrite = false
audience = "developer"
template = "templates/configs/composer.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
name = "scripts"
path = "scripts"
description = "Repo-specific scripts not managed by MokoStandards sync"
required = false
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
files = [
{
name = "MokoStandards.override.xml"
extension = "xml"
description = "MokoStandards sync override configuration"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = true
template = ".github/workflows/standards-compliance.yml"
},
{
name = "enterprise-firewall-setup.yml"
extension = "yml"
description = "Enterprise firewall configuration for trusted domain access"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment of src/ to the development server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment of src/ to the demo server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment of src/ to the release staging server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-rs.yml.template"
},
{
name = "sync-version-on-merge.yml"
extension = "yml"
description = "Auto-bump patch version on merge and propagate to all file headers"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
},
{
name = "auto-release.yml"
extension = "yml"
description = "Auto-create GitHub Release on push to main with version from README.md"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/repository-cleanup.yml.template"
},
{
name = "auto-dev-issue.yml"
extension = "yml"
description = "Auto-create tracking issue when a dev/** branch is pushed"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates synced from MokoStandards"
requirement_status = "required"
files = [
{
name = "config.yml"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/config.yml"
},
{
name = "adr.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/adr.md"
},
{
name = "bug_report.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
},
{
name = "documentation.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
},
{
name = "enterprise_support.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
},
{
name = "feature_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
},
{
name = "firewall-request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
},
{
name = "question.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/question.md"
},
{
name = "request-license.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
},
{
name = "rfc.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
},
{
name = "security.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/security.md"
}
]
}
]
},
{
name = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-734
View File
@@ -1,734 +0,0 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/MokoWinSetup
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:39:49+00:00
* Platform : default-repository
* Description: A setup script for Windows
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/MokoWinSetup"
default_branch = "main"
detected_platform = "default-repository"
description = "A setup script for Windows"
sync_timestamp = "2026-04-02T15:39:49+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 11
updated_files = 31
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
{ path = ".github/workflows/deploy-dev.yml" action = "updated" },
{ path = ".github/workflows/deploy-demo.yml" action = "updated" },
{ path = ".github/workflows/deploy-rs.yml" action = "updated" },
{ path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
{ path = ".github/workflows/auto-release.yml" action = "updated" },
{ path = ".github/workflows/repository-cleanup.yml" action = "updated" },
{ path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
{ path = ".github/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/MokoWinSetup/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "05.00.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
name = "composer.json"
extension = "json"
description = "Composer manifest requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
required = true
always_overwrite = false
audience = "developer"
template = "templates/configs/composer.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
name = "scripts"
path = "scripts"
description = "Repo-specific scripts not managed by MokoStandards sync"
required = false
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
files = [
{
name = "MokoStandards.override.xml"
extension = "xml"
description = "MokoStandards sync override configuration"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = true
template = ".github/workflows/standards-compliance.yml"
},
{
name = "enterprise-firewall-setup.yml"
extension = "yml"
description = "Enterprise firewall configuration for trusted domain access"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment of src/ to the development server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment of src/ to the demo server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment of src/ to the release staging server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-rs.yml.template"
},
{
name = "sync-version-on-merge.yml"
extension = "yml"
description = "Auto-bump patch version on merge and propagate to all file headers"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
},
{
name = "auto-release.yml"
extension = "yml"
description = "Auto-create GitHub Release on push to main with version from README.md"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/repository-cleanup.yml.template"
},
{
name = "auto-dev-issue.yml"
extension = "yml"
description = "Auto-create tracking issue when a dev/** branch is pushed"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates synced from MokoStandards"
requirement_status = "required"
files = [
{
name = "config.yml"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/config.yml"
},
{
name = "adr.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/adr.md"
},
{
name = "bug_report.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
},
{
name = "documentation.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
},
{
name = "enterprise_support.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
},
{
name = "feature_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
},
{
name = "firewall-request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
},
{
name = "question.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/question.md"
},
{
name = "request-license.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
},
{
name = "rfc.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
},
{
name = "security.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/security.md"
}
]
}
]
},
{
name = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
@@ -1,734 +0,0 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/PLG_FINDER_MOKOVMSMARTSEARCH
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:33:10+00:00
* Platform : default-repository
* Description: Virtuemart Smart serachPlugin
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/PLG_FINDER_MOKOVMSMARTSEARCH"
default_branch = "main"
detected_platform = "default-repository"
description = "Virtuemart Smart serachPlugin "
sync_timestamp = "2026-04-02T15:33:10+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 11
updated_files = 31
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
{ path = ".github/workflows/deploy-dev.yml" action = "updated" },
{ path = ".github/workflows/deploy-demo.yml" action = "updated" },
{ path = ".github/workflows/deploy-rs.yml" action = "updated" },
{ path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
{ path = ".github/workflows/auto-release.yml" action = "updated" },
{ path = ".github/workflows/repository-cleanup.yml" action = "updated" },
{ path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
{ path = ".github/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/PLG_FINDER_MOKOVMSMARTSEARCH/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "05.00.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
name = "composer.json"
extension = "json"
description = "Composer manifest requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
required = true
always_overwrite = false
audience = "developer"
template = "templates/configs/composer.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
name = "scripts"
path = "scripts"
description = "Repo-specific scripts not managed by MokoStandards sync"
required = false
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
files = [
{
name = "MokoStandards.override.xml"
extension = "xml"
description = "MokoStandards sync override configuration"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = true
template = ".github/workflows/standards-compliance.yml"
},
{
name = "enterprise-firewall-setup.yml"
extension = "yml"
description = "Enterprise firewall configuration for trusted domain access"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment of src/ to the development server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment of src/ to the demo server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment of src/ to the release staging server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-rs.yml.template"
},
{
name = "sync-version-on-merge.yml"
extension = "yml"
description = "Auto-bump patch version on merge and propagate to all file headers"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
},
{
name = "auto-release.yml"
extension = "yml"
description = "Auto-create GitHub Release on push to main with version from README.md"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/repository-cleanup.yml.template"
},
{
name = "auto-dev-issue.yml"
extension = "yml"
description = "Auto-create tracking issue when a dev/** branch is pushed"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates synced from MokoStandards"
requirement_status = "required"
files = [
{
name = "config.yml"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/config.yml"
},
{
name = "adr.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/adr.md"
},
{
name = "bug_report.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
},
{
name = "documentation.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
},
{
name = "enterprise_support.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
},
{
name = "feature_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
},
{
name = "firewall-request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
},
{
name = "question.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/question.md"
},
{
name = "request-license.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
},
{
name = "rfc.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
},
{
name = "security.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/security.md"
}
]
}
]
},
{
name = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
@@ -1,775 +0,0 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/client-clarksvillefurs
*
* Auto-generated by MokoStandards bulk sync on 2026-03-30T08:30:51+00:00
* Platform : default-repository
* Description: The Clarksville Furs implementation of MokoCassiopeia
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/client-clarksvillefurs"
default_branch = "main"
detected_platform = "default-repository"
description = "The Clarksville Furs implementation of MokoCassiopeia"
sync_timestamp = "2026-03-30T08:30:51+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 56
created_files = 16
updated_files = 22
skipped_files = 18
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/ci.yml" action = "updated" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/workflows/deploy-dev.yml" action = "created" },
{ path = ".github/workflows/deploy-demo.yml" action = "created" },
{ path = ".github/workflows/deploy-rs.yml" action = "created" },
{ path = ".github/workflows/sync-version-on-merge.yml" action = "created" },
{ path = ".github/workflows/auto-release.yml" action = "created" },
{ path = ".github/workflows/repository-cleanup.yml" action = "created" },
{ path = ".github/workflows/auto-dev-issue.yml" action = "created" },
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/repo-health.yml" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/ci.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/client-clarksvillefurs/contents/.github/workflows/ci.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/ci.yml does not match 1f2449072e0d3304fb645b2ef45526fcf978783e\",\"documentation_url\":\"https (truncated...)
" },
{ path = ".github/workflows/test.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/client-clarksvillefurs/contents/.github/workflows/test.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/test.yml does not match 5c7b0f5639c622d5c910a066fe2415b69a49918d\",\"documentation_url\":\"htt (truncated...)
" },
{ path = ".github/workflows/code-quality.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/client-clarksvillefurs/contents/.github/workflows/code-quality.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/code-quality.yml does not match f510c81073d573669477a96a646d3a8383e77b6b\",\"documentation_u (truncated...)
" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/client-clarksvillefurs/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 04c7ef91744b3e482f495e592f268a7448dafe74\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/deploy.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/client-clarksvillefurs/contents/.github/workflows/deploy.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/deploy.yml does not match 4895e597d4ee56a976c8792ab80da574bcfa23c8\",\"documentation_url\":\"h (truncated...)
" },
{ path = ".github/workflows/repo-health.yml" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/standards-compliance.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/client-clarksvillefurs/contents/.github/workflows/standards-compliance.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/standards-compliance.yml does not match bc0516a1dbc8b2acfcf3189ac28be497c1a8c93e\",\"documen (truncated...)
" },
{ path = ".github/workflows/custom/README.md" reason = "README — never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "05.00.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
name = "composer.json"
extension = "json"
description = "Composer manifest — requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
required = true
always_overwrite = false
audience = "developer"
template = "templates/configs/composer.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
name = "scripts"
path = "scripts"
description = "Repo-specific scripts — not managed by MokoStandards sync"
required = false
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
files = [
{
name = "MokoStandards.override.xml"
extension = "xml"
description = "MokoStandards sync override configuration"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "ci.yml"
extension = "yml"
description = "Continuous integration workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "ci.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "ci.yml"
create_path = true
template = "templates/workflows/generic/ci.yml.template"
},
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "repo-health.yml"
extension = "yml"
description = "Repository health monitoring"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "repo_health.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "repo-health.yml"
create_path = true
template = "templates/workflows/generic/repo_health.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = true
template = ".github/workflows/standards-compliance.yml"
},
{
name = "enterprise-firewall-setup.yml"
extension = "yml"
description = "Enterprise firewall configuration for trusted domain access"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment of src/ to the development server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment of src/ to the demo server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment of src/ to the release staging server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-rs.yml.template"
},
{
name = "sync-version-on-merge.yml"
extension = "yml"
description = "Auto-bump patch version on merge and propagate to all file headers"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
},
{
name = "auto-release.yml"
extension = "yml"
description = "Auto-create GitHub Release on push to main with version from README.md"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/repository-cleanup.yml.template"
},
{
name = "auto-dev-issue.yml"
extension = "yml"
description = "Auto-create tracking issue when a dev/** branch is pushed"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates synced from MokoStandards"
requirement_status = "required"
files = [
{
name = "config.yml"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/config.yml"
},
{
name = "adr.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/adr.md"
},
{
name = "bug_report.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
},
{
name = "documentation.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
},
{
name = "enterprise_support.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
},
{
name = "feature_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
},
{
name = "firewall-request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
},
{
name = "question.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/question.md"
},
{
name = "request-license.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
},
{
name = "rfc.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
},
{
name = "security.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/security.md"
}
]
}
]
},
{
name = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT — configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
-734
View File
@@ -1,734 +0,0 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/client-kiddieland
*
* Auto-generated by MokoStandards bulk sync on 2026-04-02T15:41:12+00:00
* Platform : default-repository
* Description: Client repo for Kiddieland Child Care Center
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/client-kiddieland"
default_branch = "main"
detected_platform = "default-repository"
description = "Client repo for Kiddieland Child Care Center"
sync_timestamp = "2026-04-02T15:41:12+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 53
created_files = 11
updated_files = 31
skipped_files = 11
}
synced_files = [
{ path = "LICENSE" action = "updated" },
{ path = "Makefile" action = "updated" },
{ path = "composer.json" action = "updated" },
{ path = "docs/index.md" action = "updated" },
{ path = "docs/INSTALLATION.md" action = "updated" },
{ path = ".github/workflows/test.yml" action = "created" },
{ path = ".github/workflows/code-quality.yml" action = "created" },
{ path = ".github/workflows/codeql-analysis.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "created" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/enterprise-firewall-setup.yml" action = "created" },
{ path = ".github/deploy-dev.yml" action = "created" },
{ path = ".github/deploy-demo.yml" action = "created" },
{ path = ".github/deploy-rs.yml" action = "created" },
{ path = ".github/sync-version-on-merge.yml" action = "created" },
{ path = ".github/auto-release.yml" action = "created" },
{ path = ".github/repository-cleanup.yml" action = "created" },
{ path = ".github/auto-dev-issue.yml" action = "created" },
{ path = ".github/workflows/test.yml" action = "updated" },
{ path = ".github/workflows/code-quality.yml" action = "updated" },
{ path = ".github/workflows/deploy.yml" action = "updated" },
{ path = ".github/workflows/standards-compliance.yml" action = "updated" },
{ path = ".github/workflows/enterprise-firewall-setup.yml" action = "updated" },
{ path = ".github/workflows/deploy-dev.yml" action = "updated" },
{ path = ".github/workflows/deploy-demo.yml" action = "updated" },
{ path = ".github/workflows/deploy-rs.yml" action = "updated" },
{ path = ".github/workflows/sync-version-on-merge.yml" action = "updated" },
{ path = ".github/workflows/auto-release.yml" action = "updated" },
{ path = ".github/workflows/repository-cleanup.yml" action = "updated" },
{ path = ".github/workflows/auto-dev-issue.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/config.yml" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/adr.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/bug_report.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/documentation.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/enterprise_support.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/feature_request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/firewall-request.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/question.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/request-license.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/rfc.md" action = "updated" },
{ path = ".github/ISSUE_TEMPLATE/security.md" action = "updated" },
{ path = ".github/CODEOWNERS" action = "updated" },
]
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
{ path = "GOVERNANCE.md" reason = "Source file not found" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/codeql-analysis.yml" reason = "API error: Request failed after 3 attempts: Client error: `PUT https://api.github.com/repos/mokoconsulting-tech/client-kiddieland/contents/.github/workflows/codeql-analysis.yml` resulted in a `409 Conflict` response:
{\"message\":\".github/workflows/codeql-analysis.yml does not match 3f50896ddc3f73cd5863338e95067692ee0e52e6\",\"documentatio (truncated...)
" },
{ path = ".github/workflows/release-cycle.yml" reason = "Source file not found" },
{ path = ".github/workflows/custom/README.md" reason = "README never overwritten" },
]
}
}
# ---- Base platform definition (reference copy) ----
/**
* Default Repository Structure Definition
* Default repository structure applicable to all repository types with minimal requirements
*
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
* Schema Version: 1.0
*/
locals {
repository_structure = {
metadata = {
name = "Default Repository Structure"
description = "Default repository structure applicable to all repository types with minimal requirements"
repository_type = "library"
platform = "multi-platform"
last_updated = "2026-01-16T00:00:00Z"
maintainer = "Moko Consulting"
version = "05.00.00"
schema_version = "1.0"
}
root_files = [
{
name = "README.md"
extension = "md"
description = "Project overview and documentation"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-README.md"
source_type = "template"
destination_path = "."
destination_filename = "README.md"
create_path = false
template = "templates/docs/required/template-README.md"
},
{
name = "LICENSE"
extension = ""
description = "License file (GPL-3.0-or-later)"
requirement_status = "required"
audience = "general"
source_path = "templates/licenses"
source_filename = "GPL-3.0"
source_type = "template"
destination_path = "."
destination_filename = "LICENSE"
create_path = false
template = "templates/licenses/GPL-3.0"
},
{
name = "CHANGELOG.md"
extension = "md"
description = "Version history and changes"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-CHANGELOG.md"
source_type = "template"
destination_path = "."
destination_filename = "CHANGELOG.md"
create_path = false
template = "templates/docs/required/template-CHANGELOG.md"
},
{
name = "CONTRIBUTING.md"
extension = "md"
description = "Contribution guidelines"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/required"
source_filename = "template-CONTRIBUTING.md"
source_type = "template"
destination_path = "."
destination_filename = "CONTRIBUTING.md"
create_path = false
template = "templates/docs/required/template-CONTRIBUTING.md"
},
{
name = "SECURITY.md"
extension = "md"
description = "Security policy and vulnerability reporting"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-SECURITY.md"
source_type = "template"
destination_path = "."
destination_filename = "SECURITY.md"
create_path = false
template = "templates/docs/required/template-SECURITY.md"
},
{
name = "CODE_OF_CONDUCT.md"
extension = "md"
description = "Community code of conduct"
requirement_status = "required"
always_overwrite = false
protected = true
audience = "contributor"
source_path = "templates/docs/extra"
source_filename = "template-CODE_OF_CONDUCT.md"
source_type = "template"
destination_path = "."
destination_filename = "CODE_OF_CONDUCT.md"
create_path = false
template = "templates/docs/extra/template-CODE_OF_CONDUCT.md"
},
{
name = "ROADMAP.md"
extension = "md"
description = "Project roadmap with version goals and milestones"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-ROADMAP.md"
source_type = "template"
destination_path = "."
destination_filename = "ROADMAP.md"
create_path = false
template = "templates/docs/extra/template-ROADMAP.md"
},
{
name = "GOVERNANCE.md"
extension = "md"
description = "Project governance model and decision-making process"
requirement_status = "suggested"
always_overwrite = false
protected = true
audience = "general"
source_path = "templates/docs/extra"
source_filename = "template-GOVERNANCE.md"
source_type = "template"
destination_path = "."
destination_filename = "GOVERNANCE.md"
create_path = false
template = "templates/docs/extra/template-GOVERNANCE.md"
},
{
name = ".gitignore"
extension = "gitignore"
description = "Git ignore patterns"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = ".gitattributes"
extension = "gitattributes"
description = "Git attributes configuration"
requirement_status = "required"
audience = "developer"
},
{
name = ".editorconfig"
extension = "editorconfig"
description = "Editor configuration for consistent coding style"
requirement_status = "required"
always_overwrite = false
audience = "developer"
},
{
name = "Makefile"
description = "Build automation"
requirement_status = "required"
always_overwrite = true
audience = "developer"
source_path = "templates/makefiles"
source_filename = "Makefile.generic.template"
source_type = "template"
destination_path = "."
destination_filename = "Makefile"
create_path = false
template = "templates/makefiles/Makefile.generic.template"
},
{
name = "composer.json"
extension = "json"
description = "Composer manifest requires mokoconsulting-tech/enterprise for CLI scripts and tooling"
required = true
always_overwrite = false
audience = "developer"
template = "templates/configs/composer.generic.json"
}
]
directories = [
{
name = "docs"
path = "docs"
description = "Documentation directory"
requirement_status = "required"
purpose = "Contains comprehensive project documentation"
files = [
{
name = "index.md"
extension = "md"
description = "Documentation index"
requirement_status = "suggested"
template = "templates/docs/index.md"
},
{
name = "INSTALLATION.md"
extension = "md"
description = "Installation and setup instructions"
requirement_status = "required"
audience = "general"
source_path = "templates/docs/required"
source_filename = "template-INSTALLATION.md"
source_type = "template"
destination_path = "docs"
destination_filename = "INSTALLATION.md"
create_path = true
template = "templates/docs/required/template-INSTALLATION.md"
},
{
name = "API.md"
extension = "md"
description = "API documentation"
requirement_status = "suggested"
},
{
name = "ARCHITECTURE.md"
extension = "md"
description = "Architecture documentation"
requirement_status = "suggested"
}
]
},
{
name = "scripts"
path = "scripts"
description = "Repo-specific scripts not managed by MokoStandards sync"
required = false
purpose = "Optional directory for repo-specific build helpers and one-off scripts. MokoStandards tools are installed via Composer (mokoconsulting-tech/enterprise) and called through vendor/bin/."
files = [
{
name = "MokoStandards.override.xml"
extension = "xml"
description = "MokoStandards sync override configuration"
requirement_status = "optional"
always_overwrite = false
}
]
},
{
name = "src"
path = "src"
description = "Source code directory"
requirement_status = "required"
purpose = "Contains application source code"
},
{
name = "tests"
path = "tests"
description = "Test files"
requirement_status = "suggested"
purpose = "Contains unit tests, integration tests, and test fixtures"
subdirectories = [
{
name = "unit"
path = "tests/unit"
description = "Unit tests"
requirement_status = "suggested"
},
{
name = "integration"
path = "tests/integration"
description = "Integration tests"
requirement_status = "optional"
}
]
},
{
name = ".github"
path = ".github"
description = "GitHub-specific configuration"
requirement_status = "required"
purpose = "Contains GitHub Actions workflows and configuration"
subdirectories = [
{
name = "workflows"
path = ".github/workflows"
description = "GitHub Actions workflows"
requirement_status = "required"
files = [
{
name = "test.yml"
extension = "yml"
description = "Comprehensive testing workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "test.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "test.yml"
create_path = true
template = "templates/workflows/generic/test.yml.template"
},
{
name = "code-quality.yml"
extension = "yml"
description = "Code quality and linting workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "code-quality.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "code-quality.yml"
create_path = true
template = "templates/workflows/generic/code-quality.yml.template"
},
{
name = "codeql-analysis.yml"
extension = "yml"
description = "CodeQL security analysis workflow"
requirement_status = "required"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "codeql-analysis.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "codeql-analysis.yml"
create_path = true
template = "templates/workflows/generic/codeql-analysis.yml.template"
},
{
name = "deploy.yml"
extension = "yml"
description = "Deployment workflow"
requirement_status = "optional"
always_overwrite = true
source_path = "templates/workflows/generic"
source_filename = "deploy.yml.template"
source_type = "template"
destination_path = ".github/workflows"
destination_filename = "deploy.yml"
create_path = true
template = "templates/workflows/generic/deploy.yml.template"
},
{
name = "release-cycle.yml"
extension = "yml"
description = "Release management workflow with automated release flow"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "release-cycle.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "release-cycle.yml"
create_path = true
template = ".github/workflows/release-cycle.yml"
},
{
name = "standards-compliance.yml"
extension = "yml"
description = "MokoStandards compliance validation"
requirement_status = "required"
always_overwrite = true
source_path = ".github/workflows"
source_filename = "standards-compliance.yml"
source_type = "copy"
destination_path = ".github/workflows"
destination_filename = "standards-compliance.yml"
create_path = true
template = ".github/workflows/standards-compliance.yml"
},
{
name = "enterprise-firewall-setup.yml"
extension = "yml"
description = "Enterprise firewall configuration for trusted domain access"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/enterprise-firewall-setup.yml.template"
},
{
name = "deploy-dev.yml"
extension = "yml"
description = "SFTP deployment of src/ to the development server"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-dev.yml.template"
},
{
name = "deploy-demo.yml"
extension = "yml"
description = "SFTP deployment of src/ to the demo server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-demo.yml.template"
},
{
name = "deploy-rs.yml"
extension = "yml"
description = "SFTP deployment of src/ to the release staging server on merge to main"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/deploy-rs.yml.template"
},
{
name = "sync-version-on-merge.yml"
extension = "yml"
description = "Auto-bump patch version on merge and propagate to all file headers"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/sync-version-on-merge.yml.template"
},
{
name = "auto-release.yml"
extension = "yml"
description = "Auto-create GitHub Release on push to main with version from README.md"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-release.yml.template"
},
{
name = "repository-cleanup.yml"
extension = "yml"
description = "Scheduled cleanup: delete retired workflows, stale branches, old workflow runs"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/repository-cleanup.yml.template"
},
{
name = "auto-dev-issue.yml"
extension = "yml"
description = "Auto-create tracking issue when a dev/** branch is pushed"
requirement_status = "required"
always_overwrite = true
template = "templates/workflows/shared/auto-dev-issue.yml.template"
}
]
},
{
name = "ISSUE_TEMPLATE"
path = ".github/ISSUE_TEMPLATE"
description = "GitHub issue templates synced from MokoStandards"
requirement_status = "required"
files = [
{
name = "config.yml"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/config.yml"
},
{
name = "adr.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/adr.md"
},
{
name = "bug_report.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/bug_report.md"
},
{
name = "documentation.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/documentation.md"
},
{
name = "enterprise_support.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/enterprise_support.md"
},
{
name = "feature_request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/feature_request.md"
},
{
name = "firewall-request.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/firewall-request.md"
},
{
name = "question.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/question.md"
},
{
name = "request-license.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/request-license.md"
},
{
name = "rfc.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/rfc.md"
},
{
name = "security.md"
always_overwrite = true
template = "templates/github/ISSUE_TEMPLATE/security.md"
}
]
}
]
},
{
name = "node_modules"
path = "node_modules"
description = "Node.js dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "vendor"
path = "vendor"
description = "PHP dependencies (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "build"
path = "build"
description = "Build artifacts (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
},
{
name = "dist"
path = "dist"
description = "Distribution files (generated)"
requirement_status = "not-allowed"
purpose = "Generated directory that should not be committed"
}
]
repository_requirements = {
secrets = [
{
name = "GH_TOKEN"
description = "Org-level GitHub PAT configure in org Actions secrets"
required = true
scope = "organisation"
used_in = "GitHub Actions workflows"
},
{
name = "CODECOV_TOKEN"
description = "Codecov upload token for code coverage reporting"
required = false
scope = "repository"
used_in = "CI workflow code coverage step"
}
]
variables = [
{
name = "NODE_VERSION"
description = "Node.js version for CI/CD"
default_value = "18"
required = false
scope = "repository"
},
{
name = "PYTHON_VERSION"
description = "Python version for CI/CD"
default_value = "3.9"
required = false
scope = "repository"
}
]
branch_protections = [
{
branch_pattern = "main"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci", "code-quality"]
enforce_admins = false
restrict_pushes = true
},
{
branch_pattern = "master"
require_pull_request = true
required_approvals = 1
require_code_owner_review = false
dismiss_stale_reviews = true
require_status_checks = true
required_status_checks = ["ci"]
enforce_admins = false
restrict_pushes = true
}
]
repository_settings = {
has_issues = true
has_projects = true
has_wiki = false
has_discussions = false
allow_merge_commit = true
allow_squash_merge = true
allow_rebase_merge = false
delete_branch_on_merge = true
allow_auto_merge = false
}
labels = [
{
name = "bug"
color = "d73a4a"
description = "Something isn't working"
},
{
name = "enhancement"
color = "a2eeef"
description = "New feature or request"
},
{
name = "documentation"
color = "0075ca"
description = "Improvements or additions to documentation"
},
{
name = "security"
color = "ee0701"
description = "Security vulnerability or concern"
}
]
}
}
}
File diff suppressed because it is too large Load Diff
-43
View File
@@ -1,43 +0,0 @@
/**
* Repository Sync Tracking Definition: mokoconsulting-tech/joomla-api-mcp
*
* Auto-generated by MokoStandards bulk sync on 2026-04-23T00:00:00+00:00
* Platform : default-repository
* Description: MCP server for Joomla Web Services API operations
*
* DO NOT EDIT MANUALLY — this file is regenerated on every successful sync.
* To change what gets synced, edit api/definitions/default/default-repository.tf
* and re-run the bulk-repo-sync workflow.
*/
locals {
sync_record = {
metadata = {
repo = "mokoconsulting-tech/joomla-api-mcp"
default_branch = "main"
detected_platform = "default-repository"
description = "MCP server for Joomla Web Services API operations"
sync_timestamp = "2026-04-23T00:00:00+00:00"
source_repo = "mokoconsulting-tech/MokoStandards"
base_definition = "api/definitions/default/default-repository.tf"
}
sync_stats = {
total_files = 0
created_files = 0
updated_files = 0
skipped_files = 0
}
synced_files = []
skipped_files = [
{ path = "README.md" reason = "README — never overwritten" },
{ path = "CHANGELOG.md" reason = "CHANGELOG — never overwritten" },
{ path = "CONTRIBUTING.md" reason = "Preserved (always_overwrite=false)" },
{ path = "SECURITY.md" reason = "Preserved (always_overwrite=false)" },
{ path = "CODE_OF_CONDUCT.md" reason = "Preserved (always_overwrite=false)" },
{ path = "ROADMAP.md" reason = "Preserved (always_overwrite=false)" },
]
}
}
+2
View File
@@ -92,6 +92,8 @@ class CircuitBreakerOpen extends RuntimeException
* );
* $response = $client->get('/repos/owner/repo');
* ```
*
* @since 04.00.00
*/
class ApiClient
{
+2
View File
@@ -58,6 +58,8 @@ use RuntimeException;
* $transaction->logSecurityEvent('file_modified', ['file' => 'README.md']);
* $transaction->end();
* ```
*
* @since 04.00.00
*/
class AuditLogger
{

Some files were not shown because too many files have changed in this diff Show More