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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +174 -0
- data/config/rubocop.yml +55 -0
- data/exe/ruby-repo-kit +6 -0
- data/lib/ruby_repo_kit/checks/commits.rb +84 -0
- data/lib/ruby_repo_kit/checks.rb +114 -0
- data/lib/ruby_repo_kit/cli.rb +128 -0
- data/lib/ruby_repo_kit/commands.rb +43 -0
- data/lib/ruby_repo_kit/github/client.rb +91 -0
- data/lib/ruby_repo_kit/github/configuration.rb +180 -0
- data/lib/ruby_repo_kit/github/policy.rb +52 -0
- data/lib/ruby_repo_kit/github.rb +14 -0
- data/lib/ruby_repo_kit/package.rb +100 -0
- data/lib/ruby_repo_kit/project.rb +100 -0
- data/lib/ruby_repo_kit/rake_tasks.rb +98 -0
- data/lib/ruby_repo_kit/release/artifact.rb +47 -0
- data/lib/ruby_repo_kit/release/metadata.rb +157 -0
- data/lib/ruby_repo_kit/release/preparation.rb +81 -0
- data/lib/ruby_repo_kit/release/publication.rb +146 -0
- data/lib/ruby_repo_kit/release/publisher.rb +27 -0
- data/lib/ruby_repo_kit/release/workflow.rb +182 -0
- data/lib/ruby_repo_kit/release.rb +20 -0
- data/lib/ruby_repo_kit/scaffold.rb +115 -0
- data/lib/ruby_repo_kit/specification.rb +44 -0
- data/lib/ruby_repo_kit/version.rb +5 -0
- data/lib/ruby_repo_kit.rb +10 -0
- data/templates/cli/.editorconfig.erb +12 -0
- data/templates/cli/.github/ISSUE_TEMPLATE/bug_report.yml.erb +18 -0
- data/templates/cli/.github/ISSUE_TEMPLATE/config.yml.erb +5 -0
- data/templates/cli/.github/ISSUE_TEMPLATE/feature_request.yml.erb +13 -0
- data/templates/cli/.github/PULL_REQUEST_TEMPLATE.md.erb +5 -0
- data/templates/cli/.github/dependabot.yml.erb +27 -0
- data/templates/cli/.github/workflows/ci.yml.erb +106 -0
- data/templates/cli/.github/workflows/release.yml.erb +139 -0
- data/templates/cli/.gitignore.erb +6 -0
- data/templates/cli/.rubocop.yml.erb +5 -0
- data/templates/cli/.ruby-repo.yml.erb +1 -0
- data/templates/cli/.ruby-version.erb +1 -0
- data/templates/cli/ARCHITECTURE.md.erb +25 -0
- data/templates/cli/CHANGELOG.md.erb +14 -0
- data/templates/cli/CODE_OF_CONDUCT.md.erb +14 -0
- data/templates/cli/CONTRIBUTING.md.erb +33 -0
- data/templates/cli/Gemfile.erb +17 -0
- data/templates/cli/LICENSE.txt.erb +21 -0
- data/templates/cli/README.md.erb +53 -0
- data/templates/cli/Rakefile.erb +38 -0
- data/templates/cli/SECURITY.md.erb +11 -0
- data/templates/cli/__name__.gemspec.erb +31 -0
- data/templates/cli/bin/release.erb +7 -0
- data/templates/cli/bin/setup.erb +8 -0
- data/templates/cli/docs/releasing.md.erb +42 -0
- data/templates/cli/exe/__name__.erb +6 -0
- data/templates/cli/lib/__require_path__/cli.rb.erb +50 -0
- data/templates/cli/lib/__require_path__/version.rb.erb +5 -0
- data/templates/cli/lib/__require_path__.rb.erb +4 -0
- data/templates/cli/test/cli_test.rb.erb +62 -0
- data/templates/cli/test/test_helper.rb.erb +15 -0
- 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,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,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,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 @@
|
|
|
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.
|