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,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module DependencyGraph
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def find_dependency_cycle(edges)
|
|
8
|
+
visiting = {}
|
|
9
|
+
visited = {}
|
|
10
|
+
stack = []
|
|
11
|
+
edges.keys.sort.each do |name|
|
|
12
|
+
next if visited[name]
|
|
13
|
+
|
|
14
|
+
cycle = depth_first_search(name, edges, visiting, visited, stack)
|
|
15
|
+
return cycle if cycle
|
|
16
|
+
end
|
|
17
|
+
nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def depth_first_search(name, edges, visiting, visited, stack)
|
|
21
|
+
return nil if visited[name]
|
|
22
|
+
if visiting[name]
|
|
23
|
+
start = stack.index(name)
|
|
24
|
+
return nil unless start
|
|
25
|
+
|
|
26
|
+
return stack[start..] + [name]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
visiting[name] = true
|
|
30
|
+
stack << name
|
|
31
|
+
Array(edges[name]).each do |dependency|
|
|
32
|
+
next unless edges.key?(dependency)
|
|
33
|
+
|
|
34
|
+
cycle = depth_first_search(dependency, edges, visiting, visited, stack)
|
|
35
|
+
return cycle if cycle
|
|
36
|
+
end
|
|
37
|
+
stack.pop
|
|
38
|
+
visiting.delete(name)
|
|
39
|
+
visited[name] = true
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/pray/destination.rb
CHANGED
|
@@ -16,8 +16,7 @@ module Pray
|
|
|
16
16
|
|
|
17
17
|
def local_path_form?(value)
|
|
18
18
|
value.start_with?(".", "/") ||
|
|
19
|
-
value.end_with?(".md", ".txt", ".markdown")
|
|
20
|
-
!value.include?("/")
|
|
19
|
+
value.end_with?(".md", ".txt", ".markdown")
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
def destination_target_name(mode, path)
|
|
@@ -106,6 +105,7 @@ module Pray
|
|
|
106
105
|
if existing.position == "after" && local.position != "after"
|
|
107
106
|
existing.position = local.position
|
|
108
107
|
end
|
|
108
|
+
existing.file ||= local.file
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
def bind_package_entry(target, package_name)
|
|
@@ -154,5 +154,35 @@ module Pray
|
|
|
154
154
|
else false
|
|
155
155
|
end
|
|
156
156
|
end
|
|
157
|
+
|
|
158
|
+
def local_compose_embed?(manifest, local)
|
|
159
|
+
return false if local.file
|
|
160
|
+
|
|
161
|
+
manifest.targets.none? do |target|
|
|
162
|
+
target.mode == "tree" &&
|
|
163
|
+
target.entries.any? { |entry| entry.kind == "local" && entry.path == local.path }
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def try_apply_local_pray(manifest, path, file_dest, other_package_signal, destination_index)
|
|
168
|
+
return false unless local_path_form?(path)
|
|
169
|
+
|
|
170
|
+
if file_dest
|
|
171
|
+
return false if other_package_signal
|
|
172
|
+
raise Error.parse("manifest", "a local file cannot use file:")
|
|
173
|
+
end
|
|
174
|
+
return false if other_package_signal
|
|
175
|
+
unless destination_index
|
|
176
|
+
raise Error.parse("manifest", "local pray paths are only valid inside compose")
|
|
177
|
+
end
|
|
178
|
+
mode = manifest.targets[destination_index].mode
|
|
179
|
+
unless mode == "compose"
|
|
180
|
+
raise Error.parse("manifest", "local pray paths are only valid inside compose")
|
|
181
|
+
end
|
|
182
|
+
local = ManifestLocal.new(path: path, bound: true)
|
|
183
|
+
bind_local_entry(manifest.targets[destination_index], local.path)
|
|
184
|
+
upsert_local(manifest, local)
|
|
185
|
+
true
|
|
186
|
+
end
|
|
157
187
|
end
|
|
158
188
|
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Pray
|
|
7
|
+
DISTRIBUTION_CONFIG_SPEC = "pray-distribution-config-1"
|
|
8
|
+
TORRENT_PROTOCOL = "torrent"
|
|
9
|
+
DISTRIBUTION_CONFIG_FIELDS = %w[spec protocols bootstrap_trackers enable_dht].freeze
|
|
10
|
+
|
|
11
|
+
RegistryDistributionSettings = Struct.new(
|
|
12
|
+
:spec, :protocols, :bootstrap_trackers, :enable_dht,
|
|
13
|
+
keyword_init: true
|
|
14
|
+
) do
|
|
15
|
+
def initialize(spec: DISTRIBUTION_CONFIG_SPEC, protocols: [], bootstrap_trackers: [], enable_dht: false)
|
|
16
|
+
super
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def allows_torrent?
|
|
20
|
+
Array(protocols).include?(TORRENT_PROTOCOL)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
module Distribution
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
def default_settings
|
|
28
|
+
RegistryDistributionSettings.new
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def parse_settings(text)
|
|
32
|
+
data = JSON.parse(text)
|
|
33
|
+
unless data.is_a?(Hash)
|
|
34
|
+
raise Error.parse("distribution config", "expected an object")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
data.each_key do |key|
|
|
38
|
+
next if DISTRIBUTION_CONFIG_FIELDS.include?(key)
|
|
39
|
+
|
|
40
|
+
raise Error.parse("distribution config", "unsupported field: #{key}")
|
|
41
|
+
end
|
|
42
|
+
settings = RegistryDistributionSettings.new(
|
|
43
|
+
spec: data.fetch("spec", DISTRIBUTION_CONFIG_SPEC),
|
|
44
|
+
protocols: Array(data["protocols"]),
|
|
45
|
+
bootstrap_trackers: Array(data["bootstrap_trackers"]),
|
|
46
|
+
enable_dht: data["enable_dht"] == true
|
|
47
|
+
)
|
|
48
|
+
validate_settings!(settings)
|
|
49
|
+
settings
|
|
50
|
+
rescue JSON::ParserError => error
|
|
51
|
+
raise Error.parse("distribution config", error.message)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def read_settings(root)
|
|
55
|
+
path = File.join(root, "v1", "distribution.json")
|
|
56
|
+
return default_settings unless File.file?(path)
|
|
57
|
+
|
|
58
|
+
parse_settings(File.read(path))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def write_settings(root, settings = default_settings)
|
|
62
|
+
validate_settings!(settings)
|
|
63
|
+
path = File.join(root, "v1", "distribution.json")
|
|
64
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
65
|
+
payload = {"spec" => settings.spec, "protocols" => Array(settings.protocols)}
|
|
66
|
+
File.write(path, JSON.pretty_generate(payload))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def validate_settings!(settings)
|
|
70
|
+
unless settings.spec == DISTRIBUTION_CONFIG_SPEC
|
|
71
|
+
raise Error.parse("distribution config", "unsupported distribution config spec: #{settings.spec}")
|
|
72
|
+
end
|
|
73
|
+
Array(settings.protocols).each do |name|
|
|
74
|
+
next if name == TORRENT_PROTOCOL
|
|
75
|
+
|
|
76
|
+
raise Error.parse("distribution config", "unsupported protocol: #{name}")
|
|
77
|
+
end
|
|
78
|
+
if settings.enable_dht
|
|
79
|
+
raise Error.parse("distribution config", "DHT announce is not implemented")
|
|
80
|
+
end
|
|
81
|
+
return if Array(settings.bootstrap_trackers).empty? || settings.allows_torrent?
|
|
82
|
+
|
|
83
|
+
raise Error.parse("distribution config", "bootstrap_trackers requires protocols to include torrent")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def fetch_settings(source_url)
|
|
87
|
+
parse_settings(Registry.http_get(Registry.join_url(source_url, "v1/distribution.json")))
|
|
88
|
+
rescue Error => error
|
|
89
|
+
return default_settings if error.message.match?(/:\s*404\b/)
|
|
90
|
+
|
|
91
|
+
raise
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -42,10 +42,7 @@ module Pray
|
|
|
42
42
|
path = target.skills.first || ""
|
|
43
43
|
lines << %(tree "#{path}" do)
|
|
44
44
|
target.entries.each do |entry|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
package = find_package(manifest, entry.name)
|
|
48
|
-
lines << " #{ManifestMethods.format_package_declaration(package)}" if package
|
|
45
|
+
lines << " #{format_destination_entry(entry, manifest)}"
|
|
49
46
|
end
|
|
50
47
|
lines << "end"
|
|
51
48
|
end
|
data/lib/pray/git_sources.rb
CHANGED
|
@@ -16,8 +16,8 @@ module Pray
|
|
|
16
16
|
next unless source.kind == "git"
|
|
17
17
|
|
|
18
18
|
clone_url = source.url.delete_prefix("git+")
|
|
19
|
-
if local_filesystem_source?(clone_url) && !local_git_repo_path(clone_url)
|
|
20
|
-
source_root = local_git_source_root(clone_url)
|
|
19
|
+
if local_filesystem_source?(clone_url) && !local_git_repo_path(project_root, clone_url)
|
|
20
|
+
source_root = local_git_source_root(project_root, clone_url)
|
|
21
21
|
if source_root
|
|
22
22
|
checkouts[source.name] = GitSourceCheckout.new(
|
|
23
23
|
cache_directory: source_root,
|
|
@@ -76,12 +76,8 @@ module Pray
|
|
|
76
76
|
File.directory?(File.join(path, "v1", "packages"))
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
-
def local_git_source_root(clone_url)
|
|
80
|
-
path =
|
|
81
|
-
clone_url.delete_prefix("file://")
|
|
82
|
-
else
|
|
83
|
-
clone_url
|
|
84
|
-
end
|
|
79
|
+
def local_git_source_root(project_root, clone_url)
|
|
80
|
+
path = clone_url_filesystem_path(project_root, clone_url)
|
|
85
81
|
return nil unless File.exist?(path)
|
|
86
82
|
|
|
87
83
|
discover_distribution_root(path)
|
|
@@ -136,12 +132,17 @@ module Pray
|
|
|
136
132
|
clone_url.start_with?("file://") || Pathname.new(clone_url).absolute?
|
|
137
133
|
end
|
|
138
134
|
|
|
139
|
-
def local_git_repo_path(clone_url)
|
|
140
|
-
path = clone_url
|
|
135
|
+
def local_git_repo_path(project_root, clone_url)
|
|
136
|
+
path = clone_url_filesystem_path(project_root, clone_url)
|
|
141
137
|
git_directory = File.join(path, ".git")
|
|
142
138
|
File.directory?(git_directory) ? path : nil
|
|
143
139
|
end
|
|
144
140
|
|
|
141
|
+
def clone_url_filesystem_path(project_root, clone_url)
|
|
142
|
+
path = clone_url.delete_prefix("file://")
|
|
143
|
+
Pathname.new(path).absolute? ? path : File.expand_path(path, project_root)
|
|
144
|
+
end
|
|
145
|
+
|
|
145
146
|
def global_cache_root
|
|
146
147
|
return ENV["PRAY_CACHE"] if ENV["PRAY_CACHE"]
|
|
147
148
|
return File.join(ENV["PRAY_HOME"], "cache") if ENV["PRAY_HOME"]
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
DEFAULT_LOCAL_PRAYER_NAME = "project"
|
|
5
|
+
DEFAULT_PATH_SOURCE_NAME = "local"
|
|
6
|
+
DEFAULT_PATH_SOURCE_DIRECTORY = "prayers"
|
|
7
|
+
|
|
8
|
+
module LocalPrayer
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def validate_name!(name)
|
|
12
|
+
trimmed = name.to_s.strip
|
|
13
|
+
raise Error.usage("prayer name is missing") if trimmed.empty?
|
|
14
|
+
if trimmed.include?("/") || trimmed.include?("\\") || trimmed == "." || trimmed == ".."
|
|
15
|
+
raise Error.usage("prayer name must be a single folder name")
|
|
16
|
+
end
|
|
17
|
+
if reserved_distribution_layout_name?(trimmed)
|
|
18
|
+
raise Error.usage("#{trimmed} is reserved for the distribution layout")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
trimmed
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def path_source_package_directory(source_name, package_name)
|
|
25
|
+
prefix = "#{source_name}/"
|
|
26
|
+
rest = package_name.delete_prefix(prefix)
|
|
27
|
+
if rest != package_name && !rest.empty? && !rest.include?("/") && !rest.include?("\\")
|
|
28
|
+
return rest
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
package_name.to_s.tr("/", "-").tr("\\", "-")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def reserved_distribution_layout_name?(name)
|
|
35
|
+
name.match?(/\Av[0-9]+\z/)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/pray/lockfile.rb
CHANGED
|
@@ -5,6 +5,8 @@ require "perfect_toml"
|
|
|
5
5
|
require_relative "lockfile_serialize"
|
|
6
6
|
|
|
7
7
|
module Pray
|
|
8
|
+
LOCAL_EMBED_PACKAGE = "local"
|
|
9
|
+
|
|
8
10
|
LockSource = Struct.new(:name, :kind, :url, :revision, :host_key_fingerprint)
|
|
9
11
|
LockedPackage = Struct.new(
|
|
10
12
|
:name, :version, :source, :path, :tree_hash, :artifact_hash, :artifact,
|
|
@@ -20,7 +22,11 @@ module Pray
|
|
|
20
22
|
ManagedSpanRecord = Struct.new(
|
|
21
23
|
:id, :target, :open_line, :close_line, :ideal_checksum, :package, :export,
|
|
22
24
|
:source_checksum, :silenced
|
|
23
|
-
)
|
|
25
|
+
) do
|
|
26
|
+
def local_embed?
|
|
27
|
+
package == LOCAL_EMBED_PACKAGE
|
|
28
|
+
end
|
|
29
|
+
end
|
|
24
30
|
ProvisionedFileRecord = Struct.new(:path, :content_hash, :package, :export)
|
|
25
31
|
|
|
26
32
|
Lockfile = Struct.new(
|
|
@@ -179,7 +185,7 @@ module Pray
|
|
|
179
185
|
package: packages.map do |package|
|
|
180
186
|
LockedPackage.new(
|
|
181
187
|
name: package.declaration.name,
|
|
182
|
-
version: package.spec.
|
|
188
|
+
version: package.spec.recorded_version,
|
|
183
189
|
source: package.declaration.source,
|
|
184
190
|
path: relative_lockfile_path(project_root, package.root),
|
|
185
191
|
tree_hash: package.tree_hash,
|
data/lib/pray/manifest.rb
CHANGED
|
@@ -46,8 +46,8 @@ module Pray
|
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
ManifestLocal = Struct.new(:path, :position, :optional, :bound) do
|
|
50
|
-
def initialize(path:, position: "after", optional: false, bound: false)
|
|
49
|
+
ManifestLocal = Struct.new(:path, :position, :optional, :bound, :file) do
|
|
50
|
+
def initialize(path:, position: "after", optional: false, bound: false, file: nil)
|
|
51
51
|
super
|
|
52
52
|
end
|
|
53
53
|
end
|
|
@@ -135,7 +135,10 @@ module Pray
|
|
|
135
135
|
PathSafety.validate_project_relative_path!(package.path) if package.path
|
|
136
136
|
PathSafety.validate_destination_path!(package.file) if package.file
|
|
137
137
|
end
|
|
138
|
-
manifest.local.each
|
|
138
|
+
manifest.local.each do |local|
|
|
139
|
+
PathSafety.validate_project_relative_path!(local.path)
|
|
140
|
+
PathSafety.validate_destination_path!(local.file) if local.file
|
|
141
|
+
end
|
|
139
142
|
end
|
|
140
143
|
end
|
|
141
144
|
|
data/lib/pray/manifest_json.rb
CHANGED
|
@@ -116,7 +116,7 @@ module Pray
|
|
|
116
116
|
while (statement = next_statement)
|
|
117
117
|
if statement == "end"
|
|
118
118
|
unless saw_package
|
|
119
|
-
raise Error.parse("manifest", "file block requires a pray package
|
|
119
|
+
raise Error.parse("manifest", "file block requires a pray package")
|
|
120
120
|
end
|
|
121
121
|
return
|
|
122
122
|
end
|
|
@@ -132,7 +132,15 @@ module Pray
|
|
|
132
132
|
return true
|
|
133
133
|
end
|
|
134
134
|
if (match = statement.match(/\A(?:pray|use|include|package) (.+)\z/))
|
|
135
|
-
|
|
135
|
+
rest = match[1]
|
|
136
|
+
values, keywords = parse_call(rest)
|
|
137
|
+
if values.length == 1 && keywords.empty?
|
|
138
|
+
first = string_from_value(values.first)
|
|
139
|
+
if Destination.local_path_form?(first)
|
|
140
|
+
raise Error.parse("manifest", "file: requires a package")
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
bind_file_package(manifest, rest, file_path)
|
|
136
144
|
return true
|
|
137
145
|
end
|
|
138
146
|
raise Error.parse("manifest", "unsupported statement inside file block: #{statement}")
|
|
@@ -161,19 +169,11 @@ module Pray
|
|
|
161
169
|
end
|
|
162
170
|
|
|
163
171
|
def apply_local_pray_path(manifest, first, keywords, values, destination_index)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
unless in_compose
|
|
170
|
-
raise Error.parse("manifest", "local pray paths are only valid inside compose blocks")
|
|
171
|
-
end
|
|
172
|
-
|
|
173
|
-
local = ManifestLocal.new(path: first, position: "after", optional: false, bound: true)
|
|
174
|
-
Destination.bind_local_entry(manifest.targets[destination_index], local.path)
|
|
175
|
-
Destination.upsert_local(manifest, local)
|
|
176
|
-
true
|
|
172
|
+
file_dest = keywords["file"]&.as_string
|
|
173
|
+
other_package_signal = values.length > 1 ||
|
|
174
|
+
%w[source export exports optional path git tag rev tarball oci targets features]
|
|
175
|
+
.any? { |key| keywords.key?(key) }
|
|
176
|
+
Destination.try_apply_local_pray(manifest, first, file_dest, other_package_signal, destination_index)
|
|
177
177
|
end
|
|
178
178
|
|
|
179
179
|
def package_signal?(values, keywords)
|
data/lib/pray/materialize.rb
CHANGED
|
@@ -54,9 +54,15 @@ module Pray
|
|
|
54
54
|
raise
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
|
+
previous_lockfile = project.previous_lockfile
|
|
58
|
+
unless locked || frozen
|
|
59
|
+
if Upstream.apply_path_upstream_refreshes(project, previous_lockfile, nil, options)
|
|
60
|
+
project = Resolve.resolve_project_in_context(manifest_path, project_root, options)
|
|
61
|
+
previous_lockfile = project.previous_lockfile
|
|
62
|
+
end
|
|
63
|
+
end
|
|
57
64
|
rendered = Render.render_project(project)
|
|
58
65
|
lockfile_path = default_lockfile_path(project.project_root)
|
|
59
|
-
previous_lockfile = project.previous_lockfile
|
|
60
66
|
next_lockfile = LockfileIO.build_lockfile(
|
|
61
67
|
project.manifest_hash,
|
|
62
68
|
project.environment,
|
|
@@ -133,6 +139,7 @@ module Pray
|
|
|
133
139
|
def resolve_project(...) = Resolve.resolve_project(...)
|
|
134
140
|
def render_project(...) = Render.render_project(...)
|
|
135
141
|
def inspect_project(...) = Verify.inspect_project(...)
|
|
142
|
+
def inspect_locked_destinations(...) = Verify.inspect_locked_destinations(...)
|
|
136
143
|
def verify_project(...) = Verify.verify_project(...)
|
|
137
144
|
def drift_project(...) = Verify.drift_project(...)
|
|
138
145
|
end
|
data/lib/pray/package_spec.rb
CHANGED
|
@@ -12,12 +12,12 @@ module Pray
|
|
|
12
12
|
PackageUpstream = Struct.new(:name, :constraint, keyword_init: true)
|
|
13
13
|
|
|
14
14
|
PackageSpec = Struct.new(
|
|
15
|
-
:name, :version, :summary, :description, :authors, :license, :homepage,
|
|
15
|
+
:name, :version, :summary, :description, :authors, :maintainers, :license, :homepage,
|
|
16
16
|
:source_code_uri, :changelog_uri, :prayfile_version, :files, :exports,
|
|
17
17
|
:skills, :templates, :adapters, :targets, :dependencies, :metadata, :upstream
|
|
18
18
|
) do
|
|
19
19
|
def initialize(
|
|
20
|
-
name: "", version: "", summary: nil, description: nil, authors: [], license: nil,
|
|
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
23
|
dependencies: [], metadata: {}, upstream: nil
|
|
@@ -47,8 +47,9 @@ module Pray
|
|
|
47
47
|
def canonicalized
|
|
48
48
|
dup.tap do |copy|
|
|
49
49
|
copy.files = files.sort
|
|
50
|
-
copy.authors = authors.sort
|
|
51
|
-
copy.
|
|
50
|
+
copy.authors = Array(authors).sort
|
|
51
|
+
copy.maintainers = Array(maintainers).sort
|
|
52
|
+
copy.targets = Array(targets).sort
|
|
52
53
|
copy.dependencies = dependencies.sort_by { |dependency| [dependency.name, dependency.constraint, dependency.optional] }
|
|
53
54
|
end
|
|
54
55
|
end
|
|
@@ -227,18 +228,29 @@ module Pray
|
|
|
227
228
|
end
|
|
228
229
|
end
|
|
229
230
|
|
|
231
|
+
STRING_ASSIGNMENTS = {
|
|
232
|
+
"name" => :name=,
|
|
233
|
+
"version" => :version=,
|
|
234
|
+
"summary" => :summary=,
|
|
235
|
+
"description" => :description=,
|
|
236
|
+
"license" => :license=,
|
|
237
|
+
"homepage" => :homepage=,
|
|
238
|
+
"source_code_uri" => :source_code_uri=,
|
|
239
|
+
"changelog_uri" => :changelog_uri=,
|
|
240
|
+
"prayfile_version" => :prayfile_version=,
|
|
241
|
+
"pray_version" => :prayfile_version=
|
|
242
|
+
}.freeze
|
|
243
|
+
|
|
230
244
|
def apply_assignment(spec, field, value)
|
|
245
|
+
setter = STRING_ASSIGNMENTS[field]
|
|
246
|
+
if setter
|
|
247
|
+
spec.public_send(setter, string_from_literal(value))
|
|
248
|
+
return
|
|
249
|
+
end
|
|
250
|
+
|
|
231
251
|
case field
|
|
232
|
-
when "name" then spec.name = string_from_literal(value)
|
|
233
|
-
when "version" then spec.version = string_from_literal(value)
|
|
234
|
-
when "summary" then spec.summary = string_from_literal(value)
|
|
235
|
-
when "description" then spec.description = string_from_literal(value)
|
|
236
252
|
when "authors" then spec.authors = array_of_strings(value)
|
|
237
|
-
when "
|
|
238
|
-
when "homepage" then spec.homepage = string_from_literal(value)
|
|
239
|
-
when "source_code_uri" then spec.source_code_uri = string_from_literal(value)
|
|
240
|
-
when "changelog_uri" then spec.changelog_uri = string_from_literal(value)
|
|
241
|
-
when "prayfile_version" then spec.prayfile_version = string_from_literal(value)
|
|
253
|
+
when "maintainers" then spec.maintainers = array_of_strings(value)
|
|
242
254
|
when "files" then spec.files = array_of_strings(value)
|
|
243
255
|
when "targets" then spec.targets = array_of_strings(value)
|
|
244
256
|
when "exports" then spec.exports = parse_exports(value)
|
|
@@ -4,9 +4,9 @@ module Pray
|
|
|
4
4
|
module PackageSpecRender
|
|
5
5
|
module_function
|
|
6
6
|
|
|
7
|
-
def fork_spec_after_refresh(local, new_upstream,
|
|
7
|
+
def fork_spec_after_refresh(local, new_upstream, clean_replica, merged_content_paths)
|
|
8
8
|
spec = local.dup
|
|
9
|
-
spec.files =
|
|
9
|
+
spec.files = merged_content_paths.dup
|
|
10
10
|
if clean_replica
|
|
11
11
|
spec.exports = new_upstream.exports.dup
|
|
12
12
|
spec.templates = new_upstream.templates.dup
|
|
@@ -32,10 +32,11 @@ module Pray
|
|
|
32
32
|
|
|
33
33
|
def push_identity_fields(lines, spec)
|
|
34
34
|
push_assignment(lines, "name", spec.name)
|
|
35
|
-
push_assignment(lines, "version", spec.version)
|
|
35
|
+
push_assignment(lines, "version", spec.version) unless spec.version.to_s.empty?
|
|
36
36
|
push_optional(lines, "summary", spec.summary)
|
|
37
37
|
push_optional(lines, "description", spec.description)
|
|
38
38
|
push_array(lines, "authors", spec.authors) unless spec.authors.nil? || spec.authors.empty?
|
|
39
|
+
push_array(lines, "maintainers", spec.maintainers) unless spec.maintainers.nil? || spec.maintainers.empty?
|
|
39
40
|
push_optional(lines, "license", spec.license)
|
|
40
41
|
push_optional(lines, "homepage", spec.homepage)
|
|
41
42
|
push_optional(lines, "source_code_uri", spec.source_code_uri)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
class PackageSpec
|
|
5
|
+
LOCAL_VERSION = "local"
|
|
6
|
+
|
|
7
|
+
def has_release_version?
|
|
8
|
+
!version.to_s.empty? && version != LOCAL_VERSION
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def recorded_version
|
|
12
|
+
has_release_version? ? version : LOCAL_VERSION
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def require_release_version!
|
|
16
|
+
return if has_release_version?
|
|
17
|
+
|
|
18
|
+
raise Error.manifest("package #{name} needs a version before it can be packaged")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def satisfy_constraint!(constraint)
|
|
22
|
+
unless has_release_version?
|
|
23
|
+
normalized = Constraint.normalize_version_constraint(constraint)
|
|
24
|
+
return if normalized.empty? || normalized == "*"
|
|
25
|
+
|
|
26
|
+
raise Error.resolution("package #{name} has no version; add spec.version or omit the constraint")
|
|
27
|
+
end
|
|
28
|
+
return if Constraint.version_satisfies(version, constraint)
|
|
29
|
+
|
|
30
|
+
raise Error.resolution("package #{name} version #{version} does not satisfy constraint #{constraint}")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
data/lib/pray/plan.rb
CHANGED
|
@@ -13,7 +13,8 @@ module Pray
|
|
|
13
13
|
def build_materialization_preview(project, rendered, lockfile, _lockfile_path, previous_lockfile)
|
|
14
14
|
destinations = RenderDest.validate_destinations!(project, previous_lockfile)
|
|
15
15
|
MaterializationPreview.new(
|
|
16
|
-
package_lines:
|
|
16
|
+
package_lines: ApplyReport.local_summary_lines(previous_lockfile, project) +
|
|
17
|
+
package_summary_lines(previous_lockfile, lockfile, project),
|
|
17
18
|
lockfile: lockfile_change_status(previous_lockfile, lockfile),
|
|
18
19
|
targets: rendered.map { |target| target_change(project, target) },
|
|
19
20
|
provisioned: destinations.map { |file, status| [file.path, status.to_s] },
|
data/lib/pray/publish.rb
CHANGED
|
@@ -17,14 +17,16 @@ module Pray
|
|
|
17
17
|
def publish_to_root(project, root, signer: "local", signer_fingerprint: nil)
|
|
18
18
|
root = File.expand_path(root)
|
|
19
19
|
index = load_registry_index(root)
|
|
20
|
+
distribution = Distribution.read_settings(root)
|
|
20
21
|
package_names = index.packages.to_set
|
|
21
22
|
|
|
22
23
|
project.packages.each do |package|
|
|
24
|
+
package.spec.require_release_version!
|
|
23
25
|
artifact_path = registry_artifact_path(package.declaration.name, package.spec.version)
|
|
24
26
|
metadata_path = registry_metadata_path(root, package.declaration.name)
|
|
25
27
|
metadata = load_registry_package_metadata(metadata_path, package.declaration.name)
|
|
26
28
|
existing = metadata.versions.find { |entry| entry.version == package.spec.version }
|
|
27
|
-
stored_artifact = stored_package_artifact(root, artifact_path, package, existing)
|
|
29
|
+
stored_artifact = stored_package_artifact(root, artifact_path, package, existing, distribution)
|
|
28
30
|
if stored_artifact && stored_publish_matches?(
|
|
29
31
|
stored_artifact, package, signer, signer_fingerprint, existing
|
|
30
32
|
)
|
|
@@ -35,6 +37,14 @@ module Pray
|
|
|
35
37
|
|
|
36
38
|
archive_bytes = Archive.build_package_archive_bytes(package)
|
|
37
39
|
write_output_bytes(File.join(root, artifact_path), archive_bytes)
|
|
40
|
+
TorrentManifest.write(
|
|
41
|
+
root,
|
|
42
|
+
name: package.declaration.name,
|
|
43
|
+
version: package.spec.version,
|
|
44
|
+
artifact_path: artifact_path,
|
|
45
|
+
archive_bytes: archive_bytes,
|
|
46
|
+
settings: distribution
|
|
47
|
+
)
|
|
38
48
|
version_entry = published_registry_package_version(
|
|
39
49
|
package,
|
|
40
50
|
signer,
|
|
@@ -54,10 +64,24 @@ module Pray
|
|
|
54
64
|
end
|
|
55
65
|
|
|
56
66
|
def publish_to_server(project, server_url, signer: "local", signer_fingerprint: nil)
|
|
67
|
+
distribution = Distribution.fetch_settings(server_url)
|
|
57
68
|
project.packages.each do |package|
|
|
58
69
|
archive_bytes = Archive.build_package_archive_bytes(package)
|
|
59
70
|
artifact_path = registry_artifact_path(package.declaration.name, package.spec.version)
|
|
60
71
|
Registry.http_put(join_url(server_url, artifact_path), "application/octet-stream", archive_bytes)
|
|
72
|
+
if distribution.allows_torrent?
|
|
73
|
+
Registry.http_put(
|
|
74
|
+
join_url(server_url, TorrentManifest.path_for(artifact_path)),
|
|
75
|
+
"application/json",
|
|
76
|
+
TorrentManifest.bytes(
|
|
77
|
+
name: package.declaration.name,
|
|
78
|
+
version: package.spec.version,
|
|
79
|
+
artifact_path: artifact_path,
|
|
80
|
+
archive_bytes: archive_bytes,
|
|
81
|
+
trackers: distribution.bootstrap_trackers
|
|
82
|
+
)
|
|
83
|
+
)
|
|
84
|
+
end
|
|
61
85
|
|
|
62
86
|
metadata = RegistryPackageMetadata.new(
|
|
63
87
|
name: package.declaration.name,
|
|
@@ -95,7 +119,7 @@ module Pray
|
|
|
95
119
|
)
|
|
96
120
|
end
|
|
97
121
|
|
|
98
|
-
def stored_package_artifact(root, artifact_path, package, existing)
|
|
122
|
+
def stored_package_artifact(root, artifact_path, package, existing, distribution)
|
|
99
123
|
return unless existing&.artifact == artifact_path && existing.tree_hash == package.tree_hash
|
|
100
124
|
|
|
101
125
|
stored_path = File.join(root, artifact_path)
|
|
@@ -104,6 +128,7 @@ module Pray
|
|
|
104
128
|
artifact_bytes = File.binread(stored_path)
|
|
105
129
|
return unless existing.artifact_hash == Hashing.sha256_prefixed(artifact_bytes)
|
|
106
130
|
return unless stored_prayspec_matches?(artifact_bytes, package.root)
|
|
131
|
+
return unless TorrentManifest.descriptor_present?(root, artifact_path, distribution)
|
|
107
132
|
|
|
108
133
|
artifact_bytes
|
|
109
134
|
end
|
data/lib/pray/registry.rb
CHANGED
|
@@ -8,6 +8,7 @@ require "pathname"
|
|
|
8
8
|
require "time"
|
|
9
9
|
require_relative "path_safety"
|
|
10
10
|
require_relative "http_body"
|
|
11
|
+
require_relative "registry_paths"
|
|
11
12
|
|
|
12
13
|
module Pray
|
|
13
14
|
RegistryPackageVersion = Struct.new(
|
|
@@ -40,6 +41,17 @@ module Pray
|
|
|
40
41
|
module_function
|
|
41
42
|
|
|
42
43
|
def resolve_registry_package_root(project_root, source_url, declaration, preferred_version: nil, offline: false)
|
|
44
|
+
if local_source?(source_url)
|
|
45
|
+
return resolve_local_registry_package_root(
|
|
46
|
+
project_root,
|
|
47
|
+
source_url,
|
|
48
|
+
RegistryPaths.local_registry_root(project_root, source_url),
|
|
49
|
+
declaration,
|
|
50
|
+
preferred_version: preferred_version,
|
|
51
|
+
offline: offline
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
43
55
|
metadata = fetch_package_metadata(source_url, declaration.name)
|
|
44
56
|
registry_latest_version = registry_latest_version_label(metadata)
|
|
45
57
|
selected = select_package_version(metadata, declaration.constraint, preferred_version)
|