From bd53fe834f02f2477fb7aae153b71f4ef26638c2 Mon Sep 17 00:00:00 2001 From: Jonathan Miller Date: Sat, 18 Apr 2026 18:17:24 -0500 Subject: [PATCH] feat: add gitignore validation, move bulk-repo-sync workflow here - Add REQUIRED_GITIGNORE_ENTRIES constant with mandatory patterns: Sublime project/workspace, sftp-config, IDE dirs, secrets, vendor, logs - Add validateGitignoreEntries() method for checking required entries - mergeGitConfigFile() still appends missing entries (non-destructive) - Add .gitea/workflows/bulk-repo-sync.yml (moved from MokoStandards) - Runs from this repo directly (checkout self, not remote) - Org updated to MokoConsulting (Gitea) Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitea/workflows/bulk-repo-sync.yml | 112 ++++++++++++++++++++++ lib/Enterprise/RepositorySynchronizer.php | 71 ++++++++++++++ 2 files changed, 183 insertions(+) create mode 100644 .gitea/workflows/bulk-repo-sync.yml diff --git a/.gitea/workflows/bulk-repo-sync.yml b/.gitea/workflows/bulk-repo-sync.yml new file mode 100644 index 0000000..5ee2082 --- /dev/null +++ b/.gitea/workflows/bulk-repo-sync.yml @@ -0,0 +1,112 @@ +# Copyright (C) 2026 Moko Consulting +# SPDX-License-Identifier: GPL-3.0-or-later +# FILE INFORMATION +# DEFGROUP: Gitea.Workflow +# INGROUP: MokoStandards-API.Automation +# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards-API +# PATH: /.gitea/workflows/bulk-repo-sync.yml +# VERSION: 04.06.12 +# BRIEF: Bulk repo sync — runs from API repo, syncs standards to all governed repos + +name: Bulk Repository Sync + +on: + schedule: + - cron: '0 0 1 * *' + workflow_dispatch: + inputs: + dry_run: + description: 'Preview mode (no changes)' + required: false + type: boolean + default: true + repos: + description: 'Comma-separated repo names (empty = all)' + required: false + type: string + default: '' + exclude: + description: 'Comma-separated repos to skip' + required: false + type: string + default: '' + force: + description: 'Force overwrite protected files' + required: false + type: boolean + default: false + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + bulk-sync: + name: Sync Standards to Repositories + runs-on: ubuntu-latest + timeout-minutes: 120 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + extensions: json, mbstring, curl + tools: composer + coverage: none + + - name: Install Dependencies + run: composer install --no-dev --no-interaction --prefer-dist --optimize-autoloader + + - name: Build CLI Arguments + id: args + run: | + ARGS="--org MokoConsulting" + if [ "${{ inputs.dry_run }}" = "true" ] || [ "${{ gitea.event_name }}" = "schedule" ]; then + ARGS="$ARGS --dry-run" + fi + if [ -n "${{ inputs.repos }}" ]; then + ARGS="$ARGS --repos ${{ inputs.repos }}" + fi + if [ -n "${{ inputs.exclude }}" ]; then + ARGS="$ARGS --exclude ${{ inputs.exclude }}" + fi + if [ "${{ inputs.force }}" = "true" ]; then + ARGS="$ARGS --force" + fi + ARGS="$ARGS --yes" + echo "args=$ARGS" >> $GITHUB_OUTPUT + + - name: Run Bulk Sync + run: | + echo "Running: php automation/bulk_sync.php ${{ steps.args.outputs.args }}" + php automation/bulk_sync.php ${{ steps.args.outputs.args }} 2>&1 | tee /tmp/bulk_sync.log + env: + GA_TOKEN: ${{ secrets.GA_TOKEN }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} + GIT_PLATFORM: gitea + GITEA_URL: https://git.mokoconsulting.tech + GITEA_ORG: MokoConsulting + + - name: Commit Updated Definitions + if: success() && inputs.dry_run != 'true' + run: | + if [ -n "$(git status --porcelain definitions/sync/)" ]; then + git config user.name "gitea-actions[bot]" + git config user.email "gitea-actions[bot]@git.mokoconsulting.tech" + git add definitions/sync/*.def.tf + git commit -m "chore: update synced repository definitions" || true + git push || true + fi + + - name: Upload Sync Log + if: always() + uses: actions/upload-artifact@v4 + with: + name: bulk-sync-log-${{ gitea.run_number }} + path: /tmp/bulk_sync.log + retention-days: 30 diff --git a/lib/Enterprise/RepositorySynchronizer.php b/lib/Enterprise/RepositorySynchronizer.php index 03358b2..5633711 100644 --- a/lib/Enterprise/RepositorySynchronizer.php +++ b/lib/Enterprise/RepositorySynchronizer.php @@ -903,6 +903,77 @@ HCL; return $entries; } + /** + * Required .gitignore entries that MUST exist in every governed repo. + * The sync validates these exist (appending if missing) without + * overwriting custom entries. Repos can add their own patterns freely. + */ + private const REQUIRED_GITIGNORE_ENTRIES = [ + // Secrets & environment + '.env', + '.env.local', + '.env.*.local', + 'secrets/', + '*.secrets.*', + + // Sublime Text project files + '*.sublime-project', + '*.sublime-workspace', + '*.sublime-settings', + + // SFTP config (Sublime SFTP, VS Code SFTP, etc.) + 'sftp-config*.json', + 'sftp-config.json.template', + 'sftp-settings.json', + + // IDE / editor + '.idea/', + '.vscode/*', + '.claude/', + '*.code-workspace', + + // OS cruft + '.DS_Store', + 'Thumbs.db', + + // Task tracking + 'TODO.md', + + // Vendor / dependencies + '/vendor/', + 'node_modules/', + + // Logs + '*.log', + ]; + + /** + * Validate that required .gitignore entries exist in a repo. + * Returns array of missing entries, empty if all present. + * + * @param string $existingContent Current .gitignore content from repo + * @return array Missing required entries + */ + public function validateGitignoreEntries(string $existingContent): array + { + $existingLines = array_map('trim', explode("\n", $existingContent)); + $existingSet = []; + foreach ($existingLines as $line) { + if ($line !== '' && !str_starts_with($line, '#')) { + $existingSet[$line] = true; + } + } + + $missing = []; + foreach (self::REQUIRED_GITIGNORE_ENTRIES as $entry) { + if (!isset($existingSet[$entry])) { + $missing[] = $entry; + } + } + + return $missing; + } + private function mergeGitConfigFile(string $existing, string $template): string { $existingLines = array_map('rtrim', explode("\n", $existing));