pray-cli 1.12.1 → 1.13.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 58da4ada7990f04bc2acf64b33bca633828a586f0918e1e3ff00cc50ddaeb7c5
4
- data.tar.gz: ffc0855f9f9dc6466fe1260905b23d9ab74a5d96905562f8307f472dde851849
3
+ metadata.gz: 55a5d9fd2741f9ecd2d6d41c18acf6c7358b8bedb15e874d3686ce4ac88c7898
4
+ data.tar.gz: 489b7b073d8194c8f90fbbbab0b757804cc05a9ee309d7e596ce8a894ead1666
5
5
  SHA512:
6
- metadata.gz: 20cab97de54d753c4f7191f6a68469bbc7f4801e16999e7ca8ee13c0e1c5c27e53b8e0c436695a6f457cb169e7f4389d41407f9e56752cf7fed33c329b5fa94b
7
- data.tar.gz: 109496e4410f4bc43f63af51a73ec3439f87348a5c1e94dbed712799416d8e47286360f97bb50e37aaad096f8c0957fc3e2a9a26840167fefe7ea1d6dbb66abd
6
+ metadata.gz: 005a627e411946e40ae7b782feb62737b8d49a3eef411fda42ecd90c705c252414e504f231f94f24f5e687b035562233ba68f424abe49d2d541700b281493406
7
+ data.tar.gz: 80f998bf51a0fa32bf40d36b38f9a1101affdd988a0cb938e34b637a8225d3903d5c98bb217a88dab2ceb99849cd11957748d22d612e4f593f082f0d40122132
data/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 1.13.0 (2026-09-08)
6
+
7
+ - Record and verify package upstream pins in `.prayspec` and `Prayfile.lock`. Refuse path-fork updates until this CLI can refresh the path tree safely (RFC 0114).
8
+
5
9
  ## 1.12.1 (2026-09-08)
6
10
 
7
11
  - Refresh a locked git catalog during `pray install` when a newly declared package is missing from the pinned revision, and name that revision with `pray update` if it is still missing.
@@ -100,6 +100,8 @@ module Pray
100
100
  raise Error.manifest("package #{package} not found")
101
101
  end
102
102
  end
103
+ current = resolve_current_project(ResolveOptions.new(offline: offline))
104
+ Upstream.ensure_update_supported!(current.packages, package)
103
105
  install_command({locked: false, frozen: false, offline: offline, refresh: true})
104
106
  end
105
107
 
data/lib/pray/lockfile.rb CHANGED
@@ -8,14 +8,15 @@ module Pray
8
8
  LockSource = Struct.new(:name, :kind, :url, :revision, :host_key_fingerprint)
9
9
  LockedPackage = Struct.new(
10
10
  :name, :version, :source, :path, :tree_hash, :artifact_hash, :artifact,
11
- :exports, :dependencies, :signer_fingerprint
11
+ :exports, :dependencies, :signer_fingerprint, :upstream
12
12
  ) do
13
- def initialize(exports: [], dependencies: [], signer_fingerprint: nil, source: nil, **kwargs)
14
- super(**kwargs, exports: exports, dependencies: dependencies, signer_fingerprint: signer_fingerprint, source: source)
13
+ def initialize(exports: [], dependencies: [], signer_fingerprint: nil, source: nil, upstream: nil, **kwargs)
14
+ super(**kwargs, exports: exports, dependencies: dependencies, signer_fingerprint: signer_fingerprint, source: source, upstream: upstream)
15
15
  end
16
16
  end
17
17
 
18
18
  LockedTarget = Struct.new(:name, :outputs)
19
+ LockedUpstream = Struct.new(:name, :version, :source, :tree_hash, :artifact_hash, keyword_init: true)
19
20
  ManagedSpanRecord = Struct.new(
20
21
  :id, :target, :open_line, :close_line, :ideal_checksum, :package, :export,
21
22
  :source_checksum, :silenced
@@ -98,6 +99,7 @@ module Pray
98
99
  }
99
100
  hash["source"] = entry.source unless entry.source.nil?
100
101
  hash["signer_fingerprint"] = entry.signer_fingerprint if entry.signer_fingerprint
102
+ hash["upstream"] = Upstream.to_lock_hash(entry.upstream) if entry.upstream
101
103
  hash
102
104
  end
103
105
 
@@ -185,7 +187,8 @@ module Pray
185
187
  artifact: normalize_lockfile_artifact(project_root, package.artifact, package.root),
186
188
  exports: package.selected_exports,
187
189
  dependencies: package.spec.dependencies.map(&:name),
188
- signer_fingerprint: package.signer_fingerprint
190
+ signer_fingerprint: package.signer_fingerprint,
191
+ upstream: package.upstream
189
192
  )
190
193
  end,
191
194
  target: manifest_targets.map { |target| LockedTarget.new(name: target.name, outputs: target.outputs) },
@@ -224,7 +227,8 @@ module Pray
224
227
  artifact: entry["artifact"],
225
228
  exports: entry["exports"] || [],
226
229
  dependencies: entry["dependencies"] || [],
227
- signer_fingerprint: entry["signer_fingerprint"]
230
+ signer_fingerprint: entry["signer_fingerprint"],
231
+ upstream: Upstream.from_lock_hash(entry["upstream"])
228
232
  )
229
233
  end,
230
234
  target: Array(data["target"]).map { |entry| LockedTarget.new(name: entry["name"], outputs: entry["outputs"] || []) },
@@ -66,6 +66,25 @@ module Pray
66
66
  lines << "exports = #{format_string_array(entry.exports)}"
67
67
  lines << "dependencies = #{format_string_array(entry.dependencies)}"
68
68
  lines << "signer_fingerprint = #{format_string(entry.signer_fingerprint)}" if entry.signer_fingerprint
69
+ if entry.upstream
70
+ lines << ""
71
+ lines << "[package.upstream]"
72
+ append_scalars(
73
+ lines,
74
+ [
75
+ ["name", entry.upstream.name],
76
+ ["version", entry.upstream.version]
77
+ ].tap do |scalars|
78
+ scalars << ["source", entry.upstream.source] unless entry.upstream.source.nil?
79
+ scalars.concat(
80
+ [
81
+ ["tree_hash", entry.upstream.tree_hash],
82
+ ["artifact_hash", entry.upstream.artifact_hash]
83
+ ]
84
+ )
85
+ end
86
+ )
87
+ end
69
88
  lines
70
89
  end
71
90
 
@@ -9,17 +9,18 @@ module Pray
9
9
  PackageSkill = Struct.new(:path, :summary)
10
10
  PackageTemplate = Struct.new(:path, :summary)
11
11
  PackageDependency = Struct.new(:name, :constraint, :optional)
12
+ PackageUpstream = Struct.new(:name, :constraint, keyword_init: true)
12
13
 
13
14
  PackageSpec = Struct.new(
14
15
  :name, :version, :summary, :description, :authors, :license, :homepage,
15
16
  :source_code_uri, :changelog_uri, :prayfile_version, :files, :exports,
16
- :skills, :templates, :adapters, :targets, :dependencies, :metadata
17
+ :skills, :templates, :adapters, :targets, :dependencies, :metadata, :upstream
17
18
  ) do
18
19
  def initialize(
19
20
  name: "", version: "", summary: nil, description: nil, authors: [], license: nil,
20
21
  homepage: nil, source_code_uri: nil, changelog_uri: nil, prayfile_version: nil,
21
22
  files: [], exports: {}, skills: {}, templates: {}, adapters: {}, targets: [],
22
- dependencies: [], metadata: {}
23
+ dependencies: [], metadata: {}, upstream: nil
23
24
  )
24
25
  super
25
26
  end
@@ -90,6 +91,13 @@ module Pray
90
91
  )
91
92
  end
92
93
 
94
+ def parse_upstream(rest)
95
+ values, _keywords = parse_call(rest)
96
+ name = string_from_value(values.first)
97
+ constraint = values[1] ? string_from_value(values[1]) : "*"
98
+ PackageUpstream.new(name: name, constraint: constraint)
99
+ end
100
+
93
101
  def parse_exports(value)
94
102
  map = Literal.parse_literal_map(value)
95
103
  exports = {}
@@ -208,6 +216,10 @@ module Pray
208
216
  spec.dependencies << parse_dependency(Regexp.last_match(1), false)
209
217
  when /\Aspec\.add_optional_dependency (.+)\z/
210
218
  spec.dependencies << parse_dependency(Regexp.last_match(1), true)
219
+ when /\Aspec\.upstream (.+)\z/
220
+ raise Error.parse("prayspec", "upstream may only be declared once") if spec.upstream
221
+
222
+ spec.upstream = parse_upstream(Regexp.last_match(1))
211
223
  when /\Aspec\.(.+) = (.+)\z/
212
224
  apply_assignment(spec, Regexp.last_match(1).strip, Regexp.last_match(2).strip)
213
225
  else
data/lib/pray/resolve.rb CHANGED
@@ -13,7 +13,7 @@ module Pray
13
13
  ResolvedPackage = Struct.new(
14
14
  :declaration, :root, :spec, :tree_hash, :artifact_hash, :artifact,
15
15
  :selected_exports, :source_checksum, :export_bodies, :skill_files,
16
- :signer_fingerprint, :registry_latest_version
16
+ :signer_fingerprint, :registry_latest_version, :upstream
17
17
  )
18
18
 
19
19
  ResolvedLocalFile = Struct.new(
@@ -75,7 +75,7 @@ module Pray
75
75
  user_config,
76
76
  declaration,
77
77
  lockfile_hints,
78
- offline: options.offline
78
+ options: options
79
79
  )
80
80
  if seen[package.declaration.name]
81
81
  raise Error.resolution("duplicate package declaration: #{package.declaration.name}")
@@ -149,9 +149,10 @@ module Pray
149
149
  "Create the file or remove the entry from Prayfile, then run `pray install`."
150
150
  end
151
151
 
152
- def resolve_package(project_root, sources, git_sources, user_config, declaration, lockfile, offline: false)
152
+ def resolve_package(project_root, sources, git_sources, user_config, declaration, lockfile, offline: false, options: nil)
153
+ options ||= ResolveOptions.new(offline: offline)
153
154
  root, registry_latest_version = resolve_package_root_with_metadata(
154
- project_root, sources, git_sources, user_config, declaration, lockfile, offline: offline
155
+ project_root, sources, git_sources, user_config, declaration, lockfile, offline: options.offline
155
156
  )
156
157
  spec_path = find_prayspec_file(root)
157
158
  spec_text = File.read(spec_path)
@@ -185,7 +186,10 @@ module Pray
185
186
  export_bodies: export_bodies,
186
187
  skill_files: skill_files,
187
188
  signer_fingerprint: nil,
188
- registry_latest_version: registry_latest_version
189
+ registry_latest_version: registry_latest_version,
190
+ upstream: Upstream.lock_path(
191
+ project_root, sources, git_sources, user_config, declaration, spec, lockfile, options
192
+ )
189
193
  )
190
194
  end
191
195
 
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pray
4
+ module Upstream
5
+ module_function
6
+
7
+ def ensure_update_supported!(packages, selected = nil)
8
+ unsupported = packages.find do |package|
9
+ package.declaration.path && package.spec.upstream &&
10
+ (selected.nil? || package.declaration.name == selected)
11
+ end
12
+ return unless unsupported
13
+
14
+ raise Error.unsupported(
15
+ "this installation cannot refresh upstream package #{unsupported.declaration.name}; " \
16
+ "install pray with Cargo and retry"
17
+ )
18
+ end
19
+
20
+ def to_lock_hash(entry)
21
+ hash = {
22
+ "name" => entry.name,
23
+ "version" => entry.version,
24
+ "tree_hash" => entry.tree_hash,
25
+ "artifact_hash" => entry.artifact_hash
26
+ }
27
+ hash["source"] = entry.source unless entry.source.nil?
28
+ hash
29
+ end
30
+
31
+ def from_lock_hash(entry)
32
+ return nil if entry.nil?
33
+
34
+ LockedUpstream.new(
35
+ name: entry["name"],
36
+ version: entry["version"],
37
+ source: entry["source"],
38
+ tree_hash: entry["tree_hash"],
39
+ artifact_hash: entry["artifact_hash"]
40
+ )
41
+ end
42
+
43
+ def lock_path(project_root, sources, git_sources, user_config, declaration, spec, lockfile, options)
44
+ return nil unless spec.upstream
45
+ if spec.name == spec.upstream.name
46
+ raise Error.resolution("package #{spec.name} cannot use itself as upstream")
47
+ end
48
+ return nil unless declaration.path
49
+
50
+ refresh = options.ignore_locked_versions || options.unlocked_packages.include?(declaration.name)
51
+ locked = lockfile&.package&.find { |entry| entry.name == declaration.name }&.upstream
52
+ name, constraint, source = resolution_input(declaration, spec, sources, locked, refresh)
53
+ resolved = Resolve.resolve_package(
54
+ project_root,
55
+ sources,
56
+ git_sources,
57
+ user_config,
58
+ ManifestPackage.new(
59
+ name: name,
60
+ constraint: constraint,
61
+ source: source
62
+ ),
63
+ lockfile,
64
+ options: options
65
+ )
66
+ resolved_upstream = LockedUpstream.new(
67
+ name: name,
68
+ version: resolved.spec.version,
69
+ source: source,
70
+ tree_hash: resolved.tree_hash,
71
+ artifact_hash: resolved.artifact_hash
72
+ )
73
+ ensure_locked_match!(locked, resolved_upstream) if !refresh && locked
74
+ resolved_upstream
75
+ end
76
+
77
+ def resolution_input(declaration, spec, sources, locked, refresh)
78
+ if locked && !refresh
79
+ if locked.name != spec.upstream.name
80
+ raise Error.integrity(
81
+ "locked upstream name mismatch for #{declaration.name}: " \
82
+ "expected #{locked.name}, found #{spec.upstream.name}"
83
+ )
84
+ end
85
+ return [locked.name, "= #{locked.version}", locked.source]
86
+ end
87
+
88
+ name = spec.upstream.name
89
+ source = ResolveSource.implied_source_name(ManifestPackage.new(name: name), sources)
90
+ [name, spec.upstream.constraint, source]
91
+ end
92
+
93
+ def ensure_locked_match!(locked, resolved)
94
+ if locked.name != resolved.name || locked.version != resolved.version
95
+ raise Error.integrity("locked upstream identity mismatch")
96
+ end
97
+ raise Error.integrity("locked upstream source mismatch") if locked.source != resolved.source
98
+ raise Error.integrity("locked upstream tree hash mismatch") if locked.tree_hash != resolved.tree_hash
99
+ if locked.artifact_hash != resolved.artifact_hash
100
+ raise Error.integrity("locked upstream artifact hash mismatch")
101
+ end
102
+ end
103
+ end
104
+ end
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.12.1"
5
- GENERATED_BY = "pray 1.12.1"
4
+ VERSION = "1.13.0"
5
+ GENERATED_BY = "pray 1.13.0"
6
6
  end
data/lib/pray.rb CHANGED
@@ -24,6 +24,7 @@ require_relative "pray/resolve_exports"
24
24
  require_relative "pray/invocation"
25
25
  require_relative "pray/git_refresh"
26
26
  require_relative "pray/resolve"
27
+ require_relative "pray/upstream"
27
28
  require_relative "pray/compose_dest"
28
29
  require_relative "pray/render_patch"
29
30
  require_relative "pray/render"
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.12.1
4
+ version: 1.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov
@@ -180,6 +180,7 @@ files:
180
180
  - lib/pray/trust.rb
181
181
  - lib/pray/trust_feed.rb
182
182
  - lib/pray/trust_ops.rb
183
+ - lib/pray/upstream.rb
183
184
  - lib/pray/verify.rb
184
185
  - lib/pray/verify_position.rb
185
186
  - lib/pray/verify_provisioned.rb