pray-cli 1.16.0 → 1.18.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 +21 -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/trust.rb +3 -3
- data/lib/pray/cli/commands/update_latest.rb +14 -16
- 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/destination.rb +32 -2
- data/lib/pray/distribution.rb +94 -0
- data/lib/pray/format_serialize.rb +1 -4
- data/lib/pray/git_cache.rb +192 -0
- data/lib/pray/git_clone.rb +32 -0
- data/lib/pray/git_materialize.rb +76 -0
- data/lib/pray/git_run.rb +41 -0
- data/lib/pray/git_sources.rb +60 -145
- 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 +7 -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 +4 -15
- data/lib/pray/registry_http_cache.rb +17 -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 +6 -37
- data/lib/pray/resolve_local.rb +39 -0
- data/lib/pray/resolve_source.rb +5 -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/version.rb +2 -2
- data/lib/pray.rb +10 -0
- metadata +17 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module RegistryHttpCache
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def fetch_package_metadata(source_url, package_name)
|
|
8
|
+
@cache ||= {}
|
|
9
|
+
key = [source_url, package_name]
|
|
10
|
+
return @cache[key] if @cache.key?(key)
|
|
11
|
+
|
|
12
|
+
PathSafety.reject_unsafe_package_name!(package_name)
|
|
13
|
+
response = Registry.http_get(Registry.join_url(source_url, "v1/packages/#{package_name}.json"))
|
|
14
|
+
@cache[key] = Registry.parse_metadata(response)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
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
|
|
@@ -61,7 +61,6 @@ module Pray
|
|
|
61
61
|
lockfile_hints,
|
|
62
62
|
refresh: options.refresh || options.refresh_source_revisions
|
|
63
63
|
)
|
|
64
|
-
source_revisions = git_sources.transform_values(&:revision)
|
|
65
64
|
source_host_keys = Trust.prepare_source_host_keys(manifest.sources)
|
|
66
65
|
|
|
67
66
|
packages = []
|
|
@@ -92,6 +91,7 @@ module Pray
|
|
|
92
91
|
local_files = []
|
|
93
92
|
local_errors = []
|
|
94
93
|
manifest.local.each do |local|
|
|
94
|
+
next unless Destination.local_compose_embed?(manifest, local)
|
|
95
95
|
local_files << resolve_local_file(project_root, local)
|
|
96
96
|
rescue Error => error
|
|
97
97
|
local_errors << "local #{local.path}: #{error.message}"
|
|
@@ -99,6 +99,7 @@ module Pray
|
|
|
99
99
|
raise Error.resolution(local_errors.join("\n")) unless local_errors.empty?
|
|
100
100
|
|
|
101
101
|
ResolveDeps.reject_dependency_cycles(packages)
|
|
102
|
+
source_revisions = git_sources.revisions
|
|
102
103
|
|
|
103
104
|
ResolvedProject.new(
|
|
104
105
|
manifest_path: manifest_path,
|
|
@@ -146,11 +147,6 @@ module Pray
|
|
|
146
147
|
GitRefresh.resolution_may_benefit_from_git_source_refresh?(error)
|
|
147
148
|
end
|
|
148
149
|
|
|
149
|
-
def missing_local_embed_guidance(path)
|
|
150
|
-
"Prayfile lists `local \"#{path}\"` but the file does not exist. " \
|
|
151
|
-
"Create the file or remove the entry from Prayfile, then run `pray install`."
|
|
152
|
-
end
|
|
153
|
-
|
|
154
150
|
def resolve_package(project_root, sources, git_sources, user_config, declaration, lockfile, offline: false, options: nil)
|
|
155
151
|
options ||= ResolveOptions.new(offline: offline)
|
|
156
152
|
root, registry_latest_version = resolve_package_root_with_metadata(
|
|
@@ -164,11 +160,7 @@ module Pray
|
|
|
164
160
|
"package path #{root.inspect} declares #{spec.name.inspect}, expected #{declaration.name.inspect}"
|
|
165
161
|
)
|
|
166
162
|
end
|
|
167
|
-
|
|
168
|
-
raise Error.resolution(
|
|
169
|
-
"package #{declaration.name} version #{spec.version} does not satisfy constraint #{declaration.constraint}"
|
|
170
|
-
)
|
|
171
|
-
end
|
|
163
|
+
spec.satisfy_constraint!(declaration.constraint)
|
|
172
164
|
|
|
173
165
|
selected_exports = select_exports(declaration, spec)
|
|
174
166
|
file_bytes = load_package_file_bytes(root, spec)
|
|
@@ -226,7 +218,8 @@ module Pray
|
|
|
226
218
|
|
|
227
219
|
case source.kind
|
|
228
220
|
when "path"
|
|
229
|
-
|
|
221
|
+
directory = LocalPrayer.path_source_package_directory(source_name, declaration.name)
|
|
222
|
+
return [File.join(project_root, source.url, directory), nil]
|
|
230
223
|
when "registry", "static index"
|
|
231
224
|
resolved = Registry.resolve_registry_package_root(
|
|
232
225
|
project_root,
|
|
@@ -277,30 +270,6 @@ module Pray
|
|
|
277
270
|
resolve_package_root_with_metadata(project_root, sources, git_sources, user_config, declaration, lockfile).first
|
|
278
271
|
end
|
|
279
272
|
|
|
280
|
-
def resolve_local_file(project_root, declaration)
|
|
281
|
-
path = File.join(project_root, declaration.path)
|
|
282
|
-
unless File.exist?(path)
|
|
283
|
-
if declaration.optional
|
|
284
|
-
return ResolvedLocalFile.new(
|
|
285
|
-
path: path,
|
|
286
|
-
manifest_path: declaration.path,
|
|
287
|
-
content: "",
|
|
288
|
-
position: declaration.position,
|
|
289
|
-
optional: true
|
|
290
|
-
)
|
|
291
|
-
end
|
|
292
|
-
raise Error.resolution(missing_local_embed_guidance(declaration.path))
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
ResolvedLocalFile.new(
|
|
296
|
-
path: path,
|
|
297
|
-
manifest_path: declaration.path,
|
|
298
|
-
content: Hashing.normalize_line_endings(File.read(path)),
|
|
299
|
-
position: declaration.position,
|
|
300
|
-
optional: declaration.optional
|
|
301
|
-
)
|
|
302
|
-
end
|
|
303
|
-
|
|
304
273
|
def find_prayspec_file(root)
|
|
305
274
|
files = Dir.children(root).filter_map do |entry|
|
|
306
275
|
path = File.join(root, entry)
|
|
@@ -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
|
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
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Pray
|
|
7
|
+
TORRENT_MANIFEST_SPEC = "pray-torrent-v1"
|
|
8
|
+
DEFAULT_TORRENT_PIECE_SIZE = 16 * 1024
|
|
9
|
+
|
|
10
|
+
module TorrentManifest
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def path_for(artifact_path)
|
|
14
|
+
"#{artifact_path}.praytorrent.json"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def payload(name:, version:, artifact_path:, archive_bytes:, trackers: [])
|
|
18
|
+
{
|
|
19
|
+
"spec" => TORRENT_MANIFEST_SPEC,
|
|
20
|
+
"name" => name,
|
|
21
|
+
"version" => version,
|
|
22
|
+
"artifact_url" => artifact_path,
|
|
23
|
+
"artifact_hash" => Hashing.sha256_prefixed(archive_bytes),
|
|
24
|
+
"piece_size" => DEFAULT_TORRENT_PIECE_SIZE,
|
|
25
|
+
"length" => archive_bytes.bytesize,
|
|
26
|
+
"pieces" => piece_hashes(archive_bytes),
|
|
27
|
+
"sources" => [artifact_path],
|
|
28
|
+
"trackers" => Array(trackers)
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def bytes(name:, version:, artifact_path:, archive_bytes:, trackers: [])
|
|
33
|
+
JSON.pretty_generate(
|
|
34
|
+
payload(
|
|
35
|
+
name: name,
|
|
36
|
+
version: version,
|
|
37
|
+
artifact_path: artifact_path,
|
|
38
|
+
archive_bytes: archive_bytes,
|
|
39
|
+
trackers: trackers
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def write(root, name:, version:, artifact_path:, archive_bytes:, settings:)
|
|
45
|
+
return unless settings.allows_torrent?
|
|
46
|
+
|
|
47
|
+
path = File.join(root, path_for(artifact_path))
|
|
48
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
49
|
+
File.write(
|
|
50
|
+
path,
|
|
51
|
+
bytes(
|
|
52
|
+
name: name,
|
|
53
|
+
version: version,
|
|
54
|
+
artifact_path: artifact_path,
|
|
55
|
+
archive_bytes: archive_bytes,
|
|
56
|
+
trackers: settings.bootstrap_trackers
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def piece_hashes(archive_bytes)
|
|
62
|
+
length = archive_bytes.bytesize
|
|
63
|
+
return [] if length.zero?
|
|
64
|
+
|
|
65
|
+
hashes = []
|
|
66
|
+
start = 0
|
|
67
|
+
while start < length
|
|
68
|
+
hashes << Hashing.sha256_prefixed(archive_bytes.byteslice(start, DEFAULT_TORRENT_PIECE_SIZE))
|
|
69
|
+
start += DEFAULT_TORRENT_PIECE_SIZE
|
|
70
|
+
end
|
|
71
|
+
hashes
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def descriptor_present?(root, artifact_path, settings)
|
|
75
|
+
!settings.allows_torrent? || File.file?(File.join(root, path_for(artifact_path)))
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module Upstream
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def path_fork_drift_lines(project, previous, options)
|
|
8
|
+
user_config = Config.load_user_config
|
|
9
|
+
git_sources = GitSources.prepare_git_sources(
|
|
10
|
+
project.project_root,
|
|
11
|
+
project.manifest.sources,
|
|
12
|
+
previous,
|
|
13
|
+
refresh: options.refresh || options.refresh_source_revisions
|
|
14
|
+
)
|
|
15
|
+
sources = Resolve.source_map(project.manifest.sources)
|
|
16
|
+
lines = []
|
|
17
|
+
project.packages.each do |package|
|
|
18
|
+
next unless package.declaration.path
|
|
19
|
+
next unless package.upstream
|
|
20
|
+
|
|
21
|
+
upstream = package.upstream
|
|
22
|
+
resolved = resolve_named(
|
|
23
|
+
project.project_root, sources, git_sources, user_config, previous, options,
|
|
24
|
+
upstream.name, "= #{upstream.version}", upstream.source
|
|
25
|
+
)
|
|
26
|
+
upstream_content = content_file_bytes(resolved.root, resolved.spec)
|
|
27
|
+
local_content = local_content_for_refresh(package.root, package.spec)
|
|
28
|
+
if local_content.empty?
|
|
29
|
+
lines << "#{package.declaration.name} has no content files; run pray install to copy #{upstream.name} #{upstream.version}"
|
|
30
|
+
next
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
overlay_file_changes(local_content, upstream_content).each do |path, change|
|
|
34
|
+
lines << overlay_drift_line(
|
|
35
|
+
package.declaration.name, upstream.name, upstream.version, path, change
|
|
36
|
+
)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
lines
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/pray/upstream_merge.rb
CHANGED
|
@@ -76,6 +76,31 @@ module Pray
|
|
|
76
76
|
|
|
77
77
|
"= #{new_version}"
|
|
78
78
|
end
|
|
79
|
+
|
|
80
|
+
def overlay_file_changes(local_content, upstream_content)
|
|
81
|
+
(local_content.keys + upstream_content.keys).uniq.sort.filter_map do |path|
|
|
82
|
+
local = local_content[path]
|
|
83
|
+
upstream = upstream_content[path]
|
|
84
|
+
if local && upstream && local != upstream
|
|
85
|
+
[path, :changed]
|
|
86
|
+
elsif local && upstream.nil?
|
|
87
|
+
[path, :local_only]
|
|
88
|
+
elsif local.nil? && upstream
|
|
89
|
+
[path, :missing]
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def overlay_drift_line(fork, upstream_name, upstream_version, path, change)
|
|
95
|
+
case change
|
|
96
|
+
when :changed
|
|
97
|
+
"#{fork} #{path} differs from #{upstream_name} #{upstream_version}"
|
|
98
|
+
when :local_only
|
|
99
|
+
"#{fork} #{path} local"
|
|
100
|
+
else
|
|
101
|
+
"#{fork} #{path} missing from fork"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
79
104
|
private_class_method :merge_one_content_path, :merge_all_present
|
|
80
105
|
end
|
|
81
106
|
end
|