pray-cli 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +47 -0
  3. data/LICENSE.md +21 -0
  4. data/README.md +125 -0
  5. data/SECURITY.md +28 -0
  6. data/bin/polyrun +108 -0
  7. data/bin/pray +26 -0
  8. data/lib/pray/archive.rb +68 -0
  9. data/lib/pray/auth_client.rb +97 -0
  10. data/lib/pray/cli/commands/auth.rb +42 -0
  11. data/lib/pray/cli/commands/distribution.rb +17 -0
  12. data/lib/pray/cli/commands/init.rb +62 -0
  13. data/lib/pray/cli/commands/meta.rb +15 -0
  14. data/lib/pray/cli/commands/packages.rb +107 -0
  15. data/lib/pray/cli/commands/trust.rb +73 -0
  16. data/lib/pray/cli/commands/workflow.rb +126 -0
  17. data/lib/pray/cli/help.rb +168 -0
  18. data/lib/pray/cli/helpers.rb +166 -0
  19. data/lib/pray/cli/parse.rb +136 -0
  20. data/lib/pray/cli/parse_auth.rb +91 -0
  21. data/lib/pray/cli/parse_trust.rb +152 -0
  22. data/lib/pray/cli/suggest.rb +57 -0
  23. data/lib/pray/cli.rb +117 -0
  24. data/lib/pray/confess.rb +113 -0
  25. data/lib/pray/config.rb +52 -0
  26. data/lib/pray/constraint.rb +80 -0
  27. data/lib/pray/destination.rb +158 -0
  28. data/lib/pray/dotenv.rb +46 -0
  29. data/lib/pray/environment.rb +41 -0
  30. data/lib/pray/error.rb +55 -0
  31. data/lib/pray/format_manifest.rb +255 -0
  32. data/lib/pray/format_serialize.rb +135 -0
  33. data/lib/pray/git_sources.rb +228 -0
  34. data/lib/pray/hashing.rb +59 -0
  35. data/lib/pray/invocation.rb +110 -0
  36. data/lib/pray/literal.rb +382 -0
  37. data/lib/pray/lockfile.rb +259 -0
  38. data/lib/pray/lockfile_serialize.rb +125 -0
  39. data/lib/pray/manifest.rb +126 -0
  40. data/lib/pray/manifest_formatter.rb +56 -0
  41. data/lib/pray/manifest_json.rb +128 -0
  42. data/lib/pray/manifest_parser.rb +152 -0
  43. data/lib/pray/manifest_parser_blocks.rb +216 -0
  44. data/lib/pray/manifest_parser_helpers.rb +208 -0
  45. data/lib/pray/materialize.rb +120 -0
  46. data/lib/pray/package_spec.rb +245 -0
  47. data/lib/pray/path_safety.rb +41 -0
  48. data/lib/pray/plan.rb +83 -0
  49. data/lib/pray/project_context.rb +67 -0
  50. data/lib/pray/publish.rb +162 -0
  51. data/lib/pray/registry.rb +355 -0
  52. data/lib/pray/render.rb +360 -0
  53. data/lib/pray/resolve.rb +396 -0
  54. data/lib/pray/resolve_context.rb +19 -0
  55. data/lib/pray/serve.rb +130 -0
  56. data/lib/pray/serve_federation.rb +109 -0
  57. data/lib/pray/session.rb +90 -0
  58. data/lib/pray/ssh_agent.rb +115 -0
  59. data/lib/pray/statement_surface.rb +262 -0
  60. data/lib/pray/substitute.rb +50 -0
  61. data/lib/pray/sync.rb +219 -0
  62. data/lib/pray/terminal.rb +30 -0
  63. data/lib/pray/trust.rb +201 -0
  64. data/lib/pray/trust_feed.rb +110 -0
  65. data/lib/pray/trust_ops.rb +202 -0
  66. data/lib/pray/verify.rb +257 -0
  67. data/lib/pray/version.rb +6 -0
  68. data/lib/pray-cli.rb +4 -0
  69. data/lib/pray.rb +46 -0
  70. data/pray-cli.gemspec +48 -0
  71. metadata +187 -0
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Pray
6
+ module Trust
7
+ module_function
8
+
9
+ def list_policy(scope:, source_url: nil, home: trust_home)
10
+ policy = load_policy_or_default(home)
11
+ source_url ? list_policy_for_source(policy, scope, source_url) : list_policy_all(policy, scope)
12
+ end
13
+
14
+ def show_policy_toml(home = trust_home)
15
+ format_policy(load_policy_or_default(home))
16
+ end
17
+
18
+ def add_allowed_signing_key(key, match_prefix: nil, home: trust_home)
19
+ normalized = normalize_key(key)
20
+ raise Error.unsupported("signing key is empty") if normalized.empty?
21
+
22
+ policy = load_policy_or_default(home)
23
+ rule = match_prefix ? mutable_rule_for_match_prefix!(policy, match_prefix) : policy.default_rule
24
+ unless rule.allowed_signing_keys.any? { |existing| normalize_key(existing) == normalized }
25
+ rule.allowed_signing_keys << normalized
26
+ end
27
+ save_policy(policy, home)
28
+ end
29
+
30
+ def remove_allowed_signing_key(key, match_prefix: nil, home: trust_home)
31
+ normalized = normalize_key(key)
32
+ raise Error.unsupported("signing key is empty") if normalized.empty?
33
+
34
+ policy = load_policy_or_default(home)
35
+ rule = match_prefix ? mutable_rule_for_match_prefix!(policy, match_prefix) : policy.default_rule
36
+ before = rule.allowed_signing_keys.length
37
+ rule.allowed_signing_keys.reject! { |existing| normalize_key(existing) == normalized }
38
+ if rule.allowed_signing_keys.length == before
39
+ raise Error.unsupported(
40
+ "signing key not found in allowed_signing_keys for #{match_prefix || "<default>"}"
41
+ )
42
+ end
43
+ save_policy(policy, home)
44
+ end
45
+
46
+ def set_require_signed_commit(match_prefix, enabled, home: trust_home)
47
+ raise Error.unsupported("match-prefix is empty") if match_prefix.to_s.strip.empty?
48
+
49
+ policy = load_policy_or_default(home)
50
+ mutable_rule_for_match_prefix!(policy, match_prefix).require_signed_commit = enabled
51
+ save_policy(policy, home)
52
+ end
53
+
54
+ def set_allow(match_prefix, allow, home: trust_home)
55
+ raise Error.unsupported("match-prefix is empty") if match_prefix.to_s.strip.empty?
56
+
57
+ policy = load_policy_or_default(home)
58
+ mutable_rule_for_match_prefix!(policy, match_prefix).allow = allow
59
+ save_policy(policy, home)
60
+ end
61
+
62
+ def import_registry(source_url, match_prefix: nil, include_host_key: false, home: trust_home)
63
+ if source_url.start_with?("pray+ssh://", "ssh+pray://")
64
+ raise Error.unsupported("pray_ssh registry import is not implemented yet in pray-cli Ruby")
65
+ end
66
+ raise Error.unsupported("--host-key requires pray_ssh sources") if include_host_key
67
+
68
+ publishers = fetch_ssh_publisher_fingerprints(source_url)
69
+ raise Error.unsupported("no v1/ssh_publishers.json found for #{source_url}") if publishers.nil?
70
+ if publishers.empty?
71
+ raise Error.unsupported(
72
+ "v1/ssh_publishers.json for #{source_url} lists no publisher fingerprints"
73
+ )
74
+ end
75
+
76
+ prefix = match_prefix || source_url
77
+ policy = load_policy_or_default(home)
78
+ rule = mutable_rule_for_match_prefix!(policy, prefix)
79
+ publishers_added = append_missing!(rule.allowed_publishers, publishers)
80
+ save_policy(policy, home)
81
+ {publishers_added: publishers_added, host_keys_added: 0}
82
+ end
83
+
84
+ def import_repo(source_url, repository, match_prefix: nil, home: trust_home)
85
+ keys = repository_signing_keys(repository)
86
+ if keys.empty?
87
+ raise Error.unsupported(
88
+ "no commit signing key/fingerprint found for HEAD in #{repository}"
89
+ )
90
+ end
91
+
92
+ prefix = match_prefix || source_url
93
+ policy = load_policy_or_default(home)
94
+ rule = mutable_rule_for_match_prefix!(policy, prefix)
95
+ added = append_missing!(rule.allowed_signing_keys, keys)
96
+ save_policy(policy, home)
97
+ added
98
+ end
99
+
100
+ def list_policy_all(policy, scope)
101
+ lines = []
102
+ if %i[all global].include?(scope)
103
+ lines << format_rule_block("scope: global", policy.default_rule).rstrip
104
+ end
105
+ if %i[all local].include?(scope)
106
+ if policy.rules.empty?
107
+ lines << "scope: local\n (no rules)"
108
+ else
109
+ policy.rules.sort_by { |rule| rule.match_prefix.to_s }.each do |rule|
110
+ prefix = rule.match_prefix || "-"
111
+ lines << format_rule_block("scope: local (#{prefix})", rule).rstrip
112
+ end
113
+ end
114
+ end
115
+ lines.join("\n\n")
116
+ end
117
+
118
+ def list_policy_for_source(policy, scope, source_url)
119
+ lines = ["source: #{source_url}", ""]
120
+ if %i[all global].include?(scope)
121
+ lines << format_rule_block("scope: global", policy.default_rule).rstrip
122
+ lines << ""
123
+ end
124
+ if %i[all local].include?(scope)
125
+ matched = policy.rules.select do |rule|
126
+ prefix = rule.match_prefix
127
+ prefix && !prefix.empty? && source_url.start_with?(prefix)
128
+ end
129
+ matched.sort_by! { |rule| -(rule.match_prefix&.length || 0) }
130
+ lines << if matched.empty?
131
+ "scope: local\n (no matching rules)"
132
+ else
133
+ matched.map { |rule|
134
+ format_rule_block("scope: local (#{rule.match_prefix})", rule).rstrip
135
+ }.join("\n")
136
+ end
137
+ lines << ""
138
+ end
139
+ if scope == :all
140
+ effective = best_rule(policy, source_url)
141
+ lines << if effective.match_prefix
142
+ "effective_scope: local (#{effective.match_prefix})"
143
+ else
144
+ "effective_scope: global"
145
+ end
146
+ end
147
+ lines.join("\n").strip
148
+ end
149
+
150
+ def fetch_ssh_publisher_fingerprints(source_url)
151
+ body = read_distribution_bytes(source_url, "v1/ssh_publishers.json")
152
+ return nil unless body
153
+
154
+ data = JSON.parse(body)
155
+ Array(data["publishers"]).filter_map do |entry|
156
+ fingerprint = entry["fingerprint"].to_s
157
+ next if fingerprint.empty?
158
+
159
+ normalize_key(fingerprint)
160
+ end
161
+ rescue JSON::ParserError => error
162
+ raise Error.parse("ssh publishers", error.message)
163
+ end
164
+
165
+ def read_distribution_bytes(source_url, relative_path)
166
+ if source_url.start_with?("http://", "https://")
167
+ begin
168
+ return Registry.send(:http_get, Registry.send(:join_url, source_url, relative_path))
169
+ rescue Error
170
+ return nil
171
+ end
172
+ end
173
+
174
+ root = source_url.delete_prefix("file://")
175
+ path = File.join(root, relative_path)
176
+ return nil unless File.file?(path)
177
+
178
+ File.read(path)
179
+ end
180
+
181
+ def repository_signing_keys(repository)
182
+ keys = []
183
+ key = git_format(repository, "%GK")
184
+ keys << normalize_key(key) unless key.empty?
185
+ fingerprint = git_format(repository, "%GF")
186
+ keys << normalize_key(fingerprint) unless fingerprint.empty?
187
+ keys.uniq
188
+ end
189
+
190
+ def git_format(repository, format)
191
+ output = IO.popen(
192
+ ["git", "-C", repository, "log", "-1", "--format=#{format}"],
193
+ err: File::NULL, &:read
194
+ )
195
+ return "" unless $?.success?
196
+
197
+ output.to_s.strip
198
+ end
199
+ end
200
+ end
201
+
202
+ require_relative "trust_feed"
@@ -0,0 +1,257 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pray
4
+ VerificationFinding = Struct.new(:kind, :message) do
5
+ def warning? = kind == "orphan_marker"
6
+ def error? = !warning?
7
+ end
8
+
9
+ VerificationReport = Struct.new(:findings) do
10
+ def initialize(findings: [])
11
+ super
12
+ end
13
+
14
+ def clean? = findings.empty?
15
+ def warnings? = findings.any?(&:warning?)
16
+ def errors? = findings.any?(&:error?)
17
+ end
18
+
19
+ module Verify
20
+ module_function
21
+
22
+ def inspect_project(project, lockfile)
23
+ collect_verification_report(project, lockfile).first
24
+ end
25
+
26
+ def verify_project(project, lockfile, strict: false)
27
+ report = inspect_project(project, lockfile)
28
+ return report if report.clean?
29
+
30
+ if strict || report.errors?
31
+ raise Error.verify(format_verification_report(report))
32
+ end
33
+
34
+ report
35
+ end
36
+
37
+ def drift_project(project, lockfile)
38
+ report, rendered_targets = collect_verification_report(project, lockfile)
39
+ Render.render_project(project).each do |target|
40
+ normalized_fresh = Hashing.normalize_line_endings(target.content)
41
+ on_disk = rendered_targets[target.path]
42
+ on_disk = Hashing.normalize_line_endings(on_disk) if on_disk
43
+ unless on_disk == normalized_fresh
44
+ report.findings << VerificationFinding.new(
45
+ kind: "renderer_drift",
46
+ message: "#{target.path} differs from fresh render"
47
+ )
48
+ end
49
+ unless lockfile_targets(lockfile).include?(target.path)
50
+ report.findings << VerificationFinding.new(
51
+ kind: "renderer_drift",
52
+ message: "#{target.path} is not tracked in lockfile"
53
+ )
54
+ end
55
+ end
56
+
57
+ if report.clean?
58
+ report
59
+ else
60
+ raise Error.verify(format_drift_report(report))
61
+ end
62
+ end
63
+
64
+ def collect_verification_report(project, lockfile)
65
+ report = VerificationReport.new
66
+ rendered_targets = {}
67
+
68
+ if project.manifest_hash != lockfile.manifest_hash
69
+ report.findings << VerificationFinding.new(
70
+ kind: "verify_error",
71
+ message: "Prayfile changed since `Prayfile.lock` was generated. Run `pray install` to refresh the lockfile."
72
+ )
73
+ end
74
+
75
+ locked_packages = lockfile.package.to_h { |entry| [entry.name, entry] }
76
+ project.packages.each do |package|
77
+ locked = locked_packages.delete(package.declaration.name)
78
+ if locked
79
+ if locked.tree_hash != package.tree_hash
80
+ report.findings << VerificationFinding.new(
81
+ kind: "package_integrity",
82
+ message: "Package `#{package.declaration.name}` no longer matches the locked tree hash. Run `pray install` to re-resolve packages."
83
+ )
84
+ end
85
+ if locked.version != package.spec.version
86
+ report.findings << VerificationFinding.new(
87
+ kind: "verify_error",
88
+ message: "Package `#{package.declaration.name}` resolved to version #{package.spec.version} but `Prayfile.lock` has #{locked.version}. Run `pray install` to refresh the lockfile."
89
+ )
90
+ end
91
+ else
92
+ report.findings << VerificationFinding.new(
93
+ kind: "verify_error",
94
+ message: "Package `#{package.declaration.name}` is declared in Prayfile but missing from `Prayfile.lock`. Run `pray install` to update the lockfile."
95
+ )
96
+ end
97
+ end
98
+ locked_packages.each_value do |locked|
99
+ report.findings << VerificationFinding.new(
100
+ kind: "verify_error",
101
+ message: "Package `#{locked.name}` is in `Prayfile.lock` but not declared in Prayfile. Remove it from the lockfile with `pray install` or add it back to Prayfile."
102
+ )
103
+ end
104
+
105
+ target_spans = lockfile.managed_span.group_by(&:target)
106
+ target_spans.each do |target_path, spans|
107
+ absolute_path = File.join(project.project_root, target_path)
108
+ unless File.exist?(absolute_path)
109
+ report.findings << VerificationFinding.new(
110
+ kind: "verify_error",
111
+ message: "Rendered file `#{target_path}` is missing. Run `pray install` to generate it."
112
+ )
113
+ next
114
+ end
115
+
116
+ text = File.read(absolute_path)
117
+ rendered_targets[target_path] = text
118
+ lines = text.lines(chomp: true)
119
+ markers = marker_positions(lines)
120
+ spans.each do |span|
121
+ marker = markers[span.id]
122
+ if marker.nil?
123
+ report.findings << VerificationFinding.new(
124
+ kind: "removed_prayer",
125
+ message: "`#{target_path}` is missing managed marker `#{span.id}` for `#{span.package}::#{span.export}`. Run `pray install` to restore the managed span."
126
+ )
127
+ else
128
+ open_line, close_line, checksum = marker
129
+ if checksum != span.ideal_checksum
130
+ report.findings << VerificationFinding.new(
131
+ kind: "custom_implementation",
132
+ message: "`#{target_path}` marker `#{span.id}` (`#{span.package}::#{span.export}`) was edited. Restore the managed block or run `pray install` to regenerate it."
133
+ )
134
+ end
135
+ if open_line != span.open_line || close_line != span.close_line
136
+ report.findings << VerificationFinding.new(
137
+ kind: "position_drift",
138
+ message: "`#{target_path}` marker `#{span.id}` (`#{span.package}::#{span.export}`) moved to different lines. Run `pray install` to restore expected positions."
139
+ )
140
+ end
141
+ end
142
+ end
143
+ find_orphan_marker_findings(spans, markers, target_path).each do |finding|
144
+ report.findings << finding
145
+ end
146
+ end
147
+
148
+ project.local_files.each do |local|
149
+ next if local.optional
150
+ next if File.exist?(local.path)
151
+
152
+ report.findings << VerificationFinding.new(
153
+ kind: "verify_error",
154
+ message: Resolve.missing_local_embed_guidance(local.manifest_path)
155
+ )
156
+ end
157
+
158
+ [report, rendered_targets]
159
+ end
160
+
161
+ def find_orphan_marker_findings(spans, markers, target_path)
162
+ tracked_ids = spans.to_h { |span| [span.id, true] }
163
+ markers.keys.filter_map do |marker_id|
164
+ next if marker_id == "0" || tracked_ids[marker_id]
165
+
166
+ VerificationFinding.new(
167
+ kind: "orphan_marker",
168
+ message: "`#{target_path}` contains marker `#{marker_id}` that is not tracked in `Prayfile.lock`. Remove the marker or run `pray install` to reconcile."
169
+ )
170
+ end
171
+ end
172
+
173
+ def marker_positions(lines)
174
+ markers = {}
175
+ active = nil
176
+ lines.each_with_index do |line, index|
177
+ parsed = parse_marker(line)
178
+ if parsed.nil?
179
+ active[2] << line if active
180
+ next
181
+ end
182
+ case parsed
183
+ when :ignore
184
+ next
185
+ else
186
+ identifier = parsed
187
+ if active.nil?
188
+ active = [identifier, index + 1, []]
189
+ elsif active[0] == identifier
190
+ checksum = Hashing.checksum_managed_body_line_refs(active[2])
191
+ markers[active[0]] = [active[1], index + 1, checksum]
192
+ active = nil
193
+ end
194
+ end
195
+ end
196
+ markers
197
+ end
198
+
199
+ def parse_marker(line)
200
+ trimmed = line.strip
201
+ remainder = trimmed.delete_prefix("<!--").strip
202
+ return nil unless remainder.start_with?("pray:")
203
+
204
+ content = remainder.delete_prefix("pray:").strip.delete_suffix("-->").strip
205
+ return :ignore if content == "0 ignore-comments"
206
+ return content if content.match?(/\A[a-z0-9]+\z/)
207
+
208
+ nil
209
+ end
210
+
211
+ def lockfile_targets(lockfile)
212
+ lockfile.target.flat_map(&:outputs)
213
+ end
214
+
215
+ def format_verification_report(report)
216
+ report.findings.map { |finding| "#{finding.kind}: #{finding.message}" }.join("\n")
217
+ end
218
+
219
+ def format_drift_report(report)
220
+ sections = {}
221
+ report.findings.each do |finding|
222
+ section = drift_section_for_kind(finding.kind)
223
+ sections[section] ||= []
224
+ sections[section] << finding
225
+ end
226
+
227
+ ordered = [
228
+ "Lockfile changes",
229
+ "Package changes",
230
+ "Managed span changes",
231
+ "Rendered file changes",
232
+ "Warnings"
233
+ ]
234
+ lines = []
235
+ ordered.each do |section|
236
+ findings = sections[section]
237
+ next unless findings
238
+
239
+ lines << section
240
+ findings.each do |finding|
241
+ lines << " #{finding.kind}: #{finding.message}"
242
+ end
243
+ end
244
+ lines.join("\n")
245
+ end
246
+
247
+ def drift_section_for_kind(kind)
248
+ case kind
249
+ when "verify_error" then "Lockfile changes"
250
+ when "package_integrity" then "Package changes"
251
+ when "custom_implementation", "removed_prayer", "position_drift", "orphan_marker" then "Managed span changes"
252
+ when "renderer_drift" then "Rendered file changes"
253
+ else "Warnings"
254
+ end
255
+ end
256
+ end
257
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pray
4
+ VERSION = "1.6.0"
5
+ GENERATED_BY = "pray 1.6.0"
6
+ end
data/lib/pray-cli.rb ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pray"
4
+ require_relative "pray/cli"
data/lib/pray.rb ADDED
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "pray/version"
4
+ require_relative "pray/error"
5
+ require_relative "pray/literal"
6
+ require_relative "pray/statement_surface"
7
+ require_relative "pray/substitute"
8
+ require_relative "pray/hashing"
9
+ require_relative "pray/constraint"
10
+ require_relative "pray/manifest_json"
11
+ require_relative "pray/destination"
12
+ require_relative "pray/manifest"
13
+ require_relative "pray/format_serialize"
14
+ require_relative "pray/format_manifest"
15
+ require_relative "pray/package_spec"
16
+ require_relative "pray/lockfile"
17
+ require_relative "pray/config"
18
+ require_relative "pray/dotenv"
19
+ require_relative "pray/project_context"
20
+ require_relative "pray/environment"
21
+ require_relative "pray/resolve_context"
22
+ require_relative "pray/invocation"
23
+ require_relative "pray/resolve"
24
+ require_relative "pray/render"
25
+ require_relative "pray/verify"
26
+ require_relative "pray/registry"
27
+ require_relative "pray/archive"
28
+ require_relative "pray/plan"
29
+ require_relative "pray/git_sources"
30
+ require_relative "pray/publish"
31
+ require_relative "pray/serve"
32
+ require_relative "pray/session"
33
+ require_relative "pray/auth_client"
34
+ require_relative "pray/confess"
35
+ require_relative "pray/sync"
36
+ require_relative "pray/trust"
37
+ require_relative "pray/materialize"
38
+
39
+ class << Pray
40
+ public :parse_manifest, :read_manifest_text, :format_package_declaration, :replace_package_declaration,
41
+ :parse_package_spec, :parse_lockfile, :read_lockfile, :serialize_lockfile, :lockfile_hash,
42
+ :write_lockfile, :write_lockfile_if_changed, :lockfiles_equivalent?, :build_lockfile,
43
+ :resolve_project, :render_project, :materialize_project,
44
+ :inspect_project, :verify_project, :drift_project,
45
+ :default_manifest_path, :default_lockfile_path, :project_root_from_manifest
46
+ end
data/pray-cli.gemspec ADDED
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/pray/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "pray-cli"
7
+ spec.version = Pray::VERSION
8
+ spec.authors = ["Andrei Makarov"]
9
+ spec.email = ["contact@kiskolabs.com"]
10
+
11
+ spec.summary = "Package manager for the language placed before inference (Prayfile)"
12
+ spec.description = "Ruby Prayfile CLI and library: resolve, lock, render, verify, and publish inference input packages. Consumes path, git, and registry distribution points."
13
+ spec.homepage = "https://pray.kisko.dev"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.1.0"
16
+
17
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
18
+ Dir[
19
+ "lib/**/*",
20
+ "bin/**/*",
21
+ "README.md",
22
+ "LICENSE*",
23
+ "CHANGELOG.md",
24
+ "SECURITY.md",
25
+ "pray-cli.gemspec"
26
+ ].select { |path| File.file?(path) }
27
+ end
28
+
29
+ spec.bindir = "bin"
30
+ spec.executables = ["pray"]
31
+ spec.require_paths = ["lib"]
32
+
33
+ spec.metadata = {
34
+ "homepage_uri" => spec.homepage,
35
+ "source_code_uri" => "https://github.com/kiskolabs/pray/tree/main/rubygems/pray-cli",
36
+ "changelog_uri" => "https://github.com/kiskolabs/pray/blob/main/rubygems/pray-cli/CHANGELOG.md",
37
+ "bug_tracker_uri" => "https://github.com/kiskolabs/pray/issues",
38
+ "documentation_uri" => "https://github.com/kiskolabs/pray/tree/main/rubygems/pray-cli",
39
+ "rubygems_mfa_required" => "true"
40
+ }
41
+
42
+ spec.add_dependency "toml-rb", "~> 4.0"
43
+ spec.add_dependency "base64", "~> 0.2"
44
+
45
+ spec.add_development_dependency "rspec", "~> 3.13"
46
+ spec.add_development_dependency "polyrun", "~> 2.2"
47
+ spec.add_development_dependency "rbs", "~> 3.9"
48
+ end