ethos-gemkit 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 +124 -0
- data/LICENSE.txt +21 -0
- data/README.md +168 -0
- data/exe/ethos-gemkit +30 -0
- data/lib/ethos/gemkit/branch.rb +20 -0
- data/lib/ethos/gemkit/commit_message.rb +26 -0
- data/lib/ethos/gemkit/configuration.rb +86 -0
- data/lib/ethos/gemkit/package.rb +53 -0
- data/lib/ethos/gemkit/plugin.rb +40 -0
- data/lib/ethos/gemkit/rake.rb +13 -0
- data/lib/ethos/gemkit/release.rb +134 -0
- data/lib/ethos/gemkit/release_tasks.rb +31 -0
- data/lib/ethos/gemkit/tasks.rb +22 -0
- data/lib/ethos/gemkit/version.rb +7 -0
- data/lib/ethos/gemkit.rb +9 -0
- data/lib/ethos-gemkit.rb +4 -0
- metadata +133 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 5445351e6ccbd6c2155a603d7c7fcf5d57e3e6a2e275cafa307431c4f4ae63fb
|
|
4
|
+
data.tar.gz: 8b2a74b858376f970c096b2599e5607eb88ca8b78e2ba53f11485d4a2b4d8059
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 9cdf0c22ba2d534f270e02904ddc018ebad5448b68bf656ecdd3fceea8f8408b241f108da2f6024a253c67d291d63dbae7e13e3a3fd14c628d8bdcaeab2a5138
|
|
7
|
+
data.tar.gz: d9464b72094dafe244db34ac048e8dda59154784df181db4005c674b217fe946f8d873ecb323617531543b5faa13ffde4f22af1d44b352f40f5576209ed41033
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-11-04
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- bootstrap shared gem development contracts
|
|
14
|
+
|
|
15
|
+
- enforce the shared commit message policy
|
|
16
|
+
|
|
17
|
+
- centralize verified gem release orchestration
|
|
18
|
+
|
|
19
|
+
- prepare release PRs and publish after merging
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- exercise standalone core operations
|
|
27
|
+
|
|
28
|
+
- expose shared commit message validation
|
|
29
|
+
|
|
30
|
+
- replace release orchestration with native workflows
|
|
31
|
+
|
|
32
|
+
- remove the redundant bootstrap fixture
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
### Fixed
|
|
38
|
+
|
|
39
|
+
- lock toolkit development to patched Active Support
|
|
40
|
+
|
|
41
|
+
- disable inherited publishing tasks
|
|
42
|
+
|
|
43
|
+
- complete checksums for frozen hosted installs
|
|
44
|
+
|
|
45
|
+
- declare and isolate framework fixture dependencies
|
|
46
|
+
|
|
47
|
+
- bind package files to the verified tag tree
|
|
48
|
+
|
|
49
|
+
- expose scoped bundle executables to subprocesses
|
|
50
|
+
|
|
51
|
+
- retain confirmed publication evidence for recovery
|
|
52
|
+
|
|
53
|
+
- scope source fetch authentication to GitHub
|
|
54
|
+
|
|
55
|
+
- select the current artifact run explicitly
|
|
56
|
+
|
|
57
|
+
- prevent implicit pushes of unrelated tags
|
|
58
|
+
|
|
59
|
+
- bootstrap Bundler for plain rake commands
|
|
60
|
+
|
|
61
|
+
- keep Bundler's CI cache out of release source
|
|
62
|
+
|
|
63
|
+
- recycle fully merged local release branches
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
## [0.1.0] - 2026-11-04
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
### Added
|
|
71
|
+
|
|
72
|
+
- bootstrap shared gem development contracts
|
|
73
|
+
|
|
74
|
+
- enforce the shared commit message policy
|
|
75
|
+
|
|
76
|
+
- centralize verified gem release orchestration
|
|
77
|
+
|
|
78
|
+
- prepare release PRs and publish after merging
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
### Changed
|
|
84
|
+
|
|
85
|
+
- exercise standalone core operations
|
|
86
|
+
|
|
87
|
+
- expose shared commit message validation
|
|
88
|
+
|
|
89
|
+
- replace release orchestration with native workflows
|
|
90
|
+
|
|
91
|
+
- remove the redundant bootstrap fixture
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
### Fixed
|
|
97
|
+
|
|
98
|
+
- lock toolkit development to patched Active Support
|
|
99
|
+
|
|
100
|
+
- disable inherited publishing tasks
|
|
101
|
+
|
|
102
|
+
- complete checksums for frozen hosted installs
|
|
103
|
+
|
|
104
|
+
- declare and isolate framework fixture dependencies
|
|
105
|
+
|
|
106
|
+
- bind package files to the verified tag tree
|
|
107
|
+
|
|
108
|
+
- expose scoped bundle executables to subprocesses
|
|
109
|
+
|
|
110
|
+
- retain confirmed publication evidence for recovery
|
|
111
|
+
|
|
112
|
+
- scope source fetch authentication to GitHub
|
|
113
|
+
|
|
114
|
+
- select the current artifact run explicitly
|
|
115
|
+
|
|
116
|
+
- prevent implicit pushes of unrelated tags
|
|
117
|
+
|
|
118
|
+
- bootstrap Bundler for plain rake commands
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# Changelog
|
|
123
|
+
|
|
124
|
+
Changes are generated from Conventional Commits when preparing a release.
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ethos Link
|
|
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.
|
data/README.md
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# Ethos Gemkit
|
|
2
|
+
|
|
3
|
+
Shared development defaults for Ethos Link gems: Ruby 4, Standard, optional Rails
|
|
4
|
+
8 linting, hooks, package checks and one shared release workflow. The toolkit
|
|
5
|
+
belongs in the development/test Gemfile, never a consumer's runtime gemspec.
|
|
6
|
+
|
|
7
|
+
During bootstrap, pin the Git dependency and workflow callers to the same reviewed
|
|
8
|
+
commit; the toolkit has not been published. See the working
|
|
9
|
+
[Cloudflare Tunnel Gemfile](https://github.com/ethos-link/cloudflare-tunnel/blob/feat/ethos-gemkit/Gemfile).
|
|
10
|
+
|
|
11
|
+
A standard gem needs this Rakefile:
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
require "bundler/setup"
|
|
15
|
+
require "ethos/gemkit/rake"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Use the native configuration files rather than a setup generator:
|
|
19
|
+
|
|
20
|
+
```yaml
|
|
21
|
+
# .standard.yml
|
|
22
|
+
ruby_version: "4.0"
|
|
23
|
+
plugins:
|
|
24
|
+
- ethos-gemkit
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Keep Standard exclusions locally. The plugin detects Rails 8 components from the
|
|
28
|
+
bundle without booting an application; plain Ruby and Active Support-only bundles
|
|
29
|
+
use Ruby rules. SDK Rake tasks reject configurations that omit the shared plugin.
|
|
30
|
+
Existing hooks call `bundle exec ethos-gemkit guard` before commits and
|
|
31
|
+
`bundle exec ethos-gemkit commitlint --message {1}` for commit messages. Install
|
|
32
|
+
hooks with `bundle exec lefthook install`. Direct main/master commits are blocked.
|
|
33
|
+
|
|
34
|
+
No `.ethos-gemkit.yml` is needed for standard public gems. Private gems declare
|
|
35
|
+
`registry: github_packages`; custom checks use `checks: [ci:local]`. Optional
|
|
36
|
+
`gem_name`, `repository` and `version_file` override the one-gemspec, Ethos Link
|
|
37
|
+
repository and one-version-file conventions. Unknown keys and unsafe paths fail.
|
|
38
|
+
The lower-level `ethos/gemkit/tasks` entrypoint retains existing custom test and
|
|
39
|
+
release tasks while a repository is adopting the toolkit.
|
|
40
|
+
|
|
41
|
+
`bundle exec rake verify` runs lint, registered checks, a disposable native gem
|
|
42
|
+
build, and git-cliff validation.
|
|
43
|
+
|
|
44
|
+
## Releasing a gem
|
|
45
|
+
|
|
46
|
+
**Run `release:prepare` from clean, up-to-date `main`.** It creates the conventional
|
|
47
|
+
release branch and opens a PR. **Merging that release PR starts publication.**
|
|
48
|
+
This is the same process for Gemkit itself and its adopted consumer gems.
|
|
49
|
+
Ordinary feature/fix PRs run verification; only a release PR starts publication.
|
|
50
|
+
|
|
51
|
+
### One-time prerequisites
|
|
52
|
+
|
|
53
|
+
- Ignore `/.bundle/`, `/vendor/bundle/` and `/pkg/` in `.gitignore`. The shared
|
|
54
|
+
workflows use Ruby setup's native `vendor/bundle` cache; generated files must
|
|
55
|
+
leave the release checkout clean.
|
|
56
|
+
- Install the bundle, Git, git-cliff and GitHub CLI. Configure your Git identity,
|
|
57
|
+
authenticate with `gh auth login`, and install hooks with
|
|
58
|
+
`bundle exec lefthook install`. You need permission to push branches and open PRs.
|
|
59
|
+
- Public gems: configure RubyGems trusted publishing for the gem. Use environment
|
|
60
|
+
`rubygems`, workflow filename `release.yml`, and the consumer owner/repository.
|
|
61
|
+
For a consumer such as Tunnel, set workflow repository owner `ethos-link` and
|
|
62
|
+
workflow repository `ethos-gemkit`. For Gemkit's own publisher, leave those
|
|
63
|
+
optional workflow-repository fields blank because it is the same repository.
|
|
64
|
+
- A new gem also needs a pending trusted publisher. Supply its numeric RubyGems
|
|
65
|
+
owner through the caller's `rubygems-owner` input or the repository/organization
|
|
66
|
+
variable `RUBYGEMS_OWNER` (the Ethos account used by Tunnel is `27209`).
|
|
67
|
+
- Private gems: set the GitHub Packages registry profile and pass a scoped
|
|
68
|
+
`PACKAGES_TOKEN` secret to the release workflow. The two profiles use the same
|
|
69
|
+
native `gem push` with their respective host and credentials.
|
|
70
|
+
- Configure the `rubygems` GitHub environment to allow releases from `main` and
|
|
71
|
+
`v*` tags. Grant the release workflow contents-write and id-token-write
|
|
72
|
+
permissions; repository rules must permit its release tags.
|
|
73
|
+
|
|
74
|
+
The SDK release workflow listens directly for merged release PRs and version-tag
|
|
75
|
+
pushes. Consumer callers must listen for both `pull_request: types: [closed]`
|
|
76
|
+
against `main` and `push: tags: ["v*"]`, and pin the same SDK commit as the Gemfile.
|
|
77
|
+
See the [Tunnel caller](https://github.com/ethos-link/cloudflare-tunnel/blob/feat/ethos-gemkit/.github/workflows/release.yml).
|
|
78
|
+
|
|
79
|
+
### Prepare and review
|
|
80
|
+
|
|
81
|
+
For a normal patch release:
|
|
82
|
+
|
|
83
|
+
```sh
|
|
84
|
+
git switch main
|
|
85
|
+
git pull --ff-only
|
|
86
|
+
bundle install
|
|
87
|
+
bundle exec rake release:prepare
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The default is `patch`. Select another target explicitly:
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
bundle exec rake 'release:prepare[minor]'
|
|
94
|
+
bundle exec rake 'release:prepare[major]'
|
|
95
|
+
bundle exec rake 'release:prepare[1.2.0]'
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
For an initial, unpublished version that is already in the gemspec and has no
|
|
99
|
+
tag, an explicit current version is supported. Gemkit's initial release can use
|
|
100
|
+
`bundle exec rake 'release:prepare[0.1.0]'` after pending-publisher setup.
|
|
101
|
+
|
|
102
|
+
The task checks the working tree, canonical origin, current main, installed hooks
|
|
103
|
+
and target tag. It creates `chore/release-vX.Y.Z`, updates the version and
|
|
104
|
+
changelog, runs `bundle lock --local` and the full `rake verify`, then commits the
|
|
105
|
+
three metadata files with hooks enabled, pushes the branch, and opens a
|
|
106
|
+
`chore(release): prepare vX.Y.Z` PR. Main stays unchanged. You can also start on
|
|
107
|
+
that exact pre-created release branch if its HEAD still equals `origin/main`.
|
|
108
|
+
When starting from main, a leftover local release branch is recreated only if
|
|
109
|
+
all its commits are already in main; unmerged work is preserved and stops the task.
|
|
110
|
+
|
|
111
|
+
Review the generated notes, lockfile and version in the PR. Add or adjust upgrade
|
|
112
|
+
notes in a follow-up commit when needed, and wait for CI before merging. The task
|
|
113
|
+
prints the PR URL and makes clear that merging it publishes the release.
|
|
114
|
+
|
|
115
|
+
### Merge and publish
|
|
116
|
+
|
|
117
|
+
Merging a same-repository `chore/release-vX.Y.Z` PR into main runs the shared
|
|
118
|
+
workflow against its exact merge commit, including squash and rebase merges:
|
|
119
|
+
|
|
120
|
+
1. Verify the expected version, main ancestry, notes, committed package bytes and
|
|
121
|
+
all registered checks; build one release artifact through Bundler.
|
|
122
|
+
2. Create and push the annotated `vX.Y.Z` tag without overwriting existing tags
|
|
123
|
+
or following unrelated local tags.
|
|
124
|
+
3. In an isolated job, verify the artifact's SHA-256 checksums, obtain the proper
|
|
125
|
+
registry credentials, and publish that exact gem.
|
|
126
|
+
4. In a separate job, create the GitHub release with the same gem and notes.
|
|
127
|
+
|
|
128
|
+
There is no manual tagging step in the normal flow. Build, tag and publication
|
|
129
|
+
continue in the same run because a tag pushed by GitHub's `GITHUB_TOKEN` does not
|
|
130
|
+
start another workflow. Existing manually pushed annotated version tags remain a
|
|
131
|
+
supported trigger and receive full canonical-tag verification.
|
|
132
|
+
|
|
133
|
+
Publish and notes jobs never check out or evaluate consumer code. Public gems
|
|
134
|
+
use short-lived OIDC credentials; private gems use the supplied scoped token.
|
|
135
|
+
No registry fallback exists. Live private publication still needs a private pilot.
|
|
136
|
+
|
|
137
|
+
### Failures and retries
|
|
138
|
+
|
|
139
|
+
If preparation fails, inspect `git status` and `git diff`. A failed verification
|
|
140
|
+
leaves changes on the local release branch without a release commit or push.
|
|
141
|
+
Fix and finish that ordinary branch, or restore the changes before starting over.
|
|
142
|
+
If the commit/push succeeded but opening the PR failed, use
|
|
143
|
+
`gh pr create --base main --head chore/release-vX.Y.Z --fill` after fixing access.
|
|
144
|
+
There is no journal or automatic reset of your working tree.
|
|
145
|
+
|
|
146
|
+
For CI failures, retry **failed jobs in the original run** within GitHub's 30-day
|
|
147
|
+
retry window. Artifacts are retained for 90 days. A failed tag step can reuse an
|
|
148
|
+
existing annotated tag only when it points to the same verified commit. A failed
|
|
149
|
+
notes job reuses the original artifact without uploading again. Ambiguous uploads,
|
|
150
|
+
already published versions, existing GitHub releases or expired runs/artifacts
|
|
151
|
+
require registry/release reconciliation. Never replace a tag or rebuild a
|
|
152
|
+
published version to repair release notes.
|
|
153
|
+
|
|
154
|
+
Provider references: [reusable RubyGems trusted publishers](https://guides.rubygems.org/trusted-publishing/#using-reusable-workflows),
|
|
155
|
+
[GitHub token-trigger behavior](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow),
|
|
156
|
+
and [failed-job retries](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/re-run-workflows-and-jobs).
|
|
157
|
+
|
|
158
|
+
GitLab is a separate optional [workflow](.github/workflows/sync-gitlab.yml).
|
|
159
|
+
Only add its caller when a target exists, passing an SSH `url`, a project write
|
|
160
|
+
deploy key and independently verified known hosts. Without a URL the job skips;
|
|
161
|
+
Cloudflare Tunnel has no caller. It copies current main using a fast-forward
|
|
162
|
+
push, without tags, force or deletions. Provision target visibility no broader
|
|
163
|
+
than the source. Local bare-repository fixtures cover repeat/stale events and
|
|
164
|
+
divergence; live SSH credentials and host keys still require target validation.
|
|
165
|
+
|
|
166
|
+
Develop with `bundle install` and `bundle exec rake verify`. Release and registry
|
|
167
|
+
fixtures live in tests, using real Git and native gem builds with fake network
|
|
168
|
+
publishers. No production rehearsal command or registry client is required.
|
data/exe/ethos-gemkit
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "ethos/gemkit/branch"
|
|
5
|
+
require "ethos/gemkit/commit_message"
|
|
6
|
+
require "optparse"
|
|
7
|
+
|
|
8
|
+
begin
|
|
9
|
+
command = ARGV.shift
|
|
10
|
+
case command
|
|
11
|
+
when "commitlint"
|
|
12
|
+
input = nil
|
|
13
|
+
OptionParser.new do |opts|
|
|
14
|
+
opts.on("--message MESSAGE_OR_FILE") { |value| input = value }
|
|
15
|
+
end.parse!(ARGV)
|
|
16
|
+
abort "Usage: ethos-gemkit commitlint --message MESSAGE_OR_FILE" if input.nil? || !ARGV.empty?
|
|
17
|
+
message = File.file?(input) ? File.read(input) : input
|
|
18
|
+
Ethos::Gemkit::CommitMessage.validate(message)
|
|
19
|
+
puts "Commit message valid"
|
|
20
|
+
when "guard"
|
|
21
|
+
abort "Usage: ethos-gemkit guard" unless ARGV.empty?
|
|
22
|
+
Ethos::Gemkit::Branch.check
|
|
23
|
+
when "version", "--version"
|
|
24
|
+
puts Ethos::Gemkit::VERSION
|
|
25
|
+
else
|
|
26
|
+
abort "Usage: ethos-gemkit guard | commitlint --message MESSAGE_OR_FILE | --version"
|
|
27
|
+
end
|
|
28
|
+
rescue Ethos::Gemkit::Error, OptionParser::ParseError => error
|
|
29
|
+
abort error.message
|
|
30
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require_relative "../gemkit"
|
|
5
|
+
|
|
6
|
+
module Ethos
|
|
7
|
+
module Gemkit
|
|
8
|
+
class Branch
|
|
9
|
+
def self.check(root = Dir.pwd)
|
|
10
|
+
output, error, status = Open3.capture3("git", "branch", "--show-current", chdir: root)
|
|
11
|
+
raise Error, "Cannot inspect Git branch: #{error.strip}" unless status.success?
|
|
12
|
+
branch = output.strip
|
|
13
|
+
if branch.empty? || %w[main master].include?(branch)
|
|
14
|
+
raise Error, "Use a feature branch; direct commits to main/master or detached HEAD are blocked"
|
|
15
|
+
end
|
|
16
|
+
true
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../gemkit"
|
|
4
|
+
|
|
5
|
+
module Ethos
|
|
6
|
+
module Gemkit
|
|
7
|
+
class CommitMessage
|
|
8
|
+
TYPES = %w[feat fix chore refactor docs test perf ci build].freeze
|
|
9
|
+
|
|
10
|
+
def self.validate(message)
|
|
11
|
+
cleaned = message.lines.reject { |line| line.start_with?("#") }.join.strip
|
|
12
|
+
header = cleaned.lines.first.to_s.chomp
|
|
13
|
+
type = header[/\A([a-z]+)(?:\([^()\n]+\))?!?: \S/, 1]
|
|
14
|
+
unless TYPES.include?(type)
|
|
15
|
+
raise Error, "Use a Conventional Commit header with one of: #{TYPES.join(", ")}"
|
|
16
|
+
end
|
|
17
|
+
raise Error, "Commit subject must be at most 72 characters" if header.length > 72
|
|
18
|
+
if cleaned.lines.size > 1 && !cleaned.lines[1].strip.empty?
|
|
19
|
+
raise Error, "Separate the commit subject and body with a blank line"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require_relative "../gemkit"
|
|
5
|
+
|
|
6
|
+
module Ethos
|
|
7
|
+
module Gemkit
|
|
8
|
+
class Configuration
|
|
9
|
+
KEYS = %w[gem_name repository version_file registry checks].freeze
|
|
10
|
+
REGISTRIES = {"rubygems" => "https://rubygems.org", "github_packages" => "https://rubygems.pkg.github.com/ethos-link"}.freeze
|
|
11
|
+
|
|
12
|
+
attr_reader :root, :data
|
|
13
|
+
|
|
14
|
+
def self.load(root = Dir.pwd)
|
|
15
|
+
path = File.join(root, ".ethos-gemkit.yml")
|
|
16
|
+
new(root, File.exist?(path) ? YAML.safe_load_file(path, permitted_classes: [], permitted_symbols: [], aliases: false) : {})
|
|
17
|
+
rescue Psych::Exception, Errno::ENOENT => error
|
|
18
|
+
raise Error, "Cannot load .ethos-gemkit.yml: #{error.message}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(root, data)
|
|
22
|
+
@root = File.realpath(root)
|
|
23
|
+
raise Error, "Configuration must be a mapping" unless data.is_a?(Hash)
|
|
24
|
+
gemspecs = Dir[File.join(@root, "*.gemspec")]
|
|
25
|
+
raise Error, "Expected exactly one gemspec, or an explicit gem_name" if !data["gem_name"] && !gemspecs.one?
|
|
26
|
+
name = data["gem_name"] || File.basename(gemspecs.first, ".gemspec")
|
|
27
|
+
versions = Dir[File.join(@root, "lib/**/version.rb")].reject { |file| file.include?("templates/") }
|
|
28
|
+
registry = data.fetch("registry", "rubygems")
|
|
29
|
+
registry = {"kind" => registry, "host" => REGISTRIES[registry]} if registry.is_a?(String)
|
|
30
|
+
@data = {"gem_name" => name,
|
|
31
|
+
"repository" => "ethos-link/#{name}", "checks" => ["test"],
|
|
32
|
+
"version_file" => versions.one? ? versions.first.delete_prefix(@root + "/") : "lib/#{name}/version.rb"}.merge(data).merge("registry" => registry)
|
|
33
|
+
validate
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def name
|
|
37
|
+
data.fetch("gem_name")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def checks
|
|
41
|
+
data.fetch("checks")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def path(relative)
|
|
45
|
+
unless relative.is_a?(String) && !relative.empty? && !Pathname.new(relative).absolute?
|
|
46
|
+
raise Error, "Expected a relative repository path"
|
|
47
|
+
end
|
|
48
|
+
expanded = File.expand_path(relative, root)
|
|
49
|
+
unless expanded.start_with?(root + File::SEPARATOR)
|
|
50
|
+
raise Error, "Path escapes repository: #{relative}"
|
|
51
|
+
end
|
|
52
|
+
# Existing symlinks must stay inside the checkout too.
|
|
53
|
+
parent = expanded
|
|
54
|
+
parent = File.dirname(parent) until File.exist?(parent) || File.symlink?(parent)
|
|
55
|
+
if File.symlink?(parent) && !File.exist?(parent)
|
|
56
|
+
raise Error, "Dangling symlink is not a repository path: #{relative}"
|
|
57
|
+
end
|
|
58
|
+
unless File.realpath(parent).start_with?(root + File::SEPARATOR) || File.realpath(parent) == root
|
|
59
|
+
raise Error, "Symlink escapes repository: #{relative}"
|
|
60
|
+
end
|
|
61
|
+
expanded
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def validate
|
|
67
|
+
require_value(data.is_a?(Hash), "Configuration must be a mapping")
|
|
68
|
+
require_value((data.keys - KEYS).empty?, "Unknown configuration keys: #{data.keys - KEYS}")
|
|
69
|
+
require_value(name.is_a?(String) && name.match?(/\A[a-z0-9][a-z0-9_-]*\z/), "Invalid gem name")
|
|
70
|
+
require_value(data["repository"].is_a?(String) && data["repository"].match?(/\A[a-zA-Z0-9_-]+\/[a-zA-Z0-9_.-]+\z/), "Expected GitHub owner/repository")
|
|
71
|
+
path(data.fetch("version_file"))
|
|
72
|
+
registry = data["registry"]
|
|
73
|
+
require_value(registry.is_a?(Hash) && registry.keys.sort == %w[host kind], "registry requires only kind and host")
|
|
74
|
+
require_value(REGISTRIES.key?(registry["kind"]) && registry["host"] == REGISTRIES[registry["kind"]], "Unsupported registry/host pair")
|
|
75
|
+
require_value(checks.is_a?(Array) && !checks.empty? && checks.all? { |check| check.is_a?(String) && check.match?(/\A[a-zA-Z][a-zA-Z0-9_:]*\z/) }, "checks must name existing Rake tasks")
|
|
76
|
+
require_value(checks.none? { |check| %w[verify default release].include?(check) || check.start_with?("release:") }, "Verification checks must not recurse or publish")
|
|
77
|
+
rescue KeyError => error
|
|
78
|
+
raise Error, "Missing configuration: #{error.key}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def require_value(condition, message)
|
|
82
|
+
raise Error, message unless condition
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubygems/package"
|
|
4
|
+
require "tmpdir"
|
|
5
|
+
require_relative "configuration"
|
|
6
|
+
|
|
7
|
+
module Ethos
|
|
8
|
+
module Gemkit
|
|
9
|
+
class Package
|
|
10
|
+
def initialize(config)
|
|
11
|
+
@config = config
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def verify
|
|
15
|
+
Dir.chdir(@config.root) do
|
|
16
|
+
spec = Gem::Specification.load(@config.path("#{@config.name}.gemspec"))
|
|
17
|
+
raise Error, "Cannot load gemspec" unless spec
|
|
18
|
+
validate_spec(spec)
|
|
19
|
+
Dir.mktmpdir("ethos-gemkit-package-") do |directory|
|
|
20
|
+
artifact = File.join(directory, "#{spec.full_name}.gem")
|
|
21
|
+
Gem::Package.build(spec, false, false, artifact)
|
|
22
|
+
package = Gem::Package.new(artifact)
|
|
23
|
+
package.verify
|
|
24
|
+
unless package.spec.dependencies.none? { |dependency| dependency.type == :runtime && dependency.name == "ethos-gemkit" }
|
|
25
|
+
raise Error, "ethos-gemkit must not be a runtime dependency"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def validate_spec(spec)
|
|
35
|
+
raise Error, "Configured gem name differs from gemspec" unless spec.name == @config.name
|
|
36
|
+
requirement = spec.required_ruby_version
|
|
37
|
+
unless requirement.satisfied_by?(Gem::Version.new("4.0.0")) && !requirement.satisfied_by?(Gem::Version.new("3.4.99"))
|
|
38
|
+
raise Error, "Gems must require Ruby 4.0 or newer"
|
|
39
|
+
end
|
|
40
|
+
unless spec.metadata["allowed_push_host"] == @config.data.fetch("registry").fetch("host")
|
|
41
|
+
raise Error, "allowed_push_host differs from configured registry"
|
|
42
|
+
end
|
|
43
|
+
raise Error, "Package contains no files" if spec.files.empty?
|
|
44
|
+
spec.files.each do |file|
|
|
45
|
+
@config.path(file)
|
|
46
|
+
if file.match?(%r{(?:\A|/)(?:\.git|\.bundle|node_modules|coverage)(?:/|\z)|(?:\A|/)(?:master\.key|credentials\.yml|\.env(?:\..*)?)\z})
|
|
47
|
+
raise Error, "Unsafe package path: #{file}"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler"
|
|
4
|
+
require "lint_roller"
|
|
5
|
+
require_relative "../gemkit"
|
|
6
|
+
|
|
7
|
+
module Ethos
|
|
8
|
+
module Gemkit
|
|
9
|
+
# Delegate through lint_roller's public interface; never mutate RuboCop internals.
|
|
10
|
+
class Plugin < LintRoller::Plugin
|
|
11
|
+
FRAMEWORKS = %w[rails railties activerecord activemodel actionpack actionview activejob actionmailer activestorage actioncable actionmailbox actiontext].freeze
|
|
12
|
+
|
|
13
|
+
def about
|
|
14
|
+
LintRoller::About.new(name: "ethos-gemkit", version: VERSION,
|
|
15
|
+
homepage: "https://github.com/ethos-link/ethos-gemkit",
|
|
16
|
+
description: "Standard Rails rules when framework components are bundled")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def supported?(context)
|
|
20
|
+
true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def rules(context)
|
|
24
|
+
specs = Bundler.load.specs.select { |spec| FRAMEWORKS.include?(spec.name) }
|
|
25
|
+
if specs.empty?
|
|
26
|
+
LintRoller::Rules.new(type: :object, config_format: :rubocop, value: {})
|
|
27
|
+
else
|
|
28
|
+
versions = specs.map { |spec| spec.version.segments.first(2) }.uniq
|
|
29
|
+
if versions.size != 1 || versions.first.first != 8
|
|
30
|
+
raise Error, "Rails linting requires consistent Rails 8 components; found #{specs.map { |spec| "#{spec.name}=#{spec.version}" }.join(", ")}"
|
|
31
|
+
end
|
|
32
|
+
require "standard/rails"
|
|
33
|
+
Standard::Rails::Plugin.new(@config.merge("target_rails_version" => versions.first.join(".").to_f)).rules(context)
|
|
34
|
+
end
|
|
35
|
+
rescue LoadError => error
|
|
36
|
+
raise Error, "Install compatible standard-rails support with bundle install: #{error.message}"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
|
|
6
|
+
unless Rake::Task.task_defined?(:test)
|
|
7
|
+
Rake::TestTask.new(:test) { |task|
|
|
8
|
+
task.libs << "test"
|
|
9
|
+
task.pattern = "test/**/*_test.rb"
|
|
10
|
+
}
|
|
11
|
+
end
|
|
12
|
+
require_relative "release_tasks"
|
|
13
|
+
task default: :verify unless Rake::Task.task_defined?(:default)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "bundler"
|
|
5
|
+
require_relative "configuration"
|
|
6
|
+
require_relative "branch"
|
|
7
|
+
|
|
8
|
+
module Ethos
|
|
9
|
+
module Gemkit
|
|
10
|
+
class Release
|
|
11
|
+
attr_reader :spec
|
|
12
|
+
|
|
13
|
+
def initialize(config)
|
|
14
|
+
@config = config
|
|
15
|
+
@spec = Gem::Specification.load(config.path("#{config.name}.gemspec"))
|
|
16
|
+
raise Error, "Cannot load gemspec" unless spec
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Release metadata uses an ordinary branch and PR; main is never committed.
|
|
20
|
+
def prepare(target = "patch")
|
|
21
|
+
clean
|
|
22
|
+
canonical_origin
|
|
23
|
+
git("fetch", "--no-tags", "origin", "refs/heads/main:refs/remotes/origin/main")
|
|
24
|
+
unless git("rev-parse", "HEAD") == git("rev-parse", "origin/main")
|
|
25
|
+
raise Error, "Start from clean main at origin/main; fetch and fast-forward before preparing a release"
|
|
26
|
+
end
|
|
27
|
+
%w[pre-commit commit-msg].each do |hook|
|
|
28
|
+
path = File.expand_path(git("rev-parse", "--git-path", "hooks/#{hook}"), @config.root)
|
|
29
|
+
raise Error, "Install hooks with bundle exec lefthook install" unless File.executable?(path)
|
|
30
|
+
end
|
|
31
|
+
file = @config.path(@config.data.fetch("version_file"))
|
|
32
|
+
text = File.read(file)
|
|
33
|
+
current = spec.version.to_s
|
|
34
|
+
parts = current.split(".").map(&:to_i)
|
|
35
|
+
index = %w[major minor patch].index(target)
|
|
36
|
+
if index
|
|
37
|
+
parts[index] += 1
|
|
38
|
+
((index + 1)..2).each { |position| parts[position] = 0 }
|
|
39
|
+
target = parts.join(".")
|
|
40
|
+
end
|
|
41
|
+
unless target.to_s.match?(/\A\d+\.\d+\.\d+\z/) && Gem::Version.new(target) >= spec.version
|
|
42
|
+
raise Error, "Use major, minor, patch or a X.Y.Z version that has not been tagged"
|
|
43
|
+
end
|
|
44
|
+
literal = %(VERSION = "#{current}")
|
|
45
|
+
raise Error, "Expected a literal #{literal} in version_file" unless text.include?(literal)
|
|
46
|
+
raise Error, "Release tag already exists" unless git("tag", "--list", "v#{target}").empty? && git("ls-remote", "--tags", "origin", "refs/tags/v#{target}").empty?
|
|
47
|
+
branch = "chore/release-v#{target}"
|
|
48
|
+
current_branch = git("branch", "--show-current")
|
|
49
|
+
unless ["main", branch].include?(current_branch)
|
|
50
|
+
raise Error, "Start from main or #{branch} at origin/main"
|
|
51
|
+
end
|
|
52
|
+
run("gh", "auth", "status", "--hostname", "github.com")
|
|
53
|
+
if current_branch == "main"
|
|
54
|
+
unless git("branch", "--list", branch).empty?
|
|
55
|
+
unless git("rev-list", "--count", "origin/main..#{branch}") == "0"
|
|
56
|
+
raise Error, "Existing #{branch} contains unmerged commits; review it before retrying"
|
|
57
|
+
end
|
|
58
|
+
git("branch", "-d", branch)
|
|
59
|
+
end
|
|
60
|
+
git("switch", "-c", branch)
|
|
61
|
+
end
|
|
62
|
+
Branch.check(@config.root)
|
|
63
|
+
File.write(file, text.sub(literal, %(VERSION = "#{target}")))
|
|
64
|
+
run("git-cliff", "--config", "cliff.toml", "--unreleased", "--tag", "v#{target}", "--prepend", "CHANGELOG.md")
|
|
65
|
+
run("bundle", "lock", "--local")
|
|
66
|
+
run("bundle", "exec", "rake", "verify")
|
|
67
|
+
files = [@config.data.fetch("version_file"), "CHANGELOG.md", "Gemfile.lock"]
|
|
68
|
+
unless (git("diff", "--name-only").lines.map(&:strip) - files).empty? && git("ls-files", "--others", "--exclude-standard").empty?
|
|
69
|
+
raise Error, "Verification changed unrelated files; review git diff before committing"
|
|
70
|
+
end
|
|
71
|
+
title = "chore(release): prepare v#{target}"
|
|
72
|
+
body = "Prepare #{spec.name} #{target} for publication.\n\nRecord the version, generated notes and lockfile after verification."
|
|
73
|
+
git("add", "--", *files)
|
|
74
|
+
git("commit", "-m", title, "-m", body)
|
|
75
|
+
clean
|
|
76
|
+
git("push", "--no-follow-tags", "--set-upstream", "origin", branch)
|
|
77
|
+
url = run("gh", "pr", "create", "--repo", @config.data.fetch("repository"),
|
|
78
|
+
"--base", "main", "--head", branch, "--title", title,
|
|
79
|
+
"--body", "#{body}\n\nMerging this PR creates annotated tag v#{target} and publishes the verified gem.").strip
|
|
80
|
+
"#{url}\nMerge this release PR to tag and publish v#{target}."
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def verify(tag, tagged: true)
|
|
84
|
+
clean
|
|
85
|
+
raise Error, "Tag and gem version differ" unless tag == "v#{spec.version}"
|
|
86
|
+
canonical_origin
|
|
87
|
+
ref = "refs/tags/#{tag}"
|
|
88
|
+
if tagged
|
|
89
|
+
object = git("rev-parse", ref)
|
|
90
|
+
raise Error, "Release tag must be annotated" unless git("cat-file", "-t", ref) == "tag"
|
|
91
|
+
raise Error, "Tag must identify HEAD" unless git("rev-parse", "#{ref}^{}") == git("rev-parse", "HEAD")
|
|
92
|
+
raise Error, "Canonical tag object differs" unless git("ls-remote", "--refs", "origin", ref).split.first == object
|
|
93
|
+
end
|
|
94
|
+
git("fetch", "--no-tags", "origin", "refs/heads/main:refs/remotes/origin/main")
|
|
95
|
+
git("merge-base", "--is-ancestor", "HEAD", "origin/main")
|
|
96
|
+
spec.files.each do |file|
|
|
97
|
+
unless git("hash-object", "--no-filters", "--", @config.path(file)) == git("rev-parse", "HEAD:#{file}")
|
|
98
|
+
raise Error, "Package file differs from the release commit: #{file}"
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
notes = File.read(@config.path("CHANGELOG.md")).split(/^## /).find { |section| section.lines.first.to_s.strip.match?(/\A\[#{Regexp.escape(spec.version.to_s)}\](?: - .*)?\z/) }
|
|
102
|
+
raise Error, "Missing meaningful release notes" unless notes.to_s.lines.any? { |line| line.start_with?("- ") }
|
|
103
|
+
"## #{notes}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
def canonical_origin
|
|
109
|
+
origin = git("config", "--get", "remote.origin.url")
|
|
110
|
+
repo = ENV.fetch("GITHUB_REPOSITORY", @config.data.fetch("repository"))
|
|
111
|
+
unless ["https://github.com/#{repo}.git", "https://github.com/#{repo}", "git@github.com:#{repo}.git", "ssh://git@github.com/#{repo}.git"].include?(origin)
|
|
112
|
+
raise Error, "Release origin must be the canonical GitHub repository"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def clean
|
|
117
|
+
status = git("status", "--porcelain")
|
|
118
|
+
raise Error, "Release requires a clean working tree:\n#{status}" unless status.empty?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def git(*arguments)
|
|
122
|
+
run("git", *arguments).strip
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def run(*arguments)
|
|
126
|
+
environment = {"BUNDLE_GEMFILE" => File.join(@config.root, "Gemfile"),
|
|
127
|
+
"GEM_PATH" => [*Gem.path, *Gem.loaded_specs.values.map(&:base_dir)].uniq.join(File::PATH_SEPARATOR)}
|
|
128
|
+
output, error, status = Bundler.with_unbundled_env { Open3.capture3(environment, *arguments, chdir: @config.root) }
|
|
129
|
+
raise Error, "#{arguments.first(3).join(" ")} failed: #{output}#{error}" unless status.success?
|
|
130
|
+
output
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "tasks"
|
|
4
|
+
require_relative "release"
|
|
5
|
+
|
|
6
|
+
config = Ethos::Gemkit::Configuration.load
|
|
7
|
+
desc "Create a verified release PR from clean, up-to-date main"
|
|
8
|
+
task "release:prepare", [:target] do |_task, args|
|
|
9
|
+
puts Ethos::Gemkit::Release.new(config).prepare(args[:target] || "patch")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
desc "Verify release source and build using Bundler's native task"
|
|
13
|
+
task "release:build" => :verify do
|
|
14
|
+
release = Ethos::Gemkit::Release.new(config)
|
|
15
|
+
notes = release.verify(ENV.fetch("RELEASE_TAG"), tagged: ENV["RELEASE_FROM_PR"] != "true")
|
|
16
|
+
Rake::Task[:build].invoke
|
|
17
|
+
File.write(config.path("pkg/release-notes.md"), notes)
|
|
18
|
+
Dir.chdir(config.path("pkg")) do
|
|
19
|
+
sh "sha256sum", "#{release.spec.full_name}.gem", "release-notes.md", out: "SHA256SUMS"
|
|
20
|
+
end
|
|
21
|
+
if ENV["GITHUB_OUTPUT"]
|
|
22
|
+
File.open(ENV.fetch("GITHUB_OUTPUT"), "a") do |file|
|
|
23
|
+
file.puts "gem=#{release.spec.full_name}.gem", "host=#{release.spec.metadata.fetch("allowed_push_host")}", "tag=v#{release.spec.version}"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
%w[release release:source_control_push release:rubygem_push].each do |name|
|
|
29
|
+
Rake::Task[name].clear if Rake::Task.task_defined?(name)
|
|
30
|
+
task(name) { abort "Publish a reviewed tag through GitHub Actions; local publication is disabled" }
|
|
31
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "standard/rake"
|
|
4
|
+
require_relative "configuration"
|
|
5
|
+
require_relative "package"
|
|
6
|
+
|
|
7
|
+
config = Ethos::Gemkit::Configuration.load
|
|
8
|
+
standard = YAML.safe_load_file(config.path(".standard.yml"), aliases: false)
|
|
9
|
+
unless standard.is_a?(Hash) && standard["ruby_version"].to_s == "4.0" && Array(standard["plugins"]).include?("ethos-gemkit")
|
|
10
|
+
raise Ethos::Gemkit::Error, ".standard.yml must target Ruby 4.0 and register ethos-gemkit"
|
|
11
|
+
end
|
|
12
|
+
task lint: :standard
|
|
13
|
+
task "lint:fix" => "standard:fix"
|
|
14
|
+
task "package:verify" do
|
|
15
|
+
Ethos::Gemkit::Package.new(config).verify
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
desc "Run lint, gem checks, package validation and changelog validation"
|
|
19
|
+
task verify: [:lint, *config.checks, "package:verify"] do
|
|
20
|
+
require "tempfile"
|
|
21
|
+
Tempfile.create("gemkit-changelog") { |file| sh "git-cliff", "--config", "cliff.toml", "--unreleased", "--output", file.path }
|
|
22
|
+
end
|
data/lib/ethos/gemkit.rb
ADDED
data/lib/ethos-gemkit.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: ethos-gemkit
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Ethos Link
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: standard
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.57'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.57'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: standard-rails
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.6'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.6'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: lint_roller
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.1'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.1'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rake
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '13.0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '13.0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: lefthook
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '2.1'
|
|
75
|
+
type: :runtime
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '2.1'
|
|
82
|
+
description: Common Ruby gem tasks, conditional Rails linting, package validation
|
|
83
|
+
and release verification.
|
|
84
|
+
email:
|
|
85
|
+
- devel@ethos-link.com
|
|
86
|
+
executables:
|
|
87
|
+
- ethos-gemkit
|
|
88
|
+
extensions: []
|
|
89
|
+
extra_rdoc_files: []
|
|
90
|
+
files:
|
|
91
|
+
- CHANGELOG.md
|
|
92
|
+
- LICENSE.txt
|
|
93
|
+
- README.md
|
|
94
|
+
- exe/ethos-gemkit
|
|
95
|
+
- lib/ethos-gemkit.rb
|
|
96
|
+
- lib/ethos/gemkit.rb
|
|
97
|
+
- lib/ethos/gemkit/branch.rb
|
|
98
|
+
- lib/ethos/gemkit/commit_message.rb
|
|
99
|
+
- lib/ethos/gemkit/configuration.rb
|
|
100
|
+
- lib/ethos/gemkit/package.rb
|
|
101
|
+
- lib/ethos/gemkit/plugin.rb
|
|
102
|
+
- lib/ethos/gemkit/rake.rb
|
|
103
|
+
- lib/ethos/gemkit/release.rb
|
|
104
|
+
- lib/ethos/gemkit/release_tasks.rb
|
|
105
|
+
- lib/ethos/gemkit/tasks.rb
|
|
106
|
+
- lib/ethos/gemkit/version.rb
|
|
107
|
+
homepage: https://github.com/ethos-link/ethos-gemkit
|
|
108
|
+
licenses:
|
|
109
|
+
- MIT
|
|
110
|
+
metadata:
|
|
111
|
+
allowed_push_host: https://rubygems.org
|
|
112
|
+
source_code_uri: https://github.com/ethos-link/ethos-gemkit
|
|
113
|
+
changelog_uri: https://github.com/ethos-link/ethos-gemkit/blob/main/CHANGELOG.md
|
|
114
|
+
rubygems_mfa_required: 'true'
|
|
115
|
+
default_lint_roller_plugin: Ethos::Gemkit::Plugin
|
|
116
|
+
rdoc_options: []
|
|
117
|
+
require_paths:
|
|
118
|
+
- lib
|
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - ">="
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '4.0'
|
|
124
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
|
+
requirements:
|
|
126
|
+
- - ">="
|
|
127
|
+
- !ruby/object:Gem::Version
|
|
128
|
+
version: '0'
|
|
129
|
+
requirements: []
|
|
130
|
+
rubygems_version: 4.0.20
|
|
131
|
+
specification_version: 4
|
|
132
|
+
summary: Shared development and verification tooling for Ethos Link gems
|
|
133
|
+
test_files: []
|