pray-cli 1.15.0 → 1.17.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 +17 -0
- data/lib/pray/apply_report.rb +49 -0
- data/lib/pray/cli/commands/init.rb +1 -20
- data/lib/pray/cli/commands/init_prayer.rb +76 -0
- data/lib/pray/cli/commands/init_prayer_manifest.rb +102 -0
- data/lib/pray/cli/commands/packages.rb +13 -2
- data/lib/pray/cli/commands/workflow.rb +5 -0
- data/lib/pray/cli/help.rb +52 -15
- data/lib/pray/cli/helpers.rb +13 -5
- data/lib/pray/cli/parse.rb +21 -1
- data/lib/pray/cli.rb +3 -1
- data/lib/pray/constraint.rb +17 -4
- data/lib/pray/dependency_graph.rb +43 -0
- data/lib/pray/destination.rb +32 -2
- data/lib/pray/distribution.rb +94 -0
- data/lib/pray/format_serialize.rb +1 -4
- data/lib/pray/git_sources.rb +11 -10
- data/lib/pray/local_prayer.rb +38 -0
- data/lib/pray/lockfile.rb +8 -2
- data/lib/pray/manifest.rb +6 -3
- data/lib/pray/manifest_json.rb +1 -0
- data/lib/pray/manifest_parser_blocks.rb +15 -15
- data/lib/pray/materialize.rb +8 -1
- data/lib/pray/package_spec.rb +25 -13
- data/lib/pray/package_spec_render.rb +4 -3
- data/lib/pray/package_spec_version.rb +33 -0
- data/lib/pray/plan.rb +2 -1
- data/lib/pray/publish.rb +27 -2
- data/lib/pray/registry.rb +12 -0
- data/lib/pray/registry_paths.rb +14 -0
- data/lib/pray/render.rb +6 -36
- data/lib/pray/render_managed.rb +64 -0
- data/lib/pray/render_patch.rb +63 -12
- data/lib/pray/resolve.rb +17 -50
- data/lib/pray/resolve_context.rb +7 -0
- data/lib/pray/resolve_deps.rb +18 -0
- data/lib/pray/resolve_local.rb +39 -0
- data/lib/pray/resolve_source.rb +5 -0
- data/lib/pray/resolve_tarball.rb +66 -0
- data/lib/pray/serve.rb +15 -4
- data/lib/pray/serve_range.rb +36 -0
- data/lib/pray/torrent_manifest.rb +78 -0
- data/lib/pray/upstream_drift.rb +42 -0
- data/lib/pray/upstream_merge.rb +25 -0
- data/lib/pray/upstream_refresh.rb +28 -2
- data/lib/pray/verify.rb +2 -2
- data/lib/pray/verify_locked_dest.rb +47 -0
- data/lib/pray/version.rb +2 -2
- data/lib/pray.rb +15 -1
- metadata +17 -1
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module Pray
|
|
6
|
+
module RegistryPaths
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def local_registry_root(project_root, source_url)
|
|
10
|
+
path = source_url.delete_prefix("file://")
|
|
11
|
+
Pathname.new(path).absolute? ? path : File.expand_path(path, project_root)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/pray/render.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
require "pathname"
|
|
5
5
|
require_relative "render_content"
|
|
6
|
+
require_relative "render_managed"
|
|
6
7
|
|
|
7
8
|
module Pray
|
|
8
9
|
RenderedTarget = Struct.new(:path, :content, :managed_spans) do
|
|
@@ -76,7 +77,7 @@ module Pray
|
|
|
76
77
|
|
|
77
78
|
(target.entries || []).each do |entry|
|
|
78
79
|
if entry.kind == "local"
|
|
79
|
-
append_scoped_local(builder, project, entry.path, symbols)
|
|
80
|
+
append_scoped_local(builder, managed_spans, project, target, output, entry.path, symbols)
|
|
80
81
|
else
|
|
81
82
|
append_scoped_package(builder, managed_spans, project, target, output, entry.name, symbols)
|
|
82
83
|
end
|
|
@@ -99,18 +100,17 @@ module Pray
|
|
|
99
100
|
builder.append_empty_line
|
|
100
101
|
end
|
|
101
102
|
symbols = project.manifest.symbols || {}
|
|
103
|
+
managed_spans = []
|
|
102
104
|
unbound_locals.each do |local|
|
|
103
105
|
next if local.content.empty? && local.optional
|
|
104
106
|
|
|
105
107
|
builder.append_line("### #{local.manifest_path}")
|
|
106
|
-
builder
|
|
107
|
-
builder.append_empty_line
|
|
108
|
+
append_managed_local(builder, managed_spans, local, target, output, symbols)
|
|
108
109
|
end
|
|
109
110
|
|
|
110
111
|
builder.append_line("## Shared instructions")
|
|
111
112
|
builder.append_empty_line
|
|
112
113
|
|
|
113
|
-
managed_spans = []
|
|
114
114
|
project.packages.each do |package|
|
|
115
115
|
next unless Environment.package_matches_environment?(package.declaration.groups, project.environment)
|
|
116
116
|
next unless Destination.package_bound_to_compose?(package.declaration, target)
|
|
@@ -133,13 +133,11 @@ module Pray
|
|
|
133
133
|
builder.append_empty_line
|
|
134
134
|
end
|
|
135
135
|
|
|
136
|
-
def append_scoped_local(builder, project, path, symbols)
|
|
136
|
+
def append_scoped_local(builder, managed_spans, project, target, output, path, symbols)
|
|
137
137
|
local = project.local_files.find { |candidate| candidate.manifest_path == path }
|
|
138
138
|
return unless local
|
|
139
|
-
return if local.content.empty? && local.optional
|
|
140
139
|
|
|
141
|
-
builder
|
|
142
|
-
builder.append_empty_line
|
|
140
|
+
append_managed_local(builder, managed_spans, local, target, output, symbols)
|
|
143
141
|
end
|
|
144
142
|
|
|
145
143
|
def append_scoped_package(builder, managed_spans, project, target, output, package_name, symbols)
|
|
@@ -154,34 +152,6 @@ module Pray
|
|
|
154
152
|
end
|
|
155
153
|
end
|
|
156
154
|
|
|
157
|
-
def append_managed_export(builder, managed_spans, package, export, target, output, symbols)
|
|
158
|
-
body = package.export_bodies[export]
|
|
159
|
-
unless body
|
|
160
|
-
raise Error.integrity("compose cannot write binary export #{export}; use file: for unmarked bytes") if package.spec.exports[export]&.kind == "file"
|
|
161
|
-
raise Error.render("package #{package.declaration.name} is missing cached export #{export}")
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
body = Substitute.substitute_pray_symbols(body, symbols)
|
|
165
|
-
identifier = Hashing.marker_id("#{package.declaration.name}:#{export}:#{target.name}")
|
|
166
|
-
open_line = builder.next_line_number
|
|
167
|
-
builder.append_line("<!-- pray:#{identifier} -->")
|
|
168
|
-
builder.append_body(body)
|
|
169
|
-
close_line = builder.next_line_number
|
|
170
|
-
builder.append_line("<!-- pray:#{identifier} -->")
|
|
171
|
-
managed_spans << ManagedSpanRecord.new(
|
|
172
|
-
id: identifier,
|
|
173
|
-
target: output,
|
|
174
|
-
open_line: open_line,
|
|
175
|
-
close_line: close_line,
|
|
176
|
-
ideal_checksum: Hashing.checksum_managed_span_content(body),
|
|
177
|
-
package: package.declaration.name,
|
|
178
|
-
export: export,
|
|
179
|
-
source_checksum: package.source_checksum,
|
|
180
|
-
silenced: false
|
|
181
|
-
)
|
|
182
|
-
builder.append_empty_line
|
|
183
|
-
end
|
|
184
|
-
|
|
185
155
|
def should_inline_export?(package, export_name)
|
|
186
156
|
export = package.spec.exports[export_name]
|
|
187
157
|
export.nil? || %w[fragment file].include?(export.kind)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module Render
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def append_managed_local(builder, managed_spans, local, target, output, symbols)
|
|
8
|
+
return if local.content.empty? && local.optional
|
|
9
|
+
|
|
10
|
+
body = Substitute.substitute_pray_symbols(local.content, symbols)
|
|
11
|
+
append_managed_span(
|
|
12
|
+
builder,
|
|
13
|
+
managed_spans,
|
|
14
|
+
"local:#{local.manifest_path}:#{target.name}",
|
|
15
|
+
body,
|
|
16
|
+
output,
|
|
17
|
+
LOCAL_EMBED_PACKAGE,
|
|
18
|
+
local.manifest_path,
|
|
19
|
+
local.source_checksum
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def append_managed_export(builder, managed_spans, package, export, target, output, symbols)
|
|
24
|
+
body = package.export_bodies[export]
|
|
25
|
+
unless body
|
|
26
|
+
raise Error.integrity("compose cannot write binary export #{export}; use file: for unmarked bytes") if package.spec.exports[export]&.kind == "file"
|
|
27
|
+
raise Error.render("package #{package.declaration.name} is missing cached export #{export}")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
body = Substitute.substitute_pray_symbols(body, symbols)
|
|
31
|
+
append_managed_span(
|
|
32
|
+
builder,
|
|
33
|
+
managed_spans,
|
|
34
|
+
"#{package.declaration.name}:#{export}:#{target.name}",
|
|
35
|
+
body,
|
|
36
|
+
output,
|
|
37
|
+
package.declaration.name,
|
|
38
|
+
export,
|
|
39
|
+
package.source_checksum
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def append_managed_span(builder, managed_spans, seed, body, output, package, export, source_checksum)
|
|
44
|
+
identifier = Hashing.marker_id(seed)
|
|
45
|
+
open_line = builder.next_line_number
|
|
46
|
+
builder.append_line("<!-- pray:#{identifier} -->")
|
|
47
|
+
builder.append_body(body)
|
|
48
|
+
close_line = builder.next_line_number
|
|
49
|
+
builder.append_line("<!-- pray:#{identifier} -->")
|
|
50
|
+
managed_spans << ManagedSpanRecord.new(
|
|
51
|
+
id: identifier,
|
|
52
|
+
target: output,
|
|
53
|
+
open_line: open_line,
|
|
54
|
+
close_line: close_line,
|
|
55
|
+
ideal_checksum: Hashing.checksum_managed_span_content(body),
|
|
56
|
+
package: package,
|
|
57
|
+
export: export,
|
|
58
|
+
source_checksum: source_checksum,
|
|
59
|
+
silenced: false
|
|
60
|
+
)
|
|
61
|
+
builder.append_empty_line
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
data/lib/pray/render_patch.rb
CHANGED
|
@@ -7,28 +7,74 @@ module Pray
|
|
|
7
7
|
def patch_rendered_content(existing, fresh)
|
|
8
8
|
existing_segments = split_segments(existing)
|
|
9
9
|
fresh_segments = split_segments(fresh)
|
|
10
|
-
fresh_managed = fresh_segments
|
|
11
|
-
|
|
12
|
-
end.to_h
|
|
13
|
-
overlap = existing_segments.any? do |segment|
|
|
14
|
-
segment[:kind] == :managed && fresh_managed.key?(segment[:id])
|
|
15
|
-
end
|
|
16
|
-
return fresh unless overlap
|
|
10
|
+
fresh_managed = managed_bodies(fresh_segments)
|
|
11
|
+
return fresh unless overlapping_managed?(existing_segments, fresh_managed)
|
|
17
12
|
|
|
18
13
|
used = {}
|
|
19
|
-
output =
|
|
20
|
-
|
|
14
|
+
output = +""
|
|
15
|
+
remaining = prepend_new_leading_spans(
|
|
16
|
+
existing_segments,
|
|
17
|
+
fresh_segments,
|
|
18
|
+
managed_id_set(existing_segments),
|
|
19
|
+
used,
|
|
20
|
+
output
|
|
21
|
+
)
|
|
22
|
+
splice_existing_managed(remaining, fresh_managed, used, output)
|
|
23
|
+
append_unused_fresh_managed(fresh_segments, used, output)
|
|
24
|
+
output.end_with?("\n") ? output : "#{output}\n"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def managed_bodies(segments)
|
|
28
|
+
segments.filter_map { |segment| [segment[:id], segment[:body]] if segment[:kind] == :managed }.to_h
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def overlapping_managed?(existing_segments, fresh_managed)
|
|
32
|
+
existing_segments.any? { |segment| segment[:kind] == :managed && fresh_managed.key?(segment[:id]) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def managed_id_set(segments)
|
|
36
|
+
segments.filter_map { |segment| segment[:id] if segment[:kind] == :managed }.to_h { |id| [id, true] }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def splice_existing_managed(remaining, fresh_managed, used, output)
|
|
40
|
+
remaining.each do |segment|
|
|
41
|
+
if segment[:kind] == :text
|
|
42
|
+
output << segment[:text]
|
|
43
|
+
next
|
|
44
|
+
end
|
|
21
45
|
|
|
22
46
|
used[segment[:id]] = true
|
|
23
|
-
managed_segment(segment[:id], fresh_managed.fetch(segment[:id], segment[:body]))
|
|
24
|
-
end
|
|
47
|
+
output << managed_segment(segment[:id], fresh_managed.fetch(segment[:id], segment[:body]))
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def append_unused_fresh_managed(fresh_segments, used, output)
|
|
25
52
|
fresh_segments.each do |segment|
|
|
26
53
|
next unless segment[:kind] == :managed
|
|
27
54
|
next if used[segment[:id]]
|
|
28
55
|
|
|
29
56
|
output << managed_segment(segment[:id], segment[:body])
|
|
30
57
|
end
|
|
31
|
-
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def prepend_new_leading_spans(existing_segments, fresh_segments, existing_ids, used, output)
|
|
61
|
+
missing = fresh_segments.any? { |segment| segment[:kind] == :managed && !existing_ids[segment[:id]] }
|
|
62
|
+
return existing_segments unless missing
|
|
63
|
+
|
|
64
|
+
shared_id = fresh_segments.find { |segment| segment[:kind] == :managed && existing_ids[segment[:id]] }&.[](:id)
|
|
65
|
+
return existing_segments unless shared_id
|
|
66
|
+
|
|
67
|
+
fresh_segments.each do |segment|
|
|
68
|
+
break if segment[:kind] == :managed && segment[:id] == shared_id
|
|
69
|
+
|
|
70
|
+
if segment[:kind] == :text
|
|
71
|
+
output << segment[:text]
|
|
72
|
+
else
|
|
73
|
+
used[segment[:id]] = true
|
|
74
|
+
output << managed_segment(segment[:id], segment[:body])
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
skip_until_managed(existing_segments, shared_id)
|
|
32
78
|
end
|
|
33
79
|
|
|
34
80
|
def relocate_managed_spans(content, spans)
|
|
@@ -68,6 +114,11 @@ module Pray
|
|
|
68
114
|
segments
|
|
69
115
|
end
|
|
70
116
|
|
|
117
|
+
def skip_until_managed(segments, identifier)
|
|
118
|
+
index = segments.index { |segment| segment[:kind] == :managed && segment[:id] == identifier }
|
|
119
|
+
index ? segments[index..] : segments
|
|
120
|
+
end
|
|
121
|
+
|
|
71
122
|
def lines_of(content)
|
|
72
123
|
content.lines(chomp: true)
|
|
73
124
|
end
|
data/lib/pray/resolve.rb
CHANGED
|
@@ -17,7 +17,7 @@ module Pray
|
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
ResolvedLocalFile = Struct.new(
|
|
20
|
-
:path, :manifest_path, :content, :position, :optional
|
|
20
|
+
:path, :manifest_path, :content, :source_checksum, :position, :optional
|
|
21
21
|
)
|
|
22
22
|
|
|
23
23
|
module Resolve
|
|
@@ -92,12 +92,15 @@ module Pray
|
|
|
92
92
|
local_files = []
|
|
93
93
|
local_errors = []
|
|
94
94
|
manifest.local.each do |local|
|
|
95
|
+
next unless Destination.local_compose_embed?(manifest, local)
|
|
95
96
|
local_files << resolve_local_file(project_root, local)
|
|
96
97
|
rescue Error => error
|
|
97
98
|
local_errors << "local #{local.path}: #{error.message}"
|
|
98
99
|
end
|
|
99
100
|
raise Error.resolution(local_errors.join("\n")) unless local_errors.empty?
|
|
100
101
|
|
|
102
|
+
ResolveDeps.reject_dependency_cycles(packages)
|
|
103
|
+
|
|
101
104
|
ResolvedProject.new(
|
|
102
105
|
manifest_path: manifest_path,
|
|
103
106
|
project_root: project_root,
|
|
@@ -144,15 +147,10 @@ module Pray
|
|
|
144
147
|
GitRefresh.resolution_may_benefit_from_git_source_refresh?(error)
|
|
145
148
|
end
|
|
146
149
|
|
|
147
|
-
def missing_local_embed_guidance(path)
|
|
148
|
-
"Prayfile lists `local \"#{path}\"` but the file does not exist. " \
|
|
149
|
-
"Create the file or remove the entry from Prayfile, then run `pray install`."
|
|
150
|
-
end
|
|
151
|
-
|
|
152
150
|
def resolve_package(project_root, sources, git_sources, user_config, declaration, lockfile, offline: false, options: nil)
|
|
153
151
|
options ||= ResolveOptions.new(offline: offline)
|
|
154
152
|
root, registry_latest_version = resolve_package_root_with_metadata(
|
|
155
|
-
project_root, sources, git_sources, user_config, declaration, lockfile,
|
|
153
|
+
project_root, sources, git_sources, user_config, declaration, lockfile, options
|
|
156
154
|
)
|
|
157
155
|
spec_path = find_prayspec_file(root)
|
|
158
156
|
spec_text = File.read(spec_path)
|
|
@@ -162,11 +160,7 @@ module Pray
|
|
|
162
160
|
"package path #{root.inspect} declares #{spec.name.inspect}, expected #{declaration.name.inspect}"
|
|
163
161
|
)
|
|
164
162
|
end
|
|
165
|
-
|
|
166
|
-
raise Error.resolution(
|
|
167
|
-
"package #{declaration.name} version #{spec.version} does not satisfy constraint #{declaration.constraint}"
|
|
168
|
-
)
|
|
169
|
-
end
|
|
163
|
+
spec.satisfy_constraint!(declaration.constraint)
|
|
170
164
|
|
|
171
165
|
selected_exports = select_exports(declaration, spec)
|
|
172
166
|
file_bytes = load_package_file_bytes(root, spec)
|
|
@@ -194,12 +188,16 @@ module Pray
|
|
|
194
188
|
end
|
|
195
189
|
|
|
196
190
|
def resolve_package_root_with_metadata(
|
|
197
|
-
project_root, sources, git_sources, user_config, declaration, lockfile,
|
|
191
|
+
project_root, sources, git_sources, user_config, declaration, lockfile, options = nil
|
|
198
192
|
)
|
|
193
|
+
options ||= ResolveOptions.new
|
|
194
|
+
offline = options.offline
|
|
195
|
+
preferred_version = options.preferred_lock_version(lockfile, declaration.name)
|
|
199
196
|
if (local_path = user_config.local.package[declaration.name])
|
|
200
197
|
return [File.expand_path(local_path, project_root), nil]
|
|
201
198
|
end
|
|
202
199
|
return [File.expand_path(declaration.path, project_root), nil] if declaration.path
|
|
200
|
+
return [ResolveTarball.package_root(project_root, declaration.tarball, offline: offline), nil] if declaration.tarball
|
|
203
201
|
source_name = ResolveSource.implied_source_name(declaration, sources)
|
|
204
202
|
if source_name
|
|
205
203
|
source = sources[source_name]
|
|
@@ -212,7 +210,7 @@ module Pray
|
|
|
212
210
|
"local:#{source_name}",
|
|
213
211
|
source_root,
|
|
214
212
|
declaration,
|
|
215
|
-
preferred_version:
|
|
213
|
+
preferred_version: preferred_version,
|
|
216
214
|
offline: offline
|
|
217
215
|
)
|
|
218
216
|
return [resolved.root, resolved.registry_latest_version]
|
|
@@ -220,13 +218,14 @@ module Pray
|
|
|
220
218
|
|
|
221
219
|
case source.kind
|
|
222
220
|
when "path"
|
|
223
|
-
|
|
221
|
+
directory = LocalPrayer.path_source_package_directory(source_name, declaration.name)
|
|
222
|
+
return [File.join(project_root, source.url, directory), nil]
|
|
224
223
|
when "registry", "static index"
|
|
225
224
|
resolved = Registry.resolve_registry_package_root(
|
|
226
225
|
project_root,
|
|
227
226
|
source.url,
|
|
228
227
|
declaration,
|
|
229
|
-
preferred_version:
|
|
228
|
+
preferred_version: preferred_version,
|
|
230
229
|
offline: offline
|
|
231
230
|
)
|
|
232
231
|
return [resolved.root, resolved.registry_latest_version]
|
|
@@ -245,7 +244,7 @@ module Pray
|
|
|
245
244
|
source_key,
|
|
246
245
|
distribution_root,
|
|
247
246
|
declaration,
|
|
248
|
-
preferred_version:
|
|
247
|
+
preferred_version: preferred_version,
|
|
249
248
|
offline: offline
|
|
250
249
|
)
|
|
251
250
|
rescue Error => error
|
|
@@ -262,9 +261,7 @@ module Pray
|
|
|
262
261
|
end
|
|
263
262
|
end
|
|
264
263
|
|
|
265
|
-
|
|
266
|
-
raise Error.unsupported("remote sources are not implemented yet")
|
|
267
|
-
end
|
|
264
|
+
raise Error.unsupported("remote sources are not implemented yet") if declaration.git || declaration.oci
|
|
268
265
|
|
|
269
266
|
[File.join(project_root, declaration.name.tr("/", "-")), nil]
|
|
270
267
|
end
|
|
@@ -273,36 +270,6 @@ module Pray
|
|
|
273
270
|
resolve_package_root_with_metadata(project_root, sources, git_sources, user_config, declaration, lockfile).first
|
|
274
271
|
end
|
|
275
272
|
|
|
276
|
-
def lockfile_preferred_version(lockfile, package_name)
|
|
277
|
-
return nil unless lockfile
|
|
278
|
-
|
|
279
|
-
lockfile.package.find { |entry| entry.name == package_name }&.version
|
|
280
|
-
end
|
|
281
|
-
|
|
282
|
-
def resolve_local_file(project_root, declaration)
|
|
283
|
-
path = File.join(project_root, declaration.path)
|
|
284
|
-
unless File.exist?(path)
|
|
285
|
-
if declaration.optional
|
|
286
|
-
return ResolvedLocalFile.new(
|
|
287
|
-
path: path,
|
|
288
|
-
manifest_path: declaration.path,
|
|
289
|
-
content: "",
|
|
290
|
-
position: declaration.position,
|
|
291
|
-
optional: true
|
|
292
|
-
)
|
|
293
|
-
end
|
|
294
|
-
raise Error.resolution(missing_local_embed_guidance(declaration.path))
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
ResolvedLocalFile.new(
|
|
298
|
-
path: path,
|
|
299
|
-
manifest_path: declaration.path,
|
|
300
|
-
content: Hashing.normalize_line_endings(File.read(path)),
|
|
301
|
-
position: declaration.position,
|
|
302
|
-
optional: declaration.optional
|
|
303
|
-
)
|
|
304
|
-
end
|
|
305
|
-
|
|
306
273
|
def find_prayspec_file(root)
|
|
307
274
|
files = Dir.children(root).filter_map do |entry|
|
|
308
275
|
path = File.join(root, entry)
|
data/lib/pray/resolve_context.rb
CHANGED
|
@@ -15,5 +15,12 @@ module Pray
|
|
|
15
15
|
)
|
|
16
16
|
super
|
|
17
17
|
end
|
|
18
|
+
|
|
19
|
+
def preferred_lock_version(lockfile, package_name)
|
|
20
|
+
return nil unless lockfile
|
|
21
|
+
return nil if ignore_locked_versions || unlocked_packages.include?(package_name)
|
|
22
|
+
|
|
23
|
+
lockfile.package.find { |entry| entry.name == package_name }&.version
|
|
24
|
+
end
|
|
18
25
|
end
|
|
19
26
|
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module ResolveDeps
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def reject_dependency_cycles(packages)
|
|
8
|
+
edges = {}
|
|
9
|
+
packages.each do |package|
|
|
10
|
+
edges[package.declaration.name] = package.spec.dependencies.map(&:name)
|
|
11
|
+
end
|
|
12
|
+
cycle = DependencyGraph.find_dependency_cycle(edges)
|
|
13
|
+
return unless cycle
|
|
14
|
+
|
|
15
|
+
raise Error.resolution("dependency cycle detected: #{cycle.join(" -> ")}")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module Resolve
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def missing_local_embed_guidance(path)
|
|
8
|
+
"Prayfile lists `local \"#{path}\"` but the file does not exist. " \
|
|
9
|
+
"Create the file or remove the entry from Prayfile, then run `pray install`."
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def resolve_local_file(project_root, declaration)
|
|
13
|
+
path = File.join(project_root, declaration.path)
|
|
14
|
+
unless File.exist?(path)
|
|
15
|
+
if declaration.optional
|
|
16
|
+
return ResolvedLocalFile.new(
|
|
17
|
+
path: path,
|
|
18
|
+
manifest_path: declaration.path,
|
|
19
|
+
content: "",
|
|
20
|
+
source_checksum: Hashing.sha256_prefixed(""),
|
|
21
|
+
position: declaration.position,
|
|
22
|
+
optional: true
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
raise Error.resolution(missing_local_embed_guidance(declaration.path))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
content = Hashing.normalize_line_endings(File.read(path))
|
|
29
|
+
ResolvedLocalFile.new(
|
|
30
|
+
path: path,
|
|
31
|
+
manifest_path: declaration.path,
|
|
32
|
+
content: content,
|
|
33
|
+
source_checksum: Hashing.sha256_prefixed(content),
|
|
34
|
+
position: declaration.position,
|
|
35
|
+
optional: declaration.optional
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/pray/resolve_source.rb
CHANGED
|
@@ -10,6 +10,11 @@ module Pray
|
|
|
10
10
|
namespace = package_namespace(declaration.name)
|
|
11
11
|
return namespace if namespace && sources.key?(namespace)
|
|
12
12
|
|
|
13
|
+
unless declaration.name.include?("/")
|
|
14
|
+
path_sources = sources.select { |_name, source| source.kind == "path" }
|
|
15
|
+
return path_sources.keys.first if path_sources.length == 1
|
|
16
|
+
end
|
|
17
|
+
|
|
13
18
|
case sources.length
|
|
14
19
|
when 0 then nil
|
|
15
20
|
when 1 then sources.keys.first
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "pathname"
|
|
5
|
+
require_relative "archive_unpack"
|
|
6
|
+
require_relative "hashing"
|
|
7
|
+
require_relative "resource_limits"
|
|
8
|
+
|
|
9
|
+
module Pray
|
|
10
|
+
module ResolveTarball
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def package_root(project_root, tarball, offline:)
|
|
14
|
+
artifact_bytes = read_tarball_bytes(project_root, tarball, offline: offline)
|
|
15
|
+
if artifact_bytes.bytesize > ResourceLimits::MAX_ARCHIVE_TOTAL_BYTES
|
|
16
|
+
raise Error.integrity(
|
|
17
|
+
"package archive exceeds #{ResourceLimits::MAX_ARCHIVE_TOTAL_BYTES} bytes"
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
cache_key = Hashing.sha256_prefixed(artifact_bytes).delete_prefix("sha256:")[0, 16]
|
|
22
|
+
cache_directory = File.join(project_root, ".pray", "cache", "tarball", cache_key)
|
|
23
|
+
if File.directory?(cache_directory)
|
|
24
|
+
begin
|
|
25
|
+
Resolve.find_prayspec_file(cache_directory)
|
|
26
|
+
return cache_directory
|
|
27
|
+
rescue Error
|
|
28
|
+
nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
staging_directory = "#{cache_directory}.staging"
|
|
33
|
+
FileUtils.rm_rf(staging_directory)
|
|
34
|
+
FileUtils.mkdir_p(staging_directory)
|
|
35
|
+
ArchiveUnpack.unpack_praypkg(artifact_bytes, staging_directory)
|
|
36
|
+
Resolve.find_prayspec_file(staging_directory)
|
|
37
|
+
FileUtils.mkdir_p(File.dirname(cache_directory))
|
|
38
|
+
FileUtils.rm_rf(cache_directory)
|
|
39
|
+
FileUtils.mv(staging_directory, cache_directory)
|
|
40
|
+
cache_directory
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def read_tarball_bytes(project_root, tarball, offline:)
|
|
44
|
+
if tarball.start_with?("http://", "https://")
|
|
45
|
+
if offline
|
|
46
|
+
raise Error.resolution(
|
|
47
|
+
"tarball #{tarball} is not cached locally and offline mode is enabled"
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
return Registry.http_get(tarball)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
path = local_tarball_path(project_root, tarball)
|
|
54
|
+
unless File.file?(path)
|
|
55
|
+
raise Error.resolution("tarball missing at #{path}")
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
File.binread(path)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def local_tarball_path(project_root, tarball)
|
|
62
|
+
path = tarball.delete_prefix("file://")
|
|
63
|
+
Pathname.new(path).absolute? ? path : File.expand_path(path, project_root)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
data/lib/pray/serve.rb
CHANGED
|
@@ -65,7 +65,7 @@ module Pray
|
|
|
65
65
|
validate_body_length!(body_length)
|
|
66
66
|
body = body_length.positive? ? socket.read(body_length) : ""
|
|
67
67
|
|
|
68
|
-
response = dispatch_request(root, method, path, body)
|
|
68
|
+
response = dispatch_request(root, method, path, body, headers)
|
|
69
69
|
socket.print(response)
|
|
70
70
|
end
|
|
71
71
|
|
|
@@ -94,7 +94,7 @@ module Pray
|
|
|
94
94
|
)
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
-
def dispatch_request(root, method, path, body = "")
|
|
97
|
+
def dispatch_request(root, method, path, body = "", headers = {})
|
|
98
98
|
path = path.split("?", 2).first
|
|
99
99
|
|
|
100
100
|
case [method, path]
|
|
@@ -125,7 +125,8 @@ module Pray
|
|
|
125
125
|
|
|
126
126
|
content_type = content_type_for(file_path)
|
|
127
127
|
file_body = File.binread(file_path)
|
|
128
|
-
|
|
128
|
+
status, ranged_body, content_range = ServeRange.apply(200, file_body, headers["range"])
|
|
129
|
+
ranged_response(status, content_type, ranged_body, content_range)
|
|
129
130
|
end
|
|
130
131
|
|
|
131
132
|
def content_type_for(path)
|
|
@@ -137,7 +138,17 @@ module Pray
|
|
|
137
138
|
end
|
|
138
139
|
|
|
139
140
|
def ok_response(content_type, body)
|
|
140
|
-
|
|
141
|
+
ranged_response(200, content_type, body, nil)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def ranged_response(status, content_type, body, content_range)
|
|
145
|
+
reason = {
|
|
146
|
+
200 => "OK",
|
|
147
|
+
206 => "Partial Content",
|
|
148
|
+
416 => "Range Not Satisfiable"
|
|
149
|
+
}.fetch(status, "OK")
|
|
150
|
+
range_header = content_range ? "Content-Range: #{content_range}\r\n" : ""
|
|
151
|
+
"HTTP/1.1 #{status} #{reason}\r\nContent-Type: #{content_type}\r\n#{range_header}Content-Length: #{body.bytesize}\r\nConnection: close\r\n\r\n#{body}"
|
|
141
152
|
end
|
|
142
153
|
|
|
143
154
|
def html_response(body)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module ServeRange
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def apply(status, body, range_header)
|
|
8
|
+
return [status, body, nil] if range_header.nil? || range_header.empty?
|
|
9
|
+
return [status, body, nil] unless status == 200 && !body.empty?
|
|
10
|
+
|
|
11
|
+
start_offset, end_offset = parse_byte_range(range_header, body.bytesize)
|
|
12
|
+
unless start_offset
|
|
13
|
+
return [416, "".b, "bytes */#{body.bytesize}"]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
[
|
|
17
|
+
206,
|
|
18
|
+
body.b[start_offset..end_offset],
|
|
19
|
+
"bytes #{start_offset}-#{end_offset}/#{body.bytesize}"
|
|
20
|
+
]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def parse_byte_range(header, total)
|
|
24
|
+
range = header.delete_prefix("bytes=")
|
|
25
|
+
start_text, end_text = range.split("-", 2)
|
|
26
|
+
return unless start_text && end_text
|
|
27
|
+
|
|
28
|
+
start_offset = Integer(start_text, exception: false)
|
|
29
|
+
end_offset = Integer(end_text, exception: false)
|
|
30
|
+
return if start_offset.nil? || end_offset.nil?
|
|
31
|
+
return unless start_offset <= end_offset && end_offset < total
|
|
32
|
+
|
|
33
|
+
[start_offset, end_offset]
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|