veragem 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 +53 -0
- data/LICENSE-APACHE +201 -0
- data/LICENSE-MIT +7 -0
- data/README.md +151 -0
- data/lib/veragem/generator.rb +109 -0
- data/lib/veragem/name.rb +111 -0
- data/lib/veragem/options.rb +53 -0
- data/lib/veragem/project.rb +77 -0
- data/lib/veragem/version.rb +5 -0
- data/lib/veragem.rb +23 -0
- data/plugins.rb +15 -0
- data/sig/bundler.rbs +10 -0
- data/sig/veragem.rbs +79 -0
- data/template/%name%.gemspec.erb +30 -0
- data/template/.editorconfig +15 -0
- data/template/.githooks/commit-msg +12 -0
- data/template/.githooks/pre-commit +6 -0
- data/template/.githooks/pre-push +17 -0
- data/template/.github/ISSUE_TEMPLATE/bug_report.md +27 -0
- data/template/.github/ISSUE_TEMPLATE/config.yml.erb +5 -0
- data/template/.github/ISSUE_TEMPLATE/documentation.md +19 -0
- data/template/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
- data/template/.github/PULL_REQUEST_TEMPLATE.md +14 -0
- data/template/.github/dependabot.yml +85 -0
- data/template/.github/image-registries.sh +31 -0
- data/template/.github/release-notes.sh +13 -0
- data/template/.github/workflows/dependabot-automerge.yml +70 -0
- data/template/.github/workflows/dependency-review.yml +50 -0
- data/template/.github/workflows/image.yml +140 -0
- data/template/.github/workflows/nix.yml +46 -0
- data/template/.github/workflows/release.yml +117 -0
- data/template/.github/workflows/rubocop-autocorrect.yml +114 -0
- data/template/.github/workflows/ruby-ci.yml +62 -0
- data/template/.github/workflows/security.yml +43 -0
- data/template/.github/workflows/style-check-cocogitto.yml +56 -0
- data/template/.github/workflows/style-check-dprint.yml +44 -0
- data/template/.gitignore +12 -0
- data/template/.rubocop.yml +27 -0
- data/template/AGENTS.md.erb +75 -0
- data/template/CODE_OF_CONDUCT.md.erb +18 -0
- data/template/CONTRIBUTING.md.erb +183 -0
- data/template/Gemfile +24 -0
- data/template/LICENSE-APACHE.erb +201 -0
- data/template/LICENSE-MIT.erb +7 -0
- data/template/README.md.erb +82 -0
- data/template/Rakefile +136 -0
- data/template/SECURITY.md.erb +21 -0
- data/template/Steepfile.erb +11 -0
- data/template/bin/console.erb +8 -0
- data/template/bin/setup +5 -0
- data/template/cliff.toml +70 -0
- data/template/cog.toml +8 -0
- data/template/dprint.json +23 -0
- data/template/exe/%name%.erb +6 -0
- data/template/flake.lock +27 -0
- data/template/flake.nix +213 -0
- data/template/lib/%path%/cli.rb.erb +45 -0
- data/template/lib/%path%/version.rb.erb +3 -0
- data/template/lib/%path%.rb.erb +13 -0
- data/template/sig/%path%.rbs.erb +16 -0
- data/template/test/test_%underscored%.rb.erb +27 -0
- data/template/test/test_cli.rb.erb +40 -0
- data/template/test/test_helper.rb.erb +15 -0
- data/template/typeprof.conf.jsonc +7 -0
- metadata +111 -0
data/template/Rakefile
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# `rake` runs the Ruby checks, as ruby-ci.yml does; `rake checks` runs everything CI runs.
|
|
4
|
+
# `rake -T` lists the tasks.
|
|
5
|
+
|
|
6
|
+
require "bundler/audit/task"
|
|
7
|
+
require "bundler/gem_tasks"
|
|
8
|
+
require "json"
|
|
9
|
+
require "minitest/test_task"
|
|
10
|
+
require "rubocop/rake_task"
|
|
11
|
+
require "steep/rake_task"
|
|
12
|
+
|
|
13
|
+
Minitest::TestTask.create
|
|
14
|
+
RuboCop::RakeTask.new
|
|
15
|
+
Steep::RakeTask.new
|
|
16
|
+
Bundler::Audit::Task.new
|
|
17
|
+
|
|
18
|
+
task default: %i[rubocop steep test]
|
|
19
|
+
|
|
20
|
+
# TypeProf infers; Steep, against sig/, is the gate. TypeProf reads Ruby's core signatures alone,
|
|
21
|
+
# not the standard library's.
|
|
22
|
+
desc "Print the signatures TypeProf infers from lib/, to start sig/ from, and what it finds amiss"
|
|
23
|
+
task :typeprof do
|
|
24
|
+
sh "typeprof", "--show-errors", "lib"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
desc "Update the advisory database, then check Gemfile.lock against it (bundler-audit)"
|
|
28
|
+
task audit: %w[bundle:audit:update bundle:audit:check]
|
|
29
|
+
|
|
30
|
+
desc "Check the Markdown and TOML format (dprint)"
|
|
31
|
+
task :dprint do
|
|
32
|
+
sh "dprint", "check"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
desc "Check that every commit is a Conventional Commit (cocogitto)"
|
|
36
|
+
task :cog do
|
|
37
|
+
sh "cog", "check"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# A command's standard output; its failure fails the task, as `sh`'s does.
|
|
41
|
+
def output(*command)
|
|
42
|
+
IO.popen(command, &:read).tap { raise "#{command.join(" ")} failed" unless Process.last_status.success? }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
desc "Lint and check the format of the shell scripts git tracks (shellcheck, shfmt)"
|
|
46
|
+
task :shell do
|
|
47
|
+
tracked = output("git", "ls-files", "-z").split("\0").select { File.file?(it) }
|
|
48
|
+
scripts = output("shfmt", "--find", *tracked).lines(chomp: true)
|
|
49
|
+
sh "shellcheck", *scripts
|
|
50
|
+
sh "shfmt", "--diff", *scripts
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
desc "Lint the workflows and audit their security (actionlint, zizmor)"
|
|
54
|
+
task :workflows do
|
|
55
|
+
sh "actionlint"
|
|
56
|
+
sh "zizmor", "--offline", "--quiet", "--strict-collection", "."
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
desc "Run the flake's checks: the Nix format, the shell, the workflows, the package (git's files alone)"
|
|
60
|
+
task :nix do
|
|
61
|
+
sh "nix", "flake", "check"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
desc "Run every check CI runs"
|
|
65
|
+
task checks: %i[default audit dprint cog shell workflows nix]
|
|
66
|
+
|
|
67
|
+
desc "Format the Ruby, the Markdown and TOML, and the Nix (modifies files)"
|
|
68
|
+
task format: "rubocop:autocorrect" do
|
|
69
|
+
sh "dprint", "fmt"
|
|
70
|
+
sh "nix", "fmt"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
desc "Use .githooks/: the quick checks before a commit, Conventional Commits for every message"
|
|
74
|
+
task :hooks do
|
|
75
|
+
sh "git", "config", "core.hooksPath", ".githooks"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
gemspec = Bundler::GemHelper.gemspec
|
|
79
|
+
# git-cliff's compare links: the repository, owner/name
|
|
80
|
+
repository = gemspec.metadata.fetch("source_code_uri").delete_prefix("https://github.com/")
|
|
81
|
+
cliff = ["git-cliff", "--offline", "--github-repo", repository]
|
|
82
|
+
|
|
83
|
+
desc "Preview the changelog's next section"
|
|
84
|
+
task :changelog do
|
|
85
|
+
sh(*cliff, "--unreleased")
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# The version to release: the declared one at the first release (no tag yet), else +level+'s
|
|
89
|
+
# number bumped (0 major, 1 minor, 2 patch), those after it zeroed.
|
|
90
|
+
def next_version(current, level)
|
|
91
|
+
abort "error: #{current} is not MAJOR.MINOR.PATCH" unless current.match?(/\A\d+\.\d+\.\d+\z/)
|
|
92
|
+
return current if output("git", "tag", "--list", "v[0-9]*").empty?
|
|
93
|
+
|
|
94
|
+
parts = current.split(".").map(&:to_i)
|
|
95
|
+
parts[level] += 1
|
|
96
|
+
parts.fill(0, level + 1).join(".")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# The release commit: the version, the lock, the changelog.
|
|
100
|
+
desc "Bump the version (major, minor or patch), write the changelog and commit"
|
|
101
|
+
task :bump, [:level] => "release:guard_clean" do |_, args|
|
|
102
|
+
level = %w[major minor patch].index(args[:level]) or abort 'usage: rake "bump[major|minor|patch]"'
|
|
103
|
+
current = gemspec.version.to_s
|
|
104
|
+
version = next_version(current, level)
|
|
105
|
+
tagged = system("git", "rev-parse", "--quiet", "--verify", "refs/tags/v#{version}", out: File::NULL)
|
|
106
|
+
abort "error: v#{version} is already tagged" if tagged
|
|
107
|
+
entries = JSON.parse(output(*cliff, "--unreleased", "--context")).sum { it["commits"].size }
|
|
108
|
+
abort "error: nothing to release: no commit since the last tag enters the changelog" if entries.zero?
|
|
109
|
+
|
|
110
|
+
file = "lib/#{gemspec.name.tr("-", "/")}/version.rb"
|
|
111
|
+
abort "error: #{file} declares no \"#{current}\"" unless File.read(file).include?(%("#{current}"))
|
|
112
|
+
first = !File.exist?("CHANGELOG.md")
|
|
113
|
+
begin
|
|
114
|
+
File.write(file, File.read(file).sub(%("#{current}"), %("#{version}")))
|
|
115
|
+
sh "bundle", "lock"
|
|
116
|
+
sh(*cliff, "--tag", "v#{version}", "--output", "CHANGELOG.md")
|
|
117
|
+
sh "git", "add", file, "Gemfile.lock", "CHANGELOG.md"
|
|
118
|
+
sh "git", "commit", "--message", "chore(release): v#{version}"
|
|
119
|
+
rescue StandardError, Interrupt
|
|
120
|
+
# The tracked files were committed (release:guard_clean): put back what this changed.
|
|
121
|
+
sh "git", "reset", "--hard", "--quiet"
|
|
122
|
+
rm_f "CHANGELOG.md" if first
|
|
123
|
+
raise
|
|
124
|
+
end
|
|
125
|
+
puts "v#{version} committed: `rake release` tags and pushes it, then release.yml publishes the tag."
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The gem is published by release.yml, on the tag, through RubyGems' Trusted Publishing:
|
|
129
|
+
# `rake release` builds it, tags v<version> and pushes the branch and the tag, and pushes no gem.
|
|
130
|
+
Rake::Task["release"].clear_comments
|
|
131
|
+
Rake::Task["release"].add_description("Tag v#{gemspec.version}, push the branch and the tag: release.yml publishes it")
|
|
132
|
+
Rake::Task["release:rubygem_push"].clear_actions
|
|
133
|
+
desc "Push no gem: release.yml publishes the tag"
|
|
134
|
+
task "release:rubygem_push" do
|
|
135
|
+
puts "release.yml publishes v#{gemspec.version} to RubyGems."
|
|
136
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
Security updates are applied to the latest `main` branch and the most recent release.
|
|
6
|
+
|
|
7
|
+
## Reporting a Vulnerability
|
|
8
|
+
|
|
9
|
+
**Do not open a public issue for security vulnerabilities.** To report one, use the
|
|
10
|
+
[Private Vulnerability Reporting](https://github.com/<%= github %>/<%= name %>/security/advisories/new)
|
|
11
|
+
feature in this repository.
|
|
12
|
+
|
|
13
|
+
Please include the technical context needed to reproduce it:
|
|
14
|
+
|
|
15
|
+
- The version or commit affected.
|
|
16
|
+
- The exact input, command line, or API call.
|
|
17
|
+
- Host OS and Ruby version.
|
|
18
|
+
- Exact reproduction steps or a proof-of-concept.
|
|
19
|
+
|
|
20
|
+
You can expect an initial response within a few days. Please give us a reasonable window to ship
|
|
21
|
+
a fix before disclosing publicly.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Steep checks lib/ against sig/, its strictest diagnostics on: `rake steep`.
|
|
4
|
+
target :lib do
|
|
5
|
+
signature "sig"
|
|
6
|
+
check "lib"
|
|
7
|
+
<%- if exe -%>
|
|
8
|
+
library "optparse"
|
|
9
|
+
<%- end -%>
|
|
10
|
+
configure_code_diagnostics(Steep::Diagnostic::Ruby.strict)
|
|
11
|
+
end
|
data/template/bin/setup
ADDED
data/template/cliff.toml
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# git-cliff: CHANGELOG.md from the Conventional Commits, written by `rake bump` (never by hand).
|
|
2
|
+
# Grouped by commit type, breaking changes first; what changes nothing a user meets (chores, CI,
|
|
3
|
+
# formatting, the release commit itself) is left out.
|
|
4
|
+
#
|
|
5
|
+
# It names no project: the Rakefile passes the repository the headings' compare links need
|
|
6
|
+
# (`--github-repo`, from the gemspec's source_code_uri). Run bare, the headings carry no link.
|
|
7
|
+
|
|
8
|
+
[changelog]
|
|
9
|
+
header = """
|
|
10
|
+
# Changelog
|
|
11
|
+
|
|
12
|
+
All notable changes to this project are documented in this file, generated by
|
|
13
|
+
[git-cliff](https://git-cliff.org) from its commits; versions follow
|
|
14
|
+
[Semantic Versioning](https://semver.org).\n
|
|
15
|
+
"""
|
|
16
|
+
body = """
|
|
17
|
+
{% set url = "" -%}
|
|
18
|
+
{% if remote.github.owner -%}
|
|
19
|
+
{% set url = "https://github.com/" ~ remote.github.owner ~ "/" ~ remote.github.repo -%}
|
|
20
|
+
{% endif -%}
|
|
21
|
+
{% if version -%}
|
|
22
|
+
{% set number = version | trim_start_matches(pat="v") -%}
|
|
23
|
+
## {% if url %}[{{ number }}]({{ url }}/\
|
|
24
|
+
{%- if previous.version %}compare/{{ previous.version }}..{{ version }}\
|
|
25
|
+
{%- else %}tree/{{ version }}{% endif %})\
|
|
26
|
+
{%- else %}{{ number }}{% endif %} - \
|
|
27
|
+
{{ timestamp | date(format="%Y-%m-%d") }}
|
|
28
|
+
{% elif url and previous.version -%}
|
|
29
|
+
## [Unreleased]({{ url }}/compare/{{ previous.version }}..HEAD)
|
|
30
|
+
{% else -%}
|
|
31
|
+
## Unreleased
|
|
32
|
+
{% endif -%}
|
|
33
|
+
{% for group, commits in commits | group_by(attribute="group") %}
|
|
34
|
+
### {{ group | striptags | trim }}
|
|
35
|
+
{% for commit in commits %}
|
|
36
|
+
- {% if commit.scope %}{{ commit.scope }}: {% endif %}\
|
|
37
|
+
{{ commit.message | split(pat="\n") | first | upper_first | trim }}\
|
|
38
|
+
{% endfor %}
|
|
39
|
+
{% endfor %}\n
|
|
40
|
+
"""
|
|
41
|
+
trim = true
|
|
42
|
+
|
|
43
|
+
[git]
|
|
44
|
+
conventional_commits = true
|
|
45
|
+
# a body right under its subject, which cog takes, set apart as git-cliff's parser needs it: its
|
|
46
|
+
# footers (BREAKING CHANGE) read
|
|
47
|
+
commit_preprocessors = [{ pattern = '\A([^\n]*)\n([^\n])', replace = "${1}\n\n${2}" }]
|
|
48
|
+
# what cog lets through unconventional -- a fixup (cog.toml) -- left out
|
|
49
|
+
filter_unconventional = true
|
|
50
|
+
protect_breaking_commits = true # a breaking change is listed whatever its type, first
|
|
51
|
+
# by type, in any case (cog takes `Feat:`), the HTML comments ordering the groups
|
|
52
|
+
commit_parsers = [
|
|
53
|
+
# a merge, whatever its message: cog checks none (ignore_merge_commits)
|
|
54
|
+
{ field = "merge_commit", pattern = "true", skip = true },
|
|
55
|
+
{ message = "^[a-zA-Z]+(\\([^)]*\\))?!:", group = "<!-- 0 -->Breaking changes" },
|
|
56
|
+
{ footer = "^BREAKING[ -]CHANGE", group = "<!-- 0 -->Breaking changes" },
|
|
57
|
+
{ message = "(?i)^(chore|ci|style)", skip = true },
|
|
58
|
+
{ message = "(?i)^feat", group = "<!-- 1 -->Features" },
|
|
59
|
+
{ message = "(?i)^fix", group = "<!-- 2 -->Fixes" },
|
|
60
|
+
{ message = "(?i)^perf", group = "<!-- 3 -->Performance" },
|
|
61
|
+
{ message = "(?i)^refactor", group = "<!-- 4 -->Refactoring" },
|
|
62
|
+
{ message = "(?i)^build", group = "<!-- 5 -->Build" },
|
|
63
|
+
{ message = "(?i)^doc", group = "<!-- 6 -->Documentation" },
|
|
64
|
+
{ message = "(?i)^test", group = "<!-- 7 -->Tests" },
|
|
65
|
+
{ message = "(?i)^revert", group = "<!-- 8 -->Reverts" },
|
|
66
|
+
{ message = ".*", skip = true },
|
|
67
|
+
]
|
|
68
|
+
sort_commits = "oldest"
|
|
69
|
+
# the release tags, `v0.1.0` on (Bundler's own form, `rake release`)
|
|
70
|
+
tag_pattern = "^v[0-9]"
|
data/template/cog.toml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# cocogitto checks the commits alone (`rake cog`, the commit-msg and pre-push hooks, CI):
|
|
2
|
+
# `rake bump` and `rake release` cut the release, git-cliff writes the changelog (cliff.toml).
|
|
3
|
+
|
|
4
|
+
# every commit, not from the latest tag: with no tag, cog stops "unable to get any tag"
|
|
5
|
+
from_latest_tag = false
|
|
6
|
+
ignore_merge_commits = true
|
|
7
|
+
ignore_fixup_commits = true
|
|
8
|
+
disable_changelog = true
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Markdown and TOML (`rake dprint`, style-check-dprint.yml). The plugins pinned:
|
|
2
|
+
// `dprint config update` moves them to their latest.
|
|
3
|
+
{
|
|
4
|
+
"markdown": {
|
|
5
|
+
"textWrap": "maintain",
|
|
6
|
+
// the plugin's defaults are underscores
|
|
7
|
+
"emphasisKind": "asterisks",
|
|
8
|
+
"strongKind": "asterisks"
|
|
9
|
+
},
|
|
10
|
+
// four spaces, as cliff.toml and cog.toml are written
|
|
11
|
+
"toml": { "indentWidth": 4 },
|
|
12
|
+
"excludes": [
|
|
13
|
+
// git-cliff's (cliff.toml): regenerated at each release, never edited
|
|
14
|
+
"CHANGELOG.md",
|
|
15
|
+
"coverage/",
|
|
16
|
+
"pkg/",
|
|
17
|
+
"vendor/"
|
|
18
|
+
],
|
|
19
|
+
"plugins": [
|
|
20
|
+
"https://plugins.dprint.dev/markdown-0.26.0.wasm",
|
|
21
|
+
"https://plugins.dprint.dev/toml-0.9.0.wasm"
|
|
22
|
+
]
|
|
23
|
+
}
|
data/template/flake.lock
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"nodes": {
|
|
3
|
+
"nixpkgs": {
|
|
4
|
+
"locked": {
|
|
5
|
+
"lastModified": 1792773339,
|
|
6
|
+
"narHash": "sha256-rZLrzrC6i+jnTAlvPZHogKDulY7WWDEv9hJeHieO7Zw=",
|
|
7
|
+
"owner": "NixOS",
|
|
8
|
+
"repo": "nixpkgs",
|
|
9
|
+
"rev": "3ec636362e1816bc2f0888fa0fa2c69f1bc9e504",
|
|
10
|
+
"type": "github"
|
|
11
|
+
},
|
|
12
|
+
"original": {
|
|
13
|
+
"owner": "NixOS",
|
|
14
|
+
"ref": "nixos-unstable",
|
|
15
|
+
"repo": "nixpkgs",
|
|
16
|
+
"type": "github"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"root": {
|
|
20
|
+
"inputs": {
|
|
21
|
+
"nixpkgs": "nixpkgs"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"root": "root",
|
|
26
|
+
"version": 7
|
|
27
|
+
}
|
data/template/flake.nix
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# The development shell, the gem as a package, and the checks that need no gem from the bundle:
|
|
2
|
+
#
|
|
3
|
+
# nix develop Ruby 4.0 and every tool `rake checks` runs besides the gems (Bundler's, from
|
|
4
|
+
# Gemfile.lock: `bin/setup`): cocogitto, git-cliff, dprint, shellcheck, shfmt,
|
|
5
|
+
# actionlint, zizmor, nixfmt
|
|
6
|
+
# nix build the gem, built from its gemspec and installed (nixpkgs' buildRubyGem);
|
|
7
|
+
# with a command (exe/<name>): `nix run` runs it, and on Linux
|
|
8
|
+
# `nix build .#image` builds it as an OCI image (image.yml publishes it)
|
|
9
|
+
# nix fmt the Nix, by nixfmt
|
|
10
|
+
# nix flake check the Nix format, the shell scripts (shellcheck, shfmt), the workflows
|
|
11
|
+
# (actionlint, zizmor), the package and its library loaded -- and the command
|
|
12
|
+
# run, and its image -- offline in the sandbox: git's files alone
|
|
13
|
+
#
|
|
14
|
+
# Nothing here names the gem: its name is the gemspec's file; its version, lib/<path>/version.rb's
|
|
15
|
+
# VERSION; its summary, homepage and licences, the gemspec's lines that set them.
|
|
16
|
+
{
|
|
17
|
+
description = "A Ruby gem's development shell, package and checks";
|
|
18
|
+
|
|
19
|
+
# the unstable channel: its tools move with each `nix flake update` (Dependabot's, monthly)
|
|
20
|
+
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
|
21
|
+
|
|
22
|
+
outputs =
|
|
23
|
+
{ self, nixpkgs }:
|
|
24
|
+
let
|
|
25
|
+
inherit (nixpkgs) lib;
|
|
26
|
+
# nixpkgs-unstable's platforms: 26.05 was the last release to support x86_64-darwin
|
|
27
|
+
systems = [
|
|
28
|
+
"x86_64-linux"
|
|
29
|
+
"aarch64-linux"
|
|
30
|
+
"aarch64-darwin"
|
|
31
|
+
];
|
|
32
|
+
forAllSystems = f: lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
|
|
33
|
+
|
|
34
|
+
gemspecs = lib.filter (lib.hasSuffix ".gemspec") (lib.attrNames (builtins.readDir ./.));
|
|
35
|
+
name =
|
|
36
|
+
if lib.length gemspecs == 1 then
|
|
37
|
+
lib.removeSuffix ".gemspec" (lib.head gemspecs)
|
|
38
|
+
else
|
|
39
|
+
throw "flake.nix: one *.gemspec expected, found ${toString gemspecs}";
|
|
40
|
+
path = lib.replaceStrings [ "-" ] [ "/" ] name;
|
|
41
|
+
# the groups of the first of +file+'s lines that +pattern+ matches whole, or null
|
|
42
|
+
line =
|
|
43
|
+
pattern: file:
|
|
44
|
+
lib.findFirst (groups: groups != null) null (
|
|
45
|
+
map (builtins.match pattern) (lib.splitString "\n" (builtins.readFile file))
|
|
46
|
+
);
|
|
47
|
+
# a double-quoted Ruby string's value, its escapes \\, \" and \# read: those veragem writes
|
|
48
|
+
unquote = lib.replaceStrings [ "\\\\" "\\\"" "\\#" ] [ "\\" "\"" "#" ];
|
|
49
|
+
# a line's end after its value: `.freeze`, a comment, a carriage return
|
|
50
|
+
rest = ''(\.freeze)?[[:space:]]*(#.*)?'';
|
|
51
|
+
# the gemspec's `spec.<attribute> = "<value>"`, its value; "" if no line sets it so
|
|
52
|
+
field =
|
|
53
|
+
attribute:
|
|
54
|
+
let
|
|
55
|
+
found = line ''[[:space:]]*spec\.${attribute}[[:space:]]*=[[:space:]]*"((\\.|[^"\\])*)"${rest}'' ./${name}.gemspec;
|
|
56
|
+
in
|
|
57
|
+
if found == null then "" else unquote (lib.head found);
|
|
58
|
+
version =
|
|
59
|
+
let
|
|
60
|
+
found = line ''[[:space:]]*VERSION[[:space:]]*=[[:space:]]*"([^"]+)"${rest}'' ./lib/${path}/version.rb;
|
|
61
|
+
in
|
|
62
|
+
if found == null then
|
|
63
|
+
throw "flake.nix: no VERSION = \"...\" line in version.rb"
|
|
64
|
+
else
|
|
65
|
+
lib.head found;
|
|
66
|
+
# the SPDX identifiers of `spec.licenses = [...]` or `spec.license = "..."`
|
|
67
|
+
licences =
|
|
68
|
+
let
|
|
69
|
+
found = line ''[[:space:]]*spec\.licenses?[[:space:]]*=[[:space:]]*(\[[^]]*]|"[^"]*")${rest}'' ./${name}.gemspec;
|
|
70
|
+
in
|
|
71
|
+
if found == null then
|
|
72
|
+
[ ]
|
|
73
|
+
else
|
|
74
|
+
lib.concatLists (lib.filter lib.isList (builtins.split ''"([^"]*)"'' (lib.head found)));
|
|
75
|
+
command = builtins.pathExists ./exe/${name};
|
|
76
|
+
|
|
77
|
+
check =
|
|
78
|
+
pkgs: checkName: tools: script:
|
|
79
|
+
pkgs.runCommand checkName { nativeBuildInputs = tools; } ''
|
|
80
|
+
cd ${self}
|
|
81
|
+
${script}
|
|
82
|
+
touch $out
|
|
83
|
+
'';
|
|
84
|
+
|
|
85
|
+
perSystem =
|
|
86
|
+
pkgs:
|
|
87
|
+
let
|
|
88
|
+
ruby = pkgs.ruby_4_0;
|
|
89
|
+
# the gem's runtime dependencies (its gemspec's add_dependency), as nixpkgs packages
|
|
90
|
+
# them: pkgs.rubyPackages_4_0.<gem>. The `library` check fails on one missing
|
|
91
|
+
dependencies = [ ];
|
|
92
|
+
package = pkgs.buildRubyGem {
|
|
93
|
+
gemName = name;
|
|
94
|
+
inherit version ruby;
|
|
95
|
+
src = self;
|
|
96
|
+
gemPath = dependencies;
|
|
97
|
+
# no meta.license: nixpkgs refuses to evaluate a package whose licence is unfree or unknown
|
|
98
|
+
# to it, and the gemspec's is the author's to choose
|
|
99
|
+
meta = {
|
|
100
|
+
description = field "summary";
|
|
101
|
+
homepage = field "homepage";
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
# the command as an OCI image: its closure alone -- no base image, no /bin/sh -- run as
|
|
105
|
+
# nobody. `.#image.stream` streams the archive uncompressed (image.yml's: no second copy
|
|
106
|
+
# of the closure in the store)
|
|
107
|
+
image = pkgs.dockerTools.buildLayeredImage {
|
|
108
|
+
inherit name;
|
|
109
|
+
tag = version;
|
|
110
|
+
# Dir.tmpdir's default, and HOME's: the image has no other directory to write
|
|
111
|
+
extraCommands = "mkdir -m 1777 tmp";
|
|
112
|
+
config = {
|
|
113
|
+
Entrypoint = [ (lib.getExe package) ];
|
|
114
|
+
# nobody: nothing in it needs root, and a numeric user needs no /etc/passwd
|
|
115
|
+
User = "65534:65534";
|
|
116
|
+
Env = [
|
|
117
|
+
# UTF-8 text, ARGV and files alike (glibc's built-in locale)
|
|
118
|
+
"LANG=C.UTF-8"
|
|
119
|
+
"HOME=/tmp"
|
|
120
|
+
# what OpenSSL verifies HTTPS against: the image has no /etc/ssl
|
|
121
|
+
"SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
|
|
122
|
+
];
|
|
123
|
+
# what GHCR reads (docs.github.com, "Working with the Container registry"): the
|
|
124
|
+
# source links the image to its repository
|
|
125
|
+
Labels = lib.filterAttrs (_: value: value != "") {
|
|
126
|
+
"org.opencontainers.image.title" = name;
|
|
127
|
+
"org.opencontainers.image.description" = field "summary";
|
|
128
|
+
"org.opencontainers.image.version" = version;
|
|
129
|
+
"org.opencontainers.image.source" = field "homepage";
|
|
130
|
+
"org.opencontainers.image.licenses" = lib.concatStringsSep " OR " licences;
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
linuxCommand = command && pkgs.stdenv.hostPlatform.isLinux;
|
|
135
|
+
in
|
|
136
|
+
{
|
|
137
|
+
packages = {
|
|
138
|
+
default = package;
|
|
139
|
+
}
|
|
140
|
+
// lib.optionalAttrs linuxCommand { inherit image; };
|
|
141
|
+
|
|
142
|
+
checks = {
|
|
143
|
+
format = check pkgs "format" [ pkgs.nixfmt ] "find . -name '*.nix' -exec nixfmt --check {} +";
|
|
144
|
+
# shfmt finds the scripts, by extension or shebang, and formats them as .editorconfig says
|
|
145
|
+
shell = check pkgs "shell" [ pkgs.shellcheck pkgs.shfmt ] ''
|
|
146
|
+
shfmt --find . | xargs shellcheck
|
|
147
|
+
shfmt --diff .
|
|
148
|
+
'';
|
|
149
|
+
# actionlint runs shellcheck on each `run:` script too; zizmor audits Dependabot's
|
|
150
|
+
# configuration as well
|
|
151
|
+
workflows = check pkgs "workflows" [ pkgs.actionlint pkgs.shellcheck pkgs.zizmor ] ''
|
|
152
|
+
actionlint .github/workflows/*.yml
|
|
153
|
+
zizmor --offline --quiet --strict-collection .
|
|
154
|
+
'';
|
|
155
|
+
inherit package;
|
|
156
|
+
# the library as a user loads it: the gem activated -- its dependencies with it -- then
|
|
157
|
+
# required, in an environment of nothing else (env -i). Ruby's setup hook puts each gem of
|
|
158
|
+
# buildInputs on GEM_PATH, those it propagates too: the dependencies' own dependencies
|
|
159
|
+
library =
|
|
160
|
+
pkgs.runCommand "library"
|
|
161
|
+
{
|
|
162
|
+
buildInputs = [
|
|
163
|
+
ruby
|
|
164
|
+
package
|
|
165
|
+
];
|
|
166
|
+
}
|
|
167
|
+
''
|
|
168
|
+
env -i GEM_PATH="$GEM_PATH" ${lib.getExe ruby} -e 'gem "${name}"; require "${path}"'
|
|
169
|
+
touch $out
|
|
170
|
+
'';
|
|
171
|
+
}
|
|
172
|
+
// lib.optionalAttrs command {
|
|
173
|
+
# the command as a user runs it: its wrapper alone gives it its environment (env -i)
|
|
174
|
+
command =
|
|
175
|
+
pkgs.runCommand "command"
|
|
176
|
+
{
|
|
177
|
+
exe = lib.getExe package;
|
|
178
|
+
expected = "${name} ${version}";
|
|
179
|
+
}
|
|
180
|
+
''
|
|
181
|
+
actual=$(env -i "$exe" --version)
|
|
182
|
+
if [ "$actual" != "$expected" ]; then
|
|
183
|
+
echo "--version gave '$actual', expected '$expected'" >&2
|
|
184
|
+
exit 1
|
|
185
|
+
fi
|
|
186
|
+
touch $out
|
|
187
|
+
'';
|
|
188
|
+
}
|
|
189
|
+
// lib.optionalAttrs linuxCommand { inherit image; };
|
|
190
|
+
|
|
191
|
+
devShells.default = pkgs.mkShell {
|
|
192
|
+
packages = [
|
|
193
|
+
ruby
|
|
194
|
+
pkgs.cocogitto
|
|
195
|
+
pkgs.git-cliff
|
|
196
|
+
pkgs.dprint
|
|
197
|
+
pkgs.shellcheck
|
|
198
|
+
pkgs.shfmt
|
|
199
|
+
pkgs.actionlint
|
|
200
|
+
pkgs.zizmor
|
|
201
|
+
pkgs.nixfmt
|
|
202
|
+
];
|
|
203
|
+
};
|
|
204
|
+
};
|
|
205
|
+
output = attribute: forAllSystems (pkgs: (perSystem pkgs).${attribute});
|
|
206
|
+
in
|
|
207
|
+
{
|
|
208
|
+
packages = output "packages";
|
|
209
|
+
checks = output "checks";
|
|
210
|
+
devShells = output "devShells";
|
|
211
|
+
formatter = forAllSystems (pkgs: pkgs.nixfmt-tree);
|
|
212
|
+
};
|
|
213
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
require_relative "../<%= File.basename(path) %>"
|
|
5
|
+
|
|
6
|
+
<%= nest(<<~RUBY)
|
|
7
|
+
# The command line: `#{name} [NAME]` prints the greeting.
|
|
8
|
+
module CLI
|
|
9
|
+
# Runs the command line with +argv+; returns the exit status.
|
|
10
|
+
def self.run(argv)
|
|
11
|
+
options = { help: false, version: false }
|
|
12
|
+
names = parser.parse(argv, into: options)
|
|
13
|
+
puts output(options, names.first || "")
|
|
14
|
+
0
|
|
15
|
+
rescue ::OptionParser::ParseError => e
|
|
16
|
+
warn e.message, parser.help
|
|
17
|
+
2
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# What +options+ ask for, the help or the version, else the greeting for +name+.
|
|
21
|
+
def self.output(options, name)
|
|
22
|
+
if options[:help]
|
|
23
|
+
parser.help
|
|
24
|
+
elsif options[:version]
|
|
25
|
+
parser.ver
|
|
26
|
+
else
|
|
27
|
+
#{modules.last}.greeting(name)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
private_class_method :output
|
|
31
|
+
|
|
32
|
+
# --help as an option of its own: OptionParser's built-in one exits the process. `::`: a module of
|
|
33
|
+
# the gem's could be named OptionParser
|
|
34
|
+
def self.parser
|
|
35
|
+
::OptionParser.new("Usage: #{name} [options] [NAME]") do |opts|
|
|
36
|
+
opts.program_name = "#{name}"
|
|
37
|
+
opts.version = VERSION
|
|
38
|
+
opts.on("-h", "--help", "Print this help")
|
|
39
|
+
opts.on("--version", "Print the version")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
private_class_method :parser
|
|
43
|
+
end
|
|
44
|
+
RUBY
|
|
45
|
+
-%>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "<%= File.basename(path) %>/version"
|
|
4
|
+
|
|
5
|
+
<%= nest(<<~RUBY, doc: summary)
|
|
6
|
+
# A greeting for +name+, its surrounding whitespace stripped; nobody named, the world.
|
|
7
|
+
def self.greeting(name)
|
|
8
|
+
who = name.strip
|
|
9
|
+
who = "world" if who.empty?
|
|
10
|
+
"Hello, \#{who}!"
|
|
11
|
+
end
|
|
12
|
+
RUBY
|
|
13
|
+
-%>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<%# `::`: a module of the gem's could be named String, or Hash -%>
|
|
2
|
+
<%= nest(<<~RBS + (exe ? <<~CLI : ""))
|
|
3
|
+
VERSION: ::String
|
|
4
|
+
|
|
5
|
+
def self.greeting: (::String name) -> ::String
|
|
6
|
+
RBS
|
|
7
|
+
|
|
8
|
+
module CLI
|
|
9
|
+
def self.run: (::Array[::String] argv) -> ::Integer
|
|
10
|
+
|
|
11
|
+
private def self.output: (::Hash[::Symbol, bool] options, ::String name) -> ::String?
|
|
12
|
+
|
|
13
|
+
private def self.parser: () -> ::OptionParser
|
|
14
|
+
end
|
|
15
|
+
CLI
|
|
16
|
+
-%>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
class Test<%= constant.delete(":") %> < Minitest::Test
|
|
6
|
+
def test_the_version_is_major_minor_patch
|
|
7
|
+
assert_match(/\A\d+\.\d+\.\d+\z/, <%= constant %>::VERSION)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def test_it_greets_the_name_given
|
|
11
|
+
assert_equal "Hello, Ada!", <%= constant %>.greeting("Ada")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def test_nobody_named_it_greets_the_world
|
|
15
|
+
assert_equal "Hello, world!", <%= constant %>.greeting(" \n")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# A property, checked on many generated names (pbt): surrounding whitespace changes nothing.
|
|
19
|
+
def test_surrounding_whitespace_changes_nothing
|
|
20
|
+
gem = <%= constant %>
|
|
21
|
+
Pbt.assert do
|
|
22
|
+
Pbt.property(Pbt.printable_string) do |name|
|
|
23
|
+
assert_equal gem.greeting(name), gem.greeting(" #{name}\n")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|