pray-cli 1.25.0 → 1.26.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 +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +1 -0
- data/lib/pray/cli/commands/packages.rb +1 -0
- data/lib/pray/cli/commands/search.rb +43 -0
- data/lib/pray/cli/commands/workflow.rb +1 -1
- data/lib/pray/cli/help.rb +3 -1
- data/lib/pray/cli/helpers.rb +2 -2
- data/lib/pray/cli/parse.rb +27 -0
- data/lib/pray/cli.rb +2 -0
- data/lib/pray/lockfile.rb +1 -1
- data/lib/pray/materialize.rb +2 -2
- data/lib/pray/package_relation.rb +44 -0
- data/lib/pray/package_spec.rb +4 -2
- data/lib/pray/package_spec_render.rb +3 -0
- data/lib/pray/path_safety.rb +4 -2
- data/lib/pray/plan.rb +4 -3
- data/lib/pray/registry.rb +3 -3
- data/lib/pray/registry_search.rb +73 -0
- data/lib/pray/render_dest.rb +7 -5
- data/lib/pray/render_layout.rb +3 -2
- data/lib/pray/render_patch.rb +25 -3
- data/lib/pray/render_reconcile.rb +56 -0
- data/lib/pray/url_redaction.rb +20 -0
- data/lib/pray/verify.rb +1 -1
- data/lib/pray/version.rb +2 -2
- data/lib/pray.rb +4 -0
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '087854aaa5f5bfead826eaa843615d2a52ba5f52236cc953b55d715c66c6f84b'
|
|
4
|
+
data.tar.gz: 2600fce7bf405cdcc5f9ef78da7e7026dd408f0babd026ecbf869dc09da2de6f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1242427bff2198aafc4c74e2c6977128150dde7ba0d7fadecf9d92df8bcaf68662028f45e11419faf8e3a25b9b3c9c959034190dff054cb264c93b303ee1fe3a
|
|
7
|
+
data.tar.gz: 8d4b9ebc38d9fef0360ec9173b7e53ff92baff22f146e349f6ab049b48b1d0668883bfa0acb6bd3c8a8fd42993e91d06bc96b310b58df09d83dcfa0c377899f8
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 1.26.0 (2026-10-30)
|
|
6
|
+
|
|
7
|
+
- Add validated `related` references to prayspec parsing, rendering, and `pray explain`.
|
|
8
|
+
- Make `render --check`, frozen install, and `plan` compare destinations with the bytes the write path would produce. Remove unchanged managed spans retired by the previous lock while preserving edited or unknown spans for explicit review.
|
|
9
|
+
- Add `pray search` parity with the stable distribution-index contract. Search package names by substring through a declared source, local distribution root, or registry URL, and label catalog summaries as unverified claims in help.
|
|
10
|
+
|
|
5
11
|
## 1.25.0 (2026-10-26)
|
|
6
12
|
|
|
7
13
|
- Bump the gem version to 1.25.0 with the coordinated language-registry cut.
|
data/README.md
CHANGED
|
@@ -85,6 +85,7 @@ module Pray
|
|
|
85
85
|
lines << "source: #{package_source_summary(package)}"
|
|
86
86
|
lines << "exports: #{format_list(package.selected_exports)}"
|
|
87
87
|
lines << "dependencies: #{format_list(package.spec.dependencies.map(&:name))}"
|
|
88
|
+
lines << "related prayers: #{format_list(package.spec.related)}"
|
|
88
89
|
lines << "tree hash: #{package.tree_hash}"
|
|
89
90
|
lines << "artifact hash: #{package.artifact_hash}"
|
|
90
91
|
if lockfile_package
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module CLI
|
|
5
|
+
def search_command(query:, source:, root:, url:)
|
|
6
|
+
hits = if root
|
|
7
|
+
RegistrySearch.search_local_registry(root, query)
|
|
8
|
+
elsif url
|
|
9
|
+
RegistrySearch.search_http_registry(url, query)
|
|
10
|
+
else
|
|
11
|
+
search_declared_source(query, source)
|
|
12
|
+
end
|
|
13
|
+
if hits.empty?
|
|
14
|
+
puts "no packages matched"
|
|
15
|
+
else
|
|
16
|
+
hits.each { |hit| puts(hit.summary ? "#{hit.name} — #{hit.summary}" : hit.name) }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def search_declared_source(query, source_name)
|
|
21
|
+
manifest = Pray.parse_manifest(Pray.read_manifest_text(manifest_path))
|
|
22
|
+
source = source_name ? manifest.sources.find { |entry| entry.name == source_name } : searchable_source(manifest)
|
|
23
|
+
raise Error.resolution("unknown source: #{source_name}") if source_name && !source
|
|
24
|
+
unless source
|
|
25
|
+
raise Error.resolution("Prayfile has no searchable sources; pass --root PATH or --url URL")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
case source.kind
|
|
29
|
+
when "path"
|
|
30
|
+
root = File.expand_path(source.url, File.dirname(manifest_path))
|
|
31
|
+
RegistrySearch.search_local_registry(root, query)
|
|
32
|
+
when "registry", "static index"
|
|
33
|
+
RegistrySearch.search_http_registry(source.url, query)
|
|
34
|
+
else
|
|
35
|
+
raise Error.unsupported("search does not support source kind #{source.kind}")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def searchable_source(manifest)
|
|
40
|
+
manifest.sources.find { |source| %w[registry static\ index].include?(source.kind) } || manifest.sources.first
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/pray/cli/help.rb
CHANGED
|
@@ -36,6 +36,7 @@ module Pray
|
|
|
36
36
|
|
|
37
37
|
INSPECT_COMMANDS = [
|
|
38
38
|
"list list declared packages",
|
|
39
|
+
"search <query> [--source|--root|--url] find packages in a distribution index",
|
|
39
40
|
"outdated [--remote] show constraint vs resolved versions",
|
|
40
41
|
"explain <package> show why a package was selected",
|
|
41
42
|
"tree print the dependency tree"
|
|
@@ -102,7 +103,7 @@ module Pray
|
|
|
102
103
|
|
|
103
104
|
Without --check, render writes dest. It does not write Prayfile.lock.
|
|
104
105
|
Use pray plan to preview a write.
|
|
105
|
-
--check writes nothing. It fails
|
|
106
|
+
--check writes nothing. It fails when render would change dest.
|
|
106
107
|
TEXT
|
|
107
108
|
"format" => <<~TEXT.strip,
|
|
108
109
|
rewrite Prayfile to recommended destination DSL
|
|
@@ -187,6 +188,7 @@ module Pray
|
|
|
187
188
|
Subcommands: list, show, add-key, remove-key, set-signed, set-allow, import-repo, import-registry, check
|
|
188
189
|
TEXT
|
|
189
190
|
"list" => "list declared packages\n\nUsage: pray list",
|
|
191
|
+
"search" => "search a distribution index for package names\n\nUsage: pray search <query> [--source NAME | --root PATH | --url URL]\n\nMatches package names by case-insensitive substring. Summaries are unverified catalog claims.",
|
|
190
192
|
"outdated" => <<~TEXT.strip,
|
|
191
193
|
show constraint vs resolved versions
|
|
192
194
|
|
data/lib/pray/cli/helpers.rb
CHANGED
|
@@ -59,8 +59,8 @@ module Pray
|
|
|
59
59
|
def ensure_rendered_outputs_current(project, rendered)
|
|
60
60
|
rendered.each do |target|
|
|
61
61
|
path = File.join(project.project_root, target.path)
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
retired = RenderReconcile.retired_span_checksums(project.previous_lockfile, target)
|
|
63
|
+
unless RenderReconcile.current?(path, target.path, target.content, retired)
|
|
64
64
|
raise Error.render("#{path} is stale; rerun pray install to regenerate it or pray plan to inspect the diff")
|
|
65
65
|
end
|
|
66
66
|
end
|
data/lib/pray/cli/parse.rb
CHANGED
|
@@ -64,6 +64,7 @@ module Pray
|
|
|
64
64
|
when "serve" then [:serve, parse_serve_arguments(arguments)]
|
|
65
65
|
when "confess" then [:confess, parse_confess_arguments(arguments)]
|
|
66
66
|
when "list" then [:list]
|
|
67
|
+
when "search" then [:search, parse_search_arguments(arguments)]
|
|
67
68
|
when "outdated" then [:outdated, arguments]
|
|
68
69
|
when "explain" then [:explain, arguments.shift]
|
|
69
70
|
when "vendor" then [:vendor]
|
|
@@ -121,6 +122,32 @@ module Pray
|
|
|
121
122
|
{unused: unused}
|
|
122
123
|
end
|
|
123
124
|
|
|
125
|
+
def parse_search_arguments(arguments)
|
|
126
|
+
options = {query: nil, source: nil, root: nil, url: nil}
|
|
127
|
+
while (argument = arguments.shift)
|
|
128
|
+
case argument
|
|
129
|
+
when "--source" then options[:source] = search_option_value(arguments, argument)
|
|
130
|
+
when "--root" then options[:root] = search_option_value(arguments, argument)
|
|
131
|
+
when "--url" then options[:url] = search_option_value(arguments, argument)
|
|
132
|
+
when /^--/ then raise Error.usage("unknown search flag: #{argument}")
|
|
133
|
+
else
|
|
134
|
+
raise Error.usage("unexpected search argument: #{argument}") if options[:query]
|
|
135
|
+
|
|
136
|
+
options[:query] = argument
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
raise Error.usage("search requires a query") unless options[:query]
|
|
140
|
+
|
|
141
|
+
options
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def search_option_value(arguments, flag)
|
|
145
|
+
value = arguments.shift
|
|
146
|
+
raise Error.usage("#{flag} requires a value") unless value && !value.start_with?("--")
|
|
147
|
+
|
|
148
|
+
value
|
|
149
|
+
end
|
|
150
|
+
|
|
124
151
|
def parse_add_arguments(arguments)
|
|
125
152
|
name = arguments.shift
|
|
126
153
|
raise Error.unsupported("add requires a package name") unless name
|
data/lib/pray/cli.rb
CHANGED
|
@@ -14,6 +14,7 @@ require_relative "cli/commands/init_prayer"
|
|
|
14
14
|
require_relative "cli/commands/workflow"
|
|
15
15
|
require_relative "cli/commands/update_latest"
|
|
16
16
|
require_relative "cli/commands/packages"
|
|
17
|
+
require_relative "cli/commands/search"
|
|
17
18
|
require_relative "cli/commands/distribution"
|
|
18
19
|
require_relative "cli/commands/trust"
|
|
19
20
|
require_relative "cli/commands/auth"
|
|
@@ -119,6 +120,7 @@ module Pray
|
|
|
119
120
|
in [:confess, confess_args] then confess_command(**confess_args)
|
|
120
121
|
in [:sync, sync_args] then sync_command(**sync_args)
|
|
121
122
|
in [:list] then list_command
|
|
123
|
+
in [:search, search_args] then search_command(**search_args)
|
|
122
124
|
in [:outdated, arguments] then outdated_command(arguments)
|
|
123
125
|
in [:explain, name] then explain_command(name)
|
|
124
126
|
in [:vendor] then raise Error.unsupported("vendor is not implemented yet in pray-cli Ruby")
|
data/lib/pray/lockfile.rb
CHANGED
|
@@ -169,7 +169,7 @@ module Pray
|
|
|
169
169
|
end
|
|
170
170
|
|
|
171
171
|
def build_lockfile(manifest_hash, environment, project_root, manifest_sources, manifest_targets, rendered, packages, source_revisions, source_host_keys, project = nil)
|
|
172
|
-
rendered = Render.layout_rendered_targets(project, rendered) if project
|
|
172
|
+
rendered = Render.layout_rendered_targets(project, rendered, project.previous_lockfile) if project
|
|
173
173
|
lockfile = Lockfile.new(
|
|
174
174
|
manifest_hash: manifest_hash,
|
|
175
175
|
environment: environment,
|
data/lib/pray/materialize.rb
CHANGED
|
@@ -90,8 +90,8 @@ module Pray
|
|
|
90
90
|
if frozen
|
|
91
91
|
rendered.each do |target|
|
|
92
92
|
path = File.join(project.project_root, target.path)
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
retired = RenderReconcile.retired_span_checksums(existing, target)
|
|
94
|
+
unless RenderReconcile.current?(path, target.path, target.content, retired)
|
|
95
95
|
raise Error.render(
|
|
96
96
|
"#{path} is stale; rerun install to regenerate it or plan to inspect the diff"
|
|
97
97
|
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module PackageRelation
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def canonicalize(references)
|
|
8
|
+
Array(references).map(&:strip).uniq.sort.tap do |canonical|
|
|
9
|
+
invalid = canonical.find { |reference| !valid?(reference) }
|
|
10
|
+
raise Error.parse("prayspec", "invalid related prayer reference") if invalid
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def valid?(reference)
|
|
15
|
+
return false if reference.empty? || reference.match?(/[\\[:cntrl:]]/)
|
|
16
|
+
if reference.match?(/\A[A-Za-z][A-Za-z0-9+.-]*:.+\z/)
|
|
17
|
+
return !uri_has_userinfo?(reference)
|
|
18
|
+
end
|
|
19
|
+
if reference.start_with?("/")
|
|
20
|
+
path = locator_path(reference)
|
|
21
|
+
return path.length > 1 && !path.start_with?("//")
|
|
22
|
+
end
|
|
23
|
+
if reference.start_with?("./", "../")
|
|
24
|
+
return locator_path(reference).split("/").any? do |segment|
|
|
25
|
+
!segment.empty? && ![".", ".."].include?(segment)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
return false if reference.match?(/[?#]/)
|
|
30
|
+
|
|
31
|
+
segments = reference.split("/", -1)
|
|
32
|
+
segments.length == 2 && segments.none? { |segment| segment.empty? || [".", ".."].include?(segment) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def locator_path(reference)
|
|
36
|
+
reference.split(/[?#]/, 2).first
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def uri_has_userinfo?(reference)
|
|
40
|
+
after_scheme = reference.split("://", 2)[1]
|
|
41
|
+
after_scheme && after_scheme.split(%r{[/?#]}, 2).first.include?("@")
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
data/lib/pray/package_spec.rb
CHANGED
|
@@ -14,13 +14,13 @@ module Pray
|
|
|
14
14
|
PackageSpec = Struct.new(
|
|
15
15
|
:name, :version, :summary, :description, :authors, :maintainers, :license, :homepage,
|
|
16
16
|
:source_code_uri, :changelog_uri, :prayfile_version, :files, :exports,
|
|
17
|
-
:skills, :templates, :adapters, :targets, :dependencies, :metadata, :upstream
|
|
17
|
+
:skills, :templates, :adapters, :targets, :related, :dependencies, :metadata, :upstream
|
|
18
18
|
) do
|
|
19
19
|
def initialize(
|
|
20
20
|
name: "", version: "", summary: nil, description: nil, authors: [], maintainers: [], license: nil,
|
|
21
21
|
homepage: nil, source_code_uri: nil, changelog_uri: nil, prayfile_version: nil,
|
|
22
22
|
files: [], exports: {}, skills: {}, templates: {}, adapters: {}, targets: [],
|
|
23
|
-
dependencies: [], metadata: {}, upstream: nil
|
|
23
|
+
related: [], dependencies: [], metadata: {}, upstream: nil
|
|
24
24
|
)
|
|
25
25
|
super
|
|
26
26
|
end
|
|
@@ -50,6 +50,7 @@ module Pray
|
|
|
50
50
|
copy.authors = Array(authors).sort
|
|
51
51
|
copy.maintainers = Array(maintainers).sort
|
|
52
52
|
copy.targets = Array(targets).sort
|
|
53
|
+
copy.related = PackageRelation.canonicalize(related)
|
|
53
54
|
copy.dependencies = dependencies.sort_by { |dependency| [dependency.name, dependency.constraint, dependency.optional] }
|
|
54
55
|
end
|
|
55
56
|
end
|
|
@@ -258,6 +259,7 @@ module Pray
|
|
|
258
259
|
when "maintainers" then spec.maintainers = array_of_strings(value)
|
|
259
260
|
when "files" then spec.files = array_of_strings(value)
|
|
260
261
|
when "targets" then spec.targets = array_of_strings(value)
|
|
262
|
+
when "related" then spec.related = array_of_strings(value)
|
|
261
263
|
when "exports" then spec.exports = parse_exports(value)
|
|
262
264
|
when "skills" then spec.skills = parse_skills(value)
|
|
263
265
|
when "templates" then spec.templates = parse_templates(value)
|
|
@@ -51,6 +51,9 @@ module Pray
|
|
|
51
51
|
push_named_paths(lines, "templates", spec.templates)
|
|
52
52
|
push_string_map(lines, "adapters", spec.adapters)
|
|
53
53
|
push_array(lines, "targets", spec.targets) unless spec.targets.nil? || spec.targets.empty?
|
|
54
|
+
unless spec.related.nil? || spec.related.empty?
|
|
55
|
+
push_array(lines, "related", spec.related)
|
|
56
|
+
end
|
|
54
57
|
end
|
|
55
58
|
|
|
56
59
|
def push_relation_fields(lines, spec)
|
data/lib/pray/path_safety.rb
CHANGED
|
@@ -21,8 +21,10 @@ module Pray
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def reject_unsafe_package_name!(package_name)
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
segments = package_name&.split("/", -1)
|
|
25
|
+
if segments&.length != 2 || segments.any? do |segment|
|
|
26
|
+
segment.empty? || [".", ".."].include?(segment) || segment.include?("\0") || segment.include?("\\")
|
|
27
|
+
end
|
|
26
28
|
raise Error.resolution("invalid package name: #{package_name.inspect}")
|
|
27
29
|
end
|
|
28
30
|
|
data/lib/pray/plan.rb
CHANGED
|
@@ -16,7 +16,7 @@ module Pray
|
|
|
16
16
|
package_lines: ApplyReport.local_summary_lines(previous_lockfile, project) +
|
|
17
17
|
package_summary_lines(previous_lockfile, lockfile, project),
|
|
18
18
|
lockfile: lockfile_change_status(previous_lockfile, lockfile),
|
|
19
|
-
targets: rendered.map { |target| target_change(project, target) },
|
|
19
|
+
targets: rendered.map { |target| target_change(project, target, previous_lockfile) },
|
|
20
20
|
provisioned: destinations.map { |file, status| [file.path, status.to_s] },
|
|
21
21
|
warnings: []
|
|
22
22
|
)
|
|
@@ -57,11 +57,12 @@ module Pray
|
|
|
57
57
|
Pray.lockfiles_equivalent?(lockfile, existing) ? "unchanged" : "update"
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
def target_change(project, target)
|
|
60
|
+
def target_change(project, target, previous_lockfile)
|
|
61
61
|
path = File.join(project.project_root, target.path)
|
|
62
|
+
retired = RenderReconcile.retired_span_checksums(previous_lockfile, target)
|
|
62
63
|
change = if !File.exist?(path)
|
|
63
64
|
"write"
|
|
64
|
-
elsif
|
|
65
|
+
elsif RenderReconcile.current?(path, target.path, target.content, retired)
|
|
65
66
|
"unchanged"
|
|
66
67
|
else
|
|
67
68
|
"update"
|
data/lib/pray/registry.rb
CHANGED
|
@@ -276,7 +276,7 @@ module Pray
|
|
|
276
276
|
|
|
277
277
|
def http_get(url)
|
|
278
278
|
uri = URI(url)
|
|
279
|
-
bounded_http_exchange(uri, Net::HTTP::Get.new(uri), "HTTP request failed for #{url}")
|
|
279
|
+
bounded_http_exchange(uri, Net::HTTP::Get.new(uri), "HTTP request failed for #{UrlRedaction.redact_userinfo(url)}")
|
|
280
280
|
end
|
|
281
281
|
|
|
282
282
|
def http_put(url, content_type, body)
|
|
@@ -284,7 +284,7 @@ module Pray
|
|
|
284
284
|
request = Net::HTTP::Put.new(uri)
|
|
285
285
|
request["Content-Type"] = content_type
|
|
286
286
|
request.body = body
|
|
287
|
-
bounded_http_exchange(uri, request, "HTTP upload failed for #{url}")
|
|
287
|
+
bounded_http_exchange(uri, request, "HTTP upload failed for #{UrlRedaction.redact_userinfo(url)}")
|
|
288
288
|
end
|
|
289
289
|
|
|
290
290
|
def http_post(url, content_type, body)
|
|
@@ -292,7 +292,7 @@ module Pray
|
|
|
292
292
|
request = Net::HTTP::Post.new(uri)
|
|
293
293
|
request["Content-Type"] = content_type
|
|
294
294
|
request.body = body
|
|
295
|
-
bounded_http_exchange(uri, request, "HTTP request failed for #{url}")
|
|
295
|
+
bounded_http_exchange(uri, request, "HTTP request failed for #{UrlRedaction.redact_userinfo(url)}")
|
|
296
296
|
end
|
|
297
297
|
|
|
298
298
|
def bounded_http_exchange(uri, request, failure_message)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Pray
|
|
6
|
+
RegistrySearchHit = Struct.new(:name, :summary, keyword_init: true)
|
|
7
|
+
|
|
8
|
+
module RegistrySearch
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def search_local_registry(root, query, include_summary: true)
|
|
12
|
+
index = parse_index(File.read(File.join(root, "v1", "index.json"), encoding: "UTF-8"))
|
|
13
|
+
search_index_names(index, query).map do |name|
|
|
14
|
+
metadata_path = Registry.registry_metadata_path(root, name)
|
|
15
|
+
metadata = Registry.parse_metadata(File.read(metadata_path, encoding: "UTF-8")) if include_summary && File.file?(metadata_path)
|
|
16
|
+
search_hit(name, metadata)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def search_http_registry(url, query, include_summary: true)
|
|
21
|
+
index = parse_index(Registry.http_get(Registry.join_url(url, "v1/index.json")))
|
|
22
|
+
search_index_names(index, query).map do |name|
|
|
23
|
+
PathSafety.reject_unsafe_package_name!(name)
|
|
24
|
+
metadata = fetch_http_metadata(url, name) if include_summary
|
|
25
|
+
search_hit(name, metadata)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def search_index_names(index, query)
|
|
30
|
+
needle = query.strip.downcase
|
|
31
|
+
index.fetch("packages").select do |name|
|
|
32
|
+
raise Error.parse("registry index", "package names must be strings") unless name.is_a?(String)
|
|
33
|
+
|
|
34
|
+
PathSafety.reject_unsafe_package_name!(name)
|
|
35
|
+
name.downcase.include?(needle)
|
|
36
|
+
end.sort
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def latest_non_yanked_summary(metadata)
|
|
40
|
+
return unless metadata
|
|
41
|
+
|
|
42
|
+
latest = metadata.versions.reject(&:yanked).max_by { |version| Gem::Version.new(version.version) }
|
|
43
|
+
summary = latest&.derived_metadata&.fetch("summary", nil).to_s.strip
|
|
44
|
+
summary unless summary.empty?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def parse_index(text)
|
|
48
|
+
index = JSON.parse(text)
|
|
49
|
+
unless index["spec"] == "prayfile-distribution-1" && index["packages"].is_a?(Array)
|
|
50
|
+
raise Error.parse("registry index", "expected prayfile-distribution-1 with a packages array")
|
|
51
|
+
end
|
|
52
|
+
index
|
|
53
|
+
rescue JSON::ParserError => error
|
|
54
|
+
raise Error.parse("registry index", error.message)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def fetch_http_metadata(url, name)
|
|
58
|
+
path = "v1/packages/#{name}.json"
|
|
59
|
+
Registry.parse_metadata(Registry.http_get(Registry.join_url(url, path)))
|
|
60
|
+
rescue Error => error
|
|
61
|
+
raise unless error.category == :resolution
|
|
62
|
+
|
|
63
|
+
nil
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def search_hit(name, metadata)
|
|
67
|
+
if metadata && metadata.name != name
|
|
68
|
+
raise Error.integrity("registry metadata name mismatch for #{name}")
|
|
69
|
+
end
|
|
70
|
+
RegistrySearchHit.new(name: name, summary: latest_non_yanked_summary(metadata))
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
data/lib/pray/render_dest.rb
CHANGED
|
@@ -9,13 +9,15 @@ module Pray
|
|
|
9
9
|
|
|
10
10
|
def write_rendered_targets(project, rendered, previous_lockfile = nil)
|
|
11
11
|
validate_destinations!(project, previous_lockfile)
|
|
12
|
+
RenderReconcile.reject_managed_span_conflicts!(project, rendered, previous_lockfile) if previous_lockfile
|
|
12
13
|
rendered.each do |target|
|
|
13
14
|
PathSafety.validate_destination_path!(target.path)
|
|
14
15
|
ensure_safe_destination_ancestors!(project.project_root, target.path, target.path)
|
|
15
16
|
path = File.join(project.project_root, target.path)
|
|
16
17
|
FileUtils.mkdir_p(File.dirname(path))
|
|
17
18
|
ensure_safe_destination_ancestors!(project.project_root, target.path, target.path)
|
|
18
|
-
|
|
19
|
+
retired = RenderReconcile.retired_span_checksums(previous_lockfile, target)
|
|
20
|
+
write_rendered_content(path, target.path, target.content, retired)
|
|
19
21
|
end
|
|
20
22
|
materialize(project, previous_lockfile)
|
|
21
23
|
end
|
|
@@ -147,7 +149,7 @@ module Pray
|
|
|
147
149
|
end
|
|
148
150
|
end
|
|
149
151
|
|
|
150
|
-
def layout_rendered_content(path, display, fresh)
|
|
152
|
+
def layout_rendered_content(path, display, fresh, retired = {})
|
|
151
153
|
kind = destination_kind(path)
|
|
152
154
|
return fresh if kind == :missing
|
|
153
155
|
|
|
@@ -155,15 +157,15 @@ module Pray
|
|
|
155
157
|
unless kind == :regular
|
|
156
158
|
raise Error.render("refusing to write `#{display}`; destination is not a regular file")
|
|
157
159
|
end
|
|
158
|
-
RenderPatch.patch_rendered_content(decode_utf8(read_regular_bytes(path, display), display), fresh)
|
|
160
|
+
RenderPatch.patch_rendered_content(decode_utf8(read_regular_bytes(path, display), display), fresh, retired)
|
|
159
161
|
end
|
|
160
162
|
|
|
161
|
-
def write_rendered_content(path, display, fresh)
|
|
163
|
+
def write_rendered_content(path, display, fresh, retired = {})
|
|
162
164
|
return create_bytes(path, display, fresh) if destination_kind(path) == :missing
|
|
163
165
|
|
|
164
166
|
open_regular(path, display, File::RDWR) do |file|
|
|
165
167
|
existing = decode_utf8(read_destination_bytes(file, display), display)
|
|
166
|
-
content = RenderPatch.patch_rendered_content(existing, fresh)
|
|
168
|
+
content = RenderPatch.patch_rendered_content(existing, fresh, retired)
|
|
167
169
|
return if Transaction.replace(path, existing.b, content.b)
|
|
168
170
|
|
|
169
171
|
file.rewind
|
data/lib/pray/render_layout.rb
CHANGED
|
@@ -4,12 +4,13 @@ module Pray
|
|
|
4
4
|
module Render
|
|
5
5
|
module_function
|
|
6
6
|
|
|
7
|
-
def layout_rendered_targets(project, rendered)
|
|
7
|
+
def layout_rendered_targets(project, rendered, previous_lockfile = project.previous_lockfile)
|
|
8
8
|
rendered.map do |target|
|
|
9
9
|
PathSafety.validate_destination_path!(target.path)
|
|
10
10
|
RenderDest.ensure_safe_destination_ancestors!(project.project_root, target.path, target.path)
|
|
11
11
|
path = File.join(project.project_root, target.path)
|
|
12
|
-
|
|
12
|
+
retired = RenderReconcile.retired_span_checksums(previous_lockfile, target)
|
|
13
|
+
content = RenderDest.layout_rendered_content(path, target.path, target.content, retired)
|
|
13
14
|
RenderedTarget.new(
|
|
14
15
|
path: target.path,
|
|
15
16
|
content: content,
|
data/lib/pray/render_patch.rb
CHANGED
|
@@ -4,11 +4,11 @@ module Pray
|
|
|
4
4
|
module RenderPatch
|
|
5
5
|
module_function
|
|
6
6
|
|
|
7
|
-
def patch_rendered_content(existing, fresh)
|
|
8
|
-
existing_segments = split_segments(existing)
|
|
7
|
+
def patch_rendered_content(existing, fresh, retired = {})
|
|
9
8
|
fresh_segments = split_segments(fresh)
|
|
10
9
|
fresh_managed = managed_bodies(fresh_segments)
|
|
11
|
-
|
|
10
|
+
existing_segments = drop_unchanged_retired(split_segments(existing), fresh_managed, retired)
|
|
11
|
+
return preserve_unmatched_managed(fresh, existing_segments, fresh_managed) unless overlapping_managed?(existing_segments, fresh_managed)
|
|
12
12
|
|
|
13
13
|
used = {}
|
|
14
14
|
output = +""
|
|
@@ -24,6 +24,28 @@ module Pray
|
|
|
24
24
|
output.end_with?("\n") ? output : "#{output}\n"
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
def drop_unchanged_retired(segments, fresh_managed, retired)
|
|
28
|
+
segments.reject do |segment|
|
|
29
|
+
next false unless segment[:kind] == :managed
|
|
30
|
+
next false if fresh_managed.key?(segment[:id])
|
|
31
|
+
|
|
32
|
+
expected = retired[segment[:id]]
|
|
33
|
+
expected && Hashing.checksum_managed_span_content(segment[:body]) == expected
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def preserve_unmatched_managed(fresh, segments, fresh_managed)
|
|
38
|
+
unmatched = segments.select do |segment|
|
|
39
|
+
segment[:kind] == :managed && !fresh_managed.key?(segment[:id])
|
|
40
|
+
end
|
|
41
|
+
return fresh if unmatched.empty?
|
|
42
|
+
|
|
43
|
+
output = fresh.dup
|
|
44
|
+
output << "\n" unless output.empty? || output.end_with?("\n")
|
|
45
|
+
unmatched.each { |segment| output << managed_segment(segment[:id], segment[:body]) }
|
|
46
|
+
output
|
|
47
|
+
end
|
|
48
|
+
|
|
27
49
|
def managed_bodies(segments)
|
|
28
50
|
segments.filter_map { |segment| [segment[:id], segment[:body]] if segment[:kind] == :managed }.to_h
|
|
29
51
|
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module RenderReconcile
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def current?(path, display, fresh, retired = {})
|
|
8
|
+
kind = RenderDest.destination_kind(path)
|
|
9
|
+
return false if kind == :missing
|
|
10
|
+
|
|
11
|
+
RenderDest.fail_if_symlink!(path, display)
|
|
12
|
+
unless kind == :regular
|
|
13
|
+
raise Error.render("refusing to read `#{display}`; destination is not a regular file")
|
|
14
|
+
end
|
|
15
|
+
existing = RenderDest.decode_utf8(RenderDest.read_regular_bytes(path, display), display)
|
|
16
|
+
existing == RenderPatch.patch_rendered_content(existing, fresh, retired)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def retired_span_checksums(previous_lockfile, target)
|
|
20
|
+
current_ids = target.managed_spans.to_h { |span| [span.id, true] }
|
|
21
|
+
Array(previous_lockfile&.managed_span).filter_map do |span|
|
|
22
|
+
next unless span.target == target.path
|
|
23
|
+
next if current_ids[span.id]
|
|
24
|
+
|
|
25
|
+
[span.id, span.ideal_checksum]
|
|
26
|
+
end.to_h
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def reject_managed_span_conflicts!(project, rendered, previous_lockfile)
|
|
30
|
+
active_by_target = rendered.to_h do |target|
|
|
31
|
+
[target.path, target.managed_spans.to_h { |span| [span.id, true] }]
|
|
32
|
+
end
|
|
33
|
+
Array(previous_lockfile.managed_span).group_by(&:target).each do |target_path, spans|
|
|
34
|
+
active = active_by_target[target_path]
|
|
35
|
+
next unless active
|
|
36
|
+
|
|
37
|
+
PathSafety.validate_destination_path!(target_path)
|
|
38
|
+
RenderDest.ensure_safe_destination_ancestors!(project.project_root, target_path, target_path)
|
|
39
|
+
path = File.join(project.project_root, target_path)
|
|
40
|
+
next unless File.file?(path)
|
|
41
|
+
|
|
42
|
+
content = RenderDest.decode_utf8(RenderDest.read_regular_bytes(path, target_path), target_path)
|
|
43
|
+
bodies = RenderPatch.managed_bodies(RenderPatch.split_segments(content))
|
|
44
|
+
spans.each do |span|
|
|
45
|
+
next unless active[span.id] && bodies.key?(span.id)
|
|
46
|
+
next if Hashing.checksum_managed_span_content(bodies[span.id]) == span.ideal_checksum
|
|
47
|
+
|
|
48
|
+
raise Error.render(
|
|
49
|
+
"conflict: managed span `#{span.id}` in `#{target_path}` was edited " \
|
|
50
|
+
"(`#{span.package}::#{span.export}`)"
|
|
51
|
+
)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module Pray
|
|
6
|
+
module UrlRedaction
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def redact_userinfo(url)
|
|
10
|
+
uri = URI(url)
|
|
11
|
+
return url unless uri.user || uri.password
|
|
12
|
+
|
|
13
|
+
uri.user = "***"
|
|
14
|
+
uri.password = nil
|
|
15
|
+
uri.to_s
|
|
16
|
+
rescue URI::InvalidURIError
|
|
17
|
+
url
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/pray/verify.rb
CHANGED
|
@@ -177,7 +177,7 @@ module Pray
|
|
|
177
177
|
|
|
178
178
|
VerificationFinding.new(
|
|
179
179
|
kind: "orphan_marker",
|
|
180
|
-
message: "`#{target_path}` contains marker `#{marker_id}` that is not tracked in `Prayfile.lock`.
|
|
180
|
+
message: "`#{target_path}` contains marker `#{marker_id}` that is not tracked in `Prayfile.lock`. Inspect and remove the block explicitly."
|
|
181
181
|
)
|
|
182
182
|
end
|
|
183
183
|
end
|
data/lib/pray/version.rb
CHANGED
data/lib/pray.rb
CHANGED
|
@@ -15,6 +15,7 @@ require_relative "pray/destination"
|
|
|
15
15
|
require_relative "pray/manifest"
|
|
16
16
|
require_relative "pray/format_serialize"
|
|
17
17
|
require_relative "pray/format_manifest"
|
|
18
|
+
require_relative "pray/package_relation"
|
|
18
19
|
require_relative "pray/package_spec"
|
|
19
20
|
require_relative "pray/package_spec_paths"
|
|
20
21
|
require_relative "pray/package_spec_version"
|
|
@@ -52,6 +53,7 @@ require_relative "pray/render_content"
|
|
|
52
53
|
require_relative "pray/render_managed"
|
|
53
54
|
require_relative "pray/render"
|
|
54
55
|
require_relative "pray/render_dest"
|
|
56
|
+
require_relative "pray/render_reconcile"
|
|
55
57
|
require_relative "pray/transaction"
|
|
56
58
|
require_relative "pray/render_layout"
|
|
57
59
|
require_relative "pray/verify_position"
|
|
@@ -60,9 +62,11 @@ require_relative "pray/verify"
|
|
|
60
62
|
require_relative "pray/verify_locked_dest"
|
|
61
63
|
require_relative "pray/resource_limits"
|
|
62
64
|
require_relative "pray/http_body"
|
|
65
|
+
require_relative "pray/url_redaction"
|
|
63
66
|
require_relative "pray/registry_install"
|
|
64
67
|
require_relative "pray/registry_integrity"
|
|
65
68
|
require_relative "pray/registry"
|
|
69
|
+
require_relative "pray/registry_search"
|
|
66
70
|
require_relative "pray/derived_metadata"
|
|
67
71
|
require_relative "pray/archive_unpack"
|
|
68
72
|
require_relative "pray/archive"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pray-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.26.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -115,6 +115,7 @@ files:
|
|
|
115
115
|
- lib/pray/cli/commands/init_prayer_manifest.rb
|
|
116
116
|
- lib/pray/cli/commands/meta.rb
|
|
117
117
|
- lib/pray/cli/commands/packages.rb
|
|
118
|
+
- lib/pray/cli/commands/search.rb
|
|
118
119
|
- lib/pray/cli/commands/trust.rb
|
|
119
120
|
- lib/pray/cli/commands/update_latest.rb
|
|
120
121
|
- lib/pray/cli/commands/workflow.rb
|
|
@@ -161,6 +162,7 @@ files:
|
|
|
161
162
|
- lib/pray/manifest_parser_helpers.rb
|
|
162
163
|
- lib/pray/manifest_parser_publish.rb
|
|
163
164
|
- lib/pray/materialize.rb
|
|
165
|
+
- lib/pray/package_relation.rb
|
|
164
166
|
- lib/pray/package_spec.rb
|
|
165
167
|
- lib/pray/package_spec_paths.rb
|
|
166
168
|
- lib/pray/package_spec_render.rb
|
|
@@ -176,6 +178,7 @@ files:
|
|
|
176
178
|
- lib/pray/registry_install.rb
|
|
177
179
|
- lib/pray/registry_integrity.rb
|
|
178
180
|
- lib/pray/registry_paths.rb
|
|
181
|
+
- lib/pray/registry_search.rb
|
|
179
182
|
- lib/pray/render.rb
|
|
180
183
|
- lib/pray/render_content.rb
|
|
181
184
|
- lib/pray/render_dest.rb
|
|
@@ -183,6 +186,7 @@ files:
|
|
|
183
186
|
- lib/pray/render_managed.rb
|
|
184
187
|
- lib/pray/render_patch.rb
|
|
185
188
|
- lib/pray/render_project.rb
|
|
189
|
+
- lib/pray/render_reconcile.rb
|
|
186
190
|
- lib/pray/resolve.rb
|
|
187
191
|
- lib/pray/resolve_context.rb
|
|
188
192
|
- lib/pray/resolve_deps.rb
|
|
@@ -216,6 +220,7 @@ files:
|
|
|
216
220
|
- lib/pray/upstream_merge.rb
|
|
217
221
|
- lib/pray/upstream_plan.rb
|
|
218
222
|
- lib/pray/upstream_refresh.rb
|
|
223
|
+
- lib/pray/url_redaction.rb
|
|
219
224
|
- lib/pray/verify.rb
|
|
220
225
|
- lib/pray/verify_locked_dest.rb
|
|
221
226
|
- lib/pray/verify_position.rb
|
|
@@ -246,7 +251,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
246
251
|
- !ruby/object:Gem::Version
|
|
247
252
|
version: '0'
|
|
248
253
|
requirements: []
|
|
249
|
-
rubygems_version:
|
|
254
|
+
rubygems_version: 3.6.9
|
|
250
255
|
specification_version: 4
|
|
251
256
|
summary: Package manager for the language placed before inference (Prayfile)
|
|
252
257
|
test_files: []
|