diff --git a/.github/workflows/update_workflow.yml b/.github/workflows/update_workflow.yml new file mode 100644 index 0000000..9467cf2 --- /dev/null +++ b/.github/workflows/update_workflow.yml @@ -0,0 +1,76 @@ +# Copyright MediaZ Teknoloji A.S. All Rights Reserved. + +name: Update Plugin Workflows + +on: + push: + branches: + - main + paths: + - 'plugin_build.yml.*' + - 'repositories.json' + - 'scripts/update_workflows.py' + - '.github/workflows/update_workflow.yml' + workflow_dispatch: + inputs: + repo: + description: 'Specific repository to update (owner/repo, optional)' + required: false + type: string + branch: + description: 'Specific branch to update (requires repo, optional)' + required: false + type: string + dry_run: + description: 'Dry run mode (show what would be done)' + required: false + type: boolean + default: false + +jobs: + update-workflows: + name: Update Plugin Repository Workflows + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout actions repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Update workflows + env: + GITHUB_TOKEN: ${{ secrets.CI_TOKEN }} + run: | + python3 scripts/update_workflows.py \ + --config repositories.json \ + ${{ github.event.inputs.repo && format('--repo {0}', github.event.inputs.repo) || '' }} \ + ${{ github.event.inputs.branch && format('--branch {0}', github.event.inputs.branch) || '' }} \ + ${{ github.event.inputs.dry_run == 'true' && '--dry-run' || '' }} + + - name: Summary + if: always() + run: | + echo "## Workflow Update Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${{ github.event.inputs.dry_run }}" == "true" ]; then + echo "**Mode:** Dry Run (no changes made)" >> $GITHUB_STEP_SUMMARY + else + echo "**Mode:** Live Update" >> $GITHUB_STEP_SUMMARY + fi + echo "" >> $GITHUB_STEP_SUMMARY + if [ -n "${{ github.event.inputs.repo }}" ]; then + echo "**Repository:** ${{ github.event.inputs.repo }}" >> $GITHUB_STEP_SUMMARY + if [ -n "${{ github.event.inputs.branch }}" ]; then + echo "**Branch:** ${{ github.event.inputs.branch }}" >> $GITHUB_STEP_SUMMARY + fi + else + echo "**Scope:** All repositories" >> $GITHUB_STEP_SUMMARY + fi diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0610878 --- /dev/null +++ b/.gitignore @@ -0,0 +1,37 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +venv/ +ENV/ +env/ + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db diff --git a/.nodos-template/workflow_config.json b/.nodos-template/workflow_config.json new file mode 100644 index 0000000..737efc6 --- /dev/null +++ b/.nodos-template/workflow_config.json @@ -0,0 +1,5 @@ +{ + "build_number_offset": 0, + "linux_enabled": true, + "windows_enabled": true +} diff --git a/README.md b/README.md index a4a79ac..3b1bf7b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,20 @@ -# Nodos Action Matrix +# Nodos Actions Repository + +This repository contains GitHub Actions workflows and automation tools for the Nodos project. + +## Plugin Workflow Sync System + +This repository includes an automated system for syncing build workflow files to all plugin repositories. This ensures that all plugins use consistent, up-to-date workflow configurations. + +**Key Features:** +- Templated workflow files for different branches (dev, nodos-1.3, nodos-1.2) +- Repository-specific configuration support (build number offsets, platform toggles) +- Automatic synchronization on template changes +- Manual sync capability for testing and updates + +For detailed information, see [WORKFLOW_SYNC_README.md](WORKFLOW_SYNC_README.md) + +## Build Status Matrix Badges below show the latest GitHub Actions run status for each build workflow on the `dev`, `nodos-1.3`, and `nodos-1.2` branches. diff --git a/WORKFLOW_SYNC_README.md b/WORKFLOW_SYNC_README.md new file mode 100644 index 0000000..c6b2b81 --- /dev/null +++ b/WORKFLOW_SYNC_README.md @@ -0,0 +1,116 @@ +# Plugin Workflow Sync System + +This directory contains the workflow synchronization system for plugin repositories. + +## Overview + +The system automatically syncs build workflow files from this repository to all plugin repositories, ensuring they stay in sync and up-to-date. + +## Files + +### Template Files + +- `plugin_build.yml.dev` - Workflow template for `dev` branches +- `plugin_build.yml.nodos-1.3` - Workflow template for `nodos-1.3` branches +- `plugin_build.yml.nodos-1.2` - Workflow template for `nodos-1.2` branches + +These templates contain placeholders that are replaced with repository-specific values: +- `__BUILD_NUMBER_OFFSET__` - Starting build number offset for the repository +- `__LINUX_ENABLED__` - Whether Linux builds are enabled (true/false) +- `__WINDOWS_ENABLED__` - Whether Windows builds are enabled (true/false) + +### Configuration Files + +- `repositories.json` - Maps repositories and branches to their workflow templates +- `.nodos-template/workflow_config.json` - Example repository-specific configuration + +### Scripts + +- `scripts/update_workflows.py` - Python script that performs the workflow synchronization + +### Workflows + +- `.github/workflows/update_workflow.yml` - GitHub Actions workflow that runs the sync + +## How It Works + +1. When changes are pushed to template files or configuration in this repository, the `update_workflow.yml` workflow is triggered +2. The workflow runs `update_workflows.py` which: + - Reads `repositories.json` to get the list of repositories/branches to update + - For each repository/branch: + - Clones the repository + - Reads the repository-specific configuration from `.nodos/workflow_config.json` + - Applies the template substitutions + - Writes the result to `.github/workflows/build.yml` + - Commits and pushes the changes +3. Plugin repositories are updated automatically with the latest workflow files + +## Repository-Specific Configuration + +Each plugin repository can have a `.nodos/workflow_config.json` file with the following structure: + +```json +{ + "build_number_offset": 0, + "linux_enabled": true, + "windows_enabled": true +} +``` + +**Fields:** +- `build_number_offset` (number): Offset to add to GitHub's `run_number` for build numbering. Default: 0 +- `linux_enabled` (boolean): Whether Linux builds are enabled for this repository. Default: true +- `windows_enabled` (boolean): Whether Windows builds are enabled for this repository. Default: true + +If the configuration file doesn't exist, default values are used (all enabled, no offset). + +## Adding a New Plugin Repository + +1. Add an entry to `repositories.json` for each branch you want to sync: + +```json +{ + "repo": "nodos-dev/new-plugin", + "branch": "dev", + "workflow_template": "plugin_build.yml.dev" +} +``` + +2. Optionally, create `.nodos/workflow_config.json` in the plugin repository with custom settings + +3. Push the changes to this repository, which will trigger the automatic sync + +## Manual Workflow Sync + +You can manually trigger the workflow sync from the Actions tab: + +1. Go to Actions → Update Plugin Workflows +2. Click "Run workflow" +3. Optionally specify: + - A specific repository to update (e.g., `nodos-dev/audio`) + - A specific branch (requires repository) + - Dry run mode to preview changes without applying them + +## Template Customization + +To customize the workflow templates: + +1. Edit the appropriate `plugin_build.yml.*` file +2. Use placeholders (`__PLACEHOLDER__`) for repository-specific values +3. Update `scripts/update_workflows.py` if you add new placeholders +4. Push changes to trigger automatic sync + +## Testing + +To test changes without affecting production: + +1. Use the workflow dispatch option with dry-run mode enabled +2. Or test with a specific repository/branch first +3. Check the logs to see what changes would be made + +## Notes + +- The workflow files in plugin repositories should NOT be edited directly +- Changes should be made to the templates in this repository +- The sync system uses the `CI_TOKEN` secret for authentication +- Failed syncs will be reported in the workflow logs diff --git a/plugin_build.yml.dev b/plugin_build.yml.dev new file mode 100644 index 0000000..eef5549 --- /dev/null +++ b/plugin_build.yml.dev @@ -0,0 +1,88 @@ +# Copyright MediaZ Teknoloji A.S. All Rights Reserved. +# This file is automatically synced from the nodos-dev/actions repository +# DO NOT edit this file directly in the plugin repository + +name: Build + +on: + push: + branches: [ dev ] + workflow_dispatch: + inputs: + publish_mode: + description: 'Publish Mode' + required: true + default: 'If Changed' + type: choice + options: + - If Changed + - Force + - None + plugin_names: + description: 'Publish only these plugins (semicolon-delimited) (optional)' + required: false + default: '' + type: string + run_tests: + description: 'Run Tests' + required: false + default: false + type: boolean + clean: + description: 'Clean build' + required: false + default: false + type: boolean + sign_binaries: + description: 'Sign binaries' + required: false + default: false + type: boolean + linux: + description: 'Release for Linux' + required: false + default: true + type: boolean + windows: + description: 'Release for Windows' + required: false + default: true + type: boolean + version_check: + description: 'Version Check' + required: true + default: 'strict' + type: choice + options: + - none + - loose + - strict + +run-name: >- + ${{ ((github.event_name == 'push') && format('Build Only: {0}', github.event.head_commit.message)) || + (github.event.inputs.publish_mode == 'If Changed') && 'Build & Release' || + (github.event.inputs.publish_mode == 'Force') && 'Build & Release (Force)' || + 'Build Only' }} + +concurrency: + group: single + cancel-in-progress: false + +jobs: + call-release-plugins: + name: Build + uses: nodos-dev/actions/.github/workflows/plugins.yml@main + secrets: + CI_TOKEN: ${{ secrets.CI_TOKEN }} + SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN }} + with: + ref_name: ${{ github.ref_name }} + publish_mode: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.publish_mode || 'None' }} + plugin_names: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.plugin_names || '' }} + clean: ${{ github.event_name == 'workflow_dispatch' && fromJson(github.event.inputs.clean) || false }} + build_number: ${{ github.run_number + __BUILD_NUMBER_OFFSET__ }} + sign_binaries: ${{github.event_name == 'workflow_dispatch' && fromJson(github.event.inputs.sign_binaries) || false }} + linux: ${{ __LINUX_ENABLED__ && (github.event_name == 'workflow_dispatch' && fromJson(github.event.inputs.linux) || github.event_name == 'push') }} + windows: ${{ __WINDOWS_ENABLED__ && (github.event_name == 'workflow_dispatch' && fromJson(github.event.inputs.windows) || github.event_name == 'push') }} + run_tests: ${{ github.event_name == 'workflow_dispatch' && fromJson(github.event.inputs.run_tests) || false }} + version_check: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version_check || 'strict' }} diff --git a/plugin_build.yml.nodos-1.2 b/plugin_build.yml.nodos-1.2 new file mode 100644 index 0000000..48855aa --- /dev/null +++ b/plugin_build.yml.nodos-1.2 @@ -0,0 +1,88 @@ +# Copyright MediaZ Teknoloji A.S. All Rights Reserved. +# This file is automatically synced from the nodos-dev/actions repository +# DO NOT edit this file directly in the plugin repository + +name: Build + +on: + push: + branches: [ nodos-1.2 ] + workflow_dispatch: + inputs: + publish_mode: + description: 'Publish Mode' + required: true + default: 'If Changed' + type: choice + options: + - If Changed + - Force + - None + plugin_names: + description: 'Publish only these plugins (semicolon-delimited) (optional)' + required: false + default: '' + type: string + run_tests: + description: 'Run Tests' + required: false + default: false + type: boolean + clean: + description: 'Clean build' + required: false + default: false + type: boolean + sign_binaries: + description: 'Sign binaries' + required: false + default: false + type: boolean + linux: + description: 'Release for Linux' + required: false + default: true + type: boolean + windows: + description: 'Release for Windows' + required: false + default: true + type: boolean + version_check: + description: 'Version Check' + required: true + default: 'strict' + type: choice + options: + - none + - loose + - strict + +run-name: >- + ${{ ((github.event_name == 'push') && format('Build Only: {0}', github.event.head_commit.message)) || + (github.event.inputs.publish_mode == 'If Changed') && 'Build & Release' || + (github.event.inputs.publish_mode == 'Force') && 'Build & Release (Force)' || + 'Build Only' }} + +concurrency: + group: single + cancel-in-progress: false + +jobs: + call-release-plugins: + name: Build + uses: nodos-dev/actions/.github/workflows/plugins.yml@main + secrets: + CI_TOKEN: ${{ secrets.CI_TOKEN }} + SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN }} + with: + ref_name: ${{ github.ref_name }} + publish_mode: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.publish_mode || 'None' }} + plugin_names: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.plugin_names || '' }} + clean: ${{ github.event_name == 'workflow_dispatch' && fromJson(github.event.inputs.clean) || false }} + build_number: ${{ github.run_number + __BUILD_NUMBER_OFFSET__ }} + sign_binaries: ${{github.event_name == 'workflow_dispatch' && fromJson(github.event.inputs.sign_binaries) || false }} + linux: ${{ __LINUX_ENABLED__ && (github.event_name == 'workflow_dispatch' && fromJson(github.event.inputs.linux) || github.event_name == 'push') }} + windows: ${{ __WINDOWS_ENABLED__ && (github.event_name == 'workflow_dispatch' && fromJson(github.event.inputs.windows) || github.event_name == 'push') }} + run_tests: ${{ github.event_name == 'workflow_dispatch' && fromJson(github.event.inputs.run_tests) || false }} + version_check: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version_check || 'strict' }} diff --git a/plugin_build.yml.nodos-1.3 b/plugin_build.yml.nodos-1.3 new file mode 100644 index 0000000..9a0c904 --- /dev/null +++ b/plugin_build.yml.nodos-1.3 @@ -0,0 +1,88 @@ +# Copyright MediaZ Teknoloji A.S. All Rights Reserved. +# This file is automatically synced from the nodos-dev/actions repository +# DO NOT edit this file directly in the plugin repository + +name: Build + +on: + push: + branches: [ nodos-1.3 ] + workflow_dispatch: + inputs: + publish_mode: + description: 'Publish Mode' + required: true + default: 'If Changed' + type: choice + options: + - If Changed + - Force + - None + plugin_names: + description: 'Publish only these plugins (semicolon-delimited) (optional)' + required: false + default: '' + type: string + run_tests: + description: 'Run Tests' + required: false + default: false + type: boolean + clean: + description: 'Clean build' + required: false + default: false + type: boolean + sign_binaries: + description: 'Sign binaries' + required: false + default: false + type: boolean + linux: + description: 'Release for Linux' + required: false + default: true + type: boolean + windows: + description: 'Release for Windows' + required: false + default: true + type: boolean + version_check: + description: 'Version Check' + required: true + default: 'strict' + type: choice + options: + - none + - loose + - strict + +run-name: >- + ${{ ((github.event_name == 'push') && format('Build Only: {0}', github.event.head_commit.message)) || + (github.event.inputs.publish_mode == 'If Changed') && 'Build & Release' || + (github.event.inputs.publish_mode == 'Force') && 'Build & Release (Force)' || + 'Build Only' }} + +concurrency: + group: single + cancel-in-progress: false + +jobs: + call-release-plugins: + name: Build + uses: nodos-dev/actions/.github/workflows/plugins.yml@main + secrets: + CI_TOKEN: ${{ secrets.CI_TOKEN }} + SENTRY_TOKEN: ${{ secrets.SENTRY_TOKEN }} + with: + ref_name: ${{ github.ref_name }} + publish_mode: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.publish_mode || 'None' }} + plugin_names: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.plugin_names || '' }} + clean: ${{ github.event_name == 'workflow_dispatch' && fromJson(github.event.inputs.clean) || false }} + build_number: ${{ github.run_number + __BUILD_NUMBER_OFFSET__ }} + sign_binaries: ${{github.event_name == 'workflow_dispatch' && fromJson(github.event.inputs.sign_binaries) || false }} + linux: ${{ __LINUX_ENABLED__ && (github.event_name == 'workflow_dispatch' && fromJson(github.event.inputs.linux) || github.event_name == 'push') }} + windows: ${{ __WINDOWS_ENABLED__ && (github.event_name == 'workflow_dispatch' && fromJson(github.event.inputs.windows) || github.event_name == 'push') }} + run_tests: ${{ github.event_name == 'workflow_dispatch' && fromJson(github.event.inputs.run_tests) || false }} + version_check: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.version_check || 'strict' }} diff --git a/repositories.json b/repositories.json new file mode 100644 index 0000000..1070407 --- /dev/null +++ b/repositories.json @@ -0,0 +1,199 @@ +{ + "repositories": [ + { + "repo": "nodos-dev/audio", + "branch": "dev", + "workflow_template": "plugin_build.yml.dev" + }, + { + "repo": "nodos-dev/audio", + "branch": "nodos-1.3", + "workflow_template": "plugin_build.yml.nodos-1.3" + }, + { + "repo": "nodos-dev/audio", + "branch": "nodos-1.2", + "workflow_template": "plugin_build.yml.nodos-1.2" + }, + { + "repo": "nodos-dev/aja", + "branch": "dev", + "workflow_template": "plugin_build.yml.dev" + }, + { + "repo": "nodos-dev/aja", + "branch": "nodos-1.3", + "workflow_template": "plugin_build.yml.nodos-1.3" + }, + { + "repo": "nodos-dev/aja", + "branch": "nodos-1.2", + "workflow_template": "plugin_build.yml.nodos-1.2" + }, + { + "repo": "nodos-dev/decklink", + "branch": "dev", + "workflow_template": "plugin_build.yml.dev" + }, + { + "repo": "nodos-dev/decklink", + "branch": "nodos-1.3", + "workflow_template": "plugin_build.yml.nodos-1.3" + }, + { + "repo": "nodos-dev/decklink", + "branch": "nodos-1.2", + "workflow_template": "plugin_build.yml.nodos-1.2" + }, + { + "repo": "nodos-dev/display", + "branch": "dev", + "workflow_template": "plugin_build.yml.dev" + }, + { + "repo": "nodos-dev/display", + "branch": "nodos-1.3", + "workflow_template": "plugin_build.yml.nodos-1.3" + }, + { + "repo": "nodos-dev/display", + "branch": "nodos-1.2", + "workflow_template": "plugin_build.yml.nodos-1.2" + }, + { + "repo": "nodos-dev/mediaio", + "branch": "dev", + "workflow_template": "plugin_build.yml.dev" + }, + { + "repo": "nodos-dev/mediaio", + "branch": "nodos-1.3", + "workflow_template": "plugin_build.yml.nodos-1.3" + }, + { + "repo": "nodos-dev/mediaio", + "branch": "nodos-1.2", + "workflow_template": "plugin_build.yml.nodos-1.2" + }, + { + "repo": "nodos-dev/modules", + "branch": "dev", + "workflow_template": "plugin_build.yml.dev" + }, + { + "repo": "nodos-dev/modules", + "branch": "nodos-1.3", + "workflow_template": "plugin_build.yml.nodos-1.3" + }, + { + "repo": "nodos-dev/modules", + "branch": "nodos-1.2", + "workflow_template": "plugin_build.yml.nodos-1.2" + }, + { + "repo": "mediaz/nos-sys-vulkan", + "branch": "dev", + "workflow_template": "plugin_build.yml.dev" + }, + { + "repo": "mediaz/nos-sys-vulkan", + "branch": "nodos-1.3", + "workflow_template": "plugin_build.yml.nodos-1.3" + }, + { + "repo": "mediaz/nos-sys-vulkan", + "branch": "nodos-1.2", + "workflow_template": "plugin_build.yml.nodos-1.2" + }, + { + "repo": "nodos-dev/rive", + "branch": "dev", + "workflow_template": "plugin_build.yml.dev" + }, + { + "repo": "nodos-dev/rive", + "branch": "nodos-1.3", + "workflow_template": "plugin_build.yml.nodos-1.3" + }, + { + "repo": "nodos-dev/rive", + "branch": "nodos-1.2", + "workflow_template": "plugin_build.yml.nodos-1.2" + }, + { + "repo": "nodos-dev/sys-device", + "branch": "dev", + "workflow_template": "plugin_build.yml.dev" + }, + { + "repo": "nodos-dev/sys-device", + "branch": "nodos-1.3", + "workflow_template": "plugin_build.yml.nodos-1.3" + }, + { + "repo": "nodos-dev/sys-device", + "branch": "nodos-1.2", + "workflow_template": "plugin_build.yml.nodos-1.2" + }, + { + "repo": "nodos-dev/sys-settings", + "branch": "dev", + "workflow_template": "plugin_build.yml.dev" + }, + { + "repo": "nodos-dev/sys-settings", + "branch": "nodos-1.3", + "workflow_template": "plugin_build.yml.nodos-1.3" + }, + { + "repo": "nodos-dev/sys-settings", + "branch": "nodos-1.2", + "workflow_template": "plugin_build.yml.nodos-1.2" + }, + { + "repo": "nodos-dev/test", + "branch": "dev", + "workflow_template": "plugin_build.yml.dev" + }, + { + "repo": "nodos-dev/test", + "branch": "nodos-1.3", + "workflow_template": "plugin_build.yml.nodos-1.3" + }, + { + "repo": "nodos-dev/test", + "branch": "nodos-1.2", + "workflow_template": "plugin_build.yml.nodos-1.2" + }, + { + "repo": "nodos-dev/transfer", + "branch": "dev", + "workflow_template": "plugin_build.yml.dev" + }, + { + "repo": "nodos-dev/transfer", + "branch": "nodos-1.3", + "workflow_template": "plugin_build.yml.nodos-1.3" + }, + { + "repo": "nodos-dev/transfer", + "branch": "nodos-1.2", + "workflow_template": "plugin_build.yml.nodos-1.2" + }, + { + "repo": "nodos-dev/webcam", + "branch": "dev", + "workflow_template": "plugin_build.yml.dev" + }, + { + "repo": "nodos-dev/webcam", + "branch": "nodos-1.3", + "workflow_template": "plugin_build.yml.nodos-1.3" + }, + { + "repo": "nodos-dev/webcam", + "branch": "nodos-1.2", + "workflow_template": "plugin_build.yml.nodos-1.2" + } + ] +} diff --git a/scripts/update_workflows.py b/scripts/update_workflows.py new file mode 100755 index 0000000..1de3415 --- /dev/null +++ b/scripts/update_workflows.py @@ -0,0 +1,280 @@ +#!/usr/bin/env python3 +""" +Update workflow files in plugin repositories based on templates. + +This script reads the repositories.json configuration file and updates +the build.yml workflow file in each repository/branch with the appropriate +template, applying repository-specific configuration. +""" + +import argparse +import json +import os +import sys +import subprocess +import tempfile + + +def run_command(cmd, cwd=None, check=True): + """Run a shell command and return the output.""" + result = subprocess.run( + cmd, + shell=True, + cwd=cwd, + capture_output=True, + text=True, + check=False + ) + if check and result.returncode != 0: + print(f"Error running command: {cmd}") + print(f"stdout: {result.stdout}") + print(f"stderr: {result.stderr}") + sys.exit(1) + return result + + +def get_repo_config(repo_path): + """ + Fetch repository-specific configuration from .nodos/workflow_config.json + in the plugin repository. + """ + config_path = os.path.join(repo_path, '.nodos', 'workflow_config.json') + + default_config = { + 'build_number_offset': 0, + 'linux_enabled': True, + 'windows_enabled': True + } + + if not os.path.exists(config_path): + print(f" No config file found at {config_path}, using defaults") + return default_config + + try: + with open(config_path, 'r') as f: + config = json.load(f) + # Merge with defaults + return {**default_config, **config} + except (json.JSONDecodeError, FileNotFoundError, PermissionError, OSError) as e: + print(f" Error reading config file: {e}, using defaults") + return default_config + + +def apply_template_substitutions(template_content, repo_config): + """ + Apply repository-specific substitutions to the workflow template. + + Placeholders: + - __BUILD_NUMBER_OFFSET__: Starting build number offset + - __LINUX_ENABLED__: Whether Linux builds are enabled + - __WINDOWS_ENABLED__: Whether Windows builds are enabled + """ + content = template_content + + # Replace build number offset + build_offset = repo_config.get('build_number_offset', 0) + content = content.replace('__BUILD_NUMBER_OFFSET__', str(build_offset)) + + # Replace linux/windows enabled flags + linux_enabled = str(repo_config.get('linux_enabled', True)).lower() + windows_enabled = str(repo_config.get('windows_enabled', True)).lower() + content = content.replace('__LINUX_ENABLED__', linux_enabled) + content = content.replace('__WINDOWS_ENABLED__', windows_enabled) + + return content + + +def update_repository_workflow(repo, branch, workflow_template, token, dry_run=False): + """ + Update the workflow file in a specific repository/branch. + """ + print(f"\nProcessing {repo} (branch: {branch})") + + # Read the template file + template_path = workflow_template + if not os.path.exists(template_path): + print(f" Error: Template file not found: {template_path}") + return False + + with open(template_path, 'r') as f: + template_content = f.read() + + # Create a temporary directory for cloning + with tempfile.TemporaryDirectory() as tmpdir: + repo_dir = os.path.join(tmpdir, 'repo') + + # Clone the repository + # Note: Token is passed via environment variable to avoid exposure in logs + clone_url = f"https://github.com/{repo}.git" + print(f" Cloning repository...") + + # Configure git credentials temporarily + env = os.environ.copy() + env['GIT_TERMINAL_PROMPT'] = '0' + + result = subprocess.run( + [ + 'git', 'clone', + '--depth', '1', + '--branch', branch, + f'https://x-access-token:{token}@github.com/{repo}.git', + repo_dir + ], + capture_output=True, + text=True, + check=False, + env=env + ) + + if result.returncode != 0: + print(f" Failed to clone repository (branch might not exist)") + return False + + # Get repository-specific configuration + repo_config = get_repo_config(repo_dir) + print(f" Config: {repo_config}") + + # Apply template substitutions + workflow_content = apply_template_substitutions(template_content, repo_config) + + # Ensure .github/workflows directory exists + workflows_dir = os.path.join(repo_dir, '.github', 'workflows') + os.makedirs(workflows_dir, exist_ok=True) + + # Write the workflow file + workflow_path = os.path.join(workflows_dir, 'build.yml') + + # Check if file needs updating + needs_update = True + if os.path.exists(workflow_path): + with open(workflow_path, 'r') as f: + current_content = f.read() + needs_update = current_content != workflow_content + + if not needs_update: + print(f" Workflow file is already up to date") + return True + + if dry_run: + print(f" [DRY RUN] Would update workflow file") + return True + + with open(workflow_path, 'w') as f: + f.write(workflow_content) + + # Configure git + run_command('git config user.name "nodos-bot"', cwd=repo_dir) + run_command('git config user.email "bot@nodos.dev"', cwd=repo_dir) + + # Check if there are changes + result = run_command('git status --porcelain', cwd=repo_dir) + if not result.stdout.strip(): + print(f" No changes to commit") + return True + + # Commit and push + run_command('git add .github/workflows/build.yml', cwd=repo_dir) + run_command( + 'git commit -m "Update build workflow from actions repository"', + cwd=repo_dir + ) + print(f" Pushing changes...") + run_command(f'git push origin {branch}', cwd=repo_dir) + + print(f" Successfully updated workflow") + return True + + +def main(): + parser = argparse.ArgumentParser( + description='Update plugin repository workflow files from templates' + ) + parser.add_argument( + '--config', + default='repositories.json', + help='Path to repositories configuration file' + ) + parser.add_argument( + '--token', + help='GitHub token for authentication (or set GITHUB_TOKEN env var)' + ) + parser.add_argument( + '--dry-run', + action='store_true', + help='Show what would be done without making changes' + ) + parser.add_argument( + '--repo', + help='Only update this specific repository (format: owner/repo)' + ) + parser.add_argument( + '--branch', + help='Only update this specific branch (requires --repo)' + ) + + args = parser.parse_args() + + # Get GitHub token + token = args.token or os.environ.get('GITHUB_TOKEN') + if not token: + print("Error: GitHub token not provided. Use --token or set GITHUB_TOKEN env var") + sys.exit(1) + + # Read repositories configuration + if not os.path.exists(args.config): + print(f"Error: Configuration file not found: {args.config}") + sys.exit(1) + + with open(args.config, 'r') as f: + config = json.load(f) + + repositories = config.get('repositories', []) + + # Filter by repo/branch if specified + if args.repo: + repositories = [r for r in repositories if r['repo'] == args.repo] + if args.branch: + repositories = [r for r in repositories if r['branch'] == args.branch] + + if not repositories: + print("No repositories to process") + sys.exit(0) + + print(f"Processing {len(repositories)} repository/branch combinations") + if args.dry_run: + print("DRY RUN MODE - No changes will be made") + + success_count = 0 + fail_count = 0 + + for repo_config in repositories: + repo = repo_config['repo'] + branch = repo_config['branch'] + workflow_template = repo_config['workflow_template'] + + try: + success = update_repository_workflow( + repo, + branch, + workflow_template, + token, + dry_run=args.dry_run + ) + if success: + success_count += 1 + else: + fail_count += 1 + except Exception as e: + print(f" Error processing {repo}/{branch}: {e}") + fail_count += 1 + + print(f"\n{'='*60}") + print(f"Summary: {success_count} succeeded, {fail_count} failed") + print(f"{'='*60}") + + if fail_count > 0: + sys.exit(1) + + +if __name__ == '__main__': + main()