way_of_working 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -1
  3. data/CONTRIBUTING.md +62 -0
  4. data/README.md +187 -14
  5. data/lib/way_of_working/audit/github/auditor.rb +44 -0
  6. data/lib/way_of_working/audit/github/generators/exec.rb +163 -0
  7. data/lib/way_of_working/audit/github/rules/base.rb +91 -0
  8. data/lib/way_of_working/audit/github/rules/registry.rb +36 -0
  9. data/lib/way_of_working/audit/github/rules/unknown.rb +19 -0
  10. data/lib/way_of_working/audit/github.rb +25 -0
  11. data/lib/way_of_working/changelog/keepachangelog/generators/init.rb +118 -0
  12. data/lib/way_of_working/changelog/keepachangelog/github_audit_rule.rb +32 -0
  13. data/lib/way_of_working/changelog/keepachangelog/templates/docs/way_of_working/changelog.md +73 -0
  14. data/lib/way_of_working/changelog/keepachangelog.rb +35 -0
  15. data/lib/way_of_working/code_of_conduct/contributor_covenant/generators/init.rb +39 -0
  16. data/lib/way_of_working/code_of_conduct/contributor_covenant/github_audit_rule.rb +32 -0
  17. data/lib/way_of_working/code_of_conduct/contributor_covenant/templates/CODE_OF_CONDUCT.md.tt +133 -0
  18. data/lib/way_of_working/code_of_conduct/contributor_covenant/templates/docs/way_of_working/code-of-conduct.md +78 -0
  19. data/lib/way_of_working/code_of_conduct/contributor_covenant.rb +36 -0
  20. data/lib/way_of_working/decision_record/madr/generators/init.rb +41 -0
  21. data/lib/way_of_working/decision_record/madr/generators/new.rb +69 -0
  22. data/lib/way_of_working/decision_record/madr/github_audit_rule.rb +50 -0
  23. data/lib/way_of_working/decision_record/madr/templates/.github/ISSUE_TEMPLATE/decision-record.md +26 -0
  24. data/lib/way_of_working/decision_record/madr/templates/docs/decisions/0000-use-markdown-any-decision-records.md +30 -0
  25. data/lib/way_of_working/decision_record/madr/templates/docs/decisions/README.md +7 -0
  26. data/lib/way_of_working/decision_record/madr/templates/docs/decisions/adr-template.md.tt +82 -0
  27. data/lib/way_of_working/decision_record/madr/templates/docs/decisions/index.md +48 -0
  28. data/lib/way_of_working/decision_record/madr/templates/docs/way_of_working/decision-records.md +195 -0
  29. data/lib/way_of_working/decision_record/madr.rb +53 -0
  30. data/lib/way_of_working/git/repo_reader.rb +2 -2
  31. data/lib/way_of_working/inclusive_language/alex/generators/exec.rb +51 -0
  32. data/lib/way_of_working/inclusive_language/alex/generators/init.rb +34 -0
  33. data/lib/way_of_working/inclusive_language/alex/github_audit_rule.rb +35 -0
  34. data/lib/way_of_working/inclusive_language/alex/templates/.alexignore +2 -0
  35. data/lib/way_of_working/inclusive_language/alex/templates/.alexrc +3 -0
  36. data/lib/way_of_working/inclusive_language/alex/templates/.github/workflows/inclusive-language.yml +16 -0
  37. data/lib/way_of_working/inclusive_language/alex/templates/docs/way_of_working/inclusive-language.md +28 -0
  38. data/lib/way_of_working/inclusive_language/alex.rb +48 -0
  39. data/lib/way_of_working/pull_request_template/hdi/generators/init.rb +26 -0
  40. data/lib/way_of_working/pull_request_template/hdi/github_audit_rule.rb +32 -0
  41. data/lib/way_of_working/pull_request_template/hdi/templates/.github/pull_request_template.md +51 -0
  42. data/lib/way_of_working/pull_request_template/hdi/templates/docs/way_of_working/pull-request-template-and-guidelines.md +50 -0
  43. data/lib/way_of_working/pull_request_template/hdi.rb +35 -0
  44. data/lib/way_of_working/readme_badge/github_audit_rule.rb +1 -3
  45. data/lib/way_of_working/readme_badge/paths.rb +5 -2
  46. data/lib/way_of_working/readme_badge.rb +5 -7
  47. data/lib/way_of_working/version.rb +1 -1
  48. data/lib/way_of_working/versioning/semver/generators/init.rb +20 -0
  49. data/lib/way_of_working/versioning/semver/github_audit_rule.rb +49 -0
  50. data/lib/way_of_working/versioning/semver/templates/docs/way_of_working/versioning.md +71 -0
  51. data/lib/way_of_working/versioning/semver.rb +35 -0
  52. data/lib/way_of_working.rb +15 -0
  53. data/way_of_working.gemspec +4 -1
  54. metadata +89 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fcf87e5f8b8338ec91dc10bb579a9c8e9fa3e977745ba61cf8ae106cfb27201c
4
- data.tar.gz: 3be69c82e3d055cefe8353bf440f085eb84f420427a131dbb4a5f888f12d67f6
3
+ metadata.gz: a018ad36100c846ff6876cd4db5a6228fca2c0264b505971795559dd75a39d3b
4
+ data.tar.gz: 69cb609754aba06b2fd2b3b99171cf3cb09c84651a3986aad054f9121664eca5
5
5
  SHA512:
6
- metadata.gz: 70c5587a836fd9d530647f01e96fd725ac91ac418415e0d002613da943dfc502d6460d454bf81e9ea2c5f05ccfd78ef94b2b75adf8ca16261fa28c327c490a22
7
- data.tar.gz: feb65211f0f6c7904c00a16dccdb1b8dde8de7ca3880282d7fe926543dae1de30fed6a1e722843f5bbcfb4dd2735b87dd0c5c6c4b9c9cbb72bf03a0a5a220a02
6
+ metadata.gz: cf11254e92ca8b5da97f8f77aecbcf1f3883b83d8c0aa3fd7ba51a5ac7720ff4fa4bb6a65f32fe634d2e08c250a8522997cfadd6856820aee59a7c59dec7dd8d
7
+ data.tar.gz: a7b8040aa306c657a8691ad3ce7061a4a42073ab1ac0124b2187ed6bc40f9f3411d58621c6db1d22f646534e73585d1646aeea94dd8bdb21be57381b4ff6c41c
data/CHANGELOG.md CHANGED
@@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.1.0] - 2026-06-08
11
+
12
+ ### Added
13
+
14
+ - **Audit:** bundled the GitHub audit feature into this gem. It is opt in and replaces the separate `way_of_working-audit-github` plugin gem.
15
+ - **Changelog:** bundled the Keep a Changelog feature into this gem. It is opt in and replaces the separate `way_of_working-changelog-keepachangelog` plugin gem.
16
+ - **Code of Conduct:** bundled the Contributor Covenant feature into this gem. It is opt in and replaces the separate `way_of_working-code_of_conduct-contributor_covenant` plugin gem.
17
+ - **Decision Records:** bundled the MADR feature into this gem. It is opt in and replaces the separate `way_of_working-decision_record-madr` plugin gem.
18
+ - **Inclusive Language:** bundled the alex feature into this gem. It is opt in and replaces the separate `way_of_working-inclusive_language-alex` plugin gem.
19
+ - **Pull Request Template:** bundled the HDI feature into this gem. It is opt in and replaces the separate `way_of_working-pull_request_template-hdi` plugin gem.
20
+ - **Versioning:** bundled the Semantic Versioning feature into this gem. It is opt in and replaces the separate `way_of_working-versioning-semver` plugin gem.
21
+ - **Versioning:** documented the versioning policy in [ADR-0001](docs/decisions/0001-version-the-gem-interface-not-generated-content.md) and `CONTRIBUTING.md` — SemVer governs the gem's interface, not generated content; standard refreshes are minor, and drastic upgrades ship as additive variants.
22
+
23
+ ## [2.0.1] - 2025-01-17
24
+
25
+ ### Removed
26
+
27
+ - Removed completely unused test resource file
28
+
29
+ ### Fixed
30
+
31
+ - Corrected the badge rule brittle loading and signature
32
+ - Resolved YAML linting issues
33
+
34
+ ## [2.0.0] - 2024-10-24
35
+
10
36
  ### Added
11
37
 
12
38
  - Added a panel for README badges and a Way of Working badge
@@ -48,5 +74,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
48
74
 
49
75
  - Corrected use of relative paths in the rubocop config file
50
76
 
51
- [unreleased]: https://github.com/HealthDataInsight/way_of_working/compare/v1.0.0...HEAD
77
+ [unreleased]: https://github.com/HealthDataInsight/way_of_working/compare/v2.1.0...HEAD
78
+ [2.1.0]: https://github.com/HealthDataInsight/way_of_working/compare/v2.0.1...v2.1.0
79
+ [2.0.1]: https://github.com/HealthDataInsight/way_of_working/compare/v2.0.0...v2.0.1
80
+ [2.0.0]: https://github.com/HealthDataInsight/way_of_working/compare/v1.0.0...v2.0.0
52
81
  [1.0.0]: https://github.com/HealthDataInsight/way_of_working/releases/tag/v1.0.0
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,62 @@
1
+ # Contributing
2
+
3
+ Bug reports and pull requests are welcome on GitHub at
4
+ <https://github.com/HealthDataInsight/way_of_working>. This project is intended to be a safe,
5
+ welcoming space for collaboration, and contributors are expected to adhere to the
6
+ [code of conduct](CODE_OF_CONDUCT.md).
7
+
8
+ ## Development
9
+
10
+ After checking out the repo, run `bin/setup` to install dependencies. Then run `rake test` to run
11
+ the tests. You can also run `bin/console` for an interactive prompt that lets you experiment.
12
+
13
+ ## Versioning
14
+
15
+ This project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html), but SemVer here
16
+ governs the gem's **public interface**, not the content of the artefacts its generators emit. The
17
+ versioned contract is:
18
+
19
+ * the **CLI surface** — commands and their required options;
20
+ * the **require paths** of built-in features
21
+ (e.g. `require 'way_of_working/code_of_conduct/contributor_covenant'`);
22
+ * the **plugin registration API** (`SubCommands::*.register`, the `way_of_working-*` autoload).
23
+
24
+ Refreshing the standard a feature embeds (e.g. a new Contributor Covenant or MADR version) changes
25
+ generated content but not the interface, so it is **not** a major change. See
26
+ [ADR-0001](docs/decisions/0001-version-the-gem-interface-not-generated-content.md) for the full
27
+ rationale.
28
+
29
+ ### Choosing the version bump
30
+
31
+ | Change | Bump |
32
+ | --- | --- |
33
+ | Rename/remove a command or required option, move a feature's require path, or change the plugin API | **MAJOR** |
34
+ | Refresh the standard a feature embeds; add a new feature or variant | **MINOR** |
35
+ | Drastic standard upgrade shipped as a **new variant** alongside the existing one | **MINOR** (additive) |
36
+ | Bug fix in generation or generated output | **PATCH** |
37
+
38
+ For a standard update drastic enough to surprise existing users (e.g. Contributor Covenant
39
+ 2.1 → 3, effectively a rewrite), add a **new variant** under the feature's `category/variant`
40
+ namespace rather than mutating the existing one. Both variants then coexist and consumers opt in by
41
+ require path — keeping the change additive and leaving existing users untouched.
42
+
43
+ ### Changelog convention
44
+
45
+ All notable changes are recorded in `CHANGELOG.md` following
46
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Scope feature-affecting entries with a
47
+ bold feature prefix so each feature's history stays greppable within the single file, e.g.:
48
+
49
+ ```text
50
+ - **Code of Conduct:** upgraded Contributor Covenant 2.1 → 3 via new `contributor_covenant_v3` variant
51
+ ```
52
+
53
+ ## Releasing
54
+
55
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new
56
+ version:
57
+
58
+ 1. Decide the bump using the table above.
59
+ 2. Update the version number in `lib/way_of_working/version.rb`.
60
+ 3. Move the `[Unreleased]` changelog entries under the new version heading.
61
+ 4. Run `bundle exec rake release`, which creates a git tag for the version, pushes the commits and
62
+ tag, and pushes the `.gem` file to [rubygems.org](https://rubygems.org).
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Way of Working
2
2
 
3
3
  <!-- Way of Working: Main Badge Holder Start -->
4
- ![Way of Working Badge](https://img.shields.io/badge/Way_of_Working-v2.0.0-%238169e3?labelColor=black)
4
+ ![Way of Working Badge](https://img.shields.io/badge/Way_of_Working-v2.0.1-%238169e3?labelColor=black)
5
5
  <!-- Way of Working: Additional Badge Holder Start -->
6
6
  <!-- Way of Working: Badge Holder End -->
7
7
 
@@ -21,14 +21,20 @@ There are subcommands that plugins hook into which enable them to add GitHub act
21
21
 
22
22
  Below is a list of plugins that have been implemented so far:
23
23
 
24
- | Feature | Plugin | Description |
25
- | --------------------- | -------------------------------------- | ------------------------------------------------------------------------------ |
26
- | Changelog | [changelog-keepachangelog] | Implements [keepachangelog v1.1] |
27
- | Code Linting | [code_linting-hdi] | Implements a combination of [MegaLinter] and [RuboCop] built on NDRS standards |
28
- | Code of Conduct | [code_of_conduct-contributor_covenant] | Implements [Contributor Covenant v2.1] |
29
- | Decision Records | [decision_record-madr] | Implements [MADR v3] |
30
- | Inclusive Language | [inclusive_language-alex] | Implements [alex] |
31
- | Pull Request Template | [pull_request_template-hdi] | Implements a bespoke PR template |
24
+ | Feature | Plugin | Description |
25
+ | --------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------|
26
+ | Audit | Built-in (audit/github) | A framework for rules to check for incorrect content and configuration of GitHub repos — bundled, opt in by `require` (see [Built-in Features](#built-in-features)) |
27
+ | Changelog | Built-in (changelog/keepachangelog) | Implements [keepachangelog v1.1] bundled, opt in by `require` (see [Built-in Features](#built-in-features)) |
28
+ | Code Linting | [code_linting-hdi] | Implements a combination of [MegaLinter] and [RuboCop] built on NDRS standards |
29
+ | Code of Conduct | Built-in (code_of_conduct/contributor_covenant) | Implements [Contributor Covenant v2.1] — bundled, opt in by `require` (see [Built-in Features](#built-in-features)) |
30
+ | Decision Records | Built-in (decision_record/madr) | Implements [MADR v3] — bundled, opt in by `require` (see [Built-in Features](#built-in-features)) |
31
+ | Inclusive Language | Built-in (inclusive_language/alex) | Implements [alex] bundled, opt in by `require` (see [Built-in Features](#built-in-features)) |
32
+ | Pull Request Template | Built-in (pull_request_template/hdi) | Implements a bespoke PR template — bundled, opt in by `require` (see [Built-in Features](#built-in-features)) |
33
+ | Versioning | Built-in (versioning/semver) | Implements [Semantic Versioning v2.0.0] — bundled, opt in by `require` (see [Built-in Features](#built-in-features)) |
34
+
35
+ Some features are **Built-in** — they ship inside this gem and are enabled by requiring them (see [Built-in Features](#built-in-features)); the others are separate plugin gems that you add as dependencies.
36
+
37
+ The standard versions cited above (Contributor Covenant v2.1, MADR v3, and so on) describe the content a feature generates, not the gem's interface. They advance through **minor** releases — see [Versioning Policy](#versioning-policy).
32
38
 
33
39
  ## Installation
34
40
 
@@ -56,6 +62,161 @@ way_of_working init all --contact-method [CONTACT METHOD]
56
62
 
57
63
  You will need to provide the Code of Conduct `[CONTACT METHOD]`, usually an email address, for community leaders to receive reports of unacceptable behavior.
58
64
 
65
+ ### Built-in Features
66
+
67
+ Some features are bundled with this gem rather than shipped as separate plugins. These built-in features are **opt in**: enable one by requiring it wherever you load Way of Working — for example in your organisation's Way of Working gem, or in your project's `Rakefile`.
68
+
69
+ #### Audit
70
+
71
+ Auditing your GitHub repositories against a consistent set of rules catches missing or misconfigured files and repository settings across an organisation, keeping projects aligned with your way of working. Many of the other built-in features register their own rules to check that they have been adopted properly.
72
+
73
+ This feature provides a registry and auditing tool for GitHub repositories; rules can check for both missing/incorrect files and mis-configuration of the repository itself. Enable it by requiring it:
74
+
75
+ ```ruby
76
+ require 'way_of_working/audit/github'
77
+ ```
78
+
79
+ The audit needs two environment variables:
80
+
81
+ - `GITHUB_ORGANISATION`: the name of your organisation being scanned (as used in the GitHub URL)
82
+ - `GITHUB_TOKEN`: a PAT token with sufficient permission to access repositories and their configuration
83
+
84
+ Once required, a subcommand becomes available. By default it runs only against repositories configured as git remotes in your current project:
85
+
86
+ ```bash
87
+ # Audit this project's GitHub repositories
88
+ way_of_working exec audit_github
89
+ ```
90
+
91
+ Flags let you widen or narrow the scope:
92
+
93
+ ```bash
94
+ # Audit every repository in the organisation
95
+ way_of_working exec audit_github --all
96
+
97
+ # Filter to repositories carrying a topic
98
+ way_of_working exec audit_github --all --topic way-of-working
99
+
100
+ # Audit one or more repositories by name (implies the whole organisation)
101
+ way_of_working exec audit_github --name structured_store other_repo
102
+
103
+ # Audit only public repositories
104
+ way_of_working exec audit_github --all --public
105
+
106
+ # Attempt to automatically fix issues where rules support it
107
+ way_of_working exec audit_github --fix
108
+ ```
109
+
110
+ #### Changelog
111
+
112
+ Keeping a curated, human-readable changelog helps users and contributors quickly see what has changed between releases without trawling through the commit history.
113
+
114
+ This feature uses [Keep a Changelog v1.1][keepachangelog v1.1] to generate a `CHANGELOG.md`, seeding it with sections derived from your git tags and commit messages where possible. Enable it by requiring it:
115
+
116
+ ```ruby
117
+ require 'way_of_working/changelog/keepachangelog'
118
+ ```
119
+
120
+ Once required, a subcommand becomes available:
121
+
122
+ ```bash
123
+ # Add a Keep a Changelog CHANGELOG.md and documentation to your project
124
+ way_of_working init changelog
125
+ ```
126
+
127
+ #### Code of Conduct
128
+
129
+ A code of conduct sets clear expectations for acceptable behaviour within a community or project, helping to create a safer, more welcoming and inclusive environment and giving maintainers a basis for addressing inappropriate behaviour.
130
+
131
+ This feature installs the [Contributor Covenant v2.1] code of conduct along with its accompanying documentation. Enable it by requiring it:
132
+
133
+ ```ruby
134
+ require 'way_of_working/code_of_conduct/contributor_covenant'
135
+ ```
136
+
137
+ Once required, a subcommand becomes available:
138
+
139
+ ```bash
140
+ # Add the Contributor Covenant code of conduct and documentation to your project
141
+ way_of_working init code_of_conduct --contact-method [CONTACT METHOD]
142
+ ```
143
+
144
+ You will need to provide a `[CONTACT METHOD]`, usually an email address, for community leaders to receive reports of unacceptable behavior.
145
+
146
+ #### Decision Records
147
+
148
+ Recording the context and reasoning behind significant decisions helps current and future contributors understand why a project is the way it is, avoids relitigating settled questions, and onboards newcomers faster.
149
+
150
+ This feature uses [Markdown Any Decision Records (MADR)][MADR v3] to capture decisions as version-controlled Markdown alongside your code. Enable it by requiring it:
151
+
152
+ ```ruby
153
+ require 'way_of_working/decision_record/madr'
154
+ ```
155
+
156
+ Once required, two subcommands become available:
157
+
158
+ ```bash
159
+ # Add the decision records index, template and first record to your project
160
+ way_of_working init decision_record
161
+
162
+ # Create a new decision record
163
+ way_of_working new decision_record "Title of the decision"
164
+ ```
165
+
166
+ #### Inclusive Language
167
+
168
+ Using insensitive and inconsiderate language can cause harm to others, create barriers to communication, and damage relationships. It can make people feel excluded, disrespected, and devalued, and may perpetuate negative stereotypes and biases.
169
+
170
+ This feature uses [alex] for automated testing of inclusive language, both at the command line and as a GitHub workflow. Enable it by requiring it:
171
+
172
+ ```ruby
173
+ require 'way_of_working/inclusive_language/alex'
174
+ ```
175
+
176
+ Once required, two subcommands become available:
177
+
178
+ ```bash
179
+ # Add the alex config, .alexignore, GitHub Action and documentation to your project
180
+ way_of_working init inclusive_language
181
+
182
+ # Run the inclusive language checks
183
+ way_of_working exec inclusive_language
184
+ ```
185
+
186
+ #### Pull Request Template
187
+
188
+ A consistent pull request template standardises PR submissions, giving reviewers the context they need, improving review efficiency, and keeping a searchable project history.
189
+
190
+ This feature installs a bespoke HDI pull request template along with its accompanying guidelines documentation. Enable it by requiring it:
191
+
192
+ ```ruby
193
+ require 'way_of_working/pull_request_template/hdi'
194
+ ```
195
+
196
+ Once required, a subcommand becomes available:
197
+
198
+ ```bash
199
+ # Add the pull request template and documentation to your project
200
+ way_of_working init pull_request_template
201
+ ```
202
+
203
+ #### Versioning
204
+
205
+ A shared versioning standard makes software changes clear to communicate and releases predictable to manage across projects, so consumers can reason about compatibility at a glance.
206
+
207
+ This feature documents [Semantic Versioning v2.0.0] as the project's versioning standard. Enable it by requiring it:
208
+
209
+ ```ruby
210
+ require 'way_of_working/versioning/semver'
211
+ ```
212
+
213
+ Once required, a subcommand becomes available:
214
+
215
+ ```bash
216
+ # Add the Semantic Versioning documentation to your project
217
+ way_of_working init versioning
218
+ ```
219
+
59
220
  ### Help
60
221
 
61
222
  More help on using the command line tool is found by using:
@@ -73,10 +234,26 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
73
234
 
74
235
  When creating plugins, the convention is to create them within the WayOfWorking and feature namespaces. E.g.
75
236
 
237
+ ### Versioning Policy
238
+
239
+ This project follows [Semantic Versioning][Semantic Versioning v2.0.0], but SemVer governs the gem's **public interface** — the CLI commands and their options, the require paths of built-in features, and the plugin registration API — not the content of the artefacts its generators emit.
240
+
241
+ Consequently:
242
+
243
+ - Refreshing the standard a feature embeds (e.g. a new Contributor Covenant or MADR version) is **new behaviour → MINOR**.
244
+ - A bug fix in generation is **PATCH**.
245
+ - **MAJOR** is reserved for breaking the interface above (renaming or removing a command or option, moving a require path, or changing the plugin API).
246
+
247
+ For a standard update drastic enough to surprise existing users, we add a **new variant** under the feature's `category/variant` namespace rather than mutating the existing one, so both coexist and consumers opt in by require path. This keeps the change additive (MINOR) and leaves existing users untouched.
248
+
249
+ See [ADR-0001](docs/decisions/0001-version-the-gem-interface-not-generated-content.md) for the full rationale and [`CONTRIBUTING.md`](CONTRIBUTING.md) for the version-bump checklist.
250
+
76
251
  ## Contributing
77
252
 
78
253
  Bug reports and pull requests are welcome on GitHub at <https://github.com/HealthDataInsight/way_of_working>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/HealthDataInsight/way_of_working/blob/main/CODE_OF_CONDUCT.md).
79
254
 
255
+ See [`CONTRIBUTING.md`](CONTRIBUTING.md) for development setup, the versioning policy and the release process.
256
+
80
257
  ## License
81
258
 
82
259
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -91,9 +268,5 @@ Everyone interacting in the WayOfWorking project's codebases, issue trackers, ch
91
268
  [MADR v3]: https://adr.github.io/madr/
92
269
  [MegaLinter]: https://megalinter.io/
93
270
  [RuboCop]: https://rubocop.org
94
- [changelog-keepachangelog]: https://github.com/HealthDataInsight/way_of_working-changelog-keepachangelog
271
+ [Semantic Versioning v2.0.0]: https://semver.org/spec/v2.0.0.html
95
272
  [code_linting-hdi]: https://github.com/HealthDataInsight/way_of_working-code_linting-hdi
96
- [code_of_conduct-contributor_covenant]: https://github.com/HealthDataInsight/way_of_working-code_of_conduct-contributor_covenant
97
- [decision_record-madr]: https://github.com/HealthDataInsight/way_of_working-decision_record-madr
98
- [inclusive_language-alex]: https://github.com/HealthDataInsight/way_of_working-inclusive_language-alex
99
- [pull_request_template-hdi]: https://github.com/HealthDataInsight/way_of_working-pull_request_template-hdi
@@ -0,0 +1,44 @@
1
+ require 'octokit'
2
+ require_relative 'rules/registry'
3
+
4
+ module WayOfWorking
5
+ module Audit
6
+ module Github
7
+ # This auditor runs all the rules against any given GitHub repository
8
+ class Auditor
9
+ def initialize(access_token, organisation_name, fix = false)
10
+ @access_token = access_token
11
+ @organisation_name = organisation_name
12
+ @fix = fix
13
+ end
14
+
15
+ def audit(repository)
16
+ # Get all the rules once, rather than repeatedly in individual rules
17
+ rulesets = @client.get("repos/#{repository.full_name}/rulesets").map do |rule|
18
+ @client.get("repos/#{repository.full_name}/rulesets/#{rule[:id]}")
19
+ end
20
+
21
+ Array(Rules::Registry.rules).each do |rule_name, klass|
22
+ rule = klass.new(client, rule_name, repository, rulesets, @fix)
23
+
24
+ yield rule
25
+ end
26
+ end
27
+
28
+ def repositories
29
+ @repositories ||= client.org_repos(@organisation_name)
30
+ end
31
+
32
+ private
33
+
34
+ def client
35
+ @client ||= begin
36
+ client = Octokit::Client.new(access_token: @access_token) # , per_page: 1_000
37
+ client.auto_paginate = true
38
+ client
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'git'
4
+ require 'rainbow'
5
+ require 'thor'
6
+ require 'way_of_working/audit/github/auditor'
7
+ # require 'way_of_working/github_audit/rules/all'
8
+
9
+ module WayOfWorking
10
+ module Audit
11
+ module Github
12
+ module Generators
13
+ # This generator runs the github audit
14
+ class Exec < Thor::Group
15
+ class_option :all, type: :boolean, default: false,
16
+ desc: 'Audit all repositories in the organisation (not just this repo)'
17
+
18
+ class_option :fix, type: :boolean, default: false,
19
+ desc: 'Attempt to automatically fix issues where possible'
20
+
21
+ class_option :name, type: :array, default: nil,
22
+ desc: 'Filter repositories by name (e.g., structured_store)'
23
+
24
+ class_option :public, type: :boolean, default: false,
25
+ desc: 'Filter to only public repositories'
26
+
27
+ class_option :topic, type: :string, default: nil,
28
+ desc: 'Filter repositories by topic (e.g., way-of-working)'
29
+
30
+ desc 'This runs the github audit on this project'
31
+
32
+ def check_for_github_token_environment_variables
33
+ @github_token = ENV.fetch('GITHUB_TOKEN', nil)
34
+ return unless @github_token.nil?
35
+
36
+ abort(Rainbow("\nMissing GITHUB_TOKEN environment variable").red)
37
+ end
38
+
39
+ def check_for_github_organisation_environment_variables
40
+ @github_organisation = ENV.fetch('GITHUB_ORGANISATION', nil)
41
+ return unless @github_organisation.nil?
42
+
43
+ abort(Rainbow("\nMissing GITHUB_ORGANISATION environment variable").red)
44
+ end
45
+
46
+ def start_timer
47
+ @start_time = Time.now
48
+ end
49
+
50
+ def check_github_organisation_remotes
51
+ abort(Rainbow("\nGitHub is not an upstream repository.").red) if github_organisation_remotes.empty?
52
+
53
+ # say("Limiting audit to #{path}\n", :yellow) if path
54
+ say "\nRunning..."
55
+ end
56
+
57
+ def prep_audit
58
+ @auditor = ::WayOfWorking::Audit::Github::Auditor.new(@github_token, @github_organisation, options[:fix])
59
+
60
+ # Loop though all the repos
61
+ @repositories = @auditor.repositories
62
+ rescue Octokit::Unauthorized
63
+ abort(Rainbow("\nGITHUB_TOKEN has expired or does not have sufficient permission").red)
64
+ end
65
+
66
+ def filter_all_if_specified
67
+ return if options[:all] || options[:name]
68
+
69
+ @repositories = @repositories.select do |repo|
70
+ github_organisation_remotes.include?(repo.name)
71
+ end
72
+ end
73
+
74
+ def filter_by_name_array_if_specified
75
+ return unless options[:name]
76
+
77
+ @repositories = @repositories.select do |repo|
78
+ options[:name].include?(repo.name)
79
+ end
80
+ end
81
+
82
+ def filter_by_topic_if_specified
83
+ return unless options[:topic]
84
+
85
+ @repositories = @repositories.select do |repo|
86
+ repo.topics.include?(options[:topic])
87
+ end
88
+ end
89
+
90
+ def filter_by_visibility_if_specified
91
+ return unless options[:public]
92
+
93
+ @repositories = @repositories.reject(&:private?)
94
+ end
95
+
96
+ def run_audit
97
+ @audit_ok = true
98
+ unarchived_repos.each do |repo|
99
+ @auditor.audit(repo) do |rule|
100
+ case rule.status
101
+ when :not_applicable
102
+ # Do nothing
103
+ when :passed
104
+ say " ✅ #{rule.tags.inspect} Passed #{rule.name}"
105
+ when :failed
106
+ say " ❌ #{rule.tags.inspect} Failed #{rule.name}: #{rule.errors.to_sentence}"
107
+ @audit_ok = false
108
+ else
109
+ abort(Rainbow("Unknown response #{rule.status.inspect}").red)
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ def run_last
116
+ say("\nTotal time taken: #{(Time.now - @start_time).to_i} seconds", :yellow)
117
+
118
+ if @audit_ok
119
+ say("\nGitHub audit succeeded!", :green)
120
+ else
121
+ abort(Rainbow("\nGitHub audit failed!").red)
122
+ end
123
+ end
124
+
125
+ private
126
+
127
+ def unarchived_repos(&block)
128
+ return to_enum(__method__) unless block_given?
129
+
130
+ @repositories.each do |repo|
131
+ next if repo.archived?
132
+
133
+ say
134
+ say("#{repo.name} #{repo.private? ? '🔒' : ''}", :bold)
135
+ say("#{repo.description} [#{repo.language}]")
136
+ say(repo.topics.join(' '), :cyan) unless repo.topics.empty?
137
+
138
+ block.call(repo)
139
+ end
140
+ end
141
+
142
+ # This method returns the repo names for all upstream repositories
143
+ # hosted on GitHub, for the given organisation
144
+ def github_organisation_remotes
145
+ @github_organisation_remotes ||= begin
146
+ all_remote_urls = ::Git.open('.').remotes.map(&:url)
147
+
148
+ organisation_remote_urls = all_remote_urls.select do |url|
149
+ url.start_with?("https://github.com/#{@github_organisation}")
150
+ end
151
+
152
+ organisation_remote_urls.map do |url|
153
+ matchdata = url.match(%r{\Ahttps://github.com/([^/]+)/([^/]+)\.git})
154
+
155
+ matchdata[2]
156
+ end
157
+ end
158
+ end
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext'
5
+ require 'base64'
6
+ require 'octokit'
7
+ require_relative 'registry'
8
+
9
+ module WayOfWorking
10
+ module Audit
11
+ module Github
12
+ module Rules
13
+ # This is the base class for GitHub audit rules
14
+ class Base
15
+ attr_accessor :errors, :name, :rulesets, :warnings
16
+ attr_reader :fix
17
+
18
+ class << self
19
+ # Stores and return the source root for this class
20
+ def source_root(path = nil)
21
+ @source_root = path if path
22
+ @source_root ||= nil
23
+ end
24
+ end
25
+
26
+ def initialize(client, name, repo, rulesets, fix = false)
27
+ @client = client
28
+ @name = name
29
+ @repo = repo
30
+ @repo_name = repo.full_name
31
+ @rulesets = rulesets
32
+ @fix = fix
33
+ @errors = []
34
+ @warnings = []
35
+ end
36
+
37
+ def status
38
+ @status ||= begin
39
+ result = validate
40
+
41
+ if result == :not_applicable
42
+ result
43
+ else
44
+ @errors.empty? ? :passed : :failed
45
+ end
46
+ end
47
+ end
48
+
49
+ def validate
50
+ $stdout.puts "Rule#valid? has been deprecated, use \"validate\" in #{self.class.name}"
51
+ valid?
52
+ end
53
+
54
+ def self.tags
55
+ [:way_of_working]
56
+ end
57
+
58
+ def tags
59
+ self.class.tags
60
+ end
61
+
62
+ private
63
+
64
+ def repo_file_contents(path)
65
+ response = @client.contents(@repo_name, path: path)
66
+ Base64.decode64(response.content).force_encoding('UTF-8')
67
+ rescue Octokit::NotFound
68
+ nil
69
+ end
70
+
71
+ # This method returns the content of the README file
72
+ def readme_content
73
+ @readme_content ||=
74
+ begin
75
+ response = @client.readme(@repo_name)
76
+
77
+ Base64.decode64(response.content).force_encoding('UTF-8')
78
+ rescue Octokit::NotFound
79
+ ''
80
+ end
81
+ end
82
+
83
+ # This convenience method returns the branch rules for this repo
84
+ def branch_rules(branch)
85
+ @client.get("repos/#{@repo.full_name}/rules/branches/#{branch}")
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end