pray-cli 1.18.1 → 1.20.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 +14 -0
- data/lib/pray/cli/commands/distribution.rb +53 -5
- data/lib/pray/cli/commands/init.rb +26 -0
- data/lib/pray/cli/commands/packages.rb +6 -1
- data/lib/pray/cli/help.rb +7 -5
- data/lib/pray/cli/parse.rb +11 -4
- data/lib/pray/format_manifest.rb +1 -0
- data/lib/pray/format_serialize.rb +17 -0
- data/lib/pray/git_cache.rb +16 -144
- data/lib/pray/git_clone.rb +4 -8
- data/lib/pray/git_materialize.rb +86 -34
- data/lib/pray/git_sources.rb +15 -13
- data/lib/pray/git_store.rb +113 -0
- data/lib/pray/manifest.rb +7 -1
- data/lib/pray/manifest_json.rb +10 -0
- data/lib/pray/manifest_parser.rb +3 -0
- data/lib/pray/manifest_parser_publish.rb +52 -0
- data/lib/pray/publish.rb +12 -7
- data/lib/pray/publish_remote.rb +76 -0
- data/lib/pray/publish_select.rb +110 -0
- data/lib/pray/render_patch.rb +39 -7
- data/lib/pray/resolve.rb +2 -1
- data/lib/pray/upstream_drift.rb +2 -1
- data/lib/pray/upstream_latest.rb +2 -1
- data/lib/pray/upstream_refresh.rb +2 -1
- data/lib/pray/version.rb +2 -2
- metadata +5 -1
data/lib/pray/git_sources.rb
CHANGED
|
@@ -7,10 +7,11 @@ module Pray
|
|
|
7
7
|
GitSourceCheckout = Struct.new(:cache_directory, :revision, :subdir)
|
|
8
8
|
|
|
9
9
|
class GitSourceSet
|
|
10
|
-
def initialize(project_root, sources, lockfile, refresh:)
|
|
10
|
+
def initialize(project_root, sources, lockfile, refresh:, offline: false)
|
|
11
11
|
@project_root = project_root
|
|
12
12
|
@lockfile = lockfile
|
|
13
13
|
@refresh = refresh
|
|
14
|
+
@offline = offline
|
|
14
15
|
@sources = sources.select { |source| source.kind == "git" }.to_h { |source| [source.name, source] }
|
|
15
16
|
@checkouts = {}
|
|
16
17
|
end
|
|
@@ -21,7 +22,7 @@ module Pray
|
|
|
21
22
|
source = @sources[name]
|
|
22
23
|
return nil unless source
|
|
23
24
|
|
|
24
|
-
checkout = GitSources.prepare_one_git_source(@project_root, source, @lockfile, refresh: @refresh)
|
|
25
|
+
checkout = GitSources.prepare_one_git_source(@project_root, source, @lockfile, refresh: @refresh, offline: @offline)
|
|
25
26
|
@checkouts[name] = checkout if checkout
|
|
26
27
|
checkout
|
|
27
28
|
end
|
|
@@ -38,21 +39,21 @@ module Pray
|
|
|
38
39
|
module GitSources
|
|
39
40
|
module_function
|
|
40
41
|
|
|
41
|
-
def prepare_git_sources(project_root, sources, lockfile, refresh: false)
|
|
42
|
-
GitSourceSet.new(project_root, sources, lockfile, refresh: refresh)
|
|
42
|
+
def prepare_git_sources(project_root, sources, lockfile, refresh: false, offline: false)
|
|
43
|
+
GitSourceSet.new(project_root, sources, lockfile, refresh: refresh, offline: offline)
|
|
43
44
|
end
|
|
44
45
|
|
|
45
|
-
def prepare_one_git_source(project_root, source, lockfile, refresh:)
|
|
46
|
+
def prepare_one_git_source(project_root, source, lockfile, refresh:, offline: false)
|
|
46
47
|
clone_url = source.url.delete_prefix("git+")
|
|
47
48
|
if local_filesystem_source?(clone_url) && !local_git_repo_path(project_root, clone_url)
|
|
48
49
|
source_root = local_git_source_root(project_root, clone_url)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
if source_root
|
|
51
|
+
return GitSourceCheckout.new(
|
|
52
|
+
cache_directory: source_root,
|
|
53
|
+
revision: "",
|
|
54
|
+
subdir: source.subdir
|
|
55
|
+
)
|
|
56
|
+
end
|
|
56
57
|
end
|
|
57
58
|
|
|
58
59
|
pinned_revision = refresh ? nil : pinned_revision_for_source(lockfile, source)
|
|
@@ -61,7 +62,8 @@ module Pray
|
|
|
61
62
|
clone_url,
|
|
62
63
|
refresh: refresh,
|
|
63
64
|
pinned_revision: pinned_revision,
|
|
64
|
-
sparse_subdir: source.subdir
|
|
65
|
+
sparse_subdir: source.subdir,
|
|
66
|
+
offline: offline
|
|
65
67
|
)
|
|
66
68
|
GitSourceCheckout.new(
|
|
67
69
|
cache_directory: cache_directory,
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require_relative "error"
|
|
5
|
+
require_relative "hashing"
|
|
6
|
+
require_relative "git_run"
|
|
7
|
+
require_relative "git_clone"
|
|
8
|
+
|
|
9
|
+
module Pray
|
|
10
|
+
module GitStore
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def ensure_global_git_db(clone_url, pinned_revision:, refresh:, offline:, working_directory:)
|
|
14
|
+
db = global_git_cache_directory(clone_url)
|
|
15
|
+
raise Error.resolution("git object cache is not configured") unless db
|
|
16
|
+
|
|
17
|
+
unless global_git_cache_ready?(db)
|
|
18
|
+
raise Error.resolution(offline_git_source_uncached(clone_url)) if offline
|
|
19
|
+
|
|
20
|
+
FileUtils.rm_rf(db) if File.exist?(db)
|
|
21
|
+
FileUtils.mkdir_p(File.dirname(db))
|
|
22
|
+
GitClone.clone_bare_git_db(working_directory, clone_url, db, quiet: false)
|
|
23
|
+
ensure_git_remote_origin(db, clone_url)
|
|
24
|
+
end
|
|
25
|
+
fetch_origin_tip(db, clone_url) if refresh && !offline
|
|
26
|
+
if pinned_revision
|
|
27
|
+
ensure_revision_in_db(db, clone_url, pinned_revision, !offline)
|
|
28
|
+
return [db, pinned_revision]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
[db, git_head_revision(db)]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def global_cache_root
|
|
35
|
+
return ENV["PRAY_CACHE"] if ENV["PRAY_CACHE"]
|
|
36
|
+
return File.join(ENV["PRAY_HOME"], "cache") if ENV["PRAY_HOME"]
|
|
37
|
+
|
|
38
|
+
home = ENV["HOME"]
|
|
39
|
+
home ? File.join(home, ".cache", "pray") : nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def global_git_cache_directory(clone_url)
|
|
43
|
+
root = global_cache_root
|
|
44
|
+
root ? File.join(root, "git", Hashing.sha256_prefixed(clone_url)[7, 16]) : nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def global_git_cache_ready?(global_cache)
|
|
48
|
+
File.file?(File.join(global_cache, "HEAD")) || File.directory?(File.join(global_cache, ".git"))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def offline_git_source_uncached(clone_url)
|
|
52
|
+
"git source #{clone_url} is not cached locally and offline mode is enabled"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def ensure_revision_in_db(db, clone_url, revision, allow_fetch)
|
|
56
|
+
return if GitRun.try_run_git(db, "cat-file", "-e", revision)
|
|
57
|
+
unless allow_fetch
|
|
58
|
+
raise Error.resolution(
|
|
59
|
+
"git source #{db.inspect} is locked to revision #{revision}, but that commit is not available locally and offline mode is enabled"
|
|
60
|
+
)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
ensure_git_remote_origin(db, clone_url)
|
|
64
|
+
fetch_unshallow(db) if File.file?(File.join(db, "shallow"))
|
|
65
|
+
return if GitRun.try_run_git(db, "cat-file", "-e", revision)
|
|
66
|
+
|
|
67
|
+
fetch_revision(db, revision)
|
|
68
|
+
return if GitRun.try_run_git(db, "cat-file", "-e", revision)
|
|
69
|
+
|
|
70
|
+
raise Error.resolution(
|
|
71
|
+
"git source #{db.inspect} is locked to revision #{revision}, but that commit could not be fetched"
|
|
72
|
+
)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def ensure_git_remote_origin(repository, clone_url)
|
|
76
|
+
if GitRun.try_run_git(repository, "remote", "get-url", "origin")
|
|
77
|
+
GitRun.run_git(repository, "remote", "set-url", "origin", clone_url)
|
|
78
|
+
else
|
|
79
|
+
GitRun.run_git(repository, "remote", "add", "origin", clone_url)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def fetch_origin_tip(db, clone_url)
|
|
84
|
+
ensure_git_remote_origin(db, clone_url)
|
|
85
|
+
unless GitRun.try_run_git(db, "fetch", "--depth", "1", "--filter=blob:none", "origin")
|
|
86
|
+
GitRun.run_git(db, "fetch", "--depth", "1", "origin")
|
|
87
|
+
end
|
|
88
|
+
GitRun.run_git(db, "update-ref", "HEAD", "FETCH_HEAD")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def fetch_unshallow(db)
|
|
92
|
+
return if GitRun.try_run_git(db, "fetch", "--unshallow", "--filter=blob:none", "origin")
|
|
93
|
+
|
|
94
|
+
GitRun.run_git(db, "fetch", "--unshallow", "origin")
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def fetch_revision(db, revision)
|
|
98
|
+
return if GitRun.try_run_git(db, "fetch", "--filter=blob:none", "origin", revision)
|
|
99
|
+
|
|
100
|
+
GitRun.run_git(db, "fetch", "origin", revision)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def git_head_revision(repository)
|
|
104
|
+
output, status = GitRun.capture_git(repository, "rev-parse", "HEAD")
|
|
105
|
+
raise Error.resolution(GitRun.command_error("git rev-parse HEAD", output)) unless status.success?
|
|
106
|
+
|
|
107
|
+
revision = output.strip
|
|
108
|
+
raise Error.resolution("git repository has no HEAD revision") if revision.empty?
|
|
109
|
+
|
|
110
|
+
revision
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
data/lib/pray/manifest.rb
CHANGED
|
@@ -4,8 +4,11 @@ require_relative "manifest_json"
|
|
|
4
4
|
require_relative "manifest_formatter"
|
|
5
5
|
require_relative "manifest_parser_helpers"
|
|
6
6
|
require_relative "manifest_parser_blocks"
|
|
7
|
+
require_relative "manifest_parser_publish"
|
|
7
8
|
require_relative "manifest_parser"
|
|
8
9
|
require_relative "path_safety"
|
|
10
|
+
require_relative "publish_remote"
|
|
11
|
+
require_relative "publish_select"
|
|
9
12
|
|
|
10
13
|
module Pray
|
|
11
14
|
RenderPolicy = Struct.new(
|
|
@@ -53,7 +56,7 @@ module Pray
|
|
|
53
56
|
end
|
|
54
57
|
|
|
55
58
|
Manifest = Struct.new(
|
|
56
|
-
:prayfile_version, :sources, :targets, :packages, :local, :symbols, :render,
|
|
59
|
+
:prayfile_version, :sources, :targets, :packages, :local, :publish_remotes, :symbols, :render,
|
|
57
60
|
:deprecated_keywords,
|
|
58
61
|
keyword_init: true
|
|
59
62
|
) do
|
|
@@ -63,6 +66,7 @@ module Pray
|
|
|
63
66
|
targets: [],
|
|
64
67
|
packages: [],
|
|
65
68
|
local: [],
|
|
69
|
+
publish_remotes: [],
|
|
66
70
|
symbols: {},
|
|
67
71
|
render: RenderPolicy.default,
|
|
68
72
|
deprecated_keywords: []
|
|
@@ -99,6 +103,7 @@ module Pray
|
|
|
99
103
|
copy.targets = targets.sort_by(&:name)
|
|
100
104
|
copy.packages = packages.sort_by { |package| [package.name, package.source.to_s, package.constraint] }
|
|
101
105
|
copy.local = local.sort_by(&:path)
|
|
106
|
+
copy.publish_remotes = (publish_remotes || []).sort_by(&:name)
|
|
102
107
|
copy.deprecated_keywords = []
|
|
103
108
|
end
|
|
104
109
|
end
|
|
@@ -139,6 +144,7 @@ module Pray
|
|
|
139
144
|
PathSafety.validate_project_relative_path!(local.path)
|
|
140
145
|
PathSafety.validate_destination_path!(local.file) if local.file
|
|
141
146
|
end
|
|
147
|
+
PublishRemote.validate_publish_remotes!(manifest)
|
|
142
148
|
end
|
|
143
149
|
end
|
|
144
150
|
|
data/lib/pray/manifest_json.rb
CHANGED
|
@@ -17,10 +17,20 @@ module Pray
|
|
|
17
17
|
"local" => manifest.local.map { |entry| local_fields(entry) }
|
|
18
18
|
}
|
|
19
19
|
fields["symbols"] = manifest.symbols.sort.to_h unless manifest.symbols.empty?
|
|
20
|
+
unless (manifest.publish_remotes || []).empty?
|
|
21
|
+
fields["publish_remotes"] = manifest.publish_remotes.map { |remote| publish_remote_fields(remote) }
|
|
22
|
+
end
|
|
20
23
|
fields["render"] = render_fields(manifest.render)
|
|
21
24
|
fields
|
|
22
25
|
end
|
|
23
26
|
|
|
27
|
+
def publish_remote_fields(remote)
|
|
28
|
+
fields = {"name" => remote.name, "packages" => remote.packages}
|
|
29
|
+
fields["path"] = remote.path if remote.path
|
|
30
|
+
fields["url"] = remote.url if remote.url
|
|
31
|
+
fields
|
|
32
|
+
end
|
|
33
|
+
|
|
24
34
|
def source_fields(source)
|
|
25
35
|
fields = {
|
|
26
36
|
"name" => source.name,
|
data/lib/pray/manifest_parser.rb
CHANGED
|
@@ -5,6 +5,7 @@ module Pray
|
|
|
5
5
|
class BlockParser
|
|
6
6
|
include ParserHelpers
|
|
7
7
|
include ParserBlocks
|
|
8
|
+
include ParserPublish
|
|
8
9
|
|
|
9
10
|
def initialize(lines)
|
|
10
11
|
@lines = lines
|
|
@@ -41,6 +42,8 @@ module Pray
|
|
|
41
42
|
manifest.prayfile_version = string_from_literal(Regexp.last_match(1))
|
|
42
43
|
when /\Asource (.+)\z/
|
|
43
44
|
manifest.sources << parse_source(Regexp.last_match(1))
|
|
45
|
+
when /\Apublish (.+)\z/
|
|
46
|
+
apply_publish(manifest, Regexp.last_match(1))
|
|
44
47
|
when /\Atarget (.+)\z/
|
|
45
48
|
apply_legacy_target(manifest, statement, Regexp.last_match(1), allow_target)
|
|
46
49
|
when /\Agroup (.+)\z/
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module ManifestMethods
|
|
5
|
+
module ParserPublish
|
|
6
|
+
def apply_publish(manifest, rest)
|
|
7
|
+
is_block = rest.rstrip.end_with?(" do")
|
|
8
|
+
header = rest.sub(/\s+do\z/, "").strip
|
|
9
|
+
remote = parse_publish_header(header)
|
|
10
|
+
parse_publish_block(remote) if is_block
|
|
11
|
+
manifest.publish_remotes << remote
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def parse_publish_header(rest)
|
|
15
|
+
values, keywords = parse_call(rest)
|
|
16
|
+
raise Error.parse("manifest", "publish requires a name") if values.empty?
|
|
17
|
+
|
|
18
|
+
name = string_from_value(values.first)
|
|
19
|
+
if %w[git source signing_key token].any? { |key| keywords.key?(key) }
|
|
20
|
+
raise Error.parse(
|
|
21
|
+
"manifest",
|
|
22
|
+
"publish \"#{name}\" does not take git:, source:, signing_key:, or token:"
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
ManifestPublishRemote.new(
|
|
26
|
+
name: name,
|
|
27
|
+
path: keyword_string(keywords, "path"),
|
|
28
|
+
url: values[1] && string_from_value(values[1]),
|
|
29
|
+
packages: []
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def parse_publish_block(remote)
|
|
34
|
+
while (statement = next_statement)
|
|
35
|
+
return if statement == "end"
|
|
36
|
+
|
|
37
|
+
pray_rest = statement[/\A(?:pray|use|include|agent|package) (.+)\z/, 1]
|
|
38
|
+
unless pray_rest
|
|
39
|
+
raise Error.parse("manifest", "publish blocks only support pray package names: #{statement}")
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
package = parse_package_decl(pray_rest)
|
|
43
|
+
if remote.packages.include?(package.name)
|
|
44
|
+
raise Error.parse("manifest", "duplicate package #{package.name} in publish \"#{remote.name}\"")
|
|
45
|
+
end
|
|
46
|
+
remote.packages << package.name
|
|
47
|
+
end
|
|
48
|
+
raise Error.parse("manifest", "missing 'end' for publish block")
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/pray/publish.rb
CHANGED
|
@@ -28,9 +28,7 @@ module Pray
|
|
|
28
28
|
metadata = load_registry_package_metadata(metadata_path, package.declaration.name)
|
|
29
29
|
existing = metadata.versions.find { |entry| entry.version == package.spec.version }
|
|
30
30
|
stored_artifact = stored_package_artifact(root, artifact_path, package, existing, distribution)
|
|
31
|
-
if stored_artifact && stored_publish_matches?(
|
|
32
|
-
stored_artifact, package, signer, signer_fingerprint, existing
|
|
33
|
-
)
|
|
31
|
+
if stored_artifact && stored_publish_matches?(stored_artifact, package, existing)
|
|
34
32
|
package_names << package.declaration.name
|
|
35
33
|
write_registry_package_metadata(metadata_path, metadata)
|
|
36
34
|
next
|
|
@@ -134,10 +132,17 @@ module Pray
|
|
|
134
132
|
artifact_bytes
|
|
135
133
|
end
|
|
136
134
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
135
|
+
# A stored row is current when its own recorded signature still authenticates the
|
|
136
|
+
# stored artifact. The check deliberately ignores who is publishing now: a row signed
|
|
137
|
+
# by another publisher is still a valid attestation of identical bytes, and rewriting
|
|
138
|
+
# it would restate authorship without republishing anything. A row carrying no
|
|
139
|
+
# signature is not current, so publish repairs it.
|
|
140
|
+
def stored_publish_matches?(artifact_bytes, package, existing)
|
|
141
|
+
return false if existing.signer.nil? || existing.signature.nil?
|
|
142
|
+
|
|
143
|
+
existing.signature == Registry.registry_artifact_signature(
|
|
144
|
+
artifact_bytes, package.tree_hash, existing.signer
|
|
145
|
+
)
|
|
141
146
|
end
|
|
142
147
|
|
|
143
148
|
def stored_prayspec_matches?(artifact_bytes, package_root)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
ManifestPublishRemote = Struct.new(:name, :path, :url, :packages) do
|
|
5
|
+
def initialize(name:, path: nil, url: nil, packages: [])
|
|
6
|
+
super
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module PublishRemote
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def validate_publish_remotes!(manifest)
|
|
14
|
+
seen = {}
|
|
15
|
+
(manifest.publish_remotes || []).each do |remote|
|
|
16
|
+
raise Error.parse("manifest", "publish requires a name") if remote.name.to_s.strip.empty?
|
|
17
|
+
if seen[remote.name]
|
|
18
|
+
raise Error.manifest("duplicate publish remote: #{remote.name}")
|
|
19
|
+
end
|
|
20
|
+
seen[remote.name] = true
|
|
21
|
+
if remote.path && remote.url
|
|
22
|
+
raise Error.parse("manifest", "publish \"#{remote.name}\" must set path: or a URL, not both")
|
|
23
|
+
end
|
|
24
|
+
if remote.path.nil? && remote.url.nil?
|
|
25
|
+
raise Error.parse("manifest", "publish \"#{remote.name}\" requires path: or a URL")
|
|
26
|
+
end
|
|
27
|
+
PathSafety.validate_project_relative_path!(remote.path) if remote.path
|
|
28
|
+
if remote.url && !publish_url?(remote.url)
|
|
29
|
+
raise Error.parse(
|
|
30
|
+
"manifest",
|
|
31
|
+
"publish \"#{remote.name}\" URL must be https://, http://, pray+ssh://, or ssh+pray://"
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
validate_remote_packages!(manifest, remote)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def validate_remote_packages!(manifest, remote)
|
|
39
|
+
remote.packages.each do |package_name|
|
|
40
|
+
package = manifest.packages.find { |entry| entry.name == package_name }
|
|
41
|
+
unless package
|
|
42
|
+
raise Error.manifest("publish \"#{remote.name}\" lists unknown package #{package_name}")
|
|
43
|
+
end
|
|
44
|
+
next if package_is_path_owned?(package, manifest.sources)
|
|
45
|
+
|
|
46
|
+
raise Error.manifest(
|
|
47
|
+
"publish \"#{remote.name}\" lists #{package_name}, which is not a path package"
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def package_is_path_owned?(package, sources)
|
|
53
|
+
return true if package.path
|
|
54
|
+
return false if package.git || package.tarball || package.oci
|
|
55
|
+
|
|
56
|
+
map = sources.to_h { |source| [source.name, source] }
|
|
57
|
+
name = ResolveSource.implied_source_name(package, map)
|
|
58
|
+
name && map[name]&.kind == "path"
|
|
59
|
+
rescue Error
|
|
60
|
+
false
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def path_owned_package_names(manifest)
|
|
64
|
+
manifest.packages.select { |package| package_is_path_owned?(package, manifest.sources) }
|
|
65
|
+
.map(&:name)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def allowed_publish_names(manifest, listed)
|
|
69
|
+
listed.empty? ? path_owned_package_names(manifest) : listed
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def publish_url?(url)
|
|
73
|
+
url.start_with?("https://", "http://", "pray+ssh://", "ssh+pray://")
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module PublishSelect
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
PublishDestination = Struct.new(:name, :root, :server, :packages) do
|
|
8
|
+
def initialize(name:, root: nil, server: nil, packages: [])
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
PublishCliDest = Struct.new(:to, :roots, :servers) do
|
|
13
|
+
def initialize(to: [], roots: [], servers: [])
|
|
14
|
+
super
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def resolve_publish_destinations(remotes, cli, project_root)
|
|
19
|
+
if !cli.to.empty? && (!cli.roots.empty? || !cli.servers.empty?)
|
|
20
|
+
raise Error.usage("publish --to cannot be combined with --root or --server")
|
|
21
|
+
end
|
|
22
|
+
return resolve_undeclared_destinations(cli) if remotes.empty?
|
|
23
|
+
|
|
24
|
+
select_remotes(remotes, cli, project_root).map do |remote|
|
|
25
|
+
PublishDestination.new(
|
|
26
|
+
name: remote.name,
|
|
27
|
+
root: remote.path && File.join(project_root, remote.path),
|
|
28
|
+
server: remote.url,
|
|
29
|
+
packages: remote.packages
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def resolve_path_remote(remotes, to, root, project_root)
|
|
35
|
+
path_remotes = remotes.select(&:path)
|
|
36
|
+
if to
|
|
37
|
+
remote = remotes.find { |entry| entry.name == to }
|
|
38
|
+
raise Error.usage("unknown publish remote: #{to}") unless remote
|
|
39
|
+
unless remote.path
|
|
40
|
+
raise Error.usage("publish remote #{to} is a URL; yank, serve, and token need a path remote")
|
|
41
|
+
end
|
|
42
|
+
return File.join(project_root, remote.path)
|
|
43
|
+
end
|
|
44
|
+
if remotes.empty?
|
|
45
|
+
raise Error.usage("requires --root PATH") unless root
|
|
46
|
+
|
|
47
|
+
return root
|
|
48
|
+
end
|
|
49
|
+
if root
|
|
50
|
+
matched = remotes.find { |remote| remote.path && root_matches?(root, remote.path, project_root) }
|
|
51
|
+
raise Error.usage("path #{root} is not a declared publish remote") unless matched
|
|
52
|
+
|
|
53
|
+
return root
|
|
54
|
+
end
|
|
55
|
+
if path_remotes.length == 1 && path_remotes.first.path
|
|
56
|
+
return File.join(project_root, path_remotes.first.path)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
raise Error.usage("say which path remote with --to NAME or --root PATH")
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def resolve_undeclared_destinations(cli)
|
|
63
|
+
if !cli.to.empty?
|
|
64
|
+
raise Error.usage("Prayfile has no publish remotes; pass --root PATH or --server URL")
|
|
65
|
+
end
|
|
66
|
+
if cli.roots.empty? && cli.servers.empty?
|
|
67
|
+
raise Error.unsupported("publish requires at least one --root PATH or --server URL")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
dests = cli.roots.map { |root| PublishDestination.new(name: root, root: root, packages: []) }
|
|
71
|
+
dests + cli.servers.map { |server| PublishDestination.new(name: server, server: server, packages: []) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def select_remotes(remotes, cli, project_root)
|
|
75
|
+
unless cli.to.empty?
|
|
76
|
+
return cli.to.map do |name|
|
|
77
|
+
remote = remotes.find { |entry| entry.name == name }
|
|
78
|
+
raise Error.usage("unknown publish remote: #{name}") unless remote
|
|
79
|
+
|
|
80
|
+
remote
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
return remotes if cli.roots.empty? && cli.servers.empty?
|
|
84
|
+
|
|
85
|
+
selected = cli.roots.map do |root|
|
|
86
|
+
remote = remotes.find { |entry| entry.path && root_matches?(root, entry.path, project_root) }
|
|
87
|
+
raise Error.usage("path #{root} is not a declared publish remote") unless remote
|
|
88
|
+
|
|
89
|
+
remote
|
|
90
|
+
end
|
|
91
|
+
selected + cli.servers.map do |server|
|
|
92
|
+
remote = remotes.find { |entry| entry.url == server }
|
|
93
|
+
raise Error.usage("server #{server} is not a declared publish remote") unless remote
|
|
94
|
+
|
|
95
|
+
remote
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def root_matches?(cli_root, remote_path, project_root)
|
|
100
|
+
declared = File.join(project_root, remote_path)
|
|
101
|
+
return true if cli_root == declared || cli_root == remote_path
|
|
102
|
+
|
|
103
|
+
strip_dot_slash(cli_root) == strip_dot_slash(remote_path)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def strip_dot_slash(value)
|
|
107
|
+
value.to_s.strip.delete_prefix("./").delete_suffix("/").tr("\\", "/")
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
data/lib/pray/render_patch.rb
CHANGED
|
@@ -19,7 +19,7 @@ module Pray
|
|
|
19
19
|
used,
|
|
20
20
|
output
|
|
21
21
|
)
|
|
22
|
-
splice_existing_managed(remaining, fresh_managed, used, output)
|
|
22
|
+
splice_existing_managed(remaining, fresh_segments, fresh_managed, used, output)
|
|
23
23
|
append_unused_fresh_managed(fresh_segments, used, output)
|
|
24
24
|
output.end_with?("\n") ? output : "#{output}\n"
|
|
25
25
|
end
|
|
@@ -36,8 +36,8 @@ module Pray
|
|
|
36
36
|
segments.filter_map { |segment| segment[:id] if segment[:kind] == :managed }.to_h { |id| [id, true] }
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def splice_existing_managed(remaining, fresh_managed, used, output)
|
|
40
|
-
remaining.
|
|
39
|
+
def splice_existing_managed(remaining, fresh_segments, fresh_managed, used, output)
|
|
40
|
+
remaining.each_with_index do |segment, index|
|
|
41
41
|
if segment[:kind] == :text
|
|
42
42
|
output << segment[:text]
|
|
43
43
|
next
|
|
@@ -45,15 +45,47 @@ module Pray
|
|
|
45
45
|
|
|
46
46
|
used[segment[:id]] = true
|
|
47
47
|
output << managed_segment(segment[:id], fresh_managed.fetch(segment[:id], segment[:body]))
|
|
48
|
+
next_segment = remaining[index + 1]
|
|
49
|
+
next unless next_segment && next_segment[:kind] == :managed
|
|
50
|
+
|
|
51
|
+
output << text_between(fresh_segments, segment[:id], next_segment[:id])
|
|
48
52
|
end
|
|
49
53
|
end
|
|
50
54
|
|
|
55
|
+
def text_between(segments, left, right)
|
|
56
|
+
copying = false
|
|
57
|
+
text = +""
|
|
58
|
+
segments.each do |segment|
|
|
59
|
+
if segment[:kind] == :managed
|
|
60
|
+
if segment[:id] == left
|
|
61
|
+
copying = true
|
|
62
|
+
text = +""
|
|
63
|
+
elsif copying
|
|
64
|
+
return (segment[:id] == right) ? text : ""
|
|
65
|
+
end
|
|
66
|
+
elsif copying
|
|
67
|
+
text << segment[:text]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
""
|
|
71
|
+
end
|
|
72
|
+
|
|
51
73
|
def append_unused_fresh_managed(fresh_segments, used, output)
|
|
74
|
+
pending = +""
|
|
75
|
+
seen_used = false
|
|
52
76
|
fresh_segments.each do |segment|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
77
|
+
if segment[:kind] == :managed
|
|
78
|
+
if used[segment[:id]]
|
|
79
|
+
seen_used = true
|
|
80
|
+
pending = +""
|
|
81
|
+
next
|
|
82
|
+
end
|
|
83
|
+
output << pending
|
|
84
|
+
pending = +""
|
|
85
|
+
output << managed_segment(segment[:id], segment[:body])
|
|
86
|
+
elsif seen_used
|
|
87
|
+
pending << segment[:text]
|
|
88
|
+
end
|
|
57
89
|
end
|
|
58
90
|
end
|
|
59
91
|
|
data/lib/pray/resolve.rb
CHANGED
|
@@ -59,7 +59,8 @@ module Pray
|
|
|
59
59
|
project_root,
|
|
60
60
|
manifest.sources,
|
|
61
61
|
lockfile_hints,
|
|
62
|
-
refresh: options.refresh || options.refresh_source_revisions
|
|
62
|
+
refresh: options.refresh || options.refresh_source_revisions,
|
|
63
|
+
offline: options.offline
|
|
63
64
|
)
|
|
64
65
|
source_host_keys = Trust.prepare_source_host_keys(manifest.sources)
|
|
65
66
|
|
data/lib/pray/upstream_drift.rb
CHANGED
|
@@ -10,7 +10,8 @@ module Pray
|
|
|
10
10
|
project.project_root,
|
|
11
11
|
project.manifest.sources,
|
|
12
12
|
previous,
|
|
13
|
-
refresh: options.refresh || options.refresh_source_revisions
|
|
13
|
+
refresh: options.refresh || options.refresh_source_revisions,
|
|
14
|
+
offline: options.offline
|
|
14
15
|
)
|
|
15
16
|
sources = Resolve.source_map(project.manifest.sources)
|
|
16
17
|
lines = []
|
data/lib/pray/upstream_latest.rb
CHANGED
|
@@ -15,7 +15,8 @@ module Pray
|
|
|
15
15
|
project.project_root,
|
|
16
16
|
project.manifest.sources,
|
|
17
17
|
previous,
|
|
18
|
-
refresh: options.refresh || options.refresh_source_revisions
|
|
18
|
+
refresh: options.refresh || options.refresh_source_revisions,
|
|
19
|
+
offline: options.offline
|
|
19
20
|
)
|
|
20
21
|
sources = Resolve.source_map(project.manifest.sources)
|
|
21
22
|
plans = []
|
|
@@ -10,7 +10,8 @@ module Pray
|
|
|
10
10
|
project.project_root,
|
|
11
11
|
project.manifest.sources,
|
|
12
12
|
previous,
|
|
13
|
-
refresh: options.refresh || options.refresh_source_revisions
|
|
13
|
+
refresh: options.refresh || options.refresh_source_revisions,
|
|
14
|
+
offline: options.offline
|
|
14
15
|
)
|
|
15
16
|
sources = Resolve.source_map(project.manifest.sources)
|
|
16
17
|
changed = false
|
data/lib/pray/version.rb
CHANGED