pray-cli 1.21.0 → 1.23.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 +28 -0
- data/lib/pray/cli/commands/distribution.rb +34 -3
- data/lib/pray/cli/commands/packages.rb +9 -7
- data/lib/pray/cli/commands/update_latest.rb +11 -4
- data/lib/pray/cli/commands/workflow.rb +4 -2
- data/lib/pray/cli/help.rb +7 -0
- data/lib/pray/cli.rb +14 -0
- data/lib/pray/constraint.rb +1 -1
- data/lib/pray/derived_metadata.rb +194 -0
- data/lib/pray/destination.rb +28 -8
- data/lib/pray/git_cache.rb +46 -19
- data/lib/pray/git_legacy.rb +36 -0
- data/lib/pray/git_materialize.rb +204 -38
- data/lib/pray/git_store.rb +101 -14
- data/lib/pray/literal.rb +4 -5
- data/lib/pray/lockfile_serialize.rb +6 -1
- data/lib/pray/manifest_json.rb +3 -27
- data/lib/pray/manifest_parser.rb +25 -3
- data/lib/pray/manifest_parser_blocks.rb +7 -7
- data/lib/pray/package_spec.rb +6 -1
- data/lib/pray/package_spec_paths.rb +18 -0
- data/lib/pray/publish.rb +50 -6
- data/lib/pray/registry.rb +10 -7
- data/lib/pray/render.rb +26 -32
- data/lib/pray/render_content.rb +4 -4
- data/lib/pray/render_project.rb +22 -0
- data/lib/pray/resolve.rb +17 -23
- data/lib/pray/resolve_local.rb +1 -0
- data/lib/pray/serve.rb +15 -1
- data/lib/pray/serve_federation.rb +3 -0
- data/lib/pray/source_path.rb +35 -0
- data/lib/pray/statement_surface.rb +3 -1
- data/lib/pray/sync.rb +1 -4
- data/lib/pray/tar_validation.rb +2 -0
- data/lib/pray/terminal.rb +28 -0
- data/lib/pray/trust.rb +4 -3
- data/lib/pray/upstream_merge.rb +6 -2
- data/lib/pray/upstream_plan.rb +84 -0
- data/lib/pray/version.rb +2 -2
- data/lib/pray.rb +4 -0
- metadata +8 -2
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "path_safety"
|
|
4
|
+
|
|
5
|
+
module Pray
|
|
6
|
+
class PackageSpec
|
|
7
|
+
def validate_relative_paths!
|
|
8
|
+
files.each { |file| PathSafety.validate_archive_member_path!(file) }
|
|
9
|
+
exports.each_value do |export|
|
|
10
|
+
PathSafety.validate_archive_member_path!(export.path)
|
|
11
|
+
PathSafety.validate_archive_member_path!(export.default_path) if export.default_path
|
|
12
|
+
end
|
|
13
|
+
skills.each_value { |skill| PathSafety.validate_archive_member_path!(skill.path) }
|
|
14
|
+
templates.each_value { |template| PathSafety.validate_archive_member_path!(template.path) }
|
|
15
|
+
self
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/lib/pray/publish.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "json"
|
|
|
4
4
|
require "fileutils"
|
|
5
5
|
require "time"
|
|
6
6
|
require "tmpdir"
|
|
7
|
+
require_relative "terminal"
|
|
7
8
|
|
|
8
9
|
module Pray
|
|
9
10
|
RegistryIndex = Struct.new(:spec, :packages) do
|
|
@@ -15,11 +16,14 @@ module Pray
|
|
|
15
16
|
module Publish
|
|
16
17
|
module_function
|
|
17
18
|
|
|
18
|
-
def publish_to_root(project, root, signer: "local", signer_fingerprint: nil)
|
|
19
|
+
def publish_to_root(project, root, signer: "local", signer_fingerprint: nil, dest_name: nil, target: nil)
|
|
19
20
|
root = File.expand_path(root)
|
|
21
|
+
dest_name ||= root
|
|
22
|
+
target ||= root
|
|
20
23
|
index = load_registry_index(root)
|
|
21
24
|
distribution = Distribution.read_settings(root)
|
|
22
25
|
package_names = index.packages.to_set
|
|
26
|
+
stats = {published: 0, unchanged: 0}
|
|
23
27
|
|
|
24
28
|
project.packages.each do |package|
|
|
25
29
|
package.spec.require_release_version!
|
|
@@ -30,7 +34,9 @@ module Pray
|
|
|
30
34
|
stored_artifact = stored_package_artifact(root, artifact_path, package, existing, distribution)
|
|
31
35
|
if stored_artifact && stored_publish_matches?(stored_artifact, package, existing)
|
|
32
36
|
package_names << package.declaration.name
|
|
33
|
-
|
|
37
|
+
# Intent: unchanged packages keep on-disk catalog bytes (newline, key order).
|
|
38
|
+
report_unchanged(package, dest_name, target)
|
|
39
|
+
stats[:unchanged] += 1
|
|
34
40
|
next
|
|
35
41
|
end
|
|
36
42
|
|
|
@@ -56,14 +62,20 @@ module Pray
|
|
|
56
62
|
metadata.versions << version_entry
|
|
57
63
|
write_registry_package_metadata(metadata_path, metadata)
|
|
58
64
|
package_names << package.declaration.name
|
|
65
|
+
report_published(package, dest_name, target, artifact_path)
|
|
66
|
+
stats[:published] += 1
|
|
59
67
|
end
|
|
60
68
|
|
|
61
69
|
index.packages = package_names.sort
|
|
62
70
|
write_registry_index(root, index)
|
|
71
|
+
stats
|
|
63
72
|
end
|
|
64
73
|
|
|
65
|
-
def publish_to_server(project, server_url, signer: "local", signer_fingerprint: nil)
|
|
74
|
+
def publish_to_server(project, server_url, signer: "local", signer_fingerprint: nil, dest_name: nil, target: nil)
|
|
75
|
+
dest_name ||= server_url
|
|
76
|
+
target ||= server_url
|
|
66
77
|
distribution = Distribution.fetch_settings(server_url)
|
|
78
|
+
stats = {published: 0, unchanged: 0}
|
|
67
79
|
project.packages.each do |package|
|
|
68
80
|
archive_bytes = Archive.build_package_archive_bytes(package)
|
|
69
81
|
artifact_path = registry_artifact_path(package.declaration.name, package.spec.version)
|
|
@@ -99,9 +111,38 @@ module Pray
|
|
|
99
111
|
"application/json",
|
|
100
112
|
JSON.pretty_generate(metadata_to_hash(metadata))
|
|
101
113
|
)
|
|
114
|
+
report_published(package, dest_name, target, artifact_path)
|
|
115
|
+
stats[:published] += 1
|
|
102
116
|
end
|
|
117
|
+
stats
|
|
103
118
|
end
|
|
104
119
|
|
|
120
|
+
def report_published(package, dest_name, target, artifact_path)
|
|
121
|
+
Terminal.println_stdout(
|
|
122
|
+
"published #{package.declaration.name} #{package.spec.version} -> #{redact_publish_target(dest_name)} (#{redact_publish_target(target)})"
|
|
123
|
+
)
|
|
124
|
+
Terminal.println_verbose(" artifact #{artifact_path}")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def report_unchanged(package, dest_name, target)
|
|
128
|
+
Terminal.println_stdout(
|
|
129
|
+
"unchanged #{package.declaration.name} #{package.spec.version} -> #{redact_publish_target(dest_name)} (#{redact_publish_target(target)})"
|
|
130
|
+
)
|
|
131
|
+
Terminal.println_verbose(" skip reason: stored publish matches")
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def redact_publish_target(target)
|
|
135
|
+
text = target.to_s
|
|
136
|
+
return text unless text.include?("://")
|
|
137
|
+
|
|
138
|
+
scheme, remainder = text.split("://", 2)
|
|
139
|
+
return text unless remainder&.include?("@")
|
|
140
|
+
|
|
141
|
+
_userinfo, host_and_path = remainder.split("@", 2)
|
|
142
|
+
"#{scheme}://***@#{host_and_path}"
|
|
143
|
+
end
|
|
144
|
+
private_class_method :report_published, :report_unchanged
|
|
145
|
+
|
|
105
146
|
def published_registry_package_version(package, signer, signer_fingerprint, archive_bytes, artifact_path)
|
|
106
147
|
RegistryPackageVersion.new(
|
|
107
148
|
version: package.spec.version,
|
|
@@ -114,7 +155,8 @@ module Pray
|
|
|
114
155
|
signer: signer,
|
|
115
156
|
signer_fingerprint: signer_fingerprint,
|
|
116
157
|
published_at: Time.now.to_i,
|
|
117
|
-
signature: Registry.registry_artifact_signature(archive_bytes, package.tree_hash, signer)
|
|
158
|
+
signature: Registry.registry_artifact_signature(archive_bytes, package.tree_hash, signer),
|
|
159
|
+
derived_metadata: DerivedMetadata.from_package_root(package.root, package.spec)
|
|
118
160
|
)
|
|
119
161
|
end
|
|
120
162
|
|
|
@@ -163,6 +205,7 @@ module Pray
|
|
|
163
205
|
|
|
164
206
|
version_entry.yanked = existing.yanked
|
|
165
207
|
version_entry.published_at = existing.published_at if stored_artifact
|
|
208
|
+
version_entry.derived_metadata ||= existing.derived_metadata
|
|
166
209
|
end
|
|
167
210
|
|
|
168
211
|
def load_registry_index(root)
|
|
@@ -181,7 +224,7 @@ module Pray
|
|
|
181
224
|
|
|
182
225
|
def load_registry_package_metadata(path, package_name)
|
|
183
226
|
if File.exist?(path)
|
|
184
|
-
data = JSON.parse(File.read(path))
|
|
227
|
+
data = JSON.parse(File.read(path, encoding: "UTF-8"))
|
|
185
228
|
RegistryPackageMetadata.new(
|
|
186
229
|
name: data["name"],
|
|
187
230
|
versions: Array(data["versions"]).map { |entry| Registry.version_from_hash(entry) }
|
|
@@ -193,7 +236,7 @@ module Pray
|
|
|
193
236
|
|
|
194
237
|
def write_registry_package_metadata(path, metadata)
|
|
195
238
|
FileUtils.mkdir_p(File.dirname(path))
|
|
196
|
-
File.write(path, JSON.pretty_generate(metadata_to_hash(metadata)))
|
|
239
|
+
File.write(path, JSON.pretty_generate(metadata_to_hash(metadata)), encoding: "UTF-8")
|
|
197
240
|
end
|
|
198
241
|
|
|
199
242
|
def metadata_to_hash(metadata)
|
|
@@ -217,6 +260,7 @@ module Pray
|
|
|
217
260
|
hash["signer_fingerprint"] = entry.signer_fingerprint if entry.signer_fingerprint
|
|
218
261
|
hash["published_at"] = entry.published_at if entry.published_at
|
|
219
262
|
hash["signature"] = entry.signature if entry.signature
|
|
263
|
+
hash["derived_metadata"] = entry.derived_metadata if entry.derived_metadata
|
|
220
264
|
hash
|
|
221
265
|
end
|
|
222
266
|
|
data/lib/pray/registry.rb
CHANGED
|
@@ -15,15 +15,17 @@ require_relative "git_materialize"
|
|
|
15
15
|
module Pray
|
|
16
16
|
RegistryPackageVersion = Struct.new(
|
|
17
17
|
:version, :artifact, :artifact_hash, :tree_hash, :yanked, :targets, :exports,
|
|
18
|
-
:signer, :signer_fingerprint, :published_at, :signature
|
|
18
|
+
:signer, :signer_fingerprint, :published_at, :signature, :derived_metadata
|
|
19
19
|
) do
|
|
20
20
|
def initialize(
|
|
21
21
|
artifact_hash: nil, tree_hash: nil, yanked: false, targets: [], exports: [],
|
|
22
|
-
signer: nil, signer_fingerprint: nil, published_at: nil, signature: nil,
|
|
22
|
+
signer: nil, signer_fingerprint: nil, published_at: nil, signature: nil,
|
|
23
|
+
derived_metadata: nil, **kwargs
|
|
23
24
|
)
|
|
24
25
|
super(**kwargs, artifact_hash: artifact_hash, tree_hash: tree_hash, yanked: yanked,
|
|
25
26
|
targets: targets, exports: exports, signer: signer,
|
|
26
|
-
signer_fingerprint: signer_fingerprint, published_at: published_at,
|
|
27
|
+
signer_fingerprint: signer_fingerprint, published_at: published_at,
|
|
28
|
+
signature: signature, derived_metadata: derived_metadata)
|
|
27
29
|
end
|
|
28
30
|
end
|
|
29
31
|
|
|
@@ -118,7 +120,7 @@ module Pray
|
|
|
118
120
|
|
|
119
121
|
raise Error.resolution(offline_package_error(declaration.name, selected.version)) if offline
|
|
120
122
|
|
|
121
|
-
artifact_bytes = read_local_artifact_bytes(source_root, selected.artifact)
|
|
123
|
+
artifact_bytes = read_local_artifact_bytes(source_root, selected.artifact, offline: offline)
|
|
122
124
|
RegistryInstall.install_artifact_to_cache(
|
|
123
125
|
cache_directory, declaration, selected, artifact_bytes, source_url: source_root
|
|
124
126
|
)
|
|
@@ -159,7 +161,8 @@ module Pray
|
|
|
159
161
|
signer: entry["signer"],
|
|
160
162
|
signer_fingerprint: entry["signer_fingerprint"],
|
|
161
163
|
published_at: entry.key?("published_at") ? publish_timestamp_from_value(entry["published_at"]) : nil,
|
|
162
|
-
signature: entry["signature"]
|
|
164
|
+
signature: entry["signature"],
|
|
165
|
+
derived_metadata: entry["derived_metadata"]
|
|
163
166
|
)
|
|
164
167
|
end
|
|
165
168
|
|
|
@@ -206,8 +209,8 @@ module Pray
|
|
|
206
209
|
Gem::Version.new(left) <=> Gem::Version.new(right)
|
|
207
210
|
end
|
|
208
211
|
|
|
209
|
-
def read_local_artifact_bytes(source_root, artifact)
|
|
210
|
-
GitMaterialize.read_local_artifact_bytes(source_root, artifact)
|
|
212
|
+
def read_local_artifact_bytes(source_root, artifact, offline: false)
|
|
213
|
+
GitMaterialize.read_local_artifact_bytes(source_root, artifact, offline: offline)
|
|
211
214
|
end
|
|
212
215
|
|
|
213
216
|
def read_artifact_bytes(source_url, artifact)
|
data/lib/pray/render.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "fileutils"
|
|
|
4
4
|
require "pathname"
|
|
5
5
|
require_relative "render_content"
|
|
6
6
|
require_relative "render_managed"
|
|
7
|
+
require_relative "render_project"
|
|
7
8
|
|
|
8
9
|
module Pray
|
|
9
10
|
RenderedTarget = Struct.new(:path, :content, :managed_spans) do
|
|
@@ -15,16 +16,6 @@ module Pray
|
|
|
15
16
|
module Render
|
|
16
17
|
module_function
|
|
17
18
|
|
|
18
|
-
def render_project(project)
|
|
19
|
-
rendered = []
|
|
20
|
-
project.manifest.targets.each do |target|
|
|
21
|
-
output = target.outputs.first
|
|
22
|
-
next unless output
|
|
23
|
-
rendered << render_target(project, target, output)
|
|
24
|
-
end
|
|
25
|
-
rendered
|
|
26
|
-
end
|
|
27
|
-
|
|
28
19
|
def write_rendered_targets(project, rendered, previous_lockfile = nil)
|
|
29
20
|
Transaction.run(project.project_root) { RenderDest.write_rendered_targets(project, rendered, previous_lockfile) }
|
|
30
21
|
end
|
|
@@ -56,7 +47,18 @@ module Pray
|
|
|
56
47
|
end
|
|
57
48
|
end
|
|
58
49
|
end
|
|
59
|
-
planned.sort_by
|
|
50
|
+
planned.sort_by! { |file| [file.path, file.package, file.export] }
|
|
51
|
+
planned.each_cons(2) do |left, right|
|
|
52
|
+
next unless left.path == right.path
|
|
53
|
+
next if left.package == right.package && left.export == right.export
|
|
54
|
+
|
|
55
|
+
raise Error.render(
|
|
56
|
+
"provisioned destination `#{left.path}` has multiple owners: " \
|
|
57
|
+
"package `#{left.package}` export `#{left.export}` and " \
|
|
58
|
+
"package `#{right.package}` export `#{right.export}`"
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
planned.uniq(&:path)
|
|
60
62
|
end
|
|
61
63
|
|
|
62
64
|
PlannedProvisionedFile = Struct.new(:path, :source, :package, :export, keyword_init: true)
|
|
@@ -74,12 +76,23 @@ module Pray
|
|
|
74
76
|
append_header(builder, project, target, output)
|
|
75
77
|
symbols = project.manifest.symbols || {}
|
|
76
78
|
managed_spans = []
|
|
79
|
+
locals_by_path = project.local_files.to_h { |local| [local.manifest_path, local] }
|
|
80
|
+
packages_by_name = project.packages.to_h { |package| [package.declaration.name, package] }
|
|
77
81
|
|
|
78
82
|
(target.entries || []).each do |entry|
|
|
79
83
|
if entry.kind == "local"
|
|
80
|
-
|
|
84
|
+
local = locals_by_path[entry.path]
|
|
85
|
+
append_managed_local(builder, managed_spans, local, target, output, symbols) if local
|
|
81
86
|
else
|
|
82
|
-
|
|
87
|
+
package = packages_by_name[entry.name]
|
|
88
|
+
next unless package
|
|
89
|
+
next unless Environment.package_matches_environment?(package.declaration.groups, project.environment)
|
|
90
|
+
|
|
91
|
+
package.selected_exports.each do |export|
|
|
92
|
+
next unless should_inline_export?(package, export)
|
|
93
|
+
|
|
94
|
+
append_managed_export(builder, managed_spans, package, export, target, output, symbols)
|
|
95
|
+
end
|
|
83
96
|
end
|
|
84
97
|
end
|
|
85
98
|
|
|
@@ -133,25 +146,6 @@ module Pray
|
|
|
133
146
|
builder.append_empty_line
|
|
134
147
|
end
|
|
135
148
|
|
|
136
|
-
def append_scoped_local(builder, managed_spans, project, target, output, path, symbols)
|
|
137
|
-
local = project.local_files.find { |candidate| candidate.manifest_path == path }
|
|
138
|
-
return unless local
|
|
139
|
-
|
|
140
|
-
append_managed_local(builder, managed_spans, local, target, output, symbols)
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def append_scoped_package(builder, managed_spans, project, target, output, package_name, symbols)
|
|
144
|
-
package = project.packages.find { |candidate| candidate.declaration.name == package_name }
|
|
145
|
-
return unless package
|
|
146
|
-
return unless Environment.package_matches_environment?(package.declaration.groups, project.environment)
|
|
147
|
-
|
|
148
|
-
package.selected_exports.each do |export|
|
|
149
|
-
next unless should_inline_export?(package, export)
|
|
150
|
-
|
|
151
|
-
append_managed_export(builder, managed_spans, package, export, target, output, symbols)
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
|
|
155
149
|
def should_inline_export?(package, export_name)
|
|
156
150
|
export = package.spec.exports[export_name]
|
|
157
151
|
export.nil? || %w[fragment file].include?(export.kind)
|
data/lib/pray/render_content.rb
CHANGED
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
module Pray
|
|
4
4
|
module Render
|
|
5
5
|
class ContentBuilder
|
|
6
|
+
attr_reader :next_line_number
|
|
7
|
+
|
|
6
8
|
def initialize
|
|
7
9
|
@content = +""
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def next_line_number
|
|
11
|
-
@content.count("\n") + 1
|
|
10
|
+
@next_line_number = 1
|
|
12
11
|
end
|
|
13
12
|
|
|
14
13
|
def append_line(line)
|
|
15
14
|
@content << line << "\n"
|
|
15
|
+
@next_line_number += 1
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def append_empty_line
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module Render
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def render_project(project)
|
|
8
|
+
project.manifest.targets.flat_map do |target|
|
|
9
|
+
target.outputs.map do |output|
|
|
10
|
+
rendered = render_target(project, target, output)
|
|
11
|
+
if target.max_bytes && rendered.content.bytesize > target.max_bytes
|
|
12
|
+
raise Error.render(
|
|
13
|
+
"target `#{target.name}` output `#{output}` is #{rendered.content.bytesize} bytes; " \
|
|
14
|
+
"max_bytes is #{target.max_bytes}"
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
rendered
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/pray/resolve.rb
CHANGED
|
@@ -154,8 +154,8 @@ module Pray
|
|
|
154
154
|
project_root, sources, git_sources, user_config, declaration, lockfile, options
|
|
155
155
|
)
|
|
156
156
|
spec_path = find_prayspec_file(root)
|
|
157
|
-
spec_text =
|
|
158
|
-
spec = Pray.parse_package_spec(spec_text)
|
|
157
|
+
spec_text = SourcePath.read(root, File.basename(spec_path), "package specification").force_encoding(Encoding::UTF_8)
|
|
158
|
+
spec = Pray.parse_package_spec(spec_text)
|
|
159
159
|
if spec.name != declaration.name
|
|
160
160
|
raise Error.resolution(
|
|
161
161
|
"package path #{root.inspect} declares #{spec.name.inspect}, expected #{declaration.name.inspect}"
|
|
@@ -195,9 +195,11 @@ module Pray
|
|
|
195
195
|
offline = options.offline
|
|
196
196
|
preferred_version = options.preferred_lock_version(lockfile, declaration.name)
|
|
197
197
|
if (local_path = user_config.local.package[declaration.name])
|
|
198
|
-
return [
|
|
198
|
+
return [SourcePath.declared_directory(project_root, local_path, "package directory"), nil]
|
|
199
|
+
end
|
|
200
|
+
if declaration.path
|
|
201
|
+
return [SourcePath.declared_directory(project_root, declaration.path, "package directory"), nil]
|
|
199
202
|
end
|
|
200
|
-
return [File.expand_path(declaration.path, project_root), nil] if declaration.path
|
|
201
203
|
return [ResolveTarball.package_root(project_root, declaration.tarball, offline: offline), nil] if declaration.tarball
|
|
202
204
|
source_name = ResolveSource.implied_source_name(declaration, sources)
|
|
203
205
|
if source_name
|
|
@@ -220,7 +222,8 @@ module Pray
|
|
|
220
222
|
case source.kind
|
|
221
223
|
when "path"
|
|
222
224
|
directory = LocalPrayer.path_source_package_directory(source_name, declaration.name)
|
|
223
|
-
|
|
225
|
+
relative = File.join(source.url, directory)
|
|
226
|
+
return [SourcePath.declared_directory(project_root, relative, "package directory"), nil]
|
|
224
227
|
when "registry", "static index"
|
|
225
228
|
resolved = Registry.resolve_registry_package_root(
|
|
226
229
|
project_root,
|
|
@@ -264,7 +267,8 @@ module Pray
|
|
|
264
267
|
|
|
265
268
|
raise Error.unsupported("remote sources are not implemented yet") if declaration.git || declaration.oci
|
|
266
269
|
|
|
267
|
-
|
|
270
|
+
slug = declaration.name.tr("/", "-")
|
|
271
|
+
[SourcePath.declared_directory(project_root, slug, "package directory"), nil]
|
|
268
272
|
end
|
|
269
273
|
|
|
270
274
|
def resolve_package_root(project_root, sources, git_sources, user_config, declaration, lockfile = nil)
|
|
@@ -272,9 +276,12 @@ module Pray
|
|
|
272
276
|
end
|
|
273
277
|
|
|
274
278
|
def find_prayspec_file(root)
|
|
275
|
-
files =
|
|
279
|
+
files = []
|
|
280
|
+
Dir.each_child(root) do |entry|
|
|
281
|
+
next unless File.extname(entry) == ".prayspec"
|
|
282
|
+
|
|
276
283
|
path = File.join(root, entry)
|
|
277
|
-
|
|
284
|
+
files << path if File.file?(path)
|
|
278
285
|
end
|
|
279
286
|
case files.length
|
|
280
287
|
when 1 then files.first
|
|
@@ -296,15 +303,7 @@ module Pray
|
|
|
296
303
|
end
|
|
297
304
|
|
|
298
305
|
def load_package_file_bytes(root, spec)
|
|
299
|
-
|
|
300
|
-
spec.files.each do |file|
|
|
301
|
-
path = File.join(root, file)
|
|
302
|
-
raise Error.integrity("package file missing: #{file}") unless File.exist?(path)
|
|
303
|
-
raise Error.integrity("package file is a directory: #{file}") if File.directory?(path)
|
|
304
|
-
|
|
305
|
-
file_bytes[file] = File.binread(path)
|
|
306
|
-
end
|
|
307
|
-
file_bytes
|
|
306
|
+
spec.files.to_h { |file| [file, SourcePath.read(root, file, "package file")] }
|
|
308
307
|
end
|
|
309
308
|
|
|
310
309
|
def build_skill_file_index(spec)
|
|
@@ -331,12 +330,7 @@ module Pray
|
|
|
331
330
|
end
|
|
332
331
|
|
|
333
332
|
def skill_relative_file(file, skill_prefix)
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
relative = file.delete_prefix(skill_prefix).delete_prefix("/")
|
|
337
|
-
return nil if relative.empty? || file == skill_prefix
|
|
338
|
-
|
|
339
|
-
relative
|
|
333
|
+
file.delete_prefix("#{skill_prefix.delete_suffix("/")}/") if file.start_with?("#{skill_prefix.delete_suffix("/")}/")
|
|
340
334
|
end
|
|
341
335
|
end
|
|
342
336
|
end
|
data/lib/pray/resolve_local.rb
CHANGED
|
@@ -11,6 +11,7 @@ module Pray
|
|
|
11
11
|
|
|
12
12
|
def resolve_local_file(project_root, declaration)
|
|
13
13
|
path = File.join(project_root, declaration.path)
|
|
14
|
+
SourcePath.reject_symlinks!(project_root, declaration.path, "local source")
|
|
14
15
|
unless File.exist?(path)
|
|
15
16
|
if declaration.optional
|
|
16
17
|
return ResolvedLocalFile.new(
|
data/lib/pray/serve.rb
CHANGED
|
@@ -118,7 +118,7 @@ module Pray
|
|
|
118
118
|
return html_response("<h1>Pray distribution</h1>")
|
|
119
119
|
end
|
|
120
120
|
|
|
121
|
-
file_path =
|
|
121
|
+
file_path = public_distribution_path(root, path)
|
|
122
122
|
return not_found unless file_path
|
|
123
123
|
return not_found unless File.file?(file_path)
|
|
124
124
|
return payload_too_large if File.size(file_path) > ResourceLimits::MAX_HTTP_RESPONSE_BYTES
|
|
@@ -129,6 +129,20 @@ module Pray
|
|
|
129
129
|
ranged_response(status, content_type, ranged_body, content_range)
|
|
130
130
|
end
|
|
131
131
|
|
|
132
|
+
def public_distribution_path(root, request_path)
|
|
133
|
+
relative = PathSafety.sanitize_relative_path(request_path)
|
|
134
|
+
exact = %w[v1/index.json v1/distribution.json v1/trust.json]
|
|
135
|
+
return nil unless exact.include?(relative) || relative.start_with?("v1/packages/", "v1/artifacts/")
|
|
136
|
+
|
|
137
|
+
file_path = PathSafety.join_under_root(root, relative)
|
|
138
|
+
return nil unless file_path
|
|
139
|
+
|
|
140
|
+
SourcePath.reject_symlinks!(root, relative, "distribution file")
|
|
141
|
+
file_path
|
|
142
|
+
rescue Error
|
|
143
|
+
nil
|
|
144
|
+
end
|
|
145
|
+
|
|
132
146
|
def content_type_for(path)
|
|
133
147
|
case File.extname(path)
|
|
134
148
|
when ".json" then "application/json"
|
|
@@ -51,6 +51,7 @@ module Pray
|
|
|
51
51
|
|
|
52
52
|
def package_response(root, path)
|
|
53
53
|
name = path.delete_prefix("/v1/sync/package/")
|
|
54
|
+
PathSafety.validate_registry_cache_identity!(name, "metadata")
|
|
54
55
|
metadata_path = Publish.registry_metadata_path(root, name)
|
|
55
56
|
return Serve.not_found unless File.file?(metadata_path)
|
|
56
57
|
|
|
@@ -61,6 +62,8 @@ module Pray
|
|
|
61
62
|
"versions" => metadata.versions.map { |version| transport_version(version) }
|
|
62
63
|
}
|
|
63
64
|
Serve.ok_response("application/json", JSON.pretty_generate(body))
|
|
65
|
+
rescue Error
|
|
66
|
+
Serve.not_found
|
|
64
67
|
end
|
|
65
68
|
|
|
66
69
|
def append_confession(root, body)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module Pray
|
|
6
|
+
module SourcePath
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def declared_directory(root, relative, label)
|
|
10
|
+
reject_symlinks!(root, relative, label)
|
|
11
|
+
File.expand_path(relative, root)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def reject_symlinks!(root, relative, label)
|
|
15
|
+
current = Pathname(relative).absolute? ? File::SEPARATOR : root
|
|
16
|
+
status = nil
|
|
17
|
+
Pathname(relative).each_filename do |component|
|
|
18
|
+
current = File.join(current, component)
|
|
19
|
+
status = File.lstat(current)
|
|
20
|
+
raise Error.integrity("#{label} uses a symbolic link: #{relative}") if status.symlink?
|
|
21
|
+
rescue Errno::ENOENT
|
|
22
|
+
return nil
|
|
23
|
+
end
|
|
24
|
+
status
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def read(root, relative, label)
|
|
28
|
+
status = reject_symlinks!(root, relative, label)
|
|
29
|
+
raise Error.integrity("#{label} missing: #{relative}") unless status
|
|
30
|
+
raise Error.integrity("#{label} is a directory: #{relative}") if status.directory?
|
|
31
|
+
|
|
32
|
+
File.binread(File.join(root, relative))
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -7,6 +7,7 @@ module Pray
|
|
|
7
7
|
def expand_statement_surface(statement)
|
|
8
8
|
trimmed = statement.strip
|
|
9
9
|
return [] if trimmed.empty?
|
|
10
|
+
return [normalize_keyword_call(trimmed)] unless trimmed.include?(";") || trimmed.include?("{")
|
|
10
11
|
|
|
11
12
|
Literal.split_top_level(trimmed, ";").flat_map { |segment| expand_one_surface(segment) }
|
|
12
13
|
end
|
|
@@ -15,7 +16,7 @@ module Pray
|
|
|
15
16
|
trimmed = statement.strip
|
|
16
17
|
return [] if trimmed.empty?
|
|
17
18
|
|
|
18
|
-
braced = expand_brace_block(trimmed)
|
|
19
|
+
braced = expand_brace_block(trimmed) if trimmed.include?("{")
|
|
19
20
|
return braced if braced
|
|
20
21
|
|
|
21
22
|
[normalize_keyword_call(trimmed)]
|
|
@@ -71,6 +72,7 @@ module Pray
|
|
|
71
72
|
def normalize_keyword_call(statement)
|
|
72
73
|
spaced = normalize_spaced_block_opener(statement)
|
|
73
74
|
return spaced if spaced
|
|
75
|
+
return statement unless statement.include?("(")
|
|
74
76
|
|
|
75
77
|
keyword = leading_identifier(statement)
|
|
76
78
|
return statement unless keyword
|
data/lib/pray/sync.rb
CHANGED
|
@@ -205,10 +205,7 @@ module Pray
|
|
|
205
205
|
end
|
|
206
206
|
|
|
207
207
|
def fetch_bytes(peer_source, artifact)
|
|
208
|
-
|
|
209
|
-
return Registry.http_get(artifact).b
|
|
210
|
-
end
|
|
211
|
-
|
|
208
|
+
Registry.reject_absolute_artifact!(artifact)
|
|
212
209
|
Registry.http_get("#{trim_slash(peer_source)}/#{artifact.delete_prefix("/")}").b
|
|
213
210
|
end
|
|
214
211
|
|
data/lib/pray/tar_validation.rb
CHANGED
|
@@ -38,6 +38,8 @@ module Pray
|
|
|
38
38
|
|
|
39
39
|
def process_entry(header, data, size, state, output_directory)
|
|
40
40
|
type = header.byteslice(156, 1)
|
|
41
|
+
# git archive emits a pax global header (`g`); ignore without applying path.
|
|
42
|
+
return if type == "g"
|
|
41
43
|
if type == "x"
|
|
42
44
|
state.pending_path = pax_path(data)
|
|
43
45
|
return
|
data/lib/pray/terminal.rb
CHANGED
|
@@ -20,6 +20,34 @@ module Pray
|
|
|
20
20
|
env_truthy?("PRAY_NO_INPUT")
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
def quiet?
|
|
24
|
+
env_truthy?("PRAY_QUIET")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def verbose?
|
|
28
|
+
level = ENV.fetch("PRAY_VERBOSE", "0").to_i
|
|
29
|
+
level > 0
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def configure_output!(quiet:, verbose:)
|
|
33
|
+
ENV["PRAY_QUIET"] = "1" if quiet
|
|
34
|
+
if verbose > 0
|
|
35
|
+
ENV["PRAY_VERBOSE"] = verbose.to_s
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def println_stdout(message)
|
|
40
|
+
return if quiet?
|
|
41
|
+
|
|
42
|
+
puts message
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def println_verbose(message)
|
|
46
|
+
return if quiet? || !verbose?
|
|
47
|
+
|
|
48
|
+
puts message
|
|
49
|
+
end
|
|
50
|
+
|
|
23
51
|
def env_truthy?(name)
|
|
24
52
|
value = ENV.fetch(name, nil)
|
|
25
53
|
return false if value.nil?
|
data/lib/pray/trust.rb
CHANGED
|
@@ -110,10 +110,11 @@ module Pray
|
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
def prepare_source_host_keys(sources)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
next unless source.kind == "pray_ssh"
|
|
113
|
+
ssh_sources = sources.select { |source| source.kind == "pray_ssh" }
|
|
114
|
+
return {} if ssh_sources.empty?
|
|
116
115
|
|
|
116
|
+
policy = load_policy_or_default
|
|
117
|
+
ssh_sources.each_with_object({}) do |source, host_keys|
|
|
117
118
|
rule = best_rule(policy, source.url)
|
|
118
119
|
fingerprint = rule.allowed_host_keys.first
|
|
119
120
|
host_keys[source.name] = fingerprint if fingerprint
|
data/lib/pray/upstream_merge.rb
CHANGED
|
@@ -39,13 +39,15 @@ module Pray
|
|
|
39
39
|
when [false, true, false]
|
|
40
40
|
(local == old) ? :omit : :conflict
|
|
41
41
|
when [false, false, true]
|
|
42
|
-
(old == new) ? :omit :
|
|
42
|
+
(old == new) ? :omit : :conflict
|
|
43
43
|
when [true, false, true]
|
|
44
44
|
new
|
|
45
45
|
when [true, true, false]
|
|
46
46
|
local
|
|
47
47
|
when [true, false, false]
|
|
48
48
|
(local == new) ? new : :conflict
|
|
49
|
+
when [false, true, true]
|
|
50
|
+
:omit
|
|
49
51
|
else
|
|
50
52
|
:conflict
|
|
51
53
|
end
|
|
@@ -67,7 +69,9 @@ module Pray
|
|
|
67
69
|
|
|
68
70
|
def merge_conflict_message(fork, upstream_name, old_version, new_version, paths)
|
|
69
71
|
"upstream merge conflict in #{fork} while refreshing #{upstream_name} " \
|
|
70
|
-
"#{old_version} to #{new_version}: #{paths.join(", ")}"
|
|
72
|
+
"#{old_version} to #{new_version}: #{paths.join(", ")}. " \
|
|
73
|
+
"Reset those content files to the locked upstream tree, run `pray update --latest`, " \
|
|
74
|
+
"re-apply intentional overlays, then bump the fork version (upstream_merge_conflict)."
|
|
71
75
|
end
|
|
72
76
|
|
|
73
77
|
def next_upstream_constraint(current, new_version)
|