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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +47 -0
- data/LICENSE.md +21 -0
- data/README.md +125 -0
- data/SECURITY.md +28 -0
- data/bin/polyrun +108 -0
- data/bin/pray +26 -0
- data/lib/pray/archive.rb +68 -0
- data/lib/pray/auth_client.rb +97 -0
- data/lib/pray/cli/commands/auth.rb +42 -0
- data/lib/pray/cli/commands/distribution.rb +17 -0
- data/lib/pray/cli/commands/init.rb +62 -0
- data/lib/pray/cli/commands/meta.rb +15 -0
- data/lib/pray/cli/commands/packages.rb +107 -0
- data/lib/pray/cli/commands/trust.rb +73 -0
- data/lib/pray/cli/commands/workflow.rb +126 -0
- data/lib/pray/cli/help.rb +168 -0
- data/lib/pray/cli/helpers.rb +166 -0
- data/lib/pray/cli/parse.rb +136 -0
- data/lib/pray/cli/parse_auth.rb +91 -0
- data/lib/pray/cli/parse_trust.rb +152 -0
- data/lib/pray/cli/suggest.rb +57 -0
- data/lib/pray/cli.rb +117 -0
- data/lib/pray/confess.rb +113 -0
- data/lib/pray/config.rb +52 -0
- data/lib/pray/constraint.rb +80 -0
- data/lib/pray/destination.rb +158 -0
- data/lib/pray/dotenv.rb +46 -0
- data/lib/pray/environment.rb +41 -0
- data/lib/pray/error.rb +55 -0
- data/lib/pray/format_manifest.rb +255 -0
- data/lib/pray/format_serialize.rb +135 -0
- data/lib/pray/git_sources.rb +228 -0
- data/lib/pray/hashing.rb +59 -0
- data/lib/pray/invocation.rb +110 -0
- data/lib/pray/literal.rb +382 -0
- data/lib/pray/lockfile.rb +259 -0
- data/lib/pray/lockfile_serialize.rb +125 -0
- data/lib/pray/manifest.rb +126 -0
- data/lib/pray/manifest_formatter.rb +56 -0
- data/lib/pray/manifest_json.rb +128 -0
- data/lib/pray/manifest_parser.rb +152 -0
- data/lib/pray/manifest_parser_blocks.rb +216 -0
- data/lib/pray/manifest_parser_helpers.rb +208 -0
- data/lib/pray/materialize.rb +120 -0
- data/lib/pray/package_spec.rb +245 -0
- data/lib/pray/path_safety.rb +41 -0
- data/lib/pray/plan.rb +83 -0
- data/lib/pray/project_context.rb +67 -0
- data/lib/pray/publish.rb +162 -0
- data/lib/pray/registry.rb +355 -0
- data/lib/pray/render.rb +360 -0
- data/lib/pray/resolve.rb +396 -0
- data/lib/pray/resolve_context.rb +19 -0
- data/lib/pray/serve.rb +130 -0
- data/lib/pray/serve_federation.rb +109 -0
- data/lib/pray/session.rb +90 -0
- data/lib/pray/ssh_agent.rb +115 -0
- data/lib/pray/statement_surface.rb +262 -0
- data/lib/pray/substitute.rb +50 -0
- data/lib/pray/sync.rb +219 -0
- data/lib/pray/terminal.rb +30 -0
- data/lib/pray/trust.rb +201 -0
- data/lib/pray/trust_feed.rb +110 -0
- data/lib/pray/trust_ops.rb +202 -0
- data/lib/pray/verify.rb +257 -0
- data/lib/pray/version.rb +6 -0
- data/lib/pray-cli.rb +4 -0
- data/lib/pray.rb +46 -0
- data/pray-cli.gemspec +48 -0
- 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"
|
data/lib/pray/verify.rb
ADDED
|
@@ -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
|
data/lib/pray/version.rb
ADDED
data/lib/pray-cli.rb
ADDED
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
|