veragem 0.1.0 → 0.2.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +230 -34
- data/Gemfile.lock +254 -0
- data/README.md +143 -48
- data/lib/veragem/citation.rb +116 -0
- data/lib/veragem/generator.rb +105 -56
- data/lib/veragem/license.rb +27 -0
- data/lib/veragem/name.rb +110 -48
- data/lib/veragem/options.rb +116 -30
- data/lib/veragem/project.rb +200 -39
- data/lib/veragem/questions.rb +163 -0
- data/lib/veragem/summary.rb +62 -0
- data/lib/veragem/version.rb +2 -1
- data/lib/veragem.rb +12 -3
- data/plugins.rb +4 -0
- data/sig/veragem.rbs +151 -23
- data/template/.gitattributes +3 -0
- data/template/.githooks/pre-commit +2 -3
- data/template/.github/PULL_REQUEST_TEMPLATE.md +2 -1
- data/template/.github/dependabot.yml +4 -8
- data/template/.github/image-registries.sh +4 -3
- data/template/.github/workflows/dependabot-automerge.yml +29 -34
- data/template/.github/workflows/dependency-review.yml +19 -15
- data/template/.github/workflows/image.yml +65 -14
- data/template/.github/workflows/links.yml +35 -0
- data/template/.github/workflows/nix.yml +3 -3
- data/template/.github/workflows/release.yml +135 -22
- data/template/.github/workflows/ruby-ci.yml +31 -9
- data/template/.github/workflows/security.yml +15 -9
- data/template/.github/workflows/style-check-cocogitto.yml +3 -4
- data/template/.github/workflows/style-check-dprint.yml +10 -10
- data/template/.gitignore +5 -0
- data/template/.mutineer.yml +28 -0
- data/template/.rubocop.yml +49 -10
- data/template/.simplecov.erb +10 -0
- data/template/.yard-lint.yml +386 -0
- data/template/.yardopts +2 -0
- data/template/AGENTS.md.erb +76 -38
- data/template/CITATION.cff.erb +50 -0
- data/template/CODE_OF_CONDUCT.md.erb +1 -1
- data/template/CONTRIBUTING.md.erb +107 -48
- data/template/Gemfile +10 -7
- data/template/README.md.erb +12 -12
- data/template/Rakefile +167 -31
- data/template/Steepfile.erb +1 -1
- data/template/bin/setup +2 -0
- data/template/cliff.toml +40 -23
- data/template/dprint.json +1 -6
- data/template/flake.lock +3 -3
- data/template/flake.nix +25 -10
- data/template/lib/%path%/cli.rb.erb +19 -15
- data/template/lib/%path%/version.rb.erb +1 -1
- data/template/lib/%path%.rb.erb +3 -1
- data/template/sig/%path%.rbs.erb +1 -1
- data/template/test/test_%underscored%.rb.erb +4 -3
- data/template/test/test_cli.rb.erb +37 -12
- data/template/test/test_helper.rb.erb +15 -7
- metadata +16 -4
- data/template/.github/workflows/rubocop-autocorrect.yml +0 -114
data/sig/veragem.rbs
CHANGED
|
@@ -4,25 +4,73 @@ module Veragem
|
|
|
4
4
|
class Error < Bundler::PluginError
|
|
5
5
|
end
|
|
6
6
|
|
|
7
|
-
def self.run: (Array[String] argv, ?dir: String) -> String
|
|
7
|
+
def self.run: (Array[String] argv, ?dir: String, ?input: Questions::_Input) -> String
|
|
8
8
|
|
|
9
9
|
module Name
|
|
10
10
|
PATTERN: Regexp
|
|
11
|
+
RESERVED: Regexp
|
|
12
|
+
LIBRARIES: Array[String]
|
|
11
13
|
PROBE: String
|
|
12
14
|
|
|
15
|
+
self.@probed: Hash[[String, String, Array[String]], Array[String]]
|
|
16
|
+
|
|
13
17
|
def self.modules: (String name) -> Array[String]
|
|
18
|
+
def self.path: (String name) -> String
|
|
19
|
+
def self.underscored: (String name) -> String
|
|
14
20
|
def self.check: (String name) -> String
|
|
21
|
+
private def self.check_paths: (String name) -> void
|
|
15
22
|
def self.check_modules: (String name) -> void
|
|
16
|
-
def self.check_shadowing: (String name, String shadowed, String found) -> void
|
|
17
|
-
def self.check_defined: (String name, Array[[String, String?]] defined, String? innermost) -> void
|
|
18
|
-
def self.library_of: (String name) -> String
|
|
19
|
-
def self.nestings_of: (String name) -> Array[String]
|
|
20
23
|
def self.probe: (String library, String path, Array[String] nestings) -> Array[String]
|
|
24
|
+
private def self.answers: (String library, String path, Array[String] nestings) -> Array[String]
|
|
25
|
+
private def self.unchecked: (Array[String] nestings, String why) -> bot
|
|
26
|
+
private def self.check_shadowing: (String name, String shadowed, String found) -> void
|
|
27
|
+
private def self.check_defined: (String name, Array[[String, String?]] defined, String? innermost) -> void
|
|
28
|
+
private def self.library_of: (String name) -> String
|
|
29
|
+
private def self.nestings_of: (String name) -> Array[String]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
module Citation
|
|
33
|
+
KEYS: Array[Symbol]
|
|
34
|
+
ORCID: Regexp
|
|
35
|
+
ORCID_BLOCKS: Array[Range[Integer]]
|
|
36
|
+
|
|
37
|
+
def self.cited?: (Hash[Symbol, String | bool | nil] values) -> bool
|
|
38
|
+
def self.check_values: (Hash[Symbol, String | bool | nil] values, bool citation) -> void
|
|
39
|
+
def self.check_name: (String name, bool citation) -> void
|
|
40
|
+
def self.check: (Symbol key, String value) { (String field, String text) -> String } -> String
|
|
41
|
+
def self.default: (Symbol key, Hash[Symbol, String | bool | nil] values) -> String?
|
|
42
|
+
private def self.names: (String field, String names) -> String
|
|
43
|
+
private def self.keywords: (String value) { (String keyword) -> String } -> Array[String]
|
|
44
|
+
private def self.orcid: (String value) -> String
|
|
45
|
+
private def self.valid?: (String digits) -> bool
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
module License
|
|
49
|
+
SPDX: Hash[String, Array[String]]
|
|
50
|
+
CHOICES: String
|
|
51
|
+
|
|
52
|
+
def self.check: (String value) -> String
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
module Summary
|
|
56
|
+
TAG: Regexp
|
|
57
|
+
ANNOTATION: Regexp
|
|
58
|
+
MAGIC: Regexp
|
|
59
|
+
MARKDOWN: Regexp
|
|
60
|
+
DASH: Regexp
|
|
61
|
+
|
|
62
|
+
def self.check: (String summary) -> String
|
|
63
|
+
private def self.refusal: (String summary) -> String?
|
|
64
|
+
private def self.left_open?: (String summary) -> bool
|
|
65
|
+
private def self.prose: (String summary) -> String
|
|
66
|
+
private def self.annotation?: (String summary) -> bool
|
|
21
67
|
end
|
|
22
68
|
|
|
23
69
|
class Project
|
|
24
70
|
GITHUB: Regexp
|
|
25
|
-
|
|
71
|
+
FIELDS: Hash[Symbol, String]
|
|
72
|
+
MARKUP: Regexp
|
|
73
|
+
OPTIONAL: Hash[Symbol, String]
|
|
26
74
|
|
|
27
75
|
attr_reader name: String
|
|
28
76
|
attr_reader author: String
|
|
@@ -31,49 +79,129 @@ module Veragem
|
|
|
31
79
|
attr_reader summary: String
|
|
32
80
|
attr_reader license: String
|
|
33
81
|
attr_reader exe: bool
|
|
82
|
+
attr_reader citation: bool
|
|
83
|
+
attr_reader given_names: String?
|
|
84
|
+
attr_reader family_names: String?
|
|
85
|
+
attr_reader alias_name: String?
|
|
86
|
+
attr_reader orcid: String?
|
|
87
|
+
attr_reader keywords: Array[String]?
|
|
34
88
|
|
|
35
|
-
|
|
89
|
+
# each key's own type, which no Hash type has: Options' and Questions' hashes, asserted (#:)
|
|
90
|
+
# one. Steep checks each field against the Hash's values.
|
|
91
|
+
type values = { author: String, github: String, summary: String, license: String, ?exe: bool,
|
|
92
|
+
?citation: bool, ?email: (String | false)?, ?given_names: (String | false)?,
|
|
93
|
+
?family_names: String, ?alias: (String | false)?, ?orcid: String?, ?keywords: String? }
|
|
94
|
+
|
|
95
|
+
def initialize: (String name, values values) -> void
|
|
36
96
|
def path: () -> String
|
|
37
97
|
def underscored: () -> String
|
|
38
98
|
def constant: () -> String
|
|
39
99
|
def modules: () -> Array[String]
|
|
40
100
|
def licenses: () -> Array[String]
|
|
41
101
|
def year: () -> Integer
|
|
102
|
+
def today: () -> String
|
|
103
|
+
def yaml: (String value) -> String
|
|
42
104
|
def quote: (String value) -> String
|
|
105
|
+
def markdown: (String value) -> String
|
|
43
106
|
def nest: (String body, ?doc: String?) -> String
|
|
107
|
+
def self.field: (Symbol key) -> String
|
|
108
|
+
def self.strip: (String text) -> String
|
|
109
|
+
def self.quote: (String value) -> String
|
|
110
|
+
def self.check: (Symbol key, String? value) -> String
|
|
111
|
+
private def self.utf8: (Symbol key, String? value) -> String
|
|
112
|
+
private def self.github: (String value) -> String
|
|
113
|
+
private def self.text: (String field, String value) -> String
|
|
114
|
+
private def self.line: (String field, String value) -> String
|
|
44
115
|
|
|
45
116
|
private
|
|
46
117
|
|
|
118
|
+
def extras: (values values) -> void
|
|
119
|
+
def cite: (values values) -> void
|
|
120
|
+
def optional: (Symbol key, (String | false)? value) -> String?
|
|
47
121
|
def indent: (String code) -> String
|
|
48
|
-
|
|
49
|
-
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
class Questions
|
|
125
|
+
# what is read from: a line at a time, and whether a person types it
|
|
126
|
+
interface _Input
|
|
127
|
+
def gets: () -> String?
|
|
128
|
+
def tty?: () -> bool
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
interface _Output
|
|
132
|
+
def print: (String) -> void
|
|
133
|
+
def puts: (?String) -> void
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
QUESTIONS: Hash[Symbol, String]
|
|
137
|
+
|
|
138
|
+
@input: _Input
|
|
139
|
+
@output: _Output
|
|
140
|
+
@dir: String
|
|
141
|
+
@git: Hash[Symbol, String]
|
|
142
|
+
|
|
143
|
+
def self.none?: (String text) -> bool
|
|
144
|
+
def initialize: (_Input input, _Output output, String dir, ?git: Hash[Symbol, String]) -> void
|
|
145
|
+
def name: (String? given, exe: bool, citation: bool) -> String
|
|
146
|
+
def values: (String name, Hash[Symbol, String | bool] given, Hash[Symbol, String] defaults)
|
|
147
|
+
-> Project::values
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
|
|
151
|
+
def check_name: (String name, bool exe, bool citation) -> String
|
|
152
|
+
def answer: (Symbol key, String text, String name, Hash[Symbol, String | bool | nil] values) -> (String | bool)
|
|
153
|
+
def default: (Symbol key, Hash[Symbol, String] defaults, Hash[Symbol, String | bool | nil] values) -> String?
|
|
154
|
+
def source: (Symbol key) -> String
|
|
155
|
+
def ask: (String question, ?String? default, check: ^(String) -> (String | bool), ?from: String, ?optional: bool)
|
|
156
|
+
-> (String | bool | nil)
|
|
157
|
+
def offered: (String question, String? default, String from, ^(String) -> (String | bool) check) -> String?
|
|
158
|
+
def read: (String question, String? default, bool optional) -> String?
|
|
159
|
+
def unanswered: (String question) -> nil
|
|
160
|
+
def checked: (String question, String answer, ^(String) -> (String | bool) check, ?String note) -> (String | bool | nil)
|
|
161
|
+
def terminal: [T] (String question) { () -> T } -> T
|
|
162
|
+
def yes?: (String answer) -> bool
|
|
50
163
|
end
|
|
51
164
|
|
|
52
165
|
module Options
|
|
53
166
|
GIT_CONFIG: Hash[Symbol, String]
|
|
167
|
+
DEFAULTS: Hash[Symbol, String]
|
|
168
|
+
BANNER: String
|
|
169
|
+
OPTIONS: Array[[String, String]]
|
|
54
170
|
|
|
55
|
-
def self.parse: (Array[String] argv, ?config: ^(String) -> String?
|
|
56
|
-
|
|
57
|
-
def self.
|
|
58
|
-
def self.
|
|
171
|
+
def self.parse: (Array[String] argv, ?dir: String, ?config: ^(String) -> String?, ?input: Questions::_Input,
|
|
172
|
+
?output: Questions::_Output) -> Project
|
|
173
|
+
private def self.arguments: (Array[String] argv) -> [Array[String], Hash[Symbol, String | bool]]
|
|
174
|
+
private def self.checked: (Symbol key, String value) -> String
|
|
175
|
+
private def self.ask: (Array[String] names, Hash[Symbol, String | bool] given, Hash[Symbol, String] defaults,
|
|
176
|
+
Questions questions) -> Project
|
|
177
|
+
private def self.name_of: (Array[String] names) -> String
|
|
178
|
+
private def self.required: (Hash[Symbol, String] defaults, Hash[Symbol, String | bool] given) -> Project::values
|
|
179
|
+
private def self.check_present: (Hash[Symbol, String | bool | nil] values) -> void
|
|
180
|
+
private def self.parser: () -> OptionParser
|
|
181
|
+
private def self.git_config: (String key) -> String?
|
|
59
182
|
end
|
|
60
183
|
|
|
61
184
|
module Generator
|
|
62
185
|
TEMPLATE: String
|
|
63
186
|
EXE: Array[String]
|
|
187
|
+
EXECUTABLES: Array[String]
|
|
64
188
|
GIT_LOCAL: Hash[String, nil]
|
|
65
189
|
LICENSE_FILES: Hash[String, String]
|
|
190
|
+
CITATION: String
|
|
191
|
+
TOOLS_LOCK: String
|
|
66
192
|
|
|
67
|
-
def self.files: (Project project) -> Hash[String, String]
|
|
68
|
-
def self.skipped: (Project project) -> Array[String]
|
|
69
|
-
def self.tools: () -> Array[String]
|
|
70
|
-
def self.check: (Project project, Array[String] paths) -> void
|
|
71
|
-
def self.check_command: (Project project) -> void
|
|
72
|
-
def self.path_of: (String file, Project project) -> String
|
|
73
193
|
def self.generate: (Project project, String target) -> void
|
|
74
|
-
def self.
|
|
75
|
-
def self.
|
|
76
|
-
def self.
|
|
77
|
-
def self.
|
|
194
|
+
def self.check: (String name, exe: bool, citation: bool, target: String) -> String
|
|
195
|
+
def self.check_name: (String name, bool exe) -> void
|
|
196
|
+
private def self.check_target: (String target) -> void
|
|
197
|
+
private def self.files: (Project project) -> Hash[String, String]
|
|
198
|
+
private def self.templates: (bool exe) -> Array[String]
|
|
199
|
+
private def self.tools: (?String lock) -> Array[String]
|
|
200
|
+
private def self.path_of: (String file, String name) -> String
|
|
201
|
+
private def self.removed_unless_done: (String target) { () -> void } -> void
|
|
202
|
+
private def self.write: (String file, String destination, Project project) -> void
|
|
203
|
+
private def self.stage: (String target, Array[String] executables) -> void
|
|
204
|
+
private def self.render: (String source, Project project) -> String
|
|
205
|
+
private def self.git: (String dir, *String args) -> void
|
|
78
206
|
end
|
|
79
207
|
end
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/bin/sh
|
|
2
|
-
# What fails fastest, so a commit stays quick
|
|
3
|
-
#
|
|
4
|
-
# just what is staged.
|
|
2
|
+
# What fails fastest, so a commit stays quick; the types, the tests and the rest are
|
|
3
|
+
# `bundle exec rake checks` and CI. Checks the working tree, not just what is staged.
|
|
5
4
|
set -eu
|
|
6
5
|
bundle exec rake rubocop shell workflows dprint
|
|
@@ -10,5 +10,6 @@
|
|
|
10
10
|
|
|
11
11
|
- [ ] Code passes local CI (`bundle exec rake checks`).
|
|
12
12
|
- [ ] Core logic is extracted into pure, unit-testable functions.
|
|
13
|
-
- [ ] Any new `# rubocop:disable` or `# steep:ignore` names its cop or diagnostic and says why it
|
|
13
|
+
- [ ] Any new `# rubocop:disable` or `# steep:ignore` names its cop or diagnostic and says why it
|
|
14
|
+
is wrong here (RuboCop's after `--`, Steep's on the line above: AGENTS.md).
|
|
14
15
|
- [ ] Commits follow [Conventional Commits](https://www.conventionalcommits.org/).
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# The gems (Gemfile.lock), weekly; GitHub Actions, pinned to commits, each release in a comment,
|
|
2
2
|
# and Dependabot moves both (docs.github.com, "Dependabot supported ecosystems"), weekly; the
|
|
3
3
|
# flake's inputs, monthly.
|
|
4
|
+
# No time: Dependabot picks one for the repository (docs.github.com, "Dependabot options
|
|
5
|
+
# reference").
|
|
4
6
|
version: 2
|
|
5
7
|
updates:
|
|
6
8
|
- package-ecosystem: "bundler"
|
|
@@ -8,11 +10,9 @@ updates:
|
|
|
8
10
|
schedule:
|
|
9
11
|
interval: "weekly"
|
|
10
12
|
day: "sunday"
|
|
11
|
-
time: "07:00"
|
|
12
|
-
timezone: "UTC"
|
|
13
13
|
open-pull-requests-limit: 5
|
|
14
|
-
# `chore(deps): ...` --
|
|
15
|
-
# gemspec's requirements themselves
|
|
14
|
+
# `chore(deps): ...` -- a Miscellaneous Task in the changelog, no feature or fix: the lock is not
|
|
15
|
+
# in the gem, whose users resolve its gemspec's requirements themselves
|
|
16
16
|
commit-message:
|
|
17
17
|
prefix: "chore"
|
|
18
18
|
include: "scope"
|
|
@@ -38,8 +38,6 @@ updates:
|
|
|
38
38
|
schedule:
|
|
39
39
|
interval: "weekly"
|
|
40
40
|
day: "sunday"
|
|
41
|
-
time: "07:00"
|
|
42
|
-
timezone: "UTC"
|
|
43
41
|
open-pull-requests-limit: 5
|
|
44
42
|
# `ci(deps): ...` -- nothing a user of the gem meets, so no changelog entry
|
|
45
43
|
commit-message:
|
|
@@ -66,8 +64,6 @@ updates:
|
|
|
66
64
|
directory: "/"
|
|
67
65
|
schedule:
|
|
68
66
|
interval: "monthly"
|
|
69
|
-
time: "06:00"
|
|
70
|
-
timezone: "UTC"
|
|
71
67
|
open-pull-requests-limit: 1
|
|
72
68
|
# `chore(deps): ...` -- the flake is the development shell, the checks and the Nix package,
|
|
73
69
|
# nothing RubyGems ships: a command's image takes the new inputs at its next release
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# The repositories a release's image goes to, one a line, each logged in before anything is
|
|
3
3
|
# pushed: GHCR's always (the job's token), Docker Hub's when both vars.DOCKERHUB_USERNAME and
|
|
4
|
-
# secrets.DOCKERHUB_TOKEN are set --
|
|
5
|
-
# GitHub repository's name>. image.yml's; it passes GH_TOKEN and DOCKERHUB_USERNAME,
|
|
4
|
+
# secrets.DOCKERHUB_TOKEN are set -- vars.DOCKERHUB_REPOSITORY (<namespace>/<name>), else
|
|
5
|
+
# <username>/<the GitHub repository's name>. image.yml's; it passes GH_TOKEN and DOCKERHUB_USERNAME,
|
|
6
6
|
# DOCKERHUB_TOKEN and DOCKERHUB_REPOSITORY (empty when unset).
|
|
7
7
|
set -euo pipefail
|
|
8
8
|
|
|
@@ -24,7 +24,8 @@ repository=${repository,,}
|
|
|
24
24
|
name=${repository#*/}
|
|
25
25
|
if [[ ! $repository =~ ^[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*/[a-z0-9]+((_|__|-+)[a-z0-9]+)*$ ]] ||
|
|
26
26
|
((${#name} < 2 || ${#name} > 255)); then
|
|
27
|
-
echo "error: '$repository' is no Docker Hub repository:
|
|
27
|
+
echo "error: '$repository' is no Docker Hub repository:" \
|
|
28
|
+
"set vars.DOCKERHUB_REPOSITORY to <namespace>/<name>" >&2
|
|
28
29
|
exit 1
|
|
29
30
|
fi
|
|
30
31
|
docker login docker.io --username "$username" --password-stdin <<< "$token" >&2
|
|
@@ -1,26 +1,23 @@
|
|
|
1
1
|
name: Dependabot Auto-Merge
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
# 1. Settings → General → Allow auto-merge
|
|
5
|
-
# 2. Settings → Branches →
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
# whose `pull_request:` has `paths:`, which reports only when those
|
|
11
|
-
#
|
|
3
|
+
# Repository settings, once:
|
|
4
|
+
# 1. Settings → General → Allow auto-merge: on.
|
|
5
|
+
# 2. Settings → Branches → a branch protection rule on main, its bypass to admins left allowed
|
|
6
|
+
# (`rake release` pushes its commit and tag to main):
|
|
7
|
+
# - Require a pull request before merging, no required approvals (one would hold every bump
|
|
8
|
+
# for a person).
|
|
9
|
+
# - Require status checks to pass before merging: every job the workflows run on every pull
|
|
10
|
+
# request -- not one whose `pull_request:` has `paths:`, which reports only when those paths
|
|
11
|
+
# change, so would block every other merge.
|
|
12
|
+
# Without step 2, this workflow merges bumps no CI job gated: auto-merge waits for *required*
|
|
13
|
+
# checks alone.
|
|
12
14
|
#
|
|
13
|
-
# The `permissions:` below
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
# GITHUB_TOKEN's events): main runs nothing for it, its pull request's
|
|
20
|
-
# required checks are what gated it.
|
|
21
|
-
#
|
|
22
|
-
# Without step 2, this workflow merges bumps that no CI job gated:
|
|
23
|
-
# auto-merge waits for *required* checks alone.
|
|
15
|
+
# The token needs only the `permissions:` below: the repository's default token permissions can
|
|
16
|
+
# stay read-only, and nothing here creates or approves a pull request. A merge this token makes
|
|
17
|
+
# starts no workflow run (GitHub's rule for GITHUB_TOKEN's events): main runs nothing for it; its
|
|
18
|
+
# pull request's required checks gated it, on the main its branch was last updated from. "Require
|
|
19
|
+
# branches to be up to date before merging" makes that main the current one, each bump then waiting
|
|
20
|
+
# for its branch's update (`@dependabot rebase`).
|
|
24
21
|
|
|
25
22
|
on:
|
|
26
23
|
pull_request:
|
|
@@ -29,14 +26,14 @@ permissions: {}
|
|
|
29
26
|
|
|
30
27
|
jobs:
|
|
31
28
|
dependabot:
|
|
29
|
+
name: Auto-merge
|
|
32
30
|
runs-on: ubuntu-latest
|
|
33
31
|
permissions:
|
|
34
|
-
contents: write
|
|
35
|
-
pull-requests: write
|
|
36
|
-
# user.login, not github.actor: the account that opened the pull request,
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
# Ref: https://docs.github.com/en/code-security/dependabot/working-with-dependabot/automating-dependabot-with-github-actions
|
|
32
|
+
contents: write # the merge
|
|
33
|
+
pull-requests: write # auto-merge, set on the pull request
|
|
34
|
+
# user.login, not github.actor: the account that opened the pull request, not whoever caused
|
|
35
|
+
# the event (a person pushing to Dependabot's branch too).
|
|
36
|
+
# Ref: https://docs.github.com/en/code-security/tutorials/secure-your-dependencies/automate-dependabot-with-actions
|
|
40
37
|
# github.actor: https://docs.github.com/en/actions/reference/workflows-and-actions/contexts
|
|
41
38
|
#
|
|
42
39
|
# To keep forks from auto-merging their own bumps, pin the repository:
|
|
@@ -51,14 +48,12 @@ jobs:
|
|
|
51
48
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
52
49
|
|
|
53
50
|
- name: Enable auto-merge for patch and minor updates
|
|
54
|
-
# update-type
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
# starts reads no other) and check steps.metadata.outputs.alert-state
|
|
61
|
-
# (the first dependency's alert).
|
|
51
|
+
# update-type: the highest version-update:semver-{major,minor,patch} among the updated
|
|
52
|
+
# dependencies whose level is known (Dependabot's metadata, else their versions); empty
|
|
53
|
+
# when none is, which this condition refuses. A security update carries the same labels,
|
|
54
|
+
# no "security:" prefix. To gate on security status, enable alert-lookup with a PAT or an
|
|
55
|
+
# App's token (a Dependabot secret: a run Dependabot starts reads no other) and check
|
|
56
|
+
# steps.metadata.outputs.alert-state (the first dependency's alert).
|
|
62
57
|
if: >-
|
|
63
58
|
contains(
|
|
64
59
|
fromJSON('["version-update:semver-patch", "version-update:semver-minor"]'),
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
name: Dependency Review
|
|
2
2
|
|
|
3
|
-
# The licences and advisories of what a pull request adds to Gemfile.lock and the workflows
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
3
|
+
# The licences and advisories of what a pull request adds to Gemfile.lock and the workflows.
|
|
4
|
+
# Advisory: it reports only when those files change, so no required check can be it, and
|
|
5
|
+
# dependabot-automerge.yml merges a bump it flags (security.yml's bundler-audit holds the lock
|
|
6
|
+
# daily).
|
|
7
7
|
on:
|
|
8
8
|
pull_request:
|
|
9
9
|
paths:
|
|
@@ -12,18 +12,22 @@ on:
|
|
|
12
12
|
- "*.gemspec"
|
|
13
13
|
- ".github/workflows/**"
|
|
14
14
|
|
|
15
|
+
# a pull request's newest run cancels its older ones; any other run, a group of its own: every
|
|
16
|
+
# commit on main checked (in a shared group, a pending run gives way to the newest: the default)
|
|
17
|
+
concurrency:
|
|
18
|
+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
|
|
19
|
+
cancel-in-progress: true
|
|
20
|
+
|
|
15
21
|
permissions: {}
|
|
16
22
|
|
|
17
|
-
# NOTE: it needs the repository's dependency graph (Settings → Advanced
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
# enable them, or delete the workflow.
|
|
23
|
+
# NOTE: it needs the repository's dependency graph (Settings → Advanced Security), and on a
|
|
24
|
+
# private repository an organization's GitHub Code Security too: without them the job fails, not
|
|
25
|
+
# skips -- enable them, or delete the workflow.
|
|
21
26
|
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# supported package ecosystems").
|
|
27
|
+
# Gemfile.lock has no scopes, so each of its gems counts as runtime, the gemspec's dependencies and
|
|
28
|
+
# the Gemfile's tools alike. In effect, the actions are reviewed for licences alone: advisories
|
|
29
|
+
# match an action by semantic version, not the commit it is pinned to ("Dependency graph supported
|
|
30
|
+
# package ecosystems").
|
|
27
31
|
jobs:
|
|
28
32
|
dependency-review:
|
|
29
33
|
name: License & Security Check
|
|
@@ -38,8 +42,8 @@ jobs:
|
|
|
38
42
|
fail-on-severity: moderate
|
|
39
43
|
# The licences are ClearlyDefined's, not the gemspecs'; a gem's:
|
|
40
44
|
# curl -s https://api.clearlydefined.io/definitions/gem/rubygems/-/NAME/VERSION
|
|
41
|
-
# (.licensed.declared). Add
|
|
42
|
-
#
|
|
45
|
+
# (.licensed.declared). Add what is legitimate and missing; never delete this list: an
|
|
46
|
+
# empty allow-list allows everything.
|
|
43
47
|
# Ruby: Ruby's own licence, with MIT or BSD's (concurrent-ruby, reline)
|
|
44
48
|
allow-licenses: >
|
|
45
49
|
0BSD, Apache-2.0, BlueOak-1.0.0, BSD-2-Clause, BSD-3-Clause,
|
|
@@ -5,13 +5,19 @@
|
|
|
5
5
|
# <version>-arm64) stay, the multi-architecture tag's parts. It runs beside release.yml, not after
|
|
6
6
|
# it: the image does not wait for the gem's push to RubyGems.
|
|
7
7
|
#
|
|
8
|
-
# The
|
|
9
|
-
# package public in its settings once.
|
|
8
|
+
# The jobs' token pushes to GHCR: no secret stored. A first push is private (GHCR's default): make
|
|
9
|
+
# the package public in its settings once.
|
|
10
10
|
#
|
|
11
11
|
# Docker Hub too, the same tags, once vars.DOCKERHUB_USERNAME and secrets.DOCKERHUB_TOKEN (an
|
|
12
12
|
# access token that writes) are set: as vars.DOCKERHUB_REPOSITORY, else <username>/<repository>. A
|
|
13
|
-
# Docker Hub failure in
|
|
14
|
-
#
|
|
13
|
+
# Docker Hub failure in a push (its login, its name) holds GHCR's multi-architecture tag back too;
|
|
14
|
+
# once fixed, a re-run of the failed jobs within a day (the image's artifact kept so long), or of
|
|
15
|
+
# all of them, completes both.
|
|
16
|
+
#
|
|
17
|
+
# The build, which runs the image, holds no write and no secret, and passes on the image it ran;
|
|
18
|
+
# the jobs that push, which build nothing, run in the `image` environment, which holds
|
|
19
|
+
# DOCKERHUB_TOKEN and which the generated CONTRIBUTING.md's first steps limit to the release tags: a
|
|
20
|
+
# job of no other ref can read it.
|
|
15
21
|
#
|
|
16
22
|
# Run by hand (workflow_dispatch) on a branch, it builds and runs the image on both architectures,
|
|
17
23
|
# pushing nothing: a rehearsal before the first release; on a tag, it publishes that release again.
|
|
@@ -37,8 +43,8 @@ jobs:
|
|
|
37
43
|
arch: arm64
|
|
38
44
|
permissions:
|
|
39
45
|
contents: read
|
|
40
|
-
packages: write
|
|
41
46
|
outputs:
|
|
47
|
+
image: ${{ steps.build.outputs.image }}
|
|
42
48
|
description: ${{ steps.build.outputs.description }}
|
|
43
49
|
steps:
|
|
44
50
|
- name: Checkout
|
|
@@ -47,7 +53,7 @@ jobs:
|
|
|
47
53
|
persist-credentials: false
|
|
48
54
|
|
|
49
55
|
- name: Install Nix
|
|
50
|
-
uses: cachix/install-nix-action@
|
|
56
|
+
uses: cachix/install-nix-action@75c75c31ebece974e48ebc36b4ad125bd97bd97b # v31.11.3
|
|
51
57
|
|
|
52
58
|
- name: Build and run
|
|
53
59
|
id: build
|
|
@@ -59,9 +65,10 @@ jobs:
|
|
|
59
65
|
echo "::error::$GITHUB_REF_NAME, but version.rb declares $version" >&2
|
|
60
66
|
exit 1
|
|
61
67
|
fi
|
|
62
|
-
# streamed
|
|
68
|
+
# streamed: no compressed copy of the closure in the store
|
|
63
69
|
stream="$(nix build --no-link --print-out-paths .#image.stream)"
|
|
64
|
-
"$stream"
|
|
70
|
+
"$stream" > image.tar
|
|
71
|
+
docker load --input image.tar
|
|
65
72
|
loaded="$(nix eval --raw .#image.imageName):$version"
|
|
66
73
|
# the image as a user runs it: its entrypoint, as its user
|
|
67
74
|
expected="$(nix eval --raw .#image.imageName) $version"
|
|
@@ -73,10 +80,52 @@ jobs:
|
|
|
73
80
|
# assigned first: a failing command substitution inside echo's arguments would not stop
|
|
74
81
|
# the step
|
|
75
82
|
description="$(nix eval --raw .#default.meta.description)"
|
|
76
|
-
|
|
83
|
+
{
|
|
84
|
+
echo "image=$loaded"
|
|
85
|
+
echo "description=$description"
|
|
86
|
+
} >> "$GITHUB_OUTPUT"
|
|
77
87
|
|
|
78
|
-
- name:
|
|
88
|
+
- name: Keep the image
|
|
79
89
|
if: github.ref_type == 'tag'
|
|
90
|
+
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
91
|
+
with:
|
|
92
|
+
name: image-${{ matrix.arch }}
|
|
93
|
+
path: image.tar
|
|
94
|
+
if-no-files-found: error
|
|
95
|
+
retention-days: 1
|
|
96
|
+
|
|
97
|
+
push:
|
|
98
|
+
name: Push (${{ matrix.arch }})
|
|
99
|
+
needs: build
|
|
100
|
+
if: github.ref_type == 'tag'
|
|
101
|
+
runs-on: ${{ matrix.runner }}
|
|
102
|
+
environment:
|
|
103
|
+
name: image
|
|
104
|
+
strategy:
|
|
105
|
+
matrix:
|
|
106
|
+
include:
|
|
107
|
+
- runner: ubuntu-latest
|
|
108
|
+
arch: amd64
|
|
109
|
+
- runner: ubuntu-24.04-arm
|
|
110
|
+
arch: arm64
|
|
111
|
+
permissions:
|
|
112
|
+
contents: read
|
|
113
|
+
packages: write # GHCR's pushes
|
|
114
|
+
steps:
|
|
115
|
+
- name: Checkout
|
|
116
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
117
|
+
with:
|
|
118
|
+
persist-credentials: false
|
|
119
|
+
|
|
120
|
+
- name: The build's image
|
|
121
|
+
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
122
|
+
with:
|
|
123
|
+
name: image-${{ matrix.arch }}
|
|
124
|
+
|
|
125
|
+
- name: Load it
|
|
126
|
+
run: docker load --input image.tar
|
|
127
|
+
|
|
128
|
+
- name: Push
|
|
80
129
|
env:
|
|
81
130
|
GH_TOKEN: ${{ github.token }}
|
|
82
131
|
# Docker Hub too, when both are set (.github/image-registries.sh): an unset variable or
|
|
@@ -86,25 +135,27 @@ jobs:
|
|
|
86
135
|
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
87
136
|
DOCKERHUB_REPOSITORY: ${{ vars.DOCKERHUB_REPOSITORY }}
|
|
88
137
|
ARCH: ${{ matrix.arch }}
|
|
138
|
+
LOADED: ${{ needs.build.outputs.image }}
|
|
89
139
|
run: |
|
|
90
140
|
set -euo pipefail
|
|
91
141
|
repositories="$(.github/image-registries.sh)"
|
|
92
142
|
version="${GITHUB_REF_NAME#v}"
|
|
93
|
-
loaded="$(nix eval --raw .#image.imageName):$version"
|
|
94
143
|
# the list on its own descriptor: no command in the loop can read it away
|
|
95
144
|
while read -r repository <&3; do
|
|
96
|
-
docker tag "$
|
|
145
|
+
docker tag "$LOADED" "$repository:$version-$ARCH"
|
|
97
146
|
docker push "$repository:$version-$ARCH"
|
|
98
147
|
done 3<<< "$repositories"
|
|
99
148
|
|
|
100
149
|
publish:
|
|
101
150
|
name: Multi-architecture tag
|
|
102
|
-
needs: build
|
|
151
|
+
needs: [build, push]
|
|
103
152
|
if: github.ref_type == 'tag'
|
|
104
153
|
runs-on: ubuntu-latest
|
|
154
|
+
environment:
|
|
155
|
+
name: image
|
|
105
156
|
permissions:
|
|
106
157
|
contents: read
|
|
107
|
-
packages: write
|
|
158
|
+
packages: write # GHCR's multi-architecture tag
|
|
108
159
|
steps:
|
|
109
160
|
- name: Checkout
|
|
110
161
|
# every tag, to tell whether this one is the highest
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# The links of the files git tracks but the tests' (`rake links`, lychee), monthly and on demand: a
|
|
2
|
+
# page moves or goes with nothing changed here. In the dev shell, whose lychee is the flake's;
|
|
3
|
+
# GITHUB_TOKEN, so GitHub's links are not rate-limited, lychee's alone, not the gems' installs.
|
|
4
|
+
name: Links
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
schedule:
|
|
8
|
+
- cron: "47 4 1 * *" # the 1st of each month, 04:47 UTC: off the hour, when GitHub delays runs
|
|
9
|
+
workflow_dispatch:
|
|
10
|
+
|
|
11
|
+
permissions:
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
links:
|
|
16
|
+
name: lychee
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout
|
|
20
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
21
|
+
with:
|
|
22
|
+
persist-credentials: false
|
|
23
|
+
|
|
24
|
+
- name: Install Nix
|
|
25
|
+
uses: cachix/install-nix-action@75c75c31ebece974e48ebc36b4ad125bd97bd97b # v31.11.3
|
|
26
|
+
|
|
27
|
+
- name: Install the gems
|
|
28
|
+
env:
|
|
29
|
+
BUNDLE_FROZEN: "true"
|
|
30
|
+
run: nix develop --command bundle install
|
|
31
|
+
|
|
32
|
+
- name: rake links
|
|
33
|
+
env:
|
|
34
|
+
GITHUB_TOKEN: ${{ github.token }}
|
|
35
|
+
run: nix develop --command bundle exec rake links
|
|
@@ -7,8 +7,8 @@ name: Nix
|
|
|
7
7
|
on:
|
|
8
8
|
push:
|
|
9
9
|
branches: [main]
|
|
10
|
-
# every pull request, whatever it touches: a path-filtered workflow
|
|
11
|
-
# required check that
|
|
10
|
+
# every pull request, whatever it touches: a path-filtered workflow does not report on one
|
|
11
|
+
# outside its paths, and a required check that does not report blocks the merge
|
|
12
12
|
pull_request:
|
|
13
13
|
workflow_dispatch:
|
|
14
14
|
|
|
@@ -37,7 +37,7 @@ jobs:
|
|
|
37
37
|
|
|
38
38
|
- name: Install Nix
|
|
39
39
|
# flakes on, and the job's token for GitHub fetches
|
|
40
|
-
uses: cachix/install-nix-action@
|
|
40
|
+
uses: cachix/install-nix-action@75c75c31ebece974e48ebc36b4ad125bd97bd97b # v31.11.3
|
|
41
41
|
|
|
42
42
|
- name: nix flake check
|
|
43
43
|
run: nix flake check --print-build-logs
|