pray-cli 1.17.0 → 1.18.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f7499680372bceb27c55d6d182225ea43870e4bfd94a431a085e42198ff90557
4
- data.tar.gz: 32af3f08b8ea05718970b3283f04e81552ff8355ad0ca260d3b1c61b3ab05619
3
+ metadata.gz: 91a549f989bb5872cf7307c16101a89c479bda9f919dd4f969917a80f4bf5958
4
+ data.tar.gz: 01a7042cad4e383a95ca2b15d698b9e00a46707d27b2885381ce57f6775cb15b
5
5
  SHA512:
6
- metadata.gz: 1ea412c96b4c0d3cb766f75bec70dcd9e29c368594bf67d4438f8464703dbe4cd781dff6b093eefa8f46269de3886d09ff4bf27c8c02503a6e61650413439667
7
- data.tar.gz: 389eb906dbdae17bf789290a450e10c5a6ccae9064f707d4f494e444e4efdaf62283103adf3f0bc335d705a99292efb1885c87da4c1daab40677a6a0fbd9e0d0
6
+ metadata.gz: 52f904573df3f97eb3ad240388db1ac7d81d36cd66fe24b43be8cf32f392d59abf6283fc496a92b9844e40cec4d7058bf8db54fb97716164635c395c95a27796
7
+ data.tar.gz: ccc7110154721dbc019d02c37ecdbeaaac225ccf702e8d1007cfe3af4b3f54f35627876fb78f15eb5617e691c7c7ca7016c9c05853b1719655545a087baf8eef
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 1.18.1 (2026-09-17)
4
+
5
+ - Fix `pray package` and `pray publish` when the CLI cannot create a staging directory.
6
+
7
+ ## 1.18.0 (2026-09-17)
8
+
9
+ - Clone a git catalog without unused package blobs, and fetch a used package file when install needs it.
10
+ - Skip cloning a git catalog when no package uses that source.
11
+ - Fetch a used git catalog once on `pray update` instead of fetching it twice.
12
+ - Keep packages from two git sources that share a clone URL when each source names a different `subdir`.
13
+ - Share one git object store for those subdirectory checkouts.
14
+ - Import signing keys with `pray trust import-repo` after a catalog was cloned only through `subdir` sources.
15
+ - Reuse registry package metadata during one command so `pray update --latest` does not download the same package JSON twice.
16
+ - Skip a second resolve when `pray update --latest` does not rewrite constraints or upstream pins, and still refresh path-fork files when needed.
17
+
3
18
  ## 1.17.0 (2026-09-16)
4
19
 
5
20
  - Locate local prayers through a path source of any directory name (RFC 0117). `pray prayer init` adds `source "local"` when needed and declares `pray "local/project"` once. Version is optional until `pray package` or `pray publish`. `.agents/project.md` remains a compose shortcut for one local file.
data/lib/pray/archive.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require "json"
4
4
  require "open3"
5
5
  require "fileutils"
6
+ require "tmpdir"
6
7
  require_relative "archive_unpack"
7
8
  require_relative "path_safety"
8
9
 
@@ -28,10 +28,10 @@ module Pray
28
28
 
29
29
  def trust_import_repo_command(source_url, match_prefix: nil)
30
30
  clone_url = source_url.delete_prefix("git+")
31
- repository = GitSources.git_source_cache_directory(Dir.pwd, clone_url)
32
- unless File.directory?(File.join(repository, ".git"))
31
+ repository = GitSources.git_source_cached_repository(Dir.pwd, clone_url)
32
+ unless repository
33
33
  raise Error.resolution(
34
- "no cached git repository for #{clone_url} at #{repository}"
34
+ "no cached git repository for #{clone_url}"
35
35
  )
36
36
  end
37
37
 
@@ -6,13 +6,15 @@ module Pray
6
6
  path = manifest_path
7
7
  original_text = Pray.read_manifest_text(path)
8
8
  manifest_text = original_text
9
- preview_options = ResolveOptions.new(
9
+ unlocked = package ? Set[package] : Set.new
10
+ options = ResolveOptions.new(
10
11
  offline: offline,
11
12
  refresh: true,
12
13
  refresh_source_revisions: true,
13
- ignore_locked_versions: true
14
+ ignore_locked_versions: package.nil?,
15
+ unlocked_packages: unlocked
14
16
  )
15
- project = resolve_current_project(preview_options)
17
+ project = resolve_current_project(options)
16
18
  if package && project.manifest.packages.none? { |entry| entry.name == package }
17
19
  raise Error.manifest("package #{package} not found")
18
20
  end
@@ -46,24 +48,20 @@ module Pray
46
48
 
47
49
  previous = File.exist?(lockfile_path) ? Pray.read_lockfile(lockfile_path) : nil
48
50
  upstream_plans = Upstream.plan_path_upstream_latest_constraints(
49
- project, previous, package, preview_options
51
+ project, previous, package, options
50
52
  )
51
53
  print_latest_constraint_plans(manifest_updates, upstream_plans)
52
54
 
53
55
  return if dry_run
54
56
 
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)
57
+ if !manifest_updates.empty? || !upstream_plans.empty?
58
+ Upstream.apply_path_upstream_latest_constraints(upstream_plans)
59
+ Transaction.write_file(path, manifest_text) if manifest_text != original_text
60
+ current = resolve_current_project(options)
61
+ Upstream.apply_path_upstream_refreshes(current, previous, package, options)
62
+ else
63
+ Upstream.apply_path_upstream_refreshes(project, previous, package, options)
64
+ end
67
65
  install_command(
68
66
  {
69
67
  locked: false,
@@ -0,0 +1,192 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+ require "pathname"
5
+ require_relative "error"
6
+ require_relative "hashing"
7
+ require_relative "git_run"
8
+ require_relative "git_clone"
9
+
10
+ module Pray
11
+ module GitCache
12
+ module_function
13
+
14
+ def ensure_git_repository(project_root, clone_url, refresh:, pinned_revision:, sparse_subdir:)
15
+ shared = git_source_cache_directory(project_root, clone_url)
16
+ ensure_shared_git_repository(project_root, clone_url, shared, refresh: refresh, pinned_revision: pinned_revision)
17
+ checkout = git_source_cache_directory(project_root, clone_url, sparse_subdir)
18
+ if checkout != shared
19
+ ensure_linked_worktree(shared, checkout)
20
+ GitClone.apply_sparse_checkout(checkout, sparse_subdir)
21
+ if pinned_revision
22
+ checkout_git_revision(checkout, clone_url, pinned_revision, refresh)
23
+ elsif refresh
24
+ GitRun.run_git(checkout, "reset", "--hard", git_head_revision(shared))
25
+ end
26
+ return [checkout, git_head_revision(checkout)]
27
+ end
28
+
29
+ GitClone.apply_sparse_checkout(shared)
30
+ [shared, git_head_revision(shared)]
31
+ end
32
+
33
+ def git_source_cache_directory(project_root, clone_url, subdir = nil)
34
+ identity = (subdir && !subdir.empty?) ? "#{clone_url}\n#{subdir}" : clone_url
35
+ File.join(project_root, ".pray", "cache", "git", cache_key(identity))
36
+ end
37
+
38
+ def git_source_cached_repository(project_root, clone_url)
39
+ shared = git_source_cache_directory(project_root, clone_url)
40
+ return shared if git_checkout?(shared)
41
+
42
+ cache_root = File.join(project_root, ".pray", "cache", "git")
43
+ return nil unless File.directory?(cache_root)
44
+
45
+ Dir.children(cache_root).each do |name|
46
+ path = File.join(cache_root, name)
47
+ return path if git_checkout?(path) && origin_matches?(path, clone_url)
48
+ end
49
+ nil
50
+ end
51
+
52
+ def git_checkout?(path)
53
+ File.exist?(File.join(path, ".git"))
54
+ end
55
+
56
+ def cache_key(text)
57
+ Hashing.sha256_prefixed(text)[7, 16]
58
+ end
59
+
60
+ def ensure_shared_git_repository(project_root, clone_url, shared, refresh:, pinned_revision:)
61
+ if File.directory?(File.join(shared, ".git"))
62
+ if pinned_revision
63
+ checkout_git_revision(shared, clone_url, pinned_revision, refresh)
64
+ elsif refresh
65
+ refresh_git_worktree(shared, clone_url)
66
+ end
67
+ refresh_global_from_project(clone_url, shared) if refresh
68
+ return
69
+ end
70
+
71
+ FileUtils.rm_rf(shared) if File.exist?(shared)
72
+ FileUtils.mkdir_p(File.dirname(shared))
73
+ seeded = seed_git_cache_from_global(clone_url, shared, project_root)
74
+ if seeded
75
+ GitRun.run_git(shared, "remote", "set-url", "origin", clone_url)
76
+ else
77
+ GitClone.clone_git_cache(project_root, clone_url, shared, quiet: false)
78
+ mirror_git_cache_to_global(clone_url, shared)
79
+ end
80
+ GitClone.apply_sparse_checkout(shared)
81
+ if pinned_revision
82
+ checkout_git_revision(shared, clone_url, pinned_revision, true)
83
+ elsif refresh && seeded
84
+ refresh_git_worktree(shared, clone_url)
85
+ end
86
+ refresh_global_from_project(clone_url, shared) if refresh && seeded
87
+ end
88
+
89
+ def ensure_linked_worktree(shared, checkout)
90
+ return if same_object_store?(shared, checkout)
91
+
92
+ FileUtils.rm_rf(checkout) if File.exist?(checkout)
93
+ FileUtils.mkdir_p(File.dirname(checkout))
94
+ return if GitRun.try_run_git(shared, "worktree", "add", "--detach", "--no-checkout", checkout)
95
+
96
+ GitRun.run_git(shared, "worktree", "add", "--detach", checkout)
97
+ end
98
+
99
+ def same_object_store?(shared, checkout)
100
+ return false unless git_checkout?(checkout)
101
+
102
+ git_common_dir(shared) == git_common_dir(checkout)
103
+ end
104
+
105
+ def git_common_dir(repository)
106
+ output, status = GitRun.capture_git(repository, "rev-parse", "--git-common-dir")
107
+ raise Error.resolution(GitRun.command_error("git rev-parse --git-common-dir", output)) unless status.success?
108
+
109
+ reported = output.strip
110
+ path = Pathname.new(reported).absolute? ? reported : File.expand_path(reported, repository)
111
+ File.realpath(path)
112
+ end
113
+
114
+ def origin_matches?(repository, clone_url)
115
+ output, status = GitRun.capture_git(repository, "remote", "get-url", "origin")
116
+ return false unless status.success?
117
+
118
+ origin = output.strip.delete_prefix("git+")
119
+ origin == clone_url
120
+ end
121
+
122
+ def global_cache_root
123
+ return ENV["PRAY_CACHE"] if ENV["PRAY_CACHE"]
124
+ return File.join(ENV["PRAY_HOME"], "cache") if ENV["PRAY_HOME"]
125
+
126
+ home = ENV["HOME"]
127
+ home ? File.join(home, ".cache", "pray") : nil
128
+ end
129
+
130
+ def global_git_cache_directory(clone_url)
131
+ root = global_cache_root
132
+ root ? File.join(root, "git", cache_key(clone_url)) : nil
133
+ end
134
+
135
+ def global_git_cache_ready?(global_cache)
136
+ File.directory?(File.join(global_cache, ".git")) || File.file?(File.join(global_cache, "HEAD"))
137
+ end
138
+
139
+ def seed_git_cache_from_global(clone_url, destination, working_directory)
140
+ global_cache = global_git_cache_directory(clone_url)
141
+ return false unless global_cache && global_git_cache_ready?(global_cache)
142
+
143
+ GitClone.clone_git_cache(working_directory, global_cache, destination, quiet: true)
144
+ true
145
+ end
146
+
147
+ def mirror_git_cache_to_global(clone_url, project_cache)
148
+ global_cache = global_git_cache_directory(clone_url)
149
+ return unless global_cache
150
+ return if global_git_cache_ready?(global_cache)
151
+
152
+ FileUtils.mkdir_p(File.dirname(global_cache))
153
+ FileUtils.rm_rf(global_cache) if File.exist?(global_cache)
154
+ GitRun.run_git(File.dirname(project_cache), "clone", "--bare", "--quiet", File.basename(project_cache), global_cache)
155
+ end
156
+
157
+ def fetch_origin(repository, revision = nil)
158
+ extra = revision ? [revision] : []
159
+ return if GitRun.try_run_git(repository, "fetch", "--depth", "1", "--filter=blob:none", "origin", *extra)
160
+
161
+ GitRun.run_git(repository, "fetch", "--depth", "1", "origin", *extra)
162
+ end
163
+
164
+ def checkout_git_revision(repository, _clone_url, revision, refresh)
165
+ fetch_origin(repository, revision) if refresh
166
+ GitRun.run_git(repository, "checkout", "--force", revision)
167
+ end
168
+
169
+ def refresh_git_worktree(repository, _clone_url)
170
+ fetch_origin(repository)
171
+ GitRun.run_git(repository, "reset", "--hard", "origin/HEAD")
172
+ end
173
+
174
+ def refresh_global_from_project(clone_url, project_cache)
175
+ global_cache = global_git_cache_directory(clone_url)
176
+ return unless global_cache
177
+
178
+ FileUtils.rm_rf(global_cache) if global_git_cache_ready?(global_cache) || File.exist?(global_cache)
179
+ mirror_git_cache_to_global(clone_url, project_cache)
180
+ end
181
+
182
+ def git_head_revision(repository)
183
+ output, status = GitRun.capture_git(repository, "rev-parse", "HEAD")
184
+ raise Error.resolution(GitRun.command_error("git rev-parse HEAD", output)) unless status.success?
185
+
186
+ revision = output.strip
187
+ raise Error.resolution("git repository has no HEAD revision") if revision.empty?
188
+
189
+ revision
190
+ end
191
+ end
192
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "git_run"
4
+
5
+ module Pray
6
+ module GitClone
7
+ module_function
8
+
9
+ def clone_git_cache(working_directory, source, destination, quiet:)
10
+ filtered = ["clone", "--depth", "1", "--filter=blob:none", "--sparse", source, destination]
11
+ filtered.insert(1, "--quiet") if quiet
12
+ return if GitRun.try_run_git(working_directory, *filtered)
13
+
14
+ full = ["clone", "--depth", "1", source, destination]
15
+ full.insert(1, "--quiet") if quiet
16
+ GitRun.run_git(working_directory, *full)
17
+ end
18
+
19
+ def apply_sparse_checkout(repository, subdir = nil)
20
+ GitRun.run_git(repository, "sparse-checkout", "init", "--cone")
21
+ GitRun.run_git(repository, "sparse-checkout", "set", *catalog_sparse_cones(subdir))
22
+ end
23
+
24
+ def catalog_sparse_cones(subdir)
25
+ if subdir && !subdir.empty?
26
+ ["#{subdir}/v1/packages"]
27
+ else
28
+ ["v1/packages", "prayers/v1/packages"]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "git_run"
4
+ require_relative "error"
5
+ require_relative "path_safety"
6
+
7
+ module Pray
8
+ module GitMaterialize
9
+ module_function
10
+
11
+ def read_local_artifact_bytes(source_root, artifact)
12
+ if artifact.start_with?("file://")
13
+ path = PathSafety.join_under_root(source_root, artifact.delete_prefix("file://"))
14
+ raise Error.resolution("package artifact path escapes distribution root") unless path
15
+
16
+ return File.binread(path)
17
+ end
18
+ if artifact.match?(%r{\A[a-z][a-z0-9+.-]*:}i)
19
+ raise Error.integrity("remote artifact path must be relative: #{artifact}")
20
+ end
21
+
22
+ path = PathSafety.join_under_root(source_root, artifact)
23
+ raise Error.resolution("package artifact path escapes distribution root") unless path
24
+
25
+ materialize_catalog_file(source_root, artifact) unless File.exist?(path)
26
+ raise Error.resolution("package artifact missing at #{path}") unless File.exist?(path)
27
+
28
+ File.binread(path)
29
+ end
30
+
31
+ def materialize_catalog_file(source_root, relative)
32
+ return if File.file?(File.join(source_root, relative))
33
+ return unless sparse_checkout_enabled?(source_root)
34
+
35
+ toplevel = git_toplevel(source_root)
36
+ return unless toplevel
37
+
38
+ repo_relative = repo_relative_text(source_root, relative)
39
+ return unless repo_relative
40
+
41
+ cone = artifact_sparse_cone(repo_relative)
42
+ GitRun.run_git(toplevel, "sparse-checkout", "add", cone) if cone
43
+ GitRun.run_git(toplevel, "checkout", "HEAD", "--", repo_relative)
44
+ end
45
+
46
+ def sparse_checkout_enabled?(source_root)
47
+ output, status = GitRun.capture_git(source_root, "config", "--get", "core.sparseCheckout")
48
+ status.success? && output.strip == "true"
49
+ end
50
+
51
+ def git_toplevel(source_root)
52
+ output, status = GitRun.capture_git(source_root, "rev-parse", "--show-toplevel")
53
+ return unless status.success?
54
+
55
+ text = output.strip
56
+ text.empty? ? nil : text
57
+ end
58
+
59
+ def repo_relative_text(source_root, relative)
60
+ output, status = GitRun.capture_git(source_root, "rev-parse", "--show-prefix")
61
+ return unless status.success?
62
+
63
+ prefix = output.strip
64
+ prefix.empty? ? relative : File.join(prefix, relative)
65
+ end
66
+
67
+ def artifact_sparse_cone(repo_relative)
68
+ parts = repo_relative.split("/").reject(&:empty?)
69
+ return repo_relative if parts.length < 2
70
+
71
+ parts.pop
72
+ parts.pop if parts.last != "artifacts" && parts.length > 3
73
+ parts.join("/")
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require_relative "error"
5
+
6
+ module Pray
7
+ module GitRun
8
+ module_function
9
+
10
+ def run_git(cwd, *arguments)
11
+ output, status = capture_git(cwd, *arguments)
12
+ return if status.success?
13
+
14
+ raise Error.resolution(command_error("git #{arguments.join(" ")}", output))
15
+ end
16
+
17
+ def try_run_git(cwd, *arguments)
18
+ _output, status = capture_git(cwd, *arguments)
19
+ status.success?
20
+ end
21
+
22
+ def capture_git(cwd, *arguments)
23
+ env = ENV.to_h.merge("GIT_TERMINAL_PROMPT" => "0")
24
+ Open3.capture2e(
25
+ env,
26
+ "git",
27
+ "-c",
28
+ "protocol.file.allow=always",
29
+ "-C",
30
+ cwd,
31
+ *arguments,
32
+ stdin_data: ""
33
+ )
34
+ end
35
+
36
+ def command_error(program, output)
37
+ message = output.strip
38
+ message.empty? ? "#{program} failed" : "#{program} failed: #{message}"
39
+ end
40
+ end
41
+ end
@@ -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
- checkouts = {}
15
- sources.each do |source|
16
- next unless source.kind == "git"
17
-
18
- clone_url = source.url.delete_prefix("git+")
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
- if source_root
22
- checkouts[source.name] = GitSourceCheckout.new(
23
- cache_directory: source_root,
24
- revision: "",
25
- subdir: source.subdir
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
- checkouts
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 ensure_git_repository(project_root, clone_url, refresh:, pinned_revision:, sparse_subdir:)
87
- cache_directory = git_source_cache_directory(project_root, clone_url)
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 cache_key(text)
118
- Hashing.sha256_prefixed(text)[7, 16]
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
data/lib/pray/publish.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require "json"
4
4
  require "fileutils"
5
5
  require "time"
6
+ require "tmpdir"
6
7
 
7
8
  module Pray
8
9
  RegistryIndex = Struct.new(:spec, :packages) do
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
- PathSafety.reject_unsafe_package_name!(package_name)
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
- if artifact.start_with?("file://")
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)
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pray
4
+ module RegistryHttpCache
5
+ module_function
6
+
7
+ def fetch_package_metadata(source_url, package_name)
8
+ @cache ||= {}
9
+ key = [source_url, package_name]
10
+ return @cache[key] if @cache.key?(key)
11
+
12
+ PathSafety.reject_unsafe_package_name!(package_name)
13
+ response = Registry.http_get(Registry.join_url(source_url, "v1/packages/#{package_name}.json"))
14
+ @cache[key] = Registry.parse_metadata(response)
15
+ end
16
+ end
17
+ end
data/lib/pray/resolve.rb CHANGED
@@ -61,7 +61,6 @@ module Pray
61
61
  lockfile_hints,
62
62
  refresh: options.refresh || options.refresh_source_revisions
63
63
  )
64
- source_revisions = git_sources.transform_values(&:revision)
65
64
  source_host_keys = Trust.prepare_source_host_keys(manifest.sources)
66
65
 
67
66
  packages = []
@@ -100,6 +99,7 @@ module Pray
100
99
  raise Error.resolution(local_errors.join("\n")) unless local_errors.empty?
101
100
 
102
101
  ResolveDeps.reject_dependency_cycles(packages)
102
+ source_revisions = git_sources.revisions
103
103
 
104
104
  ResolvedProject.new(
105
105
  manifest_path: manifest_path,
data/lib/pray/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pray
4
- VERSION = "1.17.0"
5
- GENERATED_BY = "pray 1.17.0"
4
+ VERSION = "1.18.1"
5
+ GENERATED_BY = "pray 1.18.1"
6
6
  end
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.17.0
4
+ version: 1.18.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov
@@ -136,7 +136,11 @@ files:
136
136
  - lib/pray/error.rb
137
137
  - lib/pray/format_manifest.rb
138
138
  - lib/pray/format_serialize.rb
139
+ - lib/pray/git_cache.rb
140
+ - lib/pray/git_clone.rb
141
+ - lib/pray/git_materialize.rb
139
142
  - lib/pray/git_refresh.rb
143
+ - lib/pray/git_run.rb
140
144
  - lib/pray/git_sources.rb
141
145
  - lib/pray/hashing.rb
142
146
  - lib/pray/http_body.rb
@@ -161,6 +165,7 @@ files:
161
165
  - lib/pray/project_context.rb
162
166
  - lib/pray/publish.rb
163
167
  - lib/pray/registry.rb
168
+ - lib/pray/registry_http_cache.rb
164
169
  - lib/pray/registry_install.rb
165
170
  - lib/pray/registry_integrity.rb
166
171
  - lib/pray/registry_paths.rb