pray-cli 1.14.0 → 1.16.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/archive.rb +21 -2
- data/lib/pray/cli/commands/update_latest.rb +94 -0
- data/lib/pray/cli/commands/workflow.rb +28 -6
- data/lib/pray/cli/help.rb +5 -1
- data/lib/pray/cli.rb +4 -0
- data/lib/pray/constraint.rb +17 -4
- data/lib/pray/dependency_graph.rb +43 -0
- data/lib/pray/git_sources.rb +11 -10
- data/lib/pray/materialize.rb +7 -1
- data/lib/pray/package_spec_render.rb +143 -0
- data/lib/pray/path_safety.rb +8 -4
- data/lib/pray/registry.rb +12 -0
- data/lib/pray/registry_paths.rb +14 -0
- data/lib/pray/resolve.rb +12 -14
- data/lib/pray/resolve_context.rb +7 -0
- data/lib/pray/resolve_deps.rb +18 -0
- data/lib/pray/resolve_tarball.rb +66 -0
- data/lib/pray/transaction.rb +7 -0
- data/lib/pray/upstream.rb +0 -13
- data/lib/pray/upstream_latest.rb +88 -0
- data/lib/pray/upstream_merge.rb +81 -0
- data/lib/pray/upstream_refresh.rb +144 -0
- data/lib/pray/verify_locked_dest.rb +47 -0
- data/lib/pray/version.rb +2 -2
- data/lib/pray.rb +9 -1
- metadata +11 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5950b63f50e919bd5864874919afde95413ed095954046604d570d4c3daf090a
|
|
4
|
+
data.tar.gz: 82056f753f0d244afd54de70b7528d1e5d98b0c67f0da18538eb5d906986f12b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e3289cc2a08751a1358c37ddfb692d042c2d522374b02ed370dd25e703f5c5fe3be66996bbd008a7e6dfbe26a9ea6b278b87f640035a55a235ef7e1ed1d00967
|
|
7
|
+
data.tar.gz: a54b4dbed546b3f301d42692033552c3878b2281f25fee70e647e02634c19541a7440542bddbade45619a2e6dd4cac8bcefc05ff89b035645195ce90a3bffad6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 1.16.0 (2026-09-15)
|
|
4
|
+
|
|
5
|
+
- Rewrite a two-component pessimistic Prayfile pin on `pray update --latest` (`~> 2.2` to `~> 2.4` when registry latest is 2.4.0), matching the Rust and TypeScript CLIs. Install a newer version that the current constraint already allows.
|
|
6
|
+
- Resolve a local `.praypkg` from `tarball:`, including offline when the archive is on disk.
|
|
7
|
+
- Refuse two-package dependency cycles during resolve, matching the Rust CLI.
|
|
8
|
+
- Check dest files against `Prayfile.lock` managed spans without resolving packages (`inspect_locked_destinations`, RFC 0106).
|
|
9
|
+
|
|
10
|
+
## 1.15.0 (2026-09-15)
|
|
11
|
+
|
|
12
|
+
- Refresh path-fork trees with `pray update` (RFC 0114). Rewrite `spec.upstream` pins with `pray update --latest` when the pin does not admit the latest upstream version. Rewrite Prayfile constraints with `pray update --latest` when they do not admit the registry latest version. `pray update --latest --dry-run` prints the planned rewrite and does not write.
|
|
13
|
+
- Keep `spec.upstream` in the packaged prayspec. Published registry metadata does not copy that pin.
|
|
14
|
+
- Keep a listed package spec in `spec.files` when packing; refuse a repeated or aliased content path in the package archive.
|
|
15
|
+
- Use the current project for a later install after an earlier in-process command in another directory.
|
|
16
|
+
|
|
3
17
|
## 1.14.0 (2026-09-14)
|
|
4
18
|
|
|
5
19
|
- Keep an unchanged package version's artifact, first-publish time, and yank when `pray publish` runs again (RFC 0061).
|
data/lib/pray/archive.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "json"
|
|
|
4
4
|
require "open3"
|
|
5
5
|
require "fileutils"
|
|
6
6
|
require_relative "archive_unpack"
|
|
7
|
+
require_relative "path_safety"
|
|
7
8
|
|
|
8
9
|
module Pray
|
|
9
10
|
module Archive
|
|
@@ -11,14 +12,23 @@ module Pray
|
|
|
11
12
|
|
|
12
13
|
def build_package_archive_bytes(package)
|
|
13
14
|
prayspec_path = Resolve.find_prayspec_file(package.root)
|
|
14
|
-
prayspec_name = File.basename(prayspec_path)
|
|
15
|
+
prayspec_name = PathSafety.validate_archive_member_path!(File.basename(prayspec_path))
|
|
15
16
|
metadata = package_metadata_json(package)
|
|
17
|
+
written_paths = Set.new
|
|
18
|
+
record_archive_path(written_paths, "metadata.json", prayspec_name)
|
|
19
|
+
record_archive_path(written_paths, prayspec_name, prayspec_name)
|
|
20
|
+
package.spec.files.each do |file|
|
|
21
|
+
record_archive_path(written_paths, file, prayspec_name)
|
|
22
|
+
end
|
|
16
23
|
|
|
17
24
|
Dir.mktmpdir("pray-package-") do |staging|
|
|
18
25
|
File.write(File.join(staging, "metadata.json"), metadata)
|
|
19
26
|
File.write(File.join(staging, prayspec_name), File.binread(prayspec_path))
|
|
20
27
|
package.spec.files.each do |file|
|
|
21
|
-
|
|
28
|
+
member = PathSafety.validate_archive_member_path!(file)
|
|
29
|
+
next if member == prayspec_name
|
|
30
|
+
|
|
31
|
+
destination = File.join(staging, member)
|
|
22
32
|
FileUtils.mkdir_p(File.dirname(destination))
|
|
23
33
|
File.binwrite(destination, File.binread(File.join(package.root, file)))
|
|
24
34
|
end
|
|
@@ -66,5 +76,14 @@ module Pray
|
|
|
66
76
|
def with_binary_process_encoding(&block)
|
|
67
77
|
ArchiveUnpack.with_binary_process_encoding(&block)
|
|
68
78
|
end
|
|
79
|
+
|
|
80
|
+
def record_archive_path(written_paths, path, auto_included_prayspec)
|
|
81
|
+
normalized = PathSafety.validate_archive_member_path!(path)
|
|
82
|
+
return if written_paths.add?(normalized)
|
|
83
|
+
return if normalized == auto_included_prayspec
|
|
84
|
+
|
|
85
|
+
raise Error.integrity("duplicate package archive path: #{normalized}")
|
|
86
|
+
end
|
|
87
|
+
private_class_method :record_archive_path
|
|
69
88
|
end
|
|
70
89
|
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module CLI
|
|
5
|
+
def update_latest_command(package, dry_run:, offline:)
|
|
6
|
+
path = manifest_path
|
|
7
|
+
original_text = Pray.read_manifest_text(path)
|
|
8
|
+
manifest_text = original_text
|
|
9
|
+
preview_options = ResolveOptions.new(
|
|
10
|
+
offline: offline,
|
|
11
|
+
refresh: true,
|
|
12
|
+
refresh_source_revisions: true,
|
|
13
|
+
ignore_locked_versions: true
|
|
14
|
+
)
|
|
15
|
+
project = resolve_current_project(preview_options)
|
|
16
|
+
if package && project.manifest.packages.none? { |entry| entry.name == package }
|
|
17
|
+
raise Error.manifest("package #{package} not found")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
manifest_updates = []
|
|
21
|
+
project.packages.each do |resolved|
|
|
22
|
+
next if package && resolved.declaration.name != package
|
|
23
|
+
next unless resolved.registry_latest_version
|
|
24
|
+
next if Constraint.version_satisfies(resolved.registry_latest_version, resolved.declaration.constraint)
|
|
25
|
+
|
|
26
|
+
new_constraint = Constraint.latest_constraint_for_package(
|
|
27
|
+
resolved.declaration.constraint,
|
|
28
|
+
resolved.registry_latest_version
|
|
29
|
+
)
|
|
30
|
+
unless Constraint.version_satisfies(resolved.registry_latest_version, new_constraint)
|
|
31
|
+
raise Error.resolution(
|
|
32
|
+
"derived constraint #{new_constraint} does not admit registry latest #{resolved.registry_latest_version} for #{resolved.declaration.name}"
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
manifest_updates << {
|
|
37
|
+
name: resolved.declaration.name,
|
|
38
|
+
from_constraint: resolved.declaration.constraint,
|
|
39
|
+
to_constraint: new_constraint,
|
|
40
|
+
registry_latest_version: resolved.registry_latest_version
|
|
41
|
+
}
|
|
42
|
+
updated = resolved.declaration.dup
|
|
43
|
+
updated.constraint = new_constraint
|
|
44
|
+
manifest_text = Pray.replace_package_declaration(manifest_text, updated)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
previous = File.exist?(lockfile_path) ? Pray.read_lockfile(lockfile_path) : nil
|
|
48
|
+
upstream_plans = Upstream.plan_path_upstream_latest_constraints(
|
|
49
|
+
project, previous, package, preview_options
|
|
50
|
+
)
|
|
51
|
+
print_latest_constraint_plans(manifest_updates, upstream_plans)
|
|
52
|
+
|
|
53
|
+
return if dry_run
|
|
54
|
+
|
|
55
|
+
Upstream.apply_path_upstream_latest_constraints(upstream_plans)
|
|
56
|
+
Transaction.write_file(path, manifest_text) if manifest_text != original_text
|
|
57
|
+
unlocked = package ? Set[package] : Set.new
|
|
58
|
+
options = ResolveOptions.new(
|
|
59
|
+
offline: offline,
|
|
60
|
+
refresh: true,
|
|
61
|
+
refresh_source_revisions: true,
|
|
62
|
+
ignore_locked_versions: package.nil?,
|
|
63
|
+
unlocked_packages: unlocked
|
|
64
|
+
)
|
|
65
|
+
current = resolve_current_project(options)
|
|
66
|
+
Upstream.apply_path_upstream_refreshes(current, previous, package, options)
|
|
67
|
+
install_command(
|
|
68
|
+
{
|
|
69
|
+
locked: false,
|
|
70
|
+
frozen: false,
|
|
71
|
+
offline: offline,
|
|
72
|
+
refresh: true,
|
|
73
|
+
ignore_locked_versions: package.nil?,
|
|
74
|
+
unlocked_packages: unlocked
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def print_latest_constraint_plans(manifest_updates, upstream_plans)
|
|
80
|
+
if manifest_updates.empty? && upstream_plans.empty?
|
|
81
|
+
puts "All package constraints already allow latest versions"
|
|
82
|
+
return
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
manifest_updates.each do |update|
|
|
86
|
+
puts "Prayfile: #{update[:name]} constraint #{update[:from_constraint]} -> #{update[:to_constraint]} " \
|
|
87
|
+
"(registry latest #{update[:registry_latest_version]})"
|
|
88
|
+
end
|
|
89
|
+
upstream_plans.each do |plan|
|
|
90
|
+
puts "#{plan.package_name} upstream #{plan.current_constraint} -> #{plan.new_constraint} (latest #{plan.latest_version})"
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -10,7 +10,9 @@ module Pray
|
|
|
10
10
|
frozen: flags[:frozen],
|
|
11
11
|
locked: flags[:locked],
|
|
12
12
|
offline: flags[:offline],
|
|
13
|
-
refresh: flags[:refresh]
|
|
13
|
+
refresh: flags[:refresh],
|
|
14
|
+
ignore_locked_versions: flags[:ignore_locked_versions],
|
|
15
|
+
unlocked_packages: flags[:unlocked_packages]
|
|
14
16
|
)
|
|
15
17
|
end
|
|
16
18
|
|
|
@@ -84,10 +86,10 @@ module Pray
|
|
|
84
86
|
end
|
|
85
87
|
|
|
86
88
|
def update_command(arguments)
|
|
87
|
-
if arguments.
|
|
89
|
+
if arguments.include?("--json") || (arguments.include?("--dry-run") && !arguments.include?("--latest"))
|
|
88
90
|
raise Error.unsupported("this Ruby CLI does not support --dry-run or --json for update; use `pray plan` to preview the current Prayfile")
|
|
89
91
|
end
|
|
90
|
-
if arguments.
|
|
92
|
+
if arguments.include?("--major")
|
|
91
93
|
raise Error.unsupported(
|
|
92
94
|
"to update beyond the current constraint, edit the package version in Prayfile, then run `pray update`"
|
|
93
95
|
)
|
|
@@ -100,9 +102,29 @@ module Pray
|
|
|
100
102
|
raise Error.manifest("package #{package} not found")
|
|
101
103
|
end
|
|
102
104
|
end
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
if arguments.include?("--latest")
|
|
106
|
+
return update_latest_command(package, dry_run: arguments.include?("--dry-run"), offline: offline)
|
|
107
|
+
end
|
|
108
|
+
unlocked = package ? Set[package] : Set.new
|
|
109
|
+
options = ResolveOptions.new(
|
|
110
|
+
offline: offline,
|
|
111
|
+
refresh: true,
|
|
112
|
+
refresh_source_revisions: true,
|
|
113
|
+
ignore_locked_versions: package.nil?,
|
|
114
|
+
unlocked_packages: unlocked
|
|
115
|
+
)
|
|
116
|
+
current = resolve_current_project(options)
|
|
117
|
+
Upstream.apply_path_upstream_refreshes(current, current.previous_lockfile, package, options)
|
|
118
|
+
install_command(
|
|
119
|
+
{
|
|
120
|
+
locked: false,
|
|
121
|
+
frozen: false,
|
|
122
|
+
offline: offline,
|
|
123
|
+
refresh: true,
|
|
124
|
+
ignore_locked_versions: package.nil?,
|
|
125
|
+
unlocked_packages: unlocked
|
|
126
|
+
}
|
|
127
|
+
)
|
|
106
128
|
end
|
|
107
129
|
|
|
108
130
|
def unlock_command(name)
|
data/lib/pray/cli/help.rb
CHANGED
|
@@ -101,7 +101,11 @@ module Pray
|
|
|
101
101
|
"update" => <<~TEXT.strip,
|
|
102
102
|
refresh package versions within constraints
|
|
103
103
|
|
|
104
|
-
Usage: pray update [package]
|
|
104
|
+
Usage: pray update [package] [--latest] [--latest --dry-run]
|
|
105
|
+
|
|
106
|
+
--latest adjusts constraints to allow the latest package versions.
|
|
107
|
+
--latest also rewrites exact spec.upstream pins in path packages, then refreshes those trees.
|
|
108
|
+
--latest --dry-run prints the planned rewrite and does not write.
|
|
105
109
|
TEXT
|
|
106
110
|
"plan" => <<~TEXT.strip,
|
|
107
111
|
preview install/apply changes
|
data/lib/pray/cli.rb
CHANGED
|
@@ -10,6 +10,7 @@ require_relative "cli/suggest"
|
|
|
10
10
|
require_relative "cli/helpers"
|
|
11
11
|
require_relative "cli/commands/init"
|
|
12
12
|
require_relative "cli/commands/workflow"
|
|
13
|
+
require_relative "cli/commands/update_latest"
|
|
13
14
|
require_relative "cli/commands/packages"
|
|
14
15
|
require_relative "cli/commands/distribution"
|
|
15
16
|
require_relative "cli/commands/trust"
|
|
@@ -22,6 +23,7 @@ module Pray
|
|
|
22
23
|
LOCKFILE_PATH = "Prayfile.lock"
|
|
23
24
|
|
|
24
25
|
def run(arguments)
|
|
26
|
+
previous_context = Invocation.context
|
|
25
27
|
arguments = arguments.dup
|
|
26
28
|
if arguments.delete("--no-input")
|
|
27
29
|
ENV["PRAY_NO_INPUT"] = "1"
|
|
@@ -35,6 +37,8 @@ module Pray
|
|
|
35
37
|
else
|
|
36
38
|
dispatch(command)
|
|
37
39
|
end
|
|
40
|
+
ensure
|
|
41
|
+
Invocation.context = previous_context
|
|
38
42
|
end
|
|
39
43
|
|
|
40
44
|
def maybe_print_help(arguments)
|
data/lib/pray/constraint.rb
CHANGED
|
@@ -17,15 +17,28 @@ module Pray
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def version_satisfies(version, constraint)
|
|
20
|
-
|
|
21
|
-
return true if
|
|
20
|
+
parts = constraint.to_s.split(",").map(&:strip).reject(&:empty?)
|
|
21
|
+
return true if parts.empty?
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
parsed = Gem::Version.new(version)
|
|
24
|
+
parts.all? { |part| version_satisfies_one(parsed, part) }
|
|
25
25
|
rescue ArgumentError => error
|
|
26
26
|
raise Error.resolution(error.message)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
+
def version_satisfies_one(version, constraint)
|
|
30
|
+
normalized = normalize_version_constraint(constraint)
|
|
31
|
+
return true if normalized.empty? || normalized == "*"
|
|
32
|
+
|
|
33
|
+
requirement_text = if normalized.lstrip.start_with?("~>")
|
|
34
|
+
ruby_pessimistic_to_semver(normalized)
|
|
35
|
+
else
|
|
36
|
+
normalized.strip
|
|
37
|
+
end
|
|
38
|
+
clauses = requirement_text.split(",").map(&:strip).reject(&:empty?)
|
|
39
|
+
Gem::Requirement.new(*clauses).satisfied_by?(version)
|
|
40
|
+
end
|
|
41
|
+
|
|
29
42
|
def pessimistic_constraint_for_version(version)
|
|
30
43
|
parsed = Gem::Version.new(version)
|
|
31
44
|
if parsed.segments[1].to_i.zero? && parsed.segments[2].to_i.zero?
|
|
@@ -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/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"]
|
data/lib/pray/materialize.rb
CHANGED
|
@@ -13,7 +13,9 @@ module Pray
|
|
|
13
13
|
frozen: false,
|
|
14
14
|
locked: false,
|
|
15
15
|
offline: false,
|
|
16
|
-
refresh: false
|
|
16
|
+
refresh: false,
|
|
17
|
+
ignore_locked_versions: false,
|
|
18
|
+
unlocked_packages: nil
|
|
17
19
|
)
|
|
18
20
|
context = Invocation.invocation_context
|
|
19
21
|
manifest_path = File.expand_path(manifest_path || context.manifest_path)
|
|
@@ -25,6 +27,9 @@ module Pray
|
|
|
25
27
|
options = ResolveOptions.new(
|
|
26
28
|
offline: offline,
|
|
27
29
|
refresh: refresh,
|
|
30
|
+
refresh_source_revisions: refresh,
|
|
31
|
+
ignore_locked_versions: ignore_locked_versions,
|
|
32
|
+
unlocked_packages: unlocked_packages ? Set.new(unlocked_packages) : Set.new,
|
|
28
33
|
environment: context.environment
|
|
29
34
|
)
|
|
30
35
|
allow_git_refresh_fallback = !locked && !frozen
|
|
@@ -128,6 +133,7 @@ module Pray
|
|
|
128
133
|
def resolve_project(...) = Resolve.resolve_project(...)
|
|
129
134
|
def render_project(...) = Render.render_project(...)
|
|
130
135
|
def inspect_project(...) = Verify.inspect_project(...)
|
|
136
|
+
def inspect_locked_destinations(...) = Verify.inspect_locked_destinations(...)
|
|
131
137
|
def verify_project(...) = Verify.verify_project(...)
|
|
132
138
|
def drift_project(...) = Verify.drift_project(...)
|
|
133
139
|
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module PackageSpecRender
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def fork_spec_after_refresh(local, new_upstream, local_prayspec_file, clean_replica, merged_content_paths)
|
|
8
|
+
spec = local.dup
|
|
9
|
+
spec.files = [local_prayspec_file, *merged_content_paths]
|
|
10
|
+
if clean_replica
|
|
11
|
+
spec.exports = new_upstream.exports.dup
|
|
12
|
+
spec.templates = new_upstream.templates.dup
|
|
13
|
+
end
|
|
14
|
+
if spec.upstream
|
|
15
|
+
spec.upstream = PackageUpstream.new(
|
|
16
|
+
name: new_upstream.name,
|
|
17
|
+
constraint: Upstream.next_upstream_constraint(spec.upstream.constraint, new_upstream.version)
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
spec
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def render_package_spec(spec)
|
|
24
|
+
lines = ["Package::Specification.new do |spec|"]
|
|
25
|
+
push_identity_fields(lines, spec)
|
|
26
|
+
push_content_fields(lines, spec)
|
|
27
|
+
push_relation_fields(lines, spec)
|
|
28
|
+
lines << "end"
|
|
29
|
+
lines << ""
|
|
30
|
+
lines.join("\n")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def push_identity_fields(lines, spec)
|
|
34
|
+
push_assignment(lines, "name", spec.name)
|
|
35
|
+
push_assignment(lines, "version", spec.version)
|
|
36
|
+
push_optional(lines, "summary", spec.summary)
|
|
37
|
+
push_optional(lines, "description", spec.description)
|
|
38
|
+
push_array(lines, "authors", spec.authors) unless spec.authors.nil? || spec.authors.empty?
|
|
39
|
+
push_optional(lines, "license", spec.license)
|
|
40
|
+
push_optional(lines, "homepage", spec.homepage)
|
|
41
|
+
push_optional(lines, "source_code_uri", spec.source_code_uri)
|
|
42
|
+
push_optional(lines, "changelog_uri", spec.changelog_uri)
|
|
43
|
+
push_optional(lines, "prayfile_version", spec.prayfile_version)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def push_content_fields(lines, spec)
|
|
47
|
+
push_array(lines, "files", spec.files)
|
|
48
|
+
push_exports(lines, spec.exports)
|
|
49
|
+
push_named_paths(lines, "skills", spec.skills)
|
|
50
|
+
push_named_paths(lines, "templates", spec.templates)
|
|
51
|
+
push_string_map(lines, "adapters", spec.adapters)
|
|
52
|
+
push_array(lines, "targets", spec.targets) unless spec.targets.nil? || spec.targets.empty?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def push_relation_fields(lines, spec)
|
|
56
|
+
(spec.dependencies || []).each do |dependency|
|
|
57
|
+
method = dependency.optional ? "add_optional_dependency" : "add_dependency"
|
|
58
|
+
lines << " spec.#{method} #{quote(dependency.name)}, #{quote(dependency.constraint)}"
|
|
59
|
+
end
|
|
60
|
+
unless spec.metadata.nil? || spec.metadata.empty?
|
|
61
|
+
lines << " spec.metadata = #{literal_map(spec.metadata)}"
|
|
62
|
+
end
|
|
63
|
+
return unless spec.upstream
|
|
64
|
+
|
|
65
|
+
lines << " spec.upstream #{quote(spec.upstream.name)}, #{quote(spec.upstream.constraint)}"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def push_assignment(lines, field, value)
|
|
69
|
+
lines << " spec.#{field} = #{quote(value)}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def push_optional(lines, field, value)
|
|
73
|
+
push_assignment(lines, field, value) unless value.nil?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def push_array(lines, field, values)
|
|
77
|
+
lines << " spec.#{field} = [#{string_array(values)}]"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def push_exports(lines, exports)
|
|
81
|
+
return if exports.nil? || exports.empty?
|
|
82
|
+
|
|
83
|
+
lines << " spec.exports = {"
|
|
84
|
+
exports.each do |name, export|
|
|
85
|
+
fields = ["type: #{quote(export.kind)}", "path: #{quote(export.path)}"]
|
|
86
|
+
fields << "summary: #{quote(export.summary)}" if export.summary
|
|
87
|
+
fields << "only: [#{string_array(export.only)}]" unless export.only.nil? || export.only.empty?
|
|
88
|
+
fields << "except: [#{string_array(export.except)}]" unless export.except.nil? || export.except.empty?
|
|
89
|
+
fields << "default_path: #{quote(export.default_path)}" if export.default_path
|
|
90
|
+
lines << " #{quote(name)} => { #{fields.join(", ")} },"
|
|
91
|
+
end
|
|
92
|
+
lines << " }"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def push_named_paths(lines, field, entries)
|
|
96
|
+
return if entries.nil? || entries.empty?
|
|
97
|
+
|
|
98
|
+
lines << " spec.#{field} = {"
|
|
99
|
+
entries.each do |name, entry|
|
|
100
|
+
summary = entry.summary ? ", summary: #{quote(entry.summary)}" : ""
|
|
101
|
+
lines << " #{quote(name)} => { path: #{quote(entry.path)}#{summary} },"
|
|
102
|
+
end
|
|
103
|
+
lines << " }"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def push_string_map(lines, field, entries)
|
|
107
|
+
return if entries.nil? || entries.empty?
|
|
108
|
+
|
|
109
|
+
values = entries.map { |name, value| "#{quote(name)} => #{quote(value)}" }.join(", ")
|
|
110
|
+
lines << " spec.#{field} = { #{values} }"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def string_array(values)
|
|
114
|
+
Array(values).map { |value| quote(value) }.join(", ")
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def quote(value)
|
|
118
|
+
escaped = value.to_s.gsub("\\", "\\\\").gsub('"', '\\"').gsub("\n", "\\n").gsub("\r", "\\r").gsub("\t", "\\t")
|
|
119
|
+
%("#{escaped}")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def literal_map(entries)
|
|
123
|
+
values = entries.map { |name, value| "#{quote(name)} => #{literal(value)}" }.join(", ")
|
|
124
|
+
"{ #{values} }"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def literal(value)
|
|
128
|
+
case value.kind
|
|
129
|
+
when :string then quote(value.value)
|
|
130
|
+
when :symbol then ":#{value.value}"
|
|
131
|
+
when :bool then value.value.to_s
|
|
132
|
+
when :null then "nil"
|
|
133
|
+
when :integer then value.value.to_s
|
|
134
|
+
when :array then "[#{value.value.map { |entry| literal(entry) }.join(", ")}]"
|
|
135
|
+
when :map then literal_map(value.value)
|
|
136
|
+
else quote(value.value)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
private_class_method :push_identity_fields, :push_content_fields, :push_relation_fields,
|
|
140
|
+
:push_assignment, :push_optional, :push_array, :push_exports,
|
|
141
|
+
:push_named_paths, :push_string_map, :string_array, :quote, :literal_map, :literal
|
|
142
|
+
end
|
|
143
|
+
end
|
data/lib/pray/path_safety.rb
CHANGED
|
@@ -39,20 +39,24 @@ module Pray
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def validate_archive_member_path!(path)
|
|
42
|
-
|
|
43
|
-
if
|
|
42
|
+
text = path.to_s.tr("\\", "/")
|
|
43
|
+
if text.empty? || Pathname.new(text).absolute? || text.start_with?("/")
|
|
44
44
|
raise Error.integrity("package path must be relative: #{path}")
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
parts = []
|
|
48
|
+
text.split("/").each do |part|
|
|
48
49
|
next if part.empty? || part == "."
|
|
49
50
|
|
|
50
51
|
if part == ".." || part.include?("\0")
|
|
51
52
|
raise Error.integrity("package path escapes package root: #{path}")
|
|
52
53
|
end
|
|
54
|
+
|
|
55
|
+
parts << part
|
|
53
56
|
end
|
|
57
|
+
raise Error.integrity("package path must be relative: #{path}") if parts.empty?
|
|
54
58
|
|
|
55
|
-
|
|
59
|
+
parts.join("/")
|
|
56
60
|
end
|
|
57
61
|
|
|
58
62
|
def validate_project_relative_path!(value)
|
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)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module Pray
|
|
6
|
+
module RegistryPaths
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def local_registry_root(project_root, source_url)
|
|
10
|
+
path = source_url.delete_prefix("file://")
|
|
11
|
+
Pathname.new(path).absolute? ? path : File.expand_path(path, project_root)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|