Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/sync-files.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Sync Managed Files

on:
schedule:
- cron: '0 6 * * *' # Daily at 06:00 UTC
workflow_dispatch:

permissions:
contents: read

jobs:
sync-files:
name: Sync files to subscribing repositories
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 # v4.2.1

- name: Sync managed files
uses: PSModule/GitHub-Script@e3b0111c93df3686061cb2c65054f9216ed265e5 # main
with:
Script: ./scripts/Sync-Files.ps1
ClientID: ${{ secrets.CUSTO_BOT_CLIENT_ID }}
PrivateKey: ${{ secrets.CUSTO_BOT_PRIVATE_KEY }}
237 changes: 236 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,237 @@
# Distributor
A central repo for rolling out files to mulitple repos in the organization.

A central repository for distributing and syncing shared files across multiple repositories in the PSModule organization.

## Overview

The Distributor service maintains a centralized collection of files that are automatically synced to subscribing repositories in the PSModule organization. This ensures consistency across repositories for configuration files, linter settings, documentation templates, GitHub Actions workflows, and other shared resources.

## How It Works

### Convention-Based Structure

Files are organized in a **two-level folder hierarchy** under the `Repos/` directory:

```
Repos/{Type}/{Selection}/
```

- **Type** (first level): Groups repositories by their kind (Module, Action, Template, Workflow, etc.)
- **Selection** (second level): Individual file sets that repositories can subscribe to
- Each selection folder mimics the root of a target repository

### Subscription Model

Repositories subscribe to file sets using **two custom properties** defined at the organization level:

| Property | Type | Description |
|----------|------|-------------|
| `Type` | Single-select | Determines which type folder to use (Module, Action, Template, Workflow, Docs, Other) |
| `SubscribeTo` | Multi-select | Determines which file sets to receive |

**Available `SubscribeTo` values** (must match folder names exactly):
- `Custom Instructions` - Copilot instructions for the repository
- `Prompts` - GitHub Copilot prompt files
- `Hooks` - GitHub Copilot agent hooks for session events (sessionStart, sessionEnd, userPromptSubmitted)
- `CODEOWNERS` - GitHub CODEOWNERS file
- `dependabot.yml` - Dependabot configuration
- `Linter Settings` - Linter configuration files
- `PSModule Settings` - PSModule-specific configuration (Module type only)
- `gitattributes` - Git attributes file
- `gitignore` - Git ignore patterns
- `License` - License file
- Additional values can be added as new folders are created

> **Important**: The `SubscribeTo` values are organization-wide, but not all values are available for all types. For example, `PSModule Settings` only exists under the `Module` type. If a repository subscribes to a selection that doesn't exist for its type, the workflow will log a warning and skip that selection.

Repositories self-manage their subscriptions by setting these custom property values.

### Sync Process

A scheduled GitHub Actions workflow runs daily and:

1. Discovers available file sets from the `Repos/` directory structure
2. Queries all organization repositories for their subscription preferences
3. For each subscribing repository:
- Clones the repository
- Copies files from the subscribed file sets
- Detects changes using git
- Creates or updates a pull request if changes are detected
4. Outputs a summary of actions taken

### Pull Request Lifecycle

When changes are detected, the workflow creates a pull request with:

- **Title**: `⚙️ [Maintenance]: Sync managed files`
- **Label**: `NoRelease`
- **Branch**: `managed-files/update`
- **Description**: Explains that files are centrally managed

If a PR already exists from a previous sync, the workflow updates it by force-pushing to the existing branch.

## Repository Structure

```
Distributor/
├── Repos/ # File sets organized by type and selection
│ ├── Module/ # Files for PowerShell modules
│ │ ├── Custom Instructions/ # Copilot instructions
│ │ ├── Linter Settings/ # Linter configurations
│ │ ├── PSModule Settings/ # PSModule-specific configs
│ │ ├── gitattributes/ # Git attributes (.gitattributes file)
│ │ ├── gitignore/ # Git ignore patterns (.gitignore file)
│ │ └── License/ # License file
│ ├── Action/ # Files for GitHub Actions
│ │ ├── Custom Instructions/
│ │ ├── gitattributes/
│ │ ├── gitignore/
│ │ └── License/
│ ├── Template/ # Files for repository templates
│ └── Workflow/ # Files for reusable workflows
├── scripts/
│ └── Sync-Files.ps1 # Main sync script
└── .github/
└── workflows/
└── sync-files.yml # Scheduled workflow
```

> **Note**: Folder names must match the custom property values exactly. For git-specific files like `.gitattributes` and `.gitignore`, the folders are named without the leading dot (`gitattributes`, `gitignore`) to avoid conflicts with Git's special handling of these filenames.

## Adding New File Sets

To add a new file set:

1. **Add the selection value** to the `SubscribeTo` custom property definition in the organization settings
2. **Create a new folder** under the appropriate type: `Repos/{Type}/{SelectionName}/`
3. **Add files** to the folder, organizing them as they should appear in target repositories
4. Commit and push the changes
5. The next scheduled sync will distribute these files to subscribing repositories

### Example: Adding a new "CODEOWNERS" file set

```bash
# 1. Add "CODEOWNERS" to the SubscribeTo custom property in GitHub organization settings

# 2. Create the folder structure
mkdir -p "Repos/Module/CODEOWNERS/.github"

# 3. Add the CODEOWNERS file
cat > "Repos/Module/CODEOWNERS/.github/CODEOWNERS" << 'EOF'
# Default owners for everything
* @PSModule/maintainers

# Specific paths
/.github/ @PSModule/infrastructure
EOF

# 4. Commit and push
git add Repos/Module/CODEOWNERS/
git commit -m "Add CODEOWNERS file set for modules"
git push
```

## Subscribing Repositories

Repository owners can subscribe to file sets by setting their repository's custom properties:

1. Go to the repository settings
2. Navigate to the **Custom properties** section
3. Set the **Type** property (e.g., "Module")
4. Select one or more **SubscribeTo** values (e.g., "Linter Settings", "License")
5. Save the changes

On the next scheduled sync (or manual trigger), the repository will receive a pull request with the selected files.

## Important Behaviors

### File Creation and Updates

- The sync process **creates new files** and **overwrites existing files**
- Files are forcefully synchronized to match the source
- Local changes to managed files will be overwritten

### File Deletion

- Files are **never deleted** from target repositories
- If a file is removed from a file set, the previously synced copy remains in target repos but is no longer managed
- Manual cleanup is required if you want to remove files from target repositories

### Change Detection

- The workflow only creates PRs when git detects actual changes
- Repositories already in sync are skipped (no empty PRs)
- Only changed files are included in commits

### Existing PRs

- If a `managed-files/update` branch already exists, the workflow force-pushes to update it
- This updates the existing PR rather than creating duplicates
- Review and merge the PR to complete the sync

## Workflow Triggers

The sync workflow runs:

- **Daily** at 06:00 UTC (scheduled via cron)
- **Manually** via workflow_dispatch in the GitHub Actions UI

## GitHub App Requirements

The workflow uses the **PSModule's Custo** GitHub App with the following permissions:

| Permission | Access | Purpose |
|------------|--------|---------|
| `contents` | Write | Clone repos, push branches |
| `pull_requests` | Write | Create PRs, apply labels |
| `repository_custom_properties` | Read | Read subscription preferences |
| `metadata` | Read | Repository information |

### Required Secrets

The following secrets must be configured in this repository:

- `CUSTO_BOT_CLIENT_ID`: The GitHub App's client ID
- `CUSTO_BOT_PRIVATE_KEY`: The GitHub App's private key (PEM format)

## Troubleshooting

### Repository not receiving files

1. Verify the repository has both `Type` and `SubscribeTo` custom properties set
2. Check that the `Type` value matches a folder under `Repos/`
3. Check that each `SubscribeTo` value has a corresponding folder under `Repos/{Type}/`
4. Review the workflow logs for warnings or errors

### Files not updating

1. Check if the repository already has an open PR from the `managed-files/update` branch
2. Verify that the source files in `Repos/` have actually changed
3. Check the workflow logs for the repository in question

### Workflow failures

1. Review the workflow run logs in the Actions tab
2. Check that the GitHub App credentials are valid
3. Verify that the GitHub App has the required permissions
4. Ensure the GitHub App is installed on the target repositories

## Development and Testing

To test changes locally:

```bash
# Install the GitHub PowerShell module
Install-Module -Name GitHub -Force

# Authenticate (using a PAT for local testing)
$env:GITHUB_TOKEN = 'your-pat-here'
Connect-GitHub

# Run the sync script
./scripts/Sync-Files.ps1
```

## License

MIT License - See LICENSE file for details
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
files:
- '**/*.yml'
- '**/*.yaml'
- 'action.yml'
- 'action.yaml'
---

# GitHub Actions Instructions

These are centrally managed instructions for developing GitHub Actions in PSModule repositories.

## Guidelines

### Action Structure
- Use semantic versioning for releases (v1.0.0, v1.1.0, etc.)
- Provide clear action.yml metadata with description and inputs
- Include comprehensive README with usage examples
- Add branding icon and color for Marketplace visibility

### Code Quality
- Follow GitHub Actions best practices
- Use TypeScript for JavaScript actions or containers for complex logic
- Include comprehensive testing (unit tests and integration tests)
- Validate all inputs and provide clear error messages

### Documentation
- Document all inputs, outputs, and usage examples
- Include a "Getting Started" section in README
- Add workflow examples showing common use cases
- Keep CHANGELOG updated with each release

### Security
- Pin action dependencies to specific SHAs
- Scan for vulnerabilities regularly
- Follow principle of least privilege for permissions
- Never log sensitive information

### Maintenance
- Keep dependencies up to date
- Respond to issues and PRs in a timely manner
- Mark breaking changes clearly in releases
- Maintain backward compatibility when possible
21 changes: 21 additions & 0 deletions Repos/Action/License/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 PSModule

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
17 changes: 17 additions & 0 deletions Repos/Action/gitattributes/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Set default behavior to automatically normalize line endings
* text=auto eol=lf

# GitHub Actions workflow files
*.yml text eol=lf
*.yaml text eol=lf

# TypeScript/JavaScript files for actions
*.js text eol=lf
*.ts text eol=lf
*.json text eol=lf

# Documentation
*.md text eol=lf

# Shell scripts
*.sh text eol=lf
32 changes: 32 additions & 0 deletions Repos/Action/gitignore/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Node.js dependencies
node_modules/

# Build output
dist/
lib/
build/

# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Testing
coverage/
.nyc_output/

# IDE
.vscode/
.idea/
*.swp
*.swo
*~

# OS files
.DS_Store
Thumbs.db

# Environment files
.env
.env.local
13 changes: 13 additions & 0 deletions Repos/Module/CODEOWNERS/.github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Default owners for everything in the repository
* @PSModule/maintainers

# GitHub-specific files and workflows
/.github/ @PSModule/infrastructure
/.github/workflows/ @PSModule/infrastructure

# Documentation
/docs/ @PSModule/documentation
*.md @PSModule/documentation

# Source code (can be customized per repository)
/src/ @PSModule/maintainers
Loading