toys-ci 0.0.0 → 0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7938bbe4359339e8e88a2db68b73a0fab34d7a56d451f81c93d829872ea2a84
4
- data.tar.gz: fedf758753479cb8b575d2ccc35e10b1678e96a37e6fe89d7bfe09442561a91f
3
+ metadata.gz: 33be1026b282eba5ffd250b26046e6de597db1ca2330ffb076c38e906a204bd0
4
+ data.tar.gz: 0d3ef0fc6fc1b0a348ee9754f2ed62a3b7880f7785c539d7411e838961a60702
5
5
  SHA512:
6
- metadata.gz: bb04a844fd4e312e36e8fa00c0259d6768ae1e1705c8946e4515e1307fe774d1ba1c6e9d94b8586943b11d4f2f5f82988f78de87a6c687637c26fd55d286c323
7
- data.tar.gz: 4e20e3835a4e28485e649bb62ab7957aaead974647c6ae24afc6df263564ea870a0333bf8f3703c5ca3c2c8f9e8f665286dccab9350075bf46a1820b50d06e55
6
+ metadata.gz: c9cde4a096e1d9119ad8995f63cf66073a5ac53ff5f7974ace7c52d979f3177235e78515f6040f541ef29583977c2ab1bcbdb79562938bcf43ad9a966545c108
7
+ data.tar.gz: dbbb802c658ca28eba170292ded6f02178293373bcb8ca7ebaad4aa0673d32948dd065493e0819adf363968c4886cae9e02976a6925d673c4332eb47d99fa6ac
data/.yardopts ADDED
@@ -0,0 +1,10 @@
1
+ --no-private
2
+ --title=Toys CI System
3
+ --markup=markdown
4
+ --markup-provider redcarpet
5
+ --main=README.md
6
+ ./lib/**/*.rb
7
+ -
8
+ README.md
9
+ LICENSE.md
10
+ CHANGELOG.md
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Release History
2
+
3
+ ### v0.1.0 / 2026-03-11
4
+
5
+ * Initial release
data/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ # License
2
+
3
+ Copyright 2026 Daniel Azuma and the Toys contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21
+ IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,9 +1,93 @@
1
- # Placeholder for toys-ci
1
+ # Toys-CI
2
2
 
3
- This is a placeholder gem, which was generated on 2026-03-11 to
4
- reserve the gem "toys-ci".
5
- The actual gem is planned for release in the near future.
3
+ Toys-CI is a framework for generating CI Toys tools.
6
4
 
7
- If this is a problem, or if the actual gem has not been released
8
- in a timely manner, you can contact the owner at
9
- `dazuma@gmail.com`.
5
+ ## Description
6
+
7
+ Continuous Integration (CI) in a project typically involves running a variety
8
+ of jobs, such as dependency installation, code linting, unit testing, build
9
+ verification, and so forth. A large code base, or a monorepo that includes a
10
+ number of smaller components, may require a large number of these jobs. Often
11
+ it is desirable to have a "coordinator" job that decides which CI jobs to run
12
+ in what order, and collects and summarizes their results.
13
+
14
+ Toys-CI is a framework for generating simple CI coordinator tools. It provides
15
+ a declarative interface for configuring the tool and specifying individual jobs
16
+ to run. The generated tool runs jobs specified using command line arguments,
17
+ and produces a final report of the results.
18
+
19
+ ### Basic example
20
+
21
+ Given the following `.toys.rb`:
22
+
23
+ # Create a "test" tool that runs minitest-based tests
24
+ expand :minitest, bundler: true
25
+
26
+ # Create a "rubocop" tool that checks the code base
27
+ expand :rubocop, bundler: true
28
+
29
+ # Create a "ci" tool that runs the above tools and
30
+ # summarizes their results
31
+ tool "ci" do
32
+ load_gem "toys-ci"
33
+
34
+ expand(Toys::CI::Template) do |ci|
35
+ ci.only_flag = true
36
+ ci.tool_job("Run Rubocop", ["rubocop"], flag: :rubocop)
37
+ ci.tool_job("Run tests", ["test"], flag: :tests)
38
+ end
39
+ end
40
+
41
+ Now you can:
42
+
43
+ $ toys ci # Runs both jobs
44
+ $ toys ci --only --tests # Runs only the tests
45
+
46
+ ### Key features
47
+
48
+ * Two interfaces: a high level Template interface that generates an entire tool
49
+ for you including configuration flags, and a low-level Mixin interface that
50
+ provides convenient methods that you can call to write your own CI tool.
51
+
52
+ * The high-level interface generates flags that allow selection of individual
53
+ jobs and groups of jobs to execute.
54
+
55
+ * Optionally analyzes diffs and skips jobs that do not need executing because
56
+ no relevant changes have occurred.
57
+
58
+ * Customize your tool, including implementing additional flags and
59
+ functionality, using the power of the Toys framework.
60
+
61
+ ### System requirements
62
+
63
+ Toys-CI requires Ruby 2.7 or later, and Toys 0.20 or later. We recommend the
64
+ latest version of Ruby, JRuby, or TruffleRuby. The Ruby provided by the
65
+ standard `setup-ruby` GitHub Action is sufficient.
66
+
67
+ ### Learning more
68
+
69
+ For more information on the underlying Toys framework, see the
70
+ [Toys README](https://dazuma.github.io/toys/gems/toys/latest) and the
71
+ [Toys User Guide](https://dazuma.github.io/toys/gems/toys/latest/file.guide.html).
72
+
73
+ ## License
74
+
75
+ Copyright 2026 Daniel Azuma and the Toys contributors
76
+
77
+ Permission is hereby granted, free of charge, to any person obtaining a copy
78
+ of this software and associated documentation files (the "Software"), to deal
79
+ in the Software without restriction, including without limitation the rights
80
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
81
+ copies of the Software, and to permit persons to whom the Software is
82
+ furnished to do so, subject to the following conditions:
83
+
84
+ The above copyright notice and this permission notice shall be included in
85
+ all copies or substantial portions of the Software.
86
+
87
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
88
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
89
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
90
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
91
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
92
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
93
+ IN THE SOFTWARE.
@@ -0,0 +1,344 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "toys-core"
4
+
5
+ module Toys
6
+ module CI
7
+ ##
8
+ # A mixin that provides methods useful for implementing CI tools.
9
+ #
10
+ # This mixin is a lower-level mechanism that depends on you to write your
11
+ # own run method and define any needed flags. For a more batteries-included
12
+ # experience, consider {Toys::CI::Template}, which does that work for you.
13
+ #
14
+ # To implement a CI tool using this mixin, you will:
15
+ #
16
+ # * Include this mixin
17
+ # * Call {toys_ci_init} to initialize the tool
18
+ # * Make calls to various `toys_ci_*_job` methods to run CI jobs
19
+ # * Call {toys_ci_report_results} to report final results
20
+ #
21
+ # This mixin adds various public and private methods, and several instance
22
+ # variables to the tool. All added method and instance variable names begin
23
+ # with `toys_ci_`, so avoid that prefix for any other methods and variables
24
+ # you are using in your tool.
25
+ #
26
+ # @example
27
+ #
28
+ # # Define the "test" tool
29
+ # expand :minitest, bundler: true
30
+ #
31
+ # # Define the "rubocop" tool
32
+ # expand :rubocop, bundler: true
33
+ #
34
+ # # Define a "ci" tool that runs both the above, controlled by
35
+ # # flags "--tests", "--rubocop", and/or "--all".
36
+ # tool "ci" do
37
+ # # Activate the toys-ci gem and pull in the mixin
38
+ # load_gem "toys-ci"
39
+ # include Toys::CI::Mixin
40
+ #
41
+ # flag :tests, desc: "Run tests"
42
+ # flag :rubocop, desc: "Run rubocop"
43
+ # flag :all, desc: "Run all CI tasks"
44
+ #
45
+ # def run
46
+ # toys_ci_init
47
+ # toys_ci_tool_job("Tests", ["test"]) if tests || all
48
+ # toys_ci_tool_job("Rubocop", ["rubocop"]) if rubocop || all
49
+ # toys_ci_report_results
50
+ # end
51
+ # end
52
+ #
53
+ module Mixin
54
+ include ::Toys::Mixin
55
+
56
+ on_include do
57
+ include :exec unless include?(:exec)
58
+ include :terminal unless include?(:terminal)
59
+ end
60
+
61
+ ##
62
+ # Initialize the CI tool. This must be called first before any other
63
+ # `toys_ci_` methods.
64
+ #
65
+ # @param fail_fast [boolean] If true, CI will terminate once any job ends
66
+ # in failure. Otherwise, all jobs will be run.
67
+ # @param limit_by_changes_since [String,nil] If set to a git ref, finds
68
+ # all the changed files since that ref, and skips CI jobs that
69
+ # declare trigger paths that do not match. Optional. By default, no
70
+ # jobs are skipped for this reason.
71
+ #
72
+ # @return [self]
73
+ #
74
+ def toys_ci_init(fail_fast: false, limit_by_changes_since: nil)
75
+ @toys_ci_fail_fast = fail_fast
76
+ @toys_ci_changed_paths = toys_ci_find_changes_since(limit_by_changes_since)
77
+ @toys_ci_successful_jobs = []
78
+ @toys_ci_failed_jobs = []
79
+ @toys_ci_skipped_jobs = []
80
+ self
81
+ end
82
+
83
+ ##
84
+ # Look for environment variables set by a GitHub workflow, and attempt to
85
+ # extract a suitable change base. Returns a git SHA, or nil if one could
86
+ # not be obtained from the current environment.
87
+ #
88
+ # This may read the `GITHUB_EVENT_NAME` and `GITHUB_EVENT_PATH`
89
+ # environment variables, and may also read the event payload file if
90
+ # found. However, the exact logic is not specified.
91
+ #
92
+ # The result can be passed to the `:limit_by_changes_since` argument of
93
+ # {#toys_ci_init}.
94
+ #
95
+ # @return [String] The git SHA for the change base
96
+ # @return [nil] if no change base can be determined
97
+ #
98
+ def toys_ci_github_event_base_sha
99
+ event_path = ::ENV["GITHUB_EVENT_PATH"].to_s
100
+ if event_path.empty?
101
+ logger.info("GITHUB_EVENT_PATH is empty or unset; cannot determine event payload file")
102
+ return nil
103
+ end
104
+ event_payload = toys_ci_read_event_payload_file(event_path)
105
+ return nil unless event_payload
106
+
107
+ event_name = ::ENV["GITHUB_EVENT_NAME"]
108
+ case event_name
109
+ when "push"
110
+ logger.info("Getting change base from push event")
111
+ event_payload["before"]
112
+ when "pull_request"
113
+ logger.info("Getting change base from pull_request event")
114
+ event_payload.dig("pull_request", "base", "sha")
115
+ else
116
+ logger.info("Did not find a change base from event #{event_name.inspect}")
117
+ nil
118
+ end
119
+ end
120
+
121
+ ##
122
+ # Run a CI job implemented by a tool, and record the results.
123
+ #
124
+ # @param name [String] A user-visible name for the job. Required.
125
+ # @param tool [Array<String>] The Toys tool to run. Required.
126
+ # @param trigger_paths [Array<String>,String,nil] An array of file or
127
+ # directory paths, relative to the repo root, that must have changes
128
+ # in order to trigger the job. If not specified, the job is always
129
+ # triggered.
130
+ # @param env [Hash{String=>String}] Environment variables to set during
131
+ # the run. Optional.
132
+ # @param chdir [String] The working directory for the run. Optional.
133
+ #
134
+ # @return [:success] If the job succeeded
135
+ # @return [:failure] If the job failed
136
+ # @return [:skipped] If the job was skipped because it did not match the
137
+ # trigger paths
138
+ #
139
+ def toys_ci_tool_job(name, tool, trigger_paths: nil, env: nil, chdir: nil)
140
+ toys_ci_job(name, trigger_paths: trigger_paths) do
141
+ opts = {name: name}
142
+ opts[:env] = env if env
143
+ opts[:chdir] = chdir if chdir
144
+ exec_separate_tool(tool, **opts).success?
145
+ end
146
+ end
147
+
148
+ ##
149
+ # Run a CI job implemented by an external process, and record the results.
150
+ #
151
+ # @param name [String] A user-visible name for the job. Required.
152
+ # @param cmd [Array<String>] The command to run. Required.
153
+ # @param trigger_paths [Array<String>,String,nil] An array of file or
154
+ # directory paths, relative to the repo root, that must have changes
155
+ # in order to trigger the job. If not specified, the job is always
156
+ # triggered.
157
+ # @param env [Hash{String=>String}] Environment variables to set during
158
+ # the run. Optional.
159
+ # @param chdir [String] The working directory for the run. Optional.
160
+ #
161
+ # @return [:success] If the job succeeded
162
+ # @return [:failure] If the job failed
163
+ # @return [:skipped] If the job was skipped because it did not match the
164
+ # trigger paths
165
+ #
166
+ def toys_ci_cmd_job(name, cmd, trigger_paths: nil, env: nil, chdir: nil)
167
+ toys_ci_job(name, trigger_paths: trigger_paths) do
168
+ opts = {name: name}
169
+ opts[:env] = env if env
170
+ opts[:chdir] = chdir if chdir
171
+ exec(cmd, **opts).success?
172
+ end
173
+ end
174
+
175
+ ##
176
+ # Run a CI job implemented by a block, and record the results.
177
+ #
178
+ # @param name [String] A user-visible name for the job. Required.
179
+ # @param trigger_paths [Array<String>,String,nil] An array of file or
180
+ # directory paths, relative to the repo root, that must have changes
181
+ # in order to trigger the job. If not specified, the job is always
182
+ # triggered.
183
+ # @param block [Proc] The block to run. It will be run with `self` set to
184
+ # the tool context, and should return true or false indicating
185
+ # success or failure.
186
+ #
187
+ # @return [:success] If the job succeeded
188
+ # @return [:failure] If the job failed
189
+ # @return [:skipped] If the job was skipped because it did not match the
190
+ # trigger paths
191
+ #
192
+ def toys_ci_job(name, trigger_paths: nil, &block)
193
+ unless defined?(@toys_ci_successful_jobs)
194
+ raise ::Toys::ToolDefinitionError, "You must call toys_ci_init before running a job"
195
+ end
196
+ return :skipped unless toys_ci_check_trigger_paths(trigger_paths, name)
197
+ puts("**** RUNNING: #{name}", :cyan, :bold)
198
+ result =
199
+ begin
200
+ instance_exec(&block)
201
+ rescue ::StandardError => e
202
+ trace = e.backtrace
203
+ write("#{trace.first}: ")
204
+ puts("#{e.message} (#{e.class})", :bold)
205
+ Array(trace[1..]).each { |line| puts " from #{line}" }
206
+ false
207
+ end
208
+ toys_ci_job_result(name, result)
209
+ end
210
+
211
+ ##
212
+ # Print out a final report of the results, including a summary of the
213
+ # failed jobs. By default, this will also exit and never return. You can
214
+ # instead get the exit value by passing `exit: false`.
215
+ #
216
+ # @param exit [boolean] Whether to exit. Default is true.
217
+ #
218
+ # @return [Integer] The exit value
219
+ #
220
+ def toys_ci_report_results(exit: true) # rubocop:disable Metrics/MethodLength
221
+ unless defined?(@toys_ci_successful_jobs)
222
+ raise ::Toys::ToolDefinitionError, "You must call toys_ci_init before reporting job results"
223
+ end
224
+ success_count = @toys_ci_successful_jobs.size
225
+ failure_count = @toys_ci_failed_jobs.size
226
+ skipped_count = @toys_ci_skipped_jobs.size
227
+ total_job_count = success_count + failure_count + skipped_count
228
+ result =
229
+ if total_job_count.zero?
230
+ puts("**** CI: NO JOBS REQUESTED", :red, :bold)
231
+ puts("Try passing --help to see how to activate CI jobs.")
232
+ 2
233
+ elsif failure_count.positive?
234
+ puts("**** CI: SKIPPED #{skipped_count} OF #{total_job_count} JOBS", :bold) unless skipped_count.zero?
235
+ puts("**** CI: FAILED #{failure_count} OF #{success_count + failure_count} RUNNABLE JOBS:", :red, :bold)
236
+ @toys_ci_failed_jobs.each { |name| puts(name, :red) }
237
+ 1
238
+ elsif success_count.positive?
239
+ puts("**** CI: SKIPPED #{skipped_count} OF #{total_job_count} JOBS", :bold) unless skipped_count.zero?
240
+ puts("**** CI: ALL #{success_count} RUNNABLE JOBS SUCCEEDED", :green, :bold)
241
+ 0
242
+ else
243
+ puts("**** CI: ALL #{skipped_count} JOBS SKIPPED", :yellow, :bold)
244
+ 0
245
+ end
246
+ self.exit(result) if exit
247
+ result
248
+ end
249
+
250
+ ##
251
+ # @return [Array<String>] The names of the failed jobs so far
252
+ #
253
+ attr_reader :toys_ci_failed_jobs
254
+
255
+ ##
256
+ # @return [Array<String>] The names of the successful jobs so far
257
+ #
258
+ attr_reader :toys_ci_successful_jobs
259
+
260
+ ##
261
+ # @return [Array<String>] The names of the skipped jobs so far
262
+ #
263
+ attr_reader :toys_ci_skipped_jobs
264
+
265
+ ##
266
+ # @private
267
+ # Read a GitHub event payload file
268
+ #
269
+ def toys_ci_read_event_payload_file(path)
270
+ require "json"
271
+ ::JSON.parse(::File.read(path))
272
+ rescue ::SystemCallError, ::JSON::ParserError => e
273
+ logger.error("Failed to read GitHub event payload from #{path.inspect}:")
274
+ logger.error(e.inspect)
275
+ nil
276
+ end
277
+
278
+ ##
279
+ # @private
280
+ # Find all the changed files since the given ref.
281
+ #
282
+ def toys_ci_find_changes_since(ref)
283
+ return nil unless ref
284
+ result = exec(["git", "rev-parse", ref], out: :capture)
285
+ unless result.success?
286
+ logger.error("Unable to find git ref #{ref.inspect}")
287
+ exit(1)
288
+ end
289
+ sha = result.captured_out.strip
290
+ logger.info("Filtering by changes since SHA: #{sha}")
291
+ result = exec(["git", "diff", "--name-only", sha], out: :capture)
292
+ unless result.success?
293
+ logger.error("Unable to get diff since SHA #{sha}")
294
+ exit(1)
295
+ end
296
+ result.captured_out.split("\n")
297
+ end
298
+
299
+ ##
300
+ # @private
301
+ # Go through the given trigger paths and see if any match the changes
302
+ #
303
+ # @return [boolean] True to run the job, or false to skip
304
+ #
305
+ def toys_ci_check_trigger_paths(trigger_paths, name)
306
+ return true if !@toys_ci_changed_paths || !trigger_paths
307
+ Array(trigger_paths).each do |trigger_path|
308
+ trigger_dir = trigger_path.end_with?("/") ? trigger_path : "#{trigger_path}/"
309
+ return true if @toys_ci_changed_paths.any? do |changed_path|
310
+ changed_path == trigger_path || changed_path.start_with?(trigger_dir)
311
+ end
312
+ end
313
+ @toys_ci_skipped_jobs << name
314
+ puts("**** SKIPPING BECAUSE NO CHANGES FOUND: #{name}", :cyan, :bold)
315
+ false
316
+ end
317
+
318
+ ##
319
+ # @private
320
+ # Report the result of a single job
321
+ #
322
+ # @param name [String] The name of the job
323
+ # @param result [boolean] The result of the job
324
+ # @return [:success] If the result was success
325
+ # @return [:failure] If the result was failure
326
+ #
327
+ def toys_ci_job_result(name, result)
328
+ if result
329
+ @toys_ci_successful_jobs << name
330
+ puts("**** SUCCEEDED: #{name}", :green, :bold)
331
+ :success
332
+ else
333
+ @toys_ci_failed_jobs << name
334
+ puts("**** FAILED: #{name}", :red, :bold)
335
+ if @toys_ci_fail_fast
336
+ puts("TERMINATING CI", :red, :bold)
337
+ exit(1)
338
+ end
339
+ :failure
340
+ end
341
+ end
342
+ end
343
+ end
344
+ end
@@ -0,0 +1,532 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "toys-core"
4
+
5
+ module Toys
6
+ module CI
7
+ ##
8
+ # A template that can be used to implement a CI tool.
9
+ #
10
+ # This template generates flags and implementation methods in the current
11
+ # tool to implement CI. In particular, it generates the `run` method
12
+ # itself. If you need more control over the CI tool's implementation,
13
+ # consider using {Toys::CI::Mixin} which provides a lower-level interface.
14
+ #
15
+ # To implement a CI tool using this template, simply expand the template
16
+ # and provide the necessary configuration, including specifying at least
17
+ # one CI task to run. The generated tool will use {Toys::CI::Mixin} under
18
+ # the hood.
19
+ #
20
+ # @example
21
+ #
22
+ # # Define the "test" tool
23
+ # expand :minitest, bundler: true
24
+ #
25
+ # # Define the "rubocop" tool
26
+ # expand :rubocop, bundler: true
27
+ #
28
+ # # Define a "ci" tool that runs both the above, controlled by
29
+ # # flags "--tests", "--rubocop", and/or "--all".
30
+ # tool "ci" do
31
+ # # Activate the toys-ci gem and expand the template
32
+ # load_gem "toys-ci"
33
+ # expand Toys::CI::Template do |ci|
34
+ # ci.all_flag = true
35
+ # ci.tool_job("Tests", ["test"], flag: :tests)
36
+ # ci.tool_job("Rubocop", ["rubocop"], flag: :rubocop)
37
+ # end
38
+ # end
39
+ #
40
+ class Template
41
+ include ::Toys::Template
42
+
43
+ ##
44
+ # Add a job implemented by a tool call.
45
+ #
46
+ # @param name [String] A user-visible name for the job. Required.
47
+ # @param tool [Array<String>] The Toys tool to run. Required.
48
+ # @param flag [Symbol] A flag key to control whether the job will run.
49
+ # If provided, it will be used to generate two flags that can be
50
+ # passed to the CI job. For example, passing `:unit_tests` will
51
+ # generate the flags `--unit-tests` and `--no-unit-tests` and set the
52
+ # context value `:unit_tests`. If not provided, no flag is generated
53
+ # and runnability will be determined by the "all" or "only" flag.
54
+ # @param override_flags [String,Array<String>,nil] Generated flags to use
55
+ # instead of the ones implied by the `:flag` argument. These must be
56
+ # specified without the leading `--`, and will automatically have
57
+ # `--[no-]` prepended. Optional. If not specified or nil, the flags
58
+ # fall back to those implied by the `:flag` argument.
59
+ # @param trigger_paths [Array<String>,String,nil] An array of file or
60
+ # directory paths, relative to the repo root, that must have changes
61
+ # in order to trigger the job. If not specified, the job is always
62
+ # triggered.
63
+ # @param env [Hash{String=>String}] Environment variables to set during
64
+ # the run. Optional.
65
+ # @param chdir [String] The working directory for the run. Optional.
66
+ #
67
+ def tool_job(name, tool, flag: nil, override_flags: nil, trigger_paths: nil, env: nil, chdir: nil)
68
+ if override_flags && !flag
69
+ raise ::Toys::ToolDefinitionError, "override_flags is meaningless without a flag"
70
+ end
71
+ @jobs << ToolJob.new(name, flag, Array(override_flags), trigger_paths, tool, env, chdir)
72
+ self
73
+ end
74
+
75
+ ##
76
+ # Add a job implemented by an external process.
77
+ #
78
+ # @param name [String] A user-visible name for the job. Required.
79
+ # @param cmd [Array<String>] The command to run. Required.
80
+ # @param flag [Symbol] A flag key to control whether the job will run.
81
+ # If provided, it will be used to generate two flags that can be
82
+ # passed to the CI job. For example, passing `:unit_tests` will
83
+ # generate the flags `--unit-tests` and `--no-unit-tests` and set the
84
+ # context value `:unit_tests`. If not provided, no flag is generated
85
+ # and runnability will be determined by the "all" or "only" flag.
86
+ # @param override_flags [String,Array<String>,nil] Generated flags to use
87
+ # instead of the ones implied by the `:flag` argument. These must be
88
+ # specified without the leading `--`, and will automatically have
89
+ # `--[no-]` prepended. Optional. If not specified or nil, the flags
90
+ # fall back to those implied by the `:flag` argument.
91
+ # @param trigger_paths [Array<String>,String,nil] An array of file or
92
+ # directory paths, relative to the repo root, that must have changes
93
+ # in order to trigger the job. If not specified, the job is always
94
+ # triggered.
95
+ # @param env [Hash{String=>String}] Environment variables to set during
96
+ # the run. Optional.
97
+ # @param chdir [String] The working directory for the run. Optional.
98
+ #
99
+ def cmd_job(name, cmd, flag: nil, override_flags: nil, trigger_paths: nil, env: nil, chdir: nil)
100
+ if override_flags && !flag
101
+ raise ::Toys::ToolDefinitionError, "override_flags is meaningless without a flag"
102
+ end
103
+ @jobs << CmdJob.new(name, flag, Array(override_flags), trigger_paths, cmd, env, chdir)
104
+ self
105
+ end
106
+
107
+ ##
108
+ # Add a job implemented by a block.
109
+ #
110
+ # The block should perform a CI job and return a boolean indicating
111
+ # whether or not the job succeeded. It will execute in the tool execution
112
+ # context, with `self` set to the `Toys::Context`.
113
+ #
114
+ # @param name [String] A user-visible name for the job. Required.
115
+ # @param block [Proc] A block that runs this job. Required.
116
+ # @param flag [Symbol,nil] A flag key to control whether the job will run.
117
+ # If provided, it will be used to generate two flags that can be
118
+ # passed to the CI job. For example, passing `:unit_tests` will
119
+ # generate the flags `--unit-tests` and `--no-unit-tests` and set the
120
+ # context value `:unit_tests`. If not provided, no flag is generated
121
+ # and runnability will be determined by the "all" or "only" flag.
122
+ # @param override_flags [String,Array<String>,nil] Generated flags to use
123
+ # instead of the ones implied by the `:flag` argument. These must be
124
+ # specified without the leading `--`, and will automatically have
125
+ # `--[no-]` prepended. Optional. If not specified or nil, the flags
126
+ # fall back to those implied by the `:flag` argument.
127
+ # @param trigger_paths [Array<String>,String,nil] An array of file or
128
+ # directory paths, relative to the repo root, that must have changes
129
+ # in order to trigger the job. If not specified, the job is always
130
+ # triggered.
131
+ #
132
+ def job(name, flag: nil, override_flags: nil, trigger_paths: nil, &block)
133
+ if override_flags && !flag
134
+ raise ::Toys::ToolDefinitionError, "override_flags is meaningless without a flag"
135
+ end
136
+ @jobs << BlockJob.new(name, flag, Array(override_flags), trigger_paths, block)
137
+ self
138
+ end
139
+
140
+ ##
141
+ # Define a collection of jobs that can be enabled/disabled as a group.
142
+ #
143
+ # @param name [String] A user-visible name for the collection. Required.
144
+ # @param flag [Symbol] A flag key to control the collection. Required.
145
+ # Used to define two flags that can be passed to the CI job. For
146
+ # example, passing `:unit_tests` will generate the flags
147
+ # `--unit-tests` and `--no-unit-tests` and set the context value
148
+ # `:unit_tests`.
149
+ # @param override_flags [String,Array<String>,nil] Generated flags to use
150
+ # instead of the ones implied by the `:flag` argument. These must be
151
+ # specified without the leading `--`, and will automatically have
152
+ # `--[no-]` prepended. Optional. If not specified or nil, the flags
153
+ # fall back to those implied by the `:flag` argument.
154
+ # @param job_flags [Array<Symbol>] The individual job flags that will be
155
+ # controlled as a group by this collection. Must be nonempty.
156
+ #
157
+ def collection(name, flag, job_flags, override_flags: nil)
158
+ if job_flags.empty?
159
+ raise ::Toys::ToolDefinitionError, "You must provide at least one entry in job_flags"
160
+ end
161
+ @collections << Collection.new(name, flag, Array(override_flags), job_flags)
162
+ self
163
+ end
164
+
165
+ ##
166
+ # Provide a block that will be run at the beginning of the CI job.
167
+ # The block will run in the tool execution context, with `self` set to
168
+ # the `Toys::Context`.
169
+ #
170
+ # @param block [Proc] The block to execute.
171
+ #
172
+ def before_run(&block)
173
+ @prerun = block
174
+ end
175
+
176
+ ##
177
+ # Create a flag that will enable all jobs. All jobs will otherwise be
178
+ # disabled by default. This setting is mutually exclusive with
179
+ # {#only_flag=} and {#jobs_disabled_by_default=}.
180
+ #
181
+ # The value can either be the flag key as a symbol, `true` to use the
182
+ # default (which is `:all`), or `false` to disable such a flag.
183
+ # For example, passing `true` will define the flag `--all` which will set
184
+ # the context key `:all`.
185
+ #
186
+ # @param value [Symbol,boolean]
187
+ #
188
+ def all_flag=(value)
189
+ @all_flag = value
190
+ end
191
+
192
+ ##
193
+ # Create a flag that will disable all jobs. All jobs will otherwise be
194
+ # enabled by default. This setting is mutually exclusive with
195
+ # {#all_flag=} and {#jobs_disabled_by_default=}.
196
+ #
197
+ # The value can either be the flag key as a symbol, `true` to use the
198
+ # default (which is `:only`), or `false` to disable such a flag.
199
+ # For example, passing `true` will define the flag `--only` which will
200
+ # set the context key `:only`.
201
+ #
202
+ # @param value [Symbol,boolean]
203
+ #
204
+ def only_flag=(value)
205
+ @only_flag = value
206
+ end
207
+
208
+ ##
209
+ # If set to true, all jobs are disabled by default unless explicitly
210
+ # enabled by their individual flags.
211
+ #
212
+ # This setting is mutually exclusive with {#all_flag=} and {#only_flag=}.
213
+ # If you set up one of those flags, the default enabling behavior is also
214
+ # set implicitly.
215
+ #
216
+ # @param value [boolean]
217
+ #
218
+ def jobs_disabled_by_default=(value)
219
+ @jobs_disabled_by_default = value
220
+ end
221
+
222
+ ##
223
+ # Create flags that will enable and disable fail-fast. The flag should be
224
+ # specified by symbol, and the actual flag will be set accordingly. You
225
+ # can also use the value `true` which will set the default `:fail_fast`.
226
+ # For example, passing `true` will define the flags `--fail-fast` and
227
+ # `--no-fail-fast`.
228
+ #
229
+ # @param value [Symbol,boolean]
230
+ #
231
+ def fail_fast_flag=(value)
232
+ @fail_fast_flag = value
233
+ end
234
+
235
+ ##
236
+ # Set the default value of fail-fast. You can also create flags that can
237
+ # override this value using {fail_fast_flag=}. Default is false.
238
+ #
239
+ # @param value [boolean]
240
+ #
241
+ def fail_fast_default=(value)
242
+ @fail_fast_default = value
243
+ end
244
+
245
+ ##
246
+ # Create a flag that can be used to specify the base ref for the change
247
+ # directly. This can be used to filter CI jobs based on what has changed.
248
+ #
249
+ # A change base ref provided in this way will override any obtained from
250
+ # other means, such as from the GitHub environment using
251
+ # {#use_github_base_ref_flag=}.
252
+ #
253
+ # @param value [Symbol,boolean] If a symbol, it is used as the flag
254
+ # key for a flag that specifies the base ref. You can also pass
255
+ # `true` to use the default, `:base_ref`.
256
+ #
257
+ def base_ref_flag=(value)
258
+ @base_ref_flag = value
259
+ end
260
+
261
+ ##
262
+ # Create a flag that enables obtaining the change base ref from the
263
+ # GitHub workflow environment. This can be used to filter CI jobs based
264
+ # on what has changed in a GitHub Actions workflow. The flag should be
265
+ # specified by symbol, and the actual flag will be set accordingly. You
266
+ # can also use the value `true` which will set the default
267
+ # `:use_github_base_ref`. For example, passing `true` will define the
268
+ # flags `--use-github-base-ref` and `--no-use-github-base-ref`.
269
+ #
270
+ # @param value [Symbol,boolean]
271
+ #
272
+ def use_github_base_ref_flag=(value)
273
+ @use_github_base_ref_flag = value
274
+ end
275
+
276
+ # @private
277
+ def initialize
278
+ @jobs = []
279
+ @collections = []
280
+ @all_flag = nil
281
+ @only_flag = nil
282
+ @jobs_disabled_by_default = nil
283
+ @fail_fast_flag = nil
284
+ @fail_fast_default = false
285
+ @base_ref_flag = nil
286
+ @use_github_base_ref_flag = nil
287
+ @prerun = nil
288
+ end
289
+
290
+ # @private
291
+ BlockJob = ::Struct.new(:name, :flag, :override_flags, :trigger_paths, :block)
292
+
293
+ # @private
294
+ ToolJob = ::Struct.new(:name, :flag, :override_flags, :trigger_paths, :tool, :env, :chdir)
295
+
296
+ # @private
297
+ CmdJob = ::Struct.new(:name, :flag, :override_flags, :trigger_paths, :cmd, :env, :chdir)
298
+
299
+ # @private
300
+ Collection = ::Struct.new(:name, :flag, :override_flags, :job_flags)
301
+
302
+ # @private
303
+ attr_reader :jobs
304
+
305
+ # @private
306
+ attr_reader :collections
307
+
308
+ # @private
309
+ attr_reader :prerun
310
+
311
+ # @private
312
+ attr_reader :jobs_disabled_by_default
313
+
314
+ # @private
315
+ attr_reader :fail_fast_default
316
+
317
+ # @private
318
+ def all_flag?
319
+ !@all_flag.nil? && @all_flag != false
320
+ end
321
+
322
+ # @private
323
+ def only_flag?
324
+ !@only_flag.nil? && @only_flag != false
325
+ end
326
+
327
+ # @private
328
+ def fail_fast_flag?
329
+ !@fail_fast_flag.nil? && @fail_fast_flag != false
330
+ end
331
+
332
+ # @private
333
+ def base_ref_flag?
334
+ !@base_ref_flag.nil? && @base_ref_flag != false
335
+ end
336
+
337
+ # @private
338
+ def use_github_base_ref_flag?
339
+ !@use_github_base_ref_flag.nil? && @use_github_base_ref_flag != false
340
+ end
341
+
342
+ # @private
343
+ def all_flag(desired_format = :symbol)
344
+ format_flag(@all_flag, :all, desired_format)
345
+ end
346
+
347
+ # @private
348
+ def only_flag(desired_format = :symbol)
349
+ format_flag(@only_flag, :only, desired_format)
350
+ end
351
+
352
+ # @private
353
+ def fail_fast_flag(desired_format = :symbol)
354
+ format_flag(@fail_fast_flag, :fail_fast, desired_format)
355
+ end
356
+
357
+ # @private
358
+ def base_ref_flag(desired_format = :symbol)
359
+ format_flag(@base_ref_flag, :base_ref, desired_format)
360
+ end
361
+
362
+ # @private
363
+ def use_github_base_ref_flag(desired_format = :symbol)
364
+ format_flag(@use_github_base_ref_flag, :use_github_base_ref, desired_format)
365
+ end
366
+
367
+ # @private
368
+ def format_flag(raw_flag, default_symbol, desired_format)
369
+ value = raw_flag == true ? default_symbol : raw_flag
370
+ desired_format == :hyphenated ? value.to_s.tr("_", "-") : value
371
+ end
372
+
373
+ on_expand do |template|
374
+ if template.all_flag? && !template.only_flag? && template.jobs_disabled_by_default.nil?
375
+ template.jobs_disabled_by_default = true
376
+ flag(template.all_flag) do
377
+ flags("--#{template.all_flag(:hyphenated)}")
378
+ desc("Run all jobs unless explicitly disabled by their flags." \
379
+ " (If not set, only explicitly enabled jobs run.)")
380
+ end
381
+ elsif !template.all_flag? && template.only_flag? && template.jobs_disabled_by_default.nil?
382
+ template.jobs_disabled_by_default = false
383
+ flag(template.only_flag) do
384
+ flags("--#{template.only_flag(:hyphenated)}")
385
+ desc("Run only jobs explicitly enabled by their flags." \
386
+ " (If not set, all jobs run unless explicitly disabled.)")
387
+ end
388
+ elsif !template.all_flag? && !template.only_flag?
389
+ template.jobs_disabled_by_default ||= false
390
+ else
391
+ raise ::Toys::ToolDefinitionError, "all_flag, only_flag, and jobs_disabled_by_default are mutually exclusive"
392
+ end
393
+
394
+ if template.fail_fast_flag?
395
+ flag(template.fail_fast_flag) do
396
+ flags("--[no-]#{template.fail_fast_flag(:hyphenated)}")
397
+ default(template.fail_fast_default)
398
+ desc("Terminate CI as soon as any job fails (default is #{template.fail_fast_default})")
399
+ end
400
+ end
401
+
402
+ if template.base_ref_flag?
403
+ flag(template.base_ref_flag) do
404
+ flags("--#{template.base_ref_flag(:hyphenated)} REF")
405
+ desc("Filter jobs that do not match changes since this git ref")
406
+ end
407
+ end
408
+
409
+ if template.use_github_base_ref_flag?
410
+ flag(template.use_github_base_ref_flag) do
411
+ flags("--[no-]#{template.use_github_base_ref_flag(:hyphenated)}")
412
+ desc("Look up the change base from GitHub to determine which jobs to filter")
413
+ end
414
+ end
415
+
416
+ flag_desc_suffix =
417
+ if template.all_flag?
418
+ "(Jobs run by default if --#{template.all_flag(:hyphenated)} is set.)"
419
+ elsif template.only_flag?
420
+ "(Jobs run by default unless --#{template.only_flag(:hyphenated)} is set.)"
421
+ elsif template.jobs_disabled_by_default
422
+ "(Jobs do not run by default.)"
423
+ else
424
+ "(All jobs run by default.)"
425
+ end
426
+
427
+ flag_group(desc: "Jobs") do
428
+ template.jobs.each do |job|
429
+ next unless job.flag
430
+ flag(job.flag) do
431
+ if job.override_flags.empty?
432
+ hyphenated_flag = job.flag.to_s.tr("_", "-")
433
+ flags("--[no-]#{hyphenated_flag}")
434
+ else
435
+ job.override_flags.each { |override_flag| flags("--[no-]#{override_flag}") }
436
+ end
437
+ desc("Run or omit the job \"#{job.name}\". #{flag_desc_suffix}")
438
+ end
439
+ end
440
+ end
441
+
442
+ unless template.collections.empty?
443
+ flag_group(desc: "Collections") do
444
+ template.collections.each do |collection|
445
+ Array(collection.job_flags).each do |job_flag|
446
+ if template.jobs.none? { |job| job.flag == job_flag }
447
+ raise ::Toys::ToolDefinitionError,
448
+ "Collection \"#{collection.name}\" referenced nonexistent job flag: #{job_flag}"
449
+ end
450
+ end
451
+ flag(collection.flag) do
452
+ if collection.override_flags.empty?
453
+ hyphenated_flag = collection.flag.to_s.tr("_", "-")
454
+ flags("--[no-]#{hyphenated_flag}")
455
+ else
456
+ collection.override_flags.each { |override_flag| flags("--[no-]#{override_flag}") }
457
+ end
458
+ desc("Run or omit all \"#{collection.name}\". #{flag_desc_suffix}")
459
+ end
460
+ end
461
+ end
462
+ end
463
+
464
+ static :toys_ci_template, template
465
+
466
+ include ::Toys::CI::Mixin
467
+
468
+ def run
469
+ ::Dir.chdir(context_directory) do
470
+ instance_exec(&toys_ci_template.prerun) if toys_ci_template.prerun
471
+ toys_ci_init(fail_fast: toys_ci_fail_fast_value, limit_by_changes_since: toys_ci_resolve_base_ref)
472
+ toys_ci_resolve_collections
473
+ toys_ci_run_all_jobs
474
+ toys_ci_report_results
475
+ end
476
+ end
477
+
478
+ def toys_ci_resolve_base_ref
479
+ base_ref = toys_ci_template.base_ref_flag? ? self[toys_ci_template.base_ref_flag] : nil
480
+ if toys_ci_template.use_github_base_ref_flag? && self[toys_ci_template.use_github_base_ref_flag]
481
+ base_ref ||= toys_ci_github_event_base_sha
482
+ end
483
+ base_ref
484
+ end
485
+
486
+ def toys_ci_fail_fast_value
487
+ if toys_ci_template.fail_fast_flag?
488
+ self[toys_ci_template.fail_fast_flag]
489
+ else
490
+ toys_ci_template.fail_fast_default
491
+ end
492
+ end
493
+
494
+ def toys_ci_resolve_collections
495
+ toys_ci_template.collections.each do |collection|
496
+ value = self[collection.flag]
497
+ next if value.nil?
498
+ Array(collection.job_flags).each do |job_flag|
499
+ set(job_flag, value) if self[job_flag].nil?
500
+ end
501
+ end
502
+ end
503
+
504
+ def toys_ci_run_all_jobs
505
+ toys_ci_template.jobs.each do |job|
506
+ next unless toys_ci_enabled_value(job.flag)
507
+ case job
508
+ when ToolJob
509
+ toys_ci_tool_job(job.name, job.tool, trigger_paths: job.trigger_paths, env: job.env, chdir: job.chdir)
510
+ when CmdJob
511
+ toys_ci_cmd_job(job.name, job.cmd, trigger_paths: job.trigger_paths, env: job.env, chdir: job.chdir)
512
+ when BlockJob
513
+ toys_ci_job(job.name, trigger_paths: job.trigger_paths, &job.block)
514
+ end
515
+ end
516
+ end
517
+
518
+ def toys_ci_enabled_value(flag)
519
+ flag_value = self[flag] if flag
520
+ return flag_value unless flag_value.nil?
521
+ if toys_ci_template.all_flag?
522
+ self[toys_ci_template.all_flag]
523
+ elsif toys_ci_template.only_flag?
524
+ !self[toys_ci_template.only_flag]
525
+ else
526
+ !toys_ci_template.jobs_disabled_by_default
527
+ end
528
+ end
529
+ end
530
+ end
531
+ end
532
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Toys
4
+ module CI
5
+ ##
6
+ # Current version of the Toys CI system.
7
+ # @return [String]
8
+ #
9
+ VERSION = "0.1.0"
10
+ end
11
+ end
data/lib/toys-ci.rb CHANGED
@@ -1,8 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # Toys is a configurable command line tool. Write commands in config files
5
+ # using a simple DSL, and Toys will provide the command line executable and
6
+ # take care of all the details such as argument parsing, online help, and error
7
+ # reporting. Toys is designed for software developers, IT professionals, and
8
+ # other power users who want to write and organize scripts to automate their
9
+ # workflows. It can also be used as a Rake replacement, providing a more
10
+ # natural command line interface for your project's build tasks.
1
11
  #
2
- # This is a placeholder Ruby file for gem "toys-ci".
3
- # It was generated on 2026-03-11 to reserve the gem name.
4
- # The actual gem is planned for release in the near future.
5
- # If this is a problem, or if the actual gem has not been
6
- # released in a timely manner, you can contact the owner at
7
- # dazuma@gmail.com
8
- #
12
+ module Toys
13
+ ##
14
+ # The Toys CI system is a mixin and template useful for generating CI tools.
15
+ #
16
+ # In this system, a CI tool is a tool that calls some set of other tools or
17
+ # processes, each of which implements an individual job such as doing a build
18
+ # or running tests. The CI tool monitors and summarizes the results of those
19
+ # jobs.
20
+ #
21
+ # See {Toys::CI::Mixin} for a lower-level mixin that provides useful methods
22
+ # for implementing a CI tool, including methods for finding changed files,
23
+ # for running individual jobs, and for reporting results.
24
+ #
25
+ # See {Toys::CI::Template} for a higher-level template that generates a full
26
+ # CI tool, including flags for controlling how CI should behave and which
27
+ # jobs should be run.
28
+ #
29
+ module CI
30
+ end
31
+ end
32
+
33
+ require "toys/ci/mixin"
34
+ require "toys/ci/template"
35
+ require "toys/ci/version"
data/toys/.toys.rb ADDED
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "toys-ci"
metadata CHANGED
@@ -1,26 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: toys-ci
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
- - dazuma@gmail.com
7
+ - Daniel Azuma
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
12
- description: This is a placeholder gem, which was generated on 2026-03-11 to reserve
13
- the gem "toys-ci". The actual gem is planned for release in the near future. If
14
- this is a problem, or if the actual gem has not been released in a timely manner,
15
- you can contact the owner at dazuma@gmail.com.
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: toys-core
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.20'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.20'
26
+ description: Toys-CI is a framework for generating simple CI coordinator tools. It
27
+ provides a declarative interface for configuring the tool and specifying individual
28
+ jobs to run. The generated tool runs jobs specified using command line arguments,
29
+ and produces a final report of the results.
30
+ email:
31
+ - dazuma@gmail.com
16
32
  executables: []
17
33
  extensions: []
18
34
  extra_rdoc_files: []
19
35
  files:
36
+ - ".yardopts"
37
+ - CHANGELOG.md
38
+ - LICENSE.md
20
39
  - README.md
21
40
  - lib/toys-ci.rb
22
- licenses: []
23
- metadata: {}
41
+ - lib/toys/ci/mixin.rb
42
+ - lib/toys/ci/template.rb
43
+ - lib/toys/ci/version.rb
44
+ - toys/.toys.rb
45
+ homepage: https://github.com/dazuma/toys
46
+ licenses:
47
+ - MIT
48
+ metadata:
49
+ changelog_uri: https://dazuma.github.io/toys/gems/toys-ci/v0.1.0/file.CHANGELOG.html
50
+ source_code_uri: https://github.com/dazuma/toys/tree/toys-ci/v0.1.0/toys-ci
51
+ bug_tracker_uri: https://github.com/dazuma/toys/issues
52
+ documentation_uri: https://dazuma.github.io/toys/gems/toys-ci/v0.1.0
24
53
  rdoc_options: []
25
54
  require_paths:
26
55
  - lib
@@ -28,7 +57,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
28
57
  requirements:
29
58
  - - ">="
30
59
  - !ruby/object:Gem::Version
31
- version: '0'
60
+ version: 2.7.0
32
61
  required_rubygems_version: !ruby/object:Gem::Requirement
33
62
  requirements:
34
63
  - - ">="
@@ -37,5 +66,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
37
66
  requirements: []
38
67
  rubygems_version: 4.0.3
39
68
  specification_version: 4
40
- summary: Placeholder gem
69
+ summary: CI system using GitHub Actions and Toys
41
70
  test_files: []