|
1 | 1 | # Label Sync |
2 | 2 |
|
3 | | -This repository is a standalone source of truth for GitHub labels. |
| 3 | +This repository is a standalone GitHub label management repo for an organization. |
4 | 4 |
|
5 | | -It stores: |
| 5 | +Its job is to: |
6 | 6 |
|
7 | | -- The canonical label definitions in `config/labels.json` |
8 | | -- The repository blacklist in `config/repositories.json` |
9 | | -- A reusable sync script in `scripts/sync-labels.mjs` |
10 | | -- GitHub Actions workflows to validate config and apply label changes |
| 7 | +- keep this repo's label config in sync with the labels currently defined on the repo |
| 8 | +- validate config changes automatically |
| 9 | +- sync the resulting label set across the rest of the organization |
11 | 10 |
|
12 | | -## What This Repo Does |
| 11 | +## How It Works |
13 | 12 |
|
14 | | -When the sync workflow runs, it: |
| 13 | +This repo uses three workflows: |
15 | 14 |
|
16 | | -1. Reads the label definitions from `config/labels.json` |
17 | | -2. Discovers every repository in the organization that owns this repo |
18 | | -3. Excludes any repositories listed in `config/repositories.json` |
19 | | -4. Creates any missing labels in each remaining repository |
20 | | -5. Updates label colors and descriptions when they drift |
21 | | -6. Optionally deletes labels that are no longer in the config |
| 15 | +1. `Config-Label_Sync` |
| 16 | +2. `Validate-Configs` |
| 17 | +3. `Org-Label-Sync` |
22 | 18 |
|
23 | | -By default, it only creates and updates labels. Deletion is opt-in. |
| 19 | +The normal flow is: |
| 20 | + |
| 21 | +1. You run `Org-Label-Sync` manually. |
| 22 | +2. It first calls `Config-Label_Sync`. |
| 23 | +3. `Config-Label_Sync` reads the labels on this repository and rewrites `config/labels.jsonc` so the file matches the repo's current managed labels. |
| 24 | +4. If that file changed, `Config-Label_Sync` commits and pushes the update. |
| 25 | +5. That config change triggers `Validate-Configs`. |
| 26 | +6. `Org-Label-Sync` then checks out the latest default branch, validates the config again, and syncs labels across the organization. |
24 | 27 |
|
25 | 28 | ## Repository Layout |
26 | 29 |
|
27 | 30 | ```text |
28 | 31 | . |
29 | 32 | |-- .github/ |
30 | 33 | | `-- workflows/ |
31 | | -| |-- sync-labels.yml |
32 | | -| `-- validate-config.yml |
| 34 | +| |-- config-label-sync.yml |
| 35 | +| |-- org-label-sync.yml |
| 36 | +| `-- validate-configs.yml |
33 | 37 | |-- config/ |
34 | | -| |-- labels.json |
35 | | -| `-- repositories.json |
| 38 | +| |-- auto-pruned-labels.jsonc |
| 39 | +| |-- blacklisted-repositories.jsonc |
| 40 | +| |-- labels.jsonc |
| 41 | +| `-- properties.jsonc |
36 | 42 | `-- scripts/ |
37 | | - `-- sync-labels.mjs |
| 43 | + |-- export-properties.mjs |
| 44 | + |-- sync-config-labels.mjs |
| 45 | + |-- sync-labels.mjs |
| 46 | + `-- lib/ |
| 47 | + `-- config-utils.mjs |
38 | 48 | ``` |
39 | 49 |
|
40 | | -## Initial Setup |
| 50 | +## Config Files |
| 51 | + |
| 52 | +All config files live under `config/` and use `jsonc`, so they can include commented examples at the top. |
| 53 | + |
| 54 | +### `config/properties.jsonc` |
| 55 | + |
| 56 | +This is the general admin config for values that will differ between forks. |
| 57 | + |
| 58 | +Fields: |
41 | 59 |
|
42 | | -1. Fork or clone this repository for the organization that will own the label definitions. |
43 | | -2. Add a repository secret named `LABEL_SYNC_TOKEN`. |
44 | | -3. Give that token access to every repository in the organization you want to manage. |
45 | | -4. Update `config/repositories.json` with any repositories you want to exclude. |
46 | | -5. Update `config/labels.json` with your organization's canonical labels. |
47 | | -6. Run the `Validate Config` workflow or open a pull request. |
48 | | -7. Run the `Sync Labels` workflow manually once to confirm the result. |
| 60 | +- `organization`: the GitHub organization to sync |
| 61 | +- `labelSyncTokenSecretName`: the name of the GitHub Actions secret containing the sync token |
| 62 | +- `sourceRepository`: the repo whose labels are treated as the source for `Config-Label_Sync` |
| 63 | +- `deleteMissingByDefault`: whether org sync should delete labels that are neither managed nor auto-pruned |
49 | 64 |
|
50 | | -## Token Guidance |
| 65 | +Example: |
51 | 66 |
|
52 | | -Use either: |
| 67 | +```jsonc |
| 68 | +{ |
| 69 | + "organization": "your-org-name", |
| 70 | + "labelSyncTokenSecretName": "LABEL_SYNC_TOKEN", |
| 71 | + "sourceRepository": "your-org-name/label-sync", |
| 72 | + "deleteMissingByDefault": false |
| 73 | +} |
| 74 | +``` |
53 | 75 |
|
54 | | -- A fine-grained personal access token with access to the target repositories |
55 | | -- A GitHub App installation token surfaced to this workflow by a separate auth step |
| 76 | +### `config/labels.jsonc` |
56 | 77 |
|
57 | | -This starter repo is wired for a repository secret named `LABEL_SYNC_TOKEN`. |
| 78 | +This is the managed label set that gets created or updated across the org. |
58 | 79 |
|
59 | | -## Configuration |
| 80 | +It is normally maintained automatically by `Config-Label_Sync`, based on the labels currently present on this repository after excluding auto-pruned labels. |
60 | 81 |
|
61 | | -### `config/labels.json` |
| 82 | +Each label object uses: |
62 | 83 |
|
63 | | -This file contains the labels you want everywhere. |
| 84 | +- `name` |
| 85 | +- `color` |
| 86 | +- `description` |
64 | 87 |
|
65 | | -```json |
| 88 | +Example: |
| 89 | + |
| 90 | +```jsonc |
66 | 91 | [ |
67 | 92 | { |
68 | | - "name": "bug", |
69 | | - "color": "d73a4a", |
70 | | - "description": "Something is not working" |
| 93 | + "name": "priority: high", |
| 94 | + "color": "b60205", |
| 95 | + "description": "Top-priority work" |
71 | 96 | } |
72 | 97 | ] |
73 | 98 | ``` |
74 | 99 |
|
75 | | -Rules: |
| 100 | +### `config/auto-pruned-labels.jsonc` |
76 | 101 |
|
77 | | -- `name` is required |
78 | | -- `color` is required and should be a 6-character hex value |
79 | | -- `description` is optional |
| 102 | +This is the list of labels that should always be removed from synced repositories. |
80 | 103 |
|
81 | | -### `config/repositories.json` |
| 104 | +The starter file is prefilled with GitHub's default labels: |
82 | 105 |
|
83 | | -This file contains the repository blacklist and the global delete behavior. |
| 106 | +- `bug` |
| 107 | +- `documentation` |
| 108 | +- `duplicate` |
| 109 | +- `enhancement` |
| 110 | +- `good first issue` |
| 111 | +- `help wanted` |
| 112 | +- `invalid` |
| 113 | +- `question` |
| 114 | +- `wontfix` |
84 | 115 |
|
85 | | -```json |
86 | | -{ |
87 | | - "deleteMissing": false, |
88 | | - "blacklist": [ |
89 | | - "sandbox-repo", |
90 | | - "octo-org/do-not-touch" |
91 | | - ] |
92 | | -} |
93 | | -``` |
| 116 | +If any of those labels exist on this repo, `Config-Label_Sync` excludes them from `labels.jsonc`. If they exist on target repos, `Org-Label-Sync` deletes them. |
| 117 | + |
| 118 | +### `config/blacklisted-repositories.jsonc` |
| 119 | + |
| 120 | +This is the repo blacklist for org sync. |
| 121 | + |
| 122 | +By default, `Org-Label-Sync` targets every repo in the configured organization. Any repo listed here is skipped. |
94 | 123 |
|
95 | | -Notes: |
| 124 | +Entries can be either: |
96 | 125 |
|
97 | | -- `deleteMissing` applies to every non-blacklisted repository |
98 | | -- Blacklist entries can be either `repo-name` or `owner/repo-name` |
99 | | -- The org name is discovered from the workflow repository owner |
100 | | -- The workflow dispatch input can temporarily force deletion for a run |
| 126 | +- `repo-name` |
| 127 | +- `owner/repo-name` |
| 128 | + |
| 129 | +Example: |
| 130 | + |
| 131 | +```jsonc |
| 132 | +[ |
| 133 | + "sandbox-repo", |
| 134 | + "your-org-name/private-internal-tools" |
| 135 | +] |
| 136 | +``` |
101 | 137 |
|
102 | 138 | ## Workflows |
103 | 139 |
|
104 | | -### `Validate Config` |
| 140 | +### `Config-Label_Sync` |
| 141 | + |
| 142 | +File: `.github/workflows/config-label-sync.yml` |
| 143 | + |
| 144 | +Trigger: |
| 145 | + |
| 146 | +- manual via `workflow_dispatch` |
| 147 | +- callable from other workflows via `workflow_call` |
| 148 | + |
| 149 | +What it does: |
| 150 | + |
| 151 | +1. Checks out the default branch |
| 152 | +2. Loads shared settings from `config/properties.jsonc` |
| 153 | +3. Reads the current labels on the source repository |
| 154 | +4. Removes any labels listed in `config/auto-pruned-labels.jsonc` |
| 155 | +5. Rewrites `config/labels.jsonc` so it exactly matches the remaining labels |
| 156 | +6. Commits and pushes the change if the config was updated |
| 157 | + |
| 158 | +This workflow is the bridge between "the labels on this repo right now" and "the managed config we sync elsewhere." |
| 159 | + |
| 160 | +### `Validate-Configs` |
| 161 | + |
| 162 | +File: `.github/workflows/validate-configs.yml` |
| 163 | + |
| 164 | +Trigger: |
105 | 165 |
|
106 | | -Runs on pull requests and manual dispatch. It validates: |
| 166 | +- runs automatically on `push` to `config/**` |
| 167 | +- runs automatically on `pull_request` changes to `config/**` |
107 | 168 |
|
108 | | -- JSON parsing |
109 | | -- Duplicate label names |
110 | | -- Invalid colors |
111 | | -- Invalid repository names |
| 169 | +What it does: |
112 | 170 |
|
113 | | -### `Sync Labels` |
| 171 | +1. Checks out the repo |
| 172 | +2. Runs `node scripts/sync-labels.mjs --validate-only` |
114 | 173 |
|
115 | | -Runs on: |
| 174 | +Validation includes: |
116 | 175 |
|
117 | | -- Pushes to `main` when label-sync files change |
118 | | -- Manual dispatch |
119 | | -- Weekly schedule |
| 176 | +- JSONC parsing |
| 177 | +- required property checks |
| 178 | +- duplicate label detection |
| 179 | +- duplicate blacklist detection |
| 180 | +- invalid colors |
| 181 | +- invalid repo names |
| 182 | +- overlap detection between `labels.jsonc` and `auto-pruned-labels.jsonc` |
120 | 183 |
|
121 | | -Workflow dispatch inputs: |
| 184 | +### `Org-Label-Sync` |
122 | 185 |
|
123 | | -- `dry_run`: shows planned changes without writing anything |
124 | | -- `delete_missing`: temporarily deletes labels not present in config |
| 186 | +File: `.github/workflows/org-label-sync.yml` |
| 187 | + |
| 188 | +Trigger: |
| 189 | + |
| 190 | +- manual via `workflow_dispatch` |
| 191 | + |
| 192 | +Inputs: |
| 193 | + |
| 194 | +- `dry_run`: preview changes without writing them |
| 195 | +- `delete_missing`: override `deleteMissingByDefault` for the run |
125 | 196 | - `repositories`: optional comma-separated subset of discovered repositories |
126 | 197 |
|
127 | | -## Safe Default Behavior |
| 198 | +What it does: |
| 199 | + |
| 200 | +1. Calls `Config-Label_Sync` |
| 201 | +2. Checks out the latest default branch |
| 202 | +3. Loads shared settings from `config/properties.jsonc` |
| 203 | +4. Validates the updated config |
| 204 | +5. Discovers repos in the configured organization |
| 205 | +6. Skips repos in `config/blacklisted-repositories.jsonc` |
| 206 | +7. Creates or updates labels from `config/labels.jsonc` |
| 207 | +8. Deletes labels listed in `config/auto-pruned-labels.jsonc` |
| 208 | +9. Optionally deletes any other unmanaged labels if `delete_missing` or `deleteMissingByDefault` is enabled |
| 209 | + |
| 210 | +## Token Requirements |
| 211 | + |
| 212 | +Create a repository secret whose name matches `labelSyncTokenSecretName` in `config/properties.jsonc`. |
| 213 | + |
| 214 | +That token needs enough access to: |
| 215 | + |
| 216 | +- read and update labels on the source repository |
| 217 | +- discover repositories in the organization |
| 218 | +- read and update labels on target repositories |
| 219 | +- push config updates back to this repository when `Config-Label_Sync` changes `labels.jsonc` |
128 | 220 |
|
129 | | -This repo is intentionally conservative: |
| 221 | +## Setup |
130 | 222 |
|
131 | | -- It syncs to the current organization by default, but you can exclude repos with the blacklist |
132 | | -- It does not delete labels unless you explicitly enable that behavior |
133 | | -- It supports dry runs before changing anything |
| 223 | +1. Fork or clone this repository into the organization you want to manage. |
| 224 | +2. Create the sync token secret in the repo. |
| 225 | +3. Update `config/properties.jsonc` for your org and repo. |
| 226 | +4. Adjust `config/auto-pruned-labels.jsonc` if you want a different always-delete list. |
| 227 | +5. Add any excluded repos to `config/blacklisted-repositories.jsonc`. |
| 228 | +6. Set the labels on this repository to the label set you want to manage. |
| 229 | +7. Run `Config-Label_Sync` once if you want to populate `config/labels.jsonc` immediately. |
| 230 | +8. Run `Org-Label-Sync` to propagate the labels across the organization. |
134 | 231 |
|
135 | | -## Suggested Next Edits |
| 232 | +## Safe Defaults |
136 | 233 |
|
137 | | -- Replace the starter labels with your real org label taxonomy |
138 | | -- Add any repos you want excluded to the blacklist |
139 | | -- Decide whether org-wide `deleteMissing` should stay off |
| 234 | +- `labels.jsonc` starts empty until you define or sync labels on this repo |
| 235 | +- all repos in the org are targeted unless blacklisted |
| 236 | +- GitHub default labels are auto-pruned by default |
| 237 | +- deleting unmanaged labels is off by default unless you enable it |
0 commit comments