ruby-repo-kit 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.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +14 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +174 -0
  5. data/config/rubocop.yml +55 -0
  6. data/exe/ruby-repo-kit +6 -0
  7. data/lib/ruby_repo_kit/checks/commits.rb +84 -0
  8. data/lib/ruby_repo_kit/checks.rb +114 -0
  9. data/lib/ruby_repo_kit/cli.rb +128 -0
  10. data/lib/ruby_repo_kit/commands.rb +43 -0
  11. data/lib/ruby_repo_kit/github/client.rb +91 -0
  12. data/lib/ruby_repo_kit/github/configuration.rb +180 -0
  13. data/lib/ruby_repo_kit/github/policy.rb +52 -0
  14. data/lib/ruby_repo_kit/github.rb +14 -0
  15. data/lib/ruby_repo_kit/package.rb +100 -0
  16. data/lib/ruby_repo_kit/project.rb +100 -0
  17. data/lib/ruby_repo_kit/rake_tasks.rb +98 -0
  18. data/lib/ruby_repo_kit/release/artifact.rb +47 -0
  19. data/lib/ruby_repo_kit/release/metadata.rb +157 -0
  20. data/lib/ruby_repo_kit/release/preparation.rb +81 -0
  21. data/lib/ruby_repo_kit/release/publication.rb +146 -0
  22. data/lib/ruby_repo_kit/release/publisher.rb +27 -0
  23. data/lib/ruby_repo_kit/release/workflow.rb +182 -0
  24. data/lib/ruby_repo_kit/release.rb +20 -0
  25. data/lib/ruby_repo_kit/scaffold.rb +115 -0
  26. data/lib/ruby_repo_kit/specification.rb +44 -0
  27. data/lib/ruby_repo_kit/version.rb +5 -0
  28. data/lib/ruby_repo_kit.rb +10 -0
  29. data/templates/cli/.editorconfig.erb +12 -0
  30. data/templates/cli/.github/ISSUE_TEMPLATE/bug_report.yml.erb +18 -0
  31. data/templates/cli/.github/ISSUE_TEMPLATE/config.yml.erb +5 -0
  32. data/templates/cli/.github/ISSUE_TEMPLATE/feature_request.yml.erb +13 -0
  33. data/templates/cli/.github/PULL_REQUEST_TEMPLATE.md.erb +5 -0
  34. data/templates/cli/.github/dependabot.yml.erb +27 -0
  35. data/templates/cli/.github/workflows/ci.yml.erb +106 -0
  36. data/templates/cli/.github/workflows/release.yml.erb +139 -0
  37. data/templates/cli/.gitignore.erb +6 -0
  38. data/templates/cli/.rubocop.yml.erb +5 -0
  39. data/templates/cli/.ruby-repo.yml.erb +1 -0
  40. data/templates/cli/.ruby-version.erb +1 -0
  41. data/templates/cli/ARCHITECTURE.md.erb +25 -0
  42. data/templates/cli/CHANGELOG.md.erb +14 -0
  43. data/templates/cli/CODE_OF_CONDUCT.md.erb +14 -0
  44. data/templates/cli/CONTRIBUTING.md.erb +33 -0
  45. data/templates/cli/Gemfile.erb +17 -0
  46. data/templates/cli/LICENSE.txt.erb +21 -0
  47. data/templates/cli/README.md.erb +53 -0
  48. data/templates/cli/Rakefile.erb +38 -0
  49. data/templates/cli/SECURITY.md.erb +11 -0
  50. data/templates/cli/__name__.gemspec.erb +31 -0
  51. data/templates/cli/bin/release.erb +7 -0
  52. data/templates/cli/bin/setup.erb +8 -0
  53. data/templates/cli/docs/releasing.md.erb +42 -0
  54. data/templates/cli/exe/__name__.erb +6 -0
  55. data/templates/cli/lib/__require_path__/cli.rb.erb +50 -0
  56. data/templates/cli/lib/__require_path__/version.rb.erb +5 -0
  57. data/templates/cli/lib/__require_path__.rb.erb +4 -0
  58. data/templates/cli/test/cli_test.rb.erb +62 -0
  59. data/templates/cli/test/test_helper.rb.erb +15 -0
  60. metadata +252 -0
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+ require "fileutils"
5
+ require "psych"
6
+
7
+ module RubyRepoKit
8
+ # Creates a project once. Generated application files belong to the consumer.
9
+ class Scaffold
10
+ TEMPLATE_ROOT = File.expand_path("../../templates/cli", __dir__)
11
+ NAME_PATTERN = /\A[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*\z/
12
+ REPOSITORY_PATTERN = %r{\A[A-Za-z0-9][A-Za-z0-9-]*/[A-Za-z0-9][A-Za-z0-9_.-]*\z}
13
+ RESERVED_NAMESPACES = %w[
14
+ Array BasicObject Binding Class Comparable Complex Data Dir Enumerable
15
+ Enumerator ENV Encoding Exception FalseClass Fiber File Float GC Hash IO
16
+ Integer Kernel Marshal MatchData Math Method Module NilClass Numeric Object OptionParser
17
+ ObjectSpace Proc Process Ractor Random Range Rational Regexp RubyRepoKit
18
+ RubyVM Signal StandardError String Struct Symbol Thread Time TracePoint
19
+ TrueClass UnboundMethod
20
+ ].freeze
21
+ EXECUTABLE_PREFIXES = %w[exe/ bin/].freeze
22
+
23
+ def initialize(name:, destination:, repository:, author:, email:, toolkit_version: RubyRepoKit::VERSION)
24
+ @name = validate_identifier(name, NAME_PATTERN, "gem name")
25
+ @repository = validate_identifier(repository, REPOSITORY_PATTERN, "repository (owner/name)")
26
+ @author = validate_text(author, "author")
27
+ @email = validate_identifier(email, /\A[^\s@]+@[^\s@]+\.[^\s@]+\z/, "email")
28
+ @toolkit_version = validate_identifier(toolkit_version, /\A\d+\.\d+\.\d+(?:[.-][a-zA-Z0-9]+)*\z/,
29
+ "toolkit version")
30
+ @destination = File.expand_path(validate_text(destination, "destination"))
31
+ @require_path = @name.tr("-", "_")
32
+ @namespace = @name.split(/[-_]/).map(&:capitalize).join
33
+ raise Error, "Reserved Ruby namespace: #{@namespace}" if RESERVED_NAMESPACES.include?(@namespace)
34
+ end
35
+
36
+ # Returns a canonical path, including when the existing parent is a symlink.
37
+ # No Git, dependency installation or network calls are performed.
38
+ def generate
39
+ validate_destination
40
+ files = rendered_files
41
+ raise Error, "CLI templates are missing from the installed toolkit" if files.empty?
42
+
43
+ Dir.mkdir(@destination)
44
+ files.each do |relative, content|
45
+ target = File.join(@destination, relative)
46
+ FileUtils.mkdir_p(File.dirname(target))
47
+ mode = EXECUTABLE_PREFIXES.any? { |prefix| relative.start_with?(prefix) } ? 0o755 : 0o644
48
+ File.open(target, File::WRONLY | File::CREAT | File::EXCL, mode) { |file| file.write(content) }
49
+ end
50
+ @destination
51
+ rescue SystemCallError => e
52
+ raise Error, "Cannot generate project without overwriting files: #{e.message}"
53
+ end
54
+
55
+ private
56
+
57
+ def validate_identifier(value, pattern, description)
58
+ text = validate_text(value, description)
59
+ raise Error, "Invalid #{description}: #{value.inspect}" unless pattern.match?(text)
60
+
61
+ text
62
+ end
63
+
64
+ def validate_text(value, description)
65
+ unless value.is_a?(String) && value.valid_encoding? && !value.strip.empty? && !value.match?(/[[:cntrl:]]/)
66
+ raise Error, "Expected a nonempty #{description} without control characters"
67
+ end
68
+
69
+ value.dup.freeze
70
+ end
71
+
72
+ def validate_destination
73
+ if File.exist?(@destination) || File.symlink?(@destination)
74
+ raise Error, "Destination already exists: #{@destination}"
75
+ end
76
+
77
+ parent = File.dirname(@destination)
78
+ raise Error, "Destination parent must be an existing directory: #{parent}" unless File.directory?(parent)
79
+
80
+ # /tmp and /var are legitimate symlinks on some supported operating systems.
81
+ # Resolve the parent once; the new destination itself is always created
82
+ # exclusively and an existing or dangling destination symlink is rejected.
83
+ @destination = File.join(File.realpath(parent), File.basename(@destination))
84
+ end
85
+
86
+ def rendered_files
87
+ paths = Dir.glob("**/*", File::FNM_DOTMATCH, base: TEMPLATE_ROOT).sort
88
+ paths.filter_map do |relative|
89
+ source = File.join(TEMPLATE_ROOT, relative)
90
+ raise Error, "Templates cannot contain symlinks: #{relative}" if File.symlink?(source)
91
+ next unless File.file?(source)
92
+
93
+ target = relative.delete_suffix(".erb").gsub("__name__", @name).gsub("__require_path__", @require_path)
94
+ [target, ERB.new(File.read(source), trim_mode: "-").result(binding)]
95
+ end
96
+ end
97
+
98
+ def ruby_literal(value) = value.dump
99
+
100
+ def project_configuration
101
+ Psych.dump(
102
+ "schema" => 1,
103
+ "name" => @name,
104
+ "repository" => @repository,
105
+ "version_file" => "lib/#{@require_path}/version.rb",
106
+ "gemspec" => "#{@name}.gemspec",
107
+ "changelog" => "CHANGELOG.md",
108
+ "generated_paths" => [],
109
+ "generate_command" => %w[bundle exec rake generate],
110
+ "check_command" => %w[bundle exec rake check],
111
+ "required_checks" => Project::DEFAULT_CHECKS
112
+ )
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "open3"
5
+ require_relative "../ruby_repo_kit"
6
+
7
+ module RubyRepoKit
8
+ # Evaluate trusted gemspec code in a fresh process. A version bump must not reuse
9
+ # constants/require caches from an earlier inspection or another consumer.
10
+ module Specification
11
+ READER = <<~'RUBY'
12
+ require "json"
13
+ spec = Gem::Specification.load(ARGV.fetch(0))
14
+ abort "Cannot load gemspec" unless spec
15
+ puts JSON.generate(
16
+ name: spec.name, version: spec.version.to_s, files: spec.files,
17
+ executables: spec.executables, bindir: spec.bindir,
18
+ require_paths: spec.require_paths, required_ruby_version: spec.required_ruby_version.to_s,
19
+ runtime_dependencies: spec.runtime_dependencies.map do |dependency|
20
+ [dependency.name, dependency.requirement.requirements.map { |operator, version| "#{operator} #{version}" }]
21
+ end
22
+ )
23
+ RUBY
24
+
25
+ def self.load(project:)
26
+ environment = ENV.keys.grep(/\ABUNDL/).to_h { |key| [key, nil] }
27
+ environment["RUBYOPT"] = nil
28
+ environment["RUBYLIB"] = nil
29
+ out, err, status = Open3.capture3(environment, RbConfig.ruby, "-rrubygems", "-e", READER,
30
+ project.path(project.gemspec), chdir: project.root)
31
+ raise Error, "Cannot load #{project.gemspec}: #{err.strip}" unless status.success?
32
+
33
+ data = JSON.parse(out)
34
+ Gem::Specification.new do |spec|
35
+ %w[name version files executables bindir require_paths required_ruby_version].each do |field|
36
+ spec.public_send("#{field}=", data.fetch(field))
37
+ end
38
+ data.fetch("runtime_dependencies").each { |name, requirements| spec.add_dependency(name, *requirements) }
39
+ end
40
+ rescue JSON::ParserError, KeyError => e
41
+ raise Error, "Unreadable gemspec metadata: #{e.message}"
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyRepoKit
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ruby_repo_kit/version"
4
+
5
+ module RubyRepoKit
6
+ class Error < StandardError; end
7
+ end
8
+
9
+ require_relative "ruby_repo_kit/project"
10
+ require_relative "ruby_repo_kit/commands"
@@ -0,0 +1,12 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ end_of_line = lf
6
+ insert_final_newline = true
7
+ indent_style = space
8
+ indent_size = 2
9
+ trim_trailing_whitespace = true
10
+
11
+ [*.md]
12
+ trim_trailing_whitespace = false
@@ -0,0 +1,18 @@
1
+ name: Bug report
2
+ description: Report reproducible unexpected behavior
3
+ labels: [bug]
4
+ body:
5
+ - type: textarea
6
+ id: reproduction
7
+ attributes:
8
+ label: Steps to reproduce
9
+ description: Include the command, expected result, and actual result. Remove credentials and private data.
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: environment
14
+ attributes:
15
+ label: Environment
16
+ description: Include the CLI version, Ruby version, operating system, and installation method.
17
+ validations:
18
+ required: true
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: true
2
+ contact_links:
3
+ - name: Security policy
4
+ url: https://github.com/<%= @repository %>/security/policy
5
+ about: Please report vulnerabilities privately.
@@ -0,0 +1,13 @@
1
+ name: Feature request
2
+ description: Explain a problem the CLI could solve
3
+ body:
4
+ - type: textarea
5
+ id: problem
6
+ attributes:
7
+ label: Problem and current workaround
8
+ validations:
9
+ required: true
10
+ - type: textarea
11
+ id: proposal
12
+ attributes:
13
+ label: Proposed behavior and alternatives
@@ -0,0 +1,5 @@
1
+ Explain the problem, the resulting behavior, and how you verified it.
2
+
3
+ - [ ] Tests cover the changed behavior.
4
+ - [ ] Local checks pass.
5
+ - [ ] User-visible changes are described under Unreleased in CHANGELOG.md.
@@ -0,0 +1,27 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ open-pull-requests-limit: 5
8
+ allow:
9
+ - dependency-type: all
10
+ cooldown:
11
+ default-days: 7
12
+ groups:
13
+ gems:
14
+ patterns: ['*']
15
+ update-types: [minor, patch]
16
+ labels: [skip-changelog]
17
+ commit-message:
18
+ prefix: chore(deps)
19
+ - package-ecosystem: github-actions
20
+ directory: /
21
+ schedule:
22
+ interval: monthly
23
+ cooldown:
24
+ default-days: 7
25
+ labels: [skip-changelog]
26
+ commit-message:
27
+ prefix: ci
@@ -0,0 +1,106 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, 'hotfix/*']
6
+ pull_request:
7
+ types: [opened, edited, synchronize, reopened, labeled, unlabeled]
8
+ schedule:
9
+ - cron: '0 7 * * 1'
10
+ workflow_call:
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ concurrency:
16
+ group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}
17
+ cancel-in-progress: true
18
+
19
+ jobs:
20
+ quality:
21
+ if: github.event_name != 'schedule'
22
+ runs-on: ubuntu-latest
23
+ timeout-minutes: 20
24
+ steps:
25
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
26
+ with:
27
+ persist-credentials: false
28
+ - uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
29
+ with:
30
+ bundler-cache: true
31
+ - run: bundle exec rake check
32
+
33
+ test:
34
+ name: test (${{ matrix.os }}, ${{ matrix.ruby }})
35
+ if: github.event_name != 'schedule'
36
+ runs-on: ${{ matrix.os }}
37
+ timeout-minutes: 20
38
+ strategy:
39
+ fail-fast: false
40
+ matrix:
41
+ os: [ubuntu-latest]
42
+ ruby: ['3.4', '4.0']
43
+ include:
44
+ - os: macos-latest
45
+ ruby: '4.0'
46
+ steps:
47
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
48
+ with:
49
+ persist-credentials: false
50
+ - uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
51
+ with:
52
+ ruby-version: ${{ matrix.ruby }}
53
+ bundler-cache: true
54
+ - run: bundle exec rake test package:check
55
+
56
+ audit:
57
+ runs-on: ubuntu-latest
58
+ timeout-minutes: 10
59
+ steps:
60
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
61
+ with:
62
+ persist-credentials: false
63
+ - uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
64
+ with:
65
+ bundler-cache: true
66
+ - run: bundle exec rake audit
67
+
68
+ fresh-dependencies:
69
+ if: github.event_name != 'schedule'
70
+ runs-on: ubuntu-latest
71
+ timeout-minutes: 20
72
+ steps:
73
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
74
+ with:
75
+ persist-credentials: false
76
+ - uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
77
+ - run: bundle update
78
+ - run: bundle exec rake test package:check
79
+
80
+ commits:
81
+ if: github.event_name == 'pull_request'
82
+ runs-on: ubuntu-latest
83
+ timeout-minutes: 10
84
+ steps:
85
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
86
+ with:
87
+ ref: ${{ github.event.pull_request.head.sha }}
88
+ fetch-depth: 0
89
+ persist-credentials: false
90
+ - uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
91
+ with:
92
+ bundler-cache: true
93
+ - name: Validate commits and the changelog
94
+ env:
95
+ BASE_REF: ${{ github.event.pull_request.base.ref }}
96
+ PR_TITLE: ${{ github.event.pull_request.title }}
97
+ PR_BODY: ${{ github.event.pull_request.body }}
98
+ SKIP_CHANGELOG: ${{ contains(github.event.pull_request.labels.*.name, 'skip-changelog') }}
99
+ run: |
100
+ COMMIT_RANGE="origin/$BASE_REF..HEAD" bundle exec rake lint:commits
101
+ changed=$(git diff --name-only "origin/$BASE_REF...HEAD")
102
+ if grep -qE '^(lib|exe)/' <<< "$changed" &&
103
+ ! grep -qx 'CHANGELOG.md' <<< "$changed" && [ "$SKIP_CHANGELOG" != true ]; then
104
+ echo '::error::Add a user-visible change under Unreleased in CHANGELOG.md.'
105
+ exit 1
106
+ fi
@@ -0,0 +1,139 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ['v*']
6
+ workflow_dispatch:
7
+ inputs:
8
+ dry_run:
9
+ description: Verify and build without publishing
10
+ type: boolean
11
+ default: true
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ concurrency:
17
+ group: release-${{ github.ref }}
18
+ cancel-in-progress: false
19
+
20
+ jobs:
21
+ ci:
22
+ uses: ./.github/workflows/ci.yml # zizmor: ignore[self-repository]
23
+ permissions:
24
+ contents: read
25
+
26
+ verify:
27
+ needs: ci
28
+ runs-on: ubuntu-latest
29
+ timeout-minutes: 15
30
+ outputs:
31
+ publish: ${{ steps.decision.outputs.publish }}
32
+ sha256: ${{ steps.artifact.outputs.sha256 }}
33
+ steps:
34
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
35
+ with:
36
+ fetch-depth: 0
37
+ persist-credentials: false
38
+ - uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
39
+ - run: bundle install
40
+ env:
41
+ BUNDLE_FROZEN: 'true'
42
+ - name: Verify release metadata
43
+ run: bundle exec rake release:verify
44
+ - name: Verify the tag ancestry
45
+ if: github.ref_type == 'tag'
46
+ run: bundle exec rake release:verify_ref
47
+ - id: artifact
48
+ run: |
49
+ bundle exec rake release:artifact
50
+ bundle exec rake "package:check[pkg/<%= @name %>-$(ruby -Ilib -r<%= @require_path %>/version -e 'print <%= @namespace %>::VERSION').gem]"
51
+ echo "sha256=$(cut -d ' ' -f 1 pkg/SHA256SUMS)" >> "$GITHUB_OUTPUT"
52
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
53
+ with:
54
+ name: release-gem
55
+ path: pkg/
56
+ if-no-files-found: error
57
+ - id: decision
58
+ env:
59
+ PUBLISH: ${{ github.ref_type == 'tag' && (github.event_name != 'workflow_dispatch' || !inputs.dry_run) }}
60
+ run: echo "publish=$PUBLISH" >> "$GITHUB_OUTPUT"
61
+
62
+ attest:
63
+ needs: verify
64
+ runs-on: ubuntu-latest
65
+ timeout-minutes: 10
66
+ permissions:
67
+ contents: read
68
+ id-token: write
69
+ attestations: write
70
+ steps:
71
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
72
+ with:
73
+ name: release-gem
74
+ path: pkg
75
+ - name: Verify the artifact before attesting it
76
+ env:
77
+ RELEASE_SHA256: ${{ needs.verify.outputs.sha256 }}
78
+ run: |
79
+ cd pkg
80
+ sha256sum --check SHA256SUMS
81
+ test "$(cut -d ' ' -f 1 SHA256SUMS)" = "$RELEASE_SHA256"
82
+ - uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
83
+ with:
84
+ subject-path: pkg/*.gem
85
+ create-storage-record: false
86
+
87
+ publish:
88
+ needs: [verify, attest]
89
+ if: needs.verify.outputs.publish == 'true'
90
+ runs-on: ubuntu-latest
91
+ timeout-minutes: 15
92
+ environment: release
93
+ permissions:
94
+ contents: read
95
+ id-token: write
96
+ steps:
97
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
98
+ with:
99
+ persist-credentials: false
100
+ - uses: ruby/setup-ruby@14594264cd68ce8a2345dd349bc3d138a4ef85c8 # v1.327.0
101
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
102
+ with:
103
+ name: release-gem
104
+ path: pkg
105
+ - run: bundle install
106
+ env:
107
+ BUNDLE_FROZEN: 'true'
108
+ - run: bundle exec rake release:verify_artifact
109
+ env:
110
+ RELEASE_SHA256: ${{ needs.verify.outputs.sha256 }}
111
+ - uses: rubygems/configure-rubygems-credentials@dc5a8d8553e6ee01fc26761a49e99e733d17954a # v2.1.0
112
+ - run: bundle exec rake release
113
+ env:
114
+ RELEASE_SHA256: ${{ needs.verify.outputs.sha256 }}
115
+
116
+ github-release:
117
+ needs: [verify, publish]
118
+ runs-on: ubuntu-latest
119
+ timeout-minutes: 10
120
+ permissions:
121
+ contents: write
122
+ steps:
123
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
124
+ with:
125
+ persist-credentials: false
126
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
127
+ with:
128
+ name: release-gem
129
+ path: pkg
130
+ - name: Verify the artifact and publish release notes
131
+ env:
132
+ GH_TOKEN: ${{ github.token }}
133
+ TAG: ${{ github.ref_name }}
134
+ RELEASE_SHA256: ${{ needs.verify.outputs.sha256 }}
135
+ run: |
136
+ cd pkg
137
+ sha256sum --check SHA256SUMS
138
+ test "$(cut -d ' ' -f 1 SHA256SUMS)" = "$RELEASE_SHA256"
139
+ gh release create "$TAG" --verify-tag --title "$TAG" --notes-file release-notes.md ./*.gem SHA256SUMS
@@ -0,0 +1,6 @@
1
+ /.bundle/
2
+ /coverage/
3
+ /pkg/
4
+ /tmp/
5
+ /vendor/
6
+ /*.gem
@@ -0,0 +1,5 @@
1
+ inherit_gem:
2
+ ruby-repo-kit: config/rubocop.yml
3
+
4
+ AllCops:
5
+ TargetRubyVersion: 3.4
@@ -0,0 +1 @@
1
+ <%= project_configuration -%>
@@ -0,0 +1 @@
1
+ 4.0
@@ -0,0 +1,25 @@
1
+ # Architecture
2
+
3
+ The executable loads the library and returns the CLI's exit status. The CLI uses
4
+ OptionParser and accepts injected output streams so behavior can be tested
5
+ without a real terminal. Version data has one source in
6
+ lib/<%= @require_path %>/version.rb.
7
+
8
+ The gemspec allows only runtime Ruby files, the executable and selected public
9
+ documents into the package. It declares OptionParser as a runtime dependency.
10
+ The maintenance toolkit, tests, linters and audit tool are development dependencies
11
+ and are not required to run an installed copy of the CLI.
12
+
13
+ The Rakefile delegates repository, package and release operations to ruby-repo-kit.
14
+ The local .ruby-repo.yml declares project identity and the CI check names. CI
15
+ jobs remain in this repository. Ruby lint rules inherit the versioned shared
16
+ preset, with project-specific overrides in .rubocop.yml.
17
+
18
+ There are no generated runtime artifacts initially. The generate and
19
+ generate:check tasks deliberately have no work. When introducing a generated
20
+ manual or similar artifact, implement these tasks and declare its paths in
21
+ .ruby-repo.yml.
22
+
23
+ The scaffold is a starting point. No process reapplies it over application code,
24
+ tests or documentation. Dependency upgrades change shared maintenance behavior;
25
+ changes to local integration files require review.
@@ -0,0 +1,14 @@
1
+ # Changelog
2
+
3
+ All notable user-visible changes are documented here.
4
+
5
+ ## [Unreleased]
6
+
7
+ ## [0.1.0] - <%= Time.now.strftime("%Y-%m-%d") %>
8
+
9
+ ### Added
10
+
11
+ - Initial text-printing CLI with help, version and predictable exit statuses.
12
+
13
+ [Unreleased]: https://github.com/<%= @repository %>/compare/v0.1.0...HEAD
14
+ [0.1.0]: https://github.com/<%= @repository %>/releases/tag/v0.1.0
@@ -0,0 +1,14 @@
1
+ # Code of conduct
2
+
3
+ Treat contributors and users respectfully. Discuss ideas and behavior without
4
+ personal attacks. Harassment, discrimination, threats, sexual harassment, and
5
+ publishing another person's private information are unacceptable.
6
+
7
+ These expectations apply in project issues, pull requests and other project
8
+ spaces. Maintainers may remove inappropriate content or restrict participation,
9
+ considering the context and severity of the behavior.
10
+
11
+ Report concerns privately to <%= @email %>. Include relevant context and links.
12
+ Maintainers will handle personal information discreetly and consider reports
13
+ without retaliation. This is a project-specific policy, not a claim of formal
14
+ certification or a staffed response service.
@@ -0,0 +1,33 @@
1
+ # Contributing
2
+
3
+ Report reproducible problems with the CLI version, Ruby version, operating system
4
+ and a minimal example. Remove credentials and private data before sharing output.
5
+ Discuss substantial interface changes in an issue before implementing them.
6
+
7
+ Fork the repository, create a branch, and install dependencies with:
8
+
9
+ bin/setup
10
+
11
+ Run the local checks and the network-dependent advisory audit:
12
+
13
+ bundle exec rake check
14
+ bundle exec rake audit
15
+
16
+ The initial tests cover help, version, text output, invalid options, missing
17
+ arguments and broken pipes. Add behavioral tests for new features.
18
+
19
+ Use Conventional Commit titles, for example:
20
+
21
+ fix: handle empty input consistently
22
+ docs: clarify installation
23
+
24
+ Add user-visible changes under Unreleased in CHANGELOG.md. Maintainers may use
25
+ the skip-changelog label for dependency updates or changes without user-visible
26
+ effects. Keep the pull request focused and describe the checks you ran.
27
+
28
+ Generated application code, tests and documentation belong to this repository.
29
+ Update them directly. Shared maintenance behavior is upgraded through the
30
+ ruby-repo-kit dependency, with CI and review for each update.
31
+
32
+ Participation follows [our code of conduct](CODE_OF_CONDUCT.md). Contributions
33
+ are licensed under the [MIT license](LICENSE.txt).
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gemspec
6
+
7
+ group :development, :test do
8
+ gem "bundler-audit", "~> 0.9", require: false
9
+ gem "minitest", "~> 6.0"
10
+ gem "rake", "~> 13.4"
11
+ gem "rubocop", "~> 1.91", require: false
12
+ gem "rubocop-minitest", "~> 0.40", require: false
13
+ gem "rubocop-performance", "~> 1.27", require: false
14
+ gem "rubocop-rake", "~> 0.7", require: false
15
+ gem "ruby-repo-kit", <%= ruby_literal("~> #{@toolkit_version}") %>, require: false
16
+ gem "simplecov", "~> 1.3", require: false
17
+ end
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) <%= Time.now.year %> <%= @author %>
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 copies
9
+ of the Software, and to permit persons to whom the Software is furnished to do
10
+ so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ 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 FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.