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
data/lib/pray/git_sources.rb
CHANGED
|
@@ -1,48 +1,73 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "open3"
|
|
4
|
-
require "fileutils"
|
|
5
3
|
require "pathname"
|
|
4
|
+
require_relative "git_cache"
|
|
6
5
|
|
|
7
6
|
module Pray
|
|
8
7
|
GitSourceCheckout = Struct.new(:cache_directory, :revision, :subdir)
|
|
9
8
|
|
|
9
|
+
class GitSourceSet
|
|
10
|
+
def initialize(project_root, sources, lockfile, refresh:)
|
|
11
|
+
@project_root = project_root
|
|
12
|
+
@lockfile = lockfile
|
|
13
|
+
@refresh = refresh
|
|
14
|
+
@sources = sources.select { |source| source.kind == "git" }.to_h { |source| [source.name, source] }
|
|
15
|
+
@checkouts = {}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def [](name)
|
|
19
|
+
return @checkouts[name] if @checkouts.key?(name)
|
|
20
|
+
|
|
21
|
+
source = @sources[name]
|
|
22
|
+
return nil unless source
|
|
23
|
+
|
|
24
|
+
checkout = GitSources.prepare_one_git_source(@project_root, source, @lockfile, refresh: @refresh)
|
|
25
|
+
@checkouts[name] = checkout if checkout
|
|
26
|
+
checkout
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def revisions
|
|
30
|
+
@checkouts.each_with_object({}) do |(name, checkout), collected|
|
|
31
|
+
next if checkout.revision.to_s.empty?
|
|
32
|
+
|
|
33
|
+
collected[name] = checkout.revision
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
10
38
|
module GitSources
|
|
11
39
|
module_function
|
|
12
40
|
|
|
13
41
|
def prepare_git_sources(project_root, sources, lockfile, refresh: false)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
)
|
|
27
|
-
end
|
|
28
|
-
next
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
pinned_revision = refresh ? nil : pinned_revision_for_source(lockfile, source)
|
|
32
|
-
cache_directory, revision = ensure_git_repository(
|
|
33
|
-
project_root,
|
|
34
|
-
clone_url,
|
|
35
|
-
refresh: refresh,
|
|
36
|
-
pinned_revision: pinned_revision,
|
|
37
|
-
sparse_subdir: source.subdir
|
|
38
|
-
)
|
|
39
|
-
checkouts[source.name] = GitSourceCheckout.new(
|
|
40
|
-
cache_directory: cache_directory,
|
|
41
|
-
revision: revision,
|
|
42
|
+
GitSourceSet.new(project_root, sources, lockfile, refresh: refresh)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def prepare_one_git_source(project_root, source, lockfile, refresh:)
|
|
46
|
+
clone_url = source.url.delete_prefix("git+")
|
|
47
|
+
if local_filesystem_source?(clone_url) && !local_git_repo_path(project_root, clone_url)
|
|
48
|
+
source_root = local_git_source_root(project_root, clone_url)
|
|
49
|
+
return unless source_root
|
|
50
|
+
|
|
51
|
+
return GitSourceCheckout.new(
|
|
52
|
+
cache_directory: source_root,
|
|
53
|
+
revision: "",
|
|
42
54
|
subdir: source.subdir
|
|
43
55
|
)
|
|
44
56
|
end
|
|
45
|
-
|
|
57
|
+
|
|
58
|
+
pinned_revision = refresh ? nil : pinned_revision_for_source(lockfile, source)
|
|
59
|
+
cache_directory, revision = GitCache.ensure_git_repository(
|
|
60
|
+
project_root,
|
|
61
|
+
clone_url,
|
|
62
|
+
refresh: refresh,
|
|
63
|
+
pinned_revision: pinned_revision,
|
|
64
|
+
sparse_subdir: source.subdir
|
|
65
|
+
)
|
|
66
|
+
GitSourceCheckout.new(
|
|
67
|
+
cache_directory: cache_directory,
|
|
68
|
+
revision: revision,
|
|
69
|
+
subdir: source.subdir
|
|
70
|
+
)
|
|
46
71
|
end
|
|
47
72
|
|
|
48
73
|
def resolve_distribution_root(repo_root, subdir)
|
|
@@ -83,39 +108,12 @@ module Pray
|
|
|
83
108
|
discover_distribution_root(path)
|
|
84
109
|
end
|
|
85
110
|
|
|
86
|
-
def
|
|
87
|
-
|
|
88
|
-
if File.directory?(File.join(cache_directory, ".git"))
|
|
89
|
-
refresh_global_git_cache(clone_url) if refresh
|
|
90
|
-
if pinned_revision
|
|
91
|
-
checkout_git_revision(cache_directory, clone_url, pinned_revision, refresh)
|
|
92
|
-
elsif refresh
|
|
93
|
-
refresh_git_worktree(cache_directory, clone_url)
|
|
94
|
-
end
|
|
95
|
-
apply_sparse_checkout(cache_directory, sparse_subdir) if sparse_subdir
|
|
96
|
-
revision = git_head_revision(cache_directory)
|
|
97
|
-
return [cache_directory, revision]
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
FileUtils.rm_rf(cache_directory) if File.exist?(cache_directory)
|
|
101
|
-
FileUtils.mkdir_p(File.dirname(cache_directory))
|
|
102
|
-
if seed_git_cache_from_global(clone_url, cache_directory, project_root)
|
|
103
|
-
run_git_in_repo(cache_directory, "remote", "set-url", "origin", clone_url)
|
|
104
|
-
else
|
|
105
|
-
run_git(project_root, "clone", "--depth", "1", clone_url, cache_directory)
|
|
106
|
-
mirror_git_cache_to_global(clone_url, cache_directory)
|
|
107
|
-
end
|
|
108
|
-
checkout_git_revision(cache_directory, clone_url, pinned_revision, true) if pinned_revision
|
|
109
|
-
apply_sparse_checkout(cache_directory, sparse_subdir) if sparse_subdir
|
|
110
|
-
[cache_directory, git_head_revision(cache_directory)]
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def git_source_cache_directory(project_root, clone_url)
|
|
114
|
-
File.join(project_root, ".pray", "cache", "git", cache_key(clone_url))
|
|
111
|
+
def git_source_cache_directory(project_root, clone_url, subdir = nil)
|
|
112
|
+
GitCache.git_source_cache_directory(project_root, clone_url, subdir)
|
|
115
113
|
end
|
|
116
114
|
|
|
117
|
-
def
|
|
118
|
-
|
|
115
|
+
def git_source_cached_repository(project_root, clone_url)
|
|
116
|
+
GitCache.git_source_cached_repository(project_root, clone_url)
|
|
119
117
|
end
|
|
120
118
|
|
|
121
119
|
def pinned_revision_for_source(lockfile, source)
|
|
@@ -142,88 +140,5 @@ module Pray
|
|
|
142
140
|
path = clone_url.delete_prefix("file://")
|
|
143
141
|
Pathname.new(path).absolute? ? path : File.expand_path(path, project_root)
|
|
144
142
|
end
|
|
145
|
-
|
|
146
|
-
def global_cache_root
|
|
147
|
-
return ENV["PRAY_CACHE"] if ENV["PRAY_CACHE"]
|
|
148
|
-
return File.join(ENV["PRAY_HOME"], "cache") if ENV["PRAY_HOME"]
|
|
149
|
-
|
|
150
|
-
home = ENV["HOME"]
|
|
151
|
-
home ? File.join(home, ".cache", "pray") : nil
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def global_git_cache_directory(clone_url)
|
|
155
|
-
root = global_cache_root
|
|
156
|
-
root ? File.join(root, "git", cache_key(clone_url)) : nil
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def global_git_cache_ready?(global_cache)
|
|
160
|
-
File.directory?(File.join(global_cache, ".git")) || File.file?(File.join(global_cache, "HEAD"))
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
def seed_git_cache_from_global(clone_url, destination, working_directory)
|
|
164
|
-
global_cache = global_git_cache_directory(clone_url)
|
|
165
|
-
return false unless global_cache && global_git_cache_ready?(global_cache)
|
|
166
|
-
|
|
167
|
-
run_git(working_directory, "clone", "--depth", "1", "--quiet", global_cache, destination)
|
|
168
|
-
true
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def mirror_git_cache_to_global(clone_url, project_cache)
|
|
172
|
-
global_cache = global_git_cache_directory(clone_url)
|
|
173
|
-
return unless global_cache
|
|
174
|
-
return if global_git_cache_ready?(global_cache)
|
|
175
|
-
|
|
176
|
-
FileUtils.mkdir_p(File.dirname(global_cache))
|
|
177
|
-
FileUtils.rm_rf(global_cache) if File.exist?(global_cache)
|
|
178
|
-
run_git(File.dirname(project_cache), "clone", "--bare", "--quiet", File.basename(project_cache), global_cache)
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
def apply_sparse_checkout(repository, subdir)
|
|
182
|
-
run_git_in_repo(repository, "sparse-checkout", "init", "--cone")
|
|
183
|
-
run_git_in_repo(repository, "sparse-checkout", "set", subdir)
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
def checkout_git_revision(repository, _clone_url, revision, refresh)
|
|
187
|
-
run_git_in_repo(repository, "fetch", "--depth", "1", "origin", revision) if refresh
|
|
188
|
-
run_git_in_repo(repository, "checkout", "--force", revision)
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def refresh_git_worktree(repository, _clone_url)
|
|
192
|
-
run_git_in_repo(repository, "fetch", "--depth", "1", "origin")
|
|
193
|
-
run_git_in_repo(repository, "reset", "--hard", "origin/HEAD")
|
|
194
|
-
end
|
|
195
|
-
|
|
196
|
-
def refresh_global_git_cache(clone_url)
|
|
197
|
-
global_cache = global_git_cache_directory(clone_url)
|
|
198
|
-
return unless global_cache && global_git_cache_ready?(global_cache)
|
|
199
|
-
|
|
200
|
-
run_git_in_repo(global_cache, "fetch", "--depth", "1", "origin")
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
def git_head_revision(repository)
|
|
204
|
-
output, status = Open3.capture2e("git", "-C", repository, "rev-parse", "HEAD")
|
|
205
|
-
raise Error.resolution(command_error("git rev-parse HEAD", output)) unless status.success?
|
|
206
|
-
|
|
207
|
-
revision = output.strip
|
|
208
|
-
raise Error.resolution("git repository has no HEAD revision") if revision.empty?
|
|
209
|
-
|
|
210
|
-
revision
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
def run_git(cwd, *arguments)
|
|
214
|
-
output, status = Open3.capture2e("git", "-C", cwd, *arguments)
|
|
215
|
-
return if status.success?
|
|
216
|
-
|
|
217
|
-
raise Error.resolution(command_error("git #{arguments.join(" ")}", output))
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
def run_git_in_repo(repository, *arguments)
|
|
221
|
-
run_git(repository, *arguments)
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
def command_error(program, output)
|
|
225
|
-
message = output.strip
|
|
226
|
-
message.empty? ? "#{program} failed" : "#{program} failed: #{message}"
|
|
227
|
-
end
|
|
228
143
|
end
|
|
229
144
|
end
|
|
@@ -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,
|
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
|
@@ -9,6 +9,8 @@ require "time"
|
|
|
9
9
|
require_relative "path_safety"
|
|
10
10
|
require_relative "http_body"
|
|
11
11
|
require_relative "registry_paths"
|
|
12
|
+
require_relative "registry_http_cache"
|
|
13
|
+
require_relative "git_materialize"
|
|
12
14
|
|
|
13
15
|
module Pray
|
|
14
16
|
RegistryPackageVersion = Struct.new(
|
|
@@ -134,9 +136,7 @@ module Pray
|
|
|
134
136
|
return parse_metadata(File.read(metadata_path, encoding: "UTF-8"))
|
|
135
137
|
end
|
|
136
138
|
|
|
137
|
-
|
|
138
|
-
response = http_get(join_url(source_url, "v1/packages/#{package_name}.json"))
|
|
139
|
-
parse_metadata(response)
|
|
139
|
+
RegistryHttpCache.fetch_package_metadata(source_url, package_name)
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
def parse_metadata(text)
|
|
@@ -207,18 +207,7 @@ module Pray
|
|
|
207
207
|
end
|
|
208
208
|
|
|
209
209
|
def read_local_artifact_bytes(source_root, artifact)
|
|
210
|
-
|
|
211
|
-
path = PathSafety.join_under_root(source_root, artifact.delete_prefix("file://"))
|
|
212
|
-
raise Error.resolution("package artifact path escapes distribution root") unless path
|
|
213
|
-
return File.binread(path)
|
|
214
|
-
end
|
|
215
|
-
reject_absolute_artifact!(artifact)
|
|
216
|
-
|
|
217
|
-
path = PathSafety.join_under_root(source_root, artifact)
|
|
218
|
-
raise Error.resolution("package artifact path escapes distribution root") unless path
|
|
219
|
-
raise Error.resolution("package artifact missing at #{path}") unless File.exist?(path)
|
|
220
|
-
|
|
221
|
-
File.binread(path)
|
|
210
|
+
GitMaterialize.read_local_artifact_bytes(source_root, artifact)
|
|
222
211
|
end
|
|
223
212
|
|
|
224
213
|
def read_artifact_bytes(source_url, artifact)
|