pray-cli 1.10.0 → 1.11.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 +9 -0
- data/lib/pray/cache_clean.rb +76 -0
- data/lib/pray/cli/commands/meta.rb +9 -4
- data/lib/pray/cli/help.rb +1 -1
- data/lib/pray/cli/parse.rb +19 -1
- data/lib/pray/cli.rb +2 -1
- data/lib/pray/path_safety.rb +20 -0
- data/lib/pray/registry.rb +6 -13
- data/lib/pray/tar_validation.rb +4 -1
- data/lib/pray/version.rb +2 -2
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cce969add8d5bb73ee6fc4b9910d785a0565e24afaeb6d4b80d9383f9fbe828b
|
|
4
|
+
data.tar.gz: f96cac68f4cf12079056d0ef300c57ab0efa47f3c9ab1e9967a9fe03cb8a5c37
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a0ec1da337a61097a65ebd7dc091879a978bbb1f13d6eba41ee4149735e8d37b3d32a7634e5b79fbc426e7ef068cf49ac5437c71b9492fe09f5ab439b1c5645b
|
|
7
|
+
data.tar.gz: 0ea56f7dd07ddf9be173bc0dd3ed399b91d280c36aaea1f703d0a60b9676dba15398cef53c876f71b3c86347611b9dd5f10af6d716152a69e4bf488d7b82996c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 1.11.0 (2026-09-04)
|
|
6
|
+
|
|
7
|
+
- Read the whole eight byte tar checksum field so `.praypkg` archives whose checksum is written as seven octal digits unpack instead of failing integrity.
|
|
8
|
+
- Share the source-keyed registry cache path with the Rust and TypeScript CLIs.
|
|
9
|
+
- Add `pray clean --unused` for lockfile-driven registry cache cleanup.
|
|
10
|
+
- Reject unsafe registry package and version path segments.
|
|
11
|
+
|
|
3
12
|
## 1.10.0 (2026-09-02)
|
|
4
13
|
|
|
5
14
|
- Refuse to overwrite exclusive `file:` and `tree:` destinations that already exist with other bytes.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Pray
|
|
6
|
+
module CacheClean
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def clean_unused_registry_cache(project_root)
|
|
10
|
+
lockfile = Pray.read_lockfile(File.join(project_root, "Prayfile.lock"))
|
|
11
|
+
validate_lockfile_for_cleanup!(lockfile)
|
|
12
|
+
registry_root = File.expand_path(".pray/cache/registry", project_root)
|
|
13
|
+
retained = lockfile.package.filter_map do |package|
|
|
14
|
+
package_path = File.expand_path(package.path, project_root)
|
|
15
|
+
package_path if PathSafety.path_under_root?(registry_root, package_path)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
begin
|
|
19
|
+
metadata = File.lstat(registry_root)
|
|
20
|
+
rescue Errno::ENOENT
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
if metadata.symlink? || !metadata.directory?
|
|
24
|
+
remove_path(registry_root)
|
|
25
|
+
return
|
|
26
|
+
end
|
|
27
|
+
prune_directory(registry_root, retained, remove_when_empty: false)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def validate_lockfile_for_cleanup!(lockfile)
|
|
31
|
+
validate_sha256_digest!("manifest_hash", lockfile.manifest_hash)
|
|
32
|
+
lockfile.package.each do |package|
|
|
33
|
+
if !package.path.is_a?(String) || package.path.empty?
|
|
34
|
+
raise Error.parse("lockfile", "package path must not be empty")
|
|
35
|
+
end
|
|
36
|
+
validate_sha256_digest!("package tree_hash", package.tree_hash)
|
|
37
|
+
validate_sha256_digest!("package artifact_hash", package.artifact_hash)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def validate_sha256_digest!(field, value)
|
|
42
|
+
digest = value.to_s.delete_prefix("sha256:")
|
|
43
|
+
return if value.is_a?(String) && value.start_with?("sha256:") && digest.match?(/\A[0-9a-f]{64}\z/)
|
|
44
|
+
|
|
45
|
+
raise Error.parse("lockfile", "#{field} must be a sha256 digest")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def prune_directory(path, retained, remove_when_empty:)
|
|
49
|
+
Dir.each_child(path) do |name|
|
|
50
|
+
entry = File.join(path, name)
|
|
51
|
+
protects_entry = retained.include?(entry)
|
|
52
|
+
leads_to_retained = retained.any? { |kept| PathSafety.path_under_root?(entry, kept) }
|
|
53
|
+
next if protects_entry
|
|
54
|
+
|
|
55
|
+
metadata = File.lstat(entry)
|
|
56
|
+
if leads_to_retained && metadata.directory? && !metadata.symlink?
|
|
57
|
+
prune_directory(entry, retained, remove_when_empty: true)
|
|
58
|
+
else
|
|
59
|
+
remove_path(entry)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
Dir.rmdir(path) if remove_when_empty && Dir.empty?(path)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def remove_path(path)
|
|
66
|
+
metadata = File.lstat(path)
|
|
67
|
+
if metadata.directory? && !metadata.symlink?
|
|
68
|
+
FileUtils.rm_rf(path)
|
|
69
|
+
else
|
|
70
|
+
File.unlink(path)
|
|
71
|
+
end
|
|
72
|
+
rescue Errno::ENOENT
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -6,10 +6,15 @@ module Pray
|
|
|
6
6
|
puts "pray #{Pray::VERSION}"
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
def clean_command
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
def clean_command(unused: false)
|
|
10
|
+
project_root = Invocation.project_root
|
|
11
|
+
if unused
|
|
12
|
+
CacheClean.clean_unused_registry_cache(project_root)
|
|
13
|
+
return
|
|
14
|
+
end
|
|
15
|
+
remove_path_if_exists(File.join(project_root, ".pray/cache"))
|
|
16
|
+
remove_path_if_exists(File.join(project_root, ".pray/vendor"))
|
|
17
|
+
remove_path_if_exists(File.join(project_root, ".pray/state.json"))
|
|
13
18
|
end
|
|
14
19
|
end
|
|
15
20
|
end
|
data/lib/pray/cli/help.rb
CHANGED
|
@@ -117,7 +117,7 @@ module Pray
|
|
|
117
117
|
"remove" => "remove a package from Prayfile\n\nUsage: pray remove <name>",
|
|
118
118
|
"unlock" => "clear a locked package pin\n\nUsage: pray unlock <package>",
|
|
119
119
|
"vendor" => "copy resolved packages locally\n\nUsage: pray vendor",
|
|
120
|
-
"clean" => "remove local cache and vendor trees\n\nUsage: pray clean",
|
|
120
|
+
"clean" => "remove local cache and vendor trees, or only unused registry entries\n\nUsage: pray clean [--unused]",
|
|
121
121
|
"publish" => <<~TEXT.strip,
|
|
122
122
|
upload packages to a registry or local root
|
|
123
123
|
|
data/lib/pray/cli/parse.rb
CHANGED
|
@@ -67,7 +67,7 @@ module Pray
|
|
|
67
67
|
when "outdated" then [:outdated, arguments]
|
|
68
68
|
when "explain" then [:explain, arguments.shift]
|
|
69
69
|
when "vendor" then [:vendor]
|
|
70
|
-
when "clean" then [:clean]
|
|
70
|
+
when "clean" then [:clean, parse_clean_arguments(arguments, flags)]
|
|
71
71
|
when "tree" then [:tree]
|
|
72
72
|
when "sync" then [:sync, parse_sync_arguments(arguments)]
|
|
73
73
|
when "trust" then parse_trust_command(arguments)
|
|
@@ -83,6 +83,24 @@ module Pray
|
|
|
83
83
|
found
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
+
def parse_clean_arguments(arguments, flags)
|
|
87
|
+
if flags.values_at(:check, :strict, :semantic, :locked, :frozen, :offline).any?
|
|
88
|
+
raise Error.unsupported("unknown clean flag")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
unused = false
|
|
92
|
+
arguments.each do |argument|
|
|
93
|
+
if argument == "--unused" && !unused
|
|
94
|
+
unused = true
|
|
95
|
+
elsif argument.start_with?("--")
|
|
96
|
+
raise Error.unsupported("unknown clean flag: #{argument}")
|
|
97
|
+
else
|
|
98
|
+
raise Error.unsupported("unexpected clean argument: #{argument}")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
{unused: unused}
|
|
102
|
+
end
|
|
103
|
+
|
|
86
104
|
def parse_add_arguments(arguments)
|
|
87
105
|
name = arguments.shift
|
|
88
106
|
raise Error.unsupported("add requires a package name") unless name
|
data/lib/pray/cli.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
|
|
5
5
|
require_relative "invocation"
|
|
6
|
+
require_relative "cache_clean"
|
|
6
7
|
require_relative "cli/parse"
|
|
7
8
|
require_relative "cli/help"
|
|
8
9
|
require_relative "cli/suggest"
|
|
@@ -97,7 +98,7 @@ module Pray
|
|
|
97
98
|
in [:outdated, arguments] then outdated_command(arguments)
|
|
98
99
|
in [:explain, name] then explain_command(name)
|
|
99
100
|
in [:vendor] then raise Error.unsupported("vendor is not implemented yet in pray-cli Ruby")
|
|
100
|
-
in [:clean] then clean_command
|
|
101
|
+
in [:clean, options] then clean_command(**options)
|
|
101
102
|
in [:tree] then tree_command
|
|
102
103
|
in [:trust_list, options] then trust_list_command(**options)
|
|
103
104
|
in [:trust_show] then trust_show_command
|
data/lib/pray/path_safety.rb
CHANGED
|
@@ -80,5 +80,25 @@ module Pray
|
|
|
80
80
|
|
|
81
81
|
validate_project_relative_path!(value)
|
|
82
82
|
end
|
|
83
|
+
|
|
84
|
+
def validate_registry_cache_identity!(package_name, version)
|
|
85
|
+
namespace, name, extra_segment = package_name.to_s.split("/", -1)
|
|
86
|
+
unless extra_segment.nil? && registry_cache_segment_safe?(namespace) && registry_cache_segment_safe?(name)
|
|
87
|
+
raise Error.integrity("invalid registry package name: #{package_name}")
|
|
88
|
+
end
|
|
89
|
+
unless registry_cache_segment_safe?(version)
|
|
90
|
+
raise Error.integrity("invalid registry package version: #{version}")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
[namespace, name]
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def registry_cache_segment_safe?(value)
|
|
97
|
+
value.is_a?(String) &&
|
|
98
|
+
!value.empty? &&
|
|
99
|
+
![".", ".."].include?(value) &&
|
|
100
|
+
!value.match?(%r{[/\\\0]})
|
|
101
|
+
end
|
|
102
|
+
private_class_method :registry_cache_segment_safe?
|
|
83
103
|
end
|
|
84
104
|
end
|
data/lib/pray/registry.rb
CHANGED
|
@@ -47,8 +47,7 @@ module Pray
|
|
|
47
47
|
project_root,
|
|
48
48
|
source_url,
|
|
49
49
|
declaration.name,
|
|
50
|
-
selected.version
|
|
51
|
-
selected.artifact_hash
|
|
50
|
+
selected.version
|
|
52
51
|
)
|
|
53
52
|
|
|
54
53
|
if cache_ready?(cache_directory, selected)
|
|
@@ -91,8 +90,7 @@ module Pray
|
|
|
91
90
|
project_root,
|
|
92
91
|
source_key,
|
|
93
92
|
declaration.name,
|
|
94
|
-
selected.version
|
|
95
|
-
selected.artifact_hash
|
|
93
|
+
selected.version
|
|
96
94
|
)
|
|
97
95
|
|
|
98
96
|
if cache_ready?(cache_directory, selected)
|
|
@@ -238,15 +236,10 @@ module Pray
|
|
|
238
236
|
)
|
|
239
237
|
end
|
|
240
238
|
|
|
241
|
-
def registry_cache_directory(project_root, source_key, package_name, version
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
version,
|
|
246
|
-
artifact_hash || "no-artifact-hash"
|
|
247
|
-
].join(":")
|
|
248
|
-
digest = Hashing.sha256_hex(identifier)[0, 16]
|
|
249
|
-
File.join(project_root, ".pray", "cache", "registry", package_name.tr("/", "-"), version, digest)
|
|
239
|
+
def registry_cache_directory(project_root, source_key, package_name, version)
|
|
240
|
+
namespace, name = PathSafety.validate_registry_cache_identity!(package_name, version)
|
|
241
|
+
source_hash = Hashing.sha256_hex(source_key)[0, 16]
|
|
242
|
+
File.join(project_root, ".pray", "cache", "registry", namespace, name, version, source_hash)
|
|
250
243
|
end
|
|
251
244
|
|
|
252
245
|
def offline_package_error(package_name, version)
|
data/lib/pray/tar_validation.rb
CHANGED
|
@@ -102,7 +102,10 @@ module Pray
|
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
def verify_checksum!(header)
|
|
105
|
-
|
|
105
|
+
# The checksum field is eight bytes. Writers fill it differently: six octal
|
|
106
|
+
# digits then NUL and space, seven digits then NUL, or space padding. Read the
|
|
107
|
+
# whole field and let parse_octal strip the terminator.
|
|
108
|
+
stored = parse_octal(header.byteslice(148, 8), "checksum")
|
|
106
109
|
sum = 0
|
|
107
110
|
header.bytes.each_with_index do |byte, index|
|
|
108
111
|
sum += (index >= 148 && index < 156) ? 32 : byte
|
data/lib/pray/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pray-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.11.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -99,6 +99,7 @@ files:
|
|
|
99
99
|
- lib/pray/archive.rb
|
|
100
100
|
- lib/pray/archive_unpack.rb
|
|
101
101
|
- lib/pray/auth_client.rb
|
|
102
|
+
- lib/pray/cache_clean.rb
|
|
102
103
|
- lib/pray/cli.rb
|
|
103
104
|
- lib/pray/cli/commands/auth.rb
|
|
104
105
|
- lib/pray/cli/commands/distribution.rb
|