pray-cli 1.9.2 → 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.
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pray
4
+ module RenderPatch
5
+ module_function
6
+
7
+ def patch_rendered_content(existing, fresh)
8
+ existing_segments = split_segments(existing)
9
+ fresh_segments = split_segments(fresh)
10
+ fresh_managed = fresh_segments.filter_map do |segment|
11
+ [segment[:id], segment[:body]] if segment[:kind] == :managed
12
+ end.to_h
13
+ overlap = existing_segments.any? do |segment|
14
+ segment[:kind] == :managed && fresh_managed.key?(segment[:id])
15
+ end
16
+ return fresh unless overlap
17
+
18
+ used = {}
19
+ output = existing_segments.map do |segment|
20
+ next segment[:text] if segment[:kind] == :text
21
+
22
+ used[segment[:id]] = true
23
+ managed_segment(segment[:id], fresh_managed.fetch(segment[:id], segment[:body]))
24
+ end.join
25
+ fresh_segments.each do |segment|
26
+ next unless segment[:kind] == :managed
27
+ next if used[segment[:id]]
28
+
29
+ output << managed_segment(segment[:id], segment[:body])
30
+ end
31
+ output.end_with?("\n") ? output : "#{output}\n"
32
+ end
33
+
34
+ def relocate_managed_spans(content, spans)
35
+ positions = marker_positions(lines_of(content))
36
+ spans.map do |span|
37
+ position = positions[span.id]
38
+ next span unless position
39
+
40
+ span.dup.tap do |relocated|
41
+ relocated.open_line = position[0]
42
+ relocated.close_line = position[1]
43
+ end
44
+ end
45
+ end
46
+
47
+ def split_segments(content)
48
+ lines = lines_of(content)
49
+ segments = []
50
+ text = +""
51
+ index = 0
52
+ while index < lines.length
53
+ identifier = marker_id(lines[index])
54
+ close = find_closing_marker(lines, index + 1, identifier) if identifier
55
+ if identifier && close
56
+ segments << {kind: :text, text: text} unless text.empty?
57
+ text = +""
58
+ body_lines = lines[(index + 1)...close]
59
+ body = body_lines.empty? ? "" : "#{body_lines.join("\n")}\n"
60
+ segments << {kind: :managed, id: identifier, body: body}
61
+ index = close + 1
62
+ else
63
+ text << "#{lines[index]}\n"
64
+ index += 1
65
+ end
66
+ end
67
+ segments << {kind: :text, text: text} unless text.empty?
68
+ segments
69
+ end
70
+
71
+ def lines_of(content)
72
+ content.lines(chomp: true)
73
+ end
74
+
75
+ def find_closing_marker(lines, start, identifier)
76
+ (start...lines.length).find { |index| marker_id(lines[index]) == identifier }
77
+ end
78
+
79
+ def marker_positions(lines)
80
+ positions = {}
81
+ active = nil
82
+ lines.each_with_index do |line, index|
83
+ identifier = marker_id(line)
84
+ next unless identifier&.match?(/\A[a-z0-9]+\z/)
85
+
86
+ if active.nil?
87
+ active = [identifier, index + 1]
88
+ elsif active[0] == identifier
89
+ positions[identifier] = [active[1], index + 1]
90
+ active = nil
91
+ end
92
+ end
93
+ positions
94
+ end
95
+
96
+ def marker_id(line)
97
+ match = line.strip.match(/\A<!-- pray:(.+) -->\z/)
98
+ identifier = match && match[1]
99
+ identifier unless identifier == "0 ignore-comments"
100
+ end
101
+
102
+ def managed_segment(identifier, body)
103
+ content = body.empty? ? "" : "#{body.sub(/\n+\z/, "")}\n"
104
+ "<!-- pray:#{identifier} -->\n#{content}<!-- pray:#{identifier} -->\n"
105
+ end
106
+ end
107
+ end
data/lib/pray/resolve.rb CHANGED
@@ -87,6 +87,7 @@ module Pray
87
87
  errors << "#{declaration.name}: #{error.message}"
88
88
  end
89
89
  raise Error.resolution(errors.join("\n")) unless errors.empty?
90
+ PackageSpec.warn_resolved_deprecations(packages)
90
91
 
91
92
  local_files = []
92
93
  local_errors = []
@@ -298,40 +299,11 @@ module Pray
298
299
  end
299
300
 
300
301
  def select_exports(declaration, spec)
301
- unless declaration.exports.empty?
302
- declaration.exports.each do |export|
303
- unless spec.exports.key?(export)
304
- raise Error.resolution("package #{declaration.name} does not export #{export}")
305
- end
306
- end
307
- return declaration.exports
308
- end
309
-
310
- roles = declaration.roles || []
311
- return spec.exports.keys.sort if roles.empty? && declaration.file.nil?
312
-
313
- effective_roles = roles.dup
314
- effective_roles << "file" if declaration.file && !effective_roles.include?("file")
302
+ ResolveExports.select_exports(declaration, spec)
303
+ end
315
304
 
316
- selected = []
317
- effective_roles.each do |role|
318
- compatible = spec.exports.filter_map do |name, export|
319
- name if Destination.export_kind_matches_role?(export.kind, role)
320
- end
321
- case compatible.length
322
- when 1
323
- selected << compatible.first unless selected.include?(compatible.first)
324
- when 0
325
- raise Error.resolution(
326
- "package #{declaration.name} has no export compatible with #{role}"
327
- )
328
- else
329
- raise Error.resolution(
330
- "package #{declaration.name} has multiple exports compatible with #{role}; set export: \"name\""
331
- )
332
- end
333
- end
334
- selected
305
+ def load_export_bodies(file_bytes, spec, selected_exports)
306
+ ResolveExports.load_export_bodies(file_bytes, spec, selected_exports)
335
307
  end
336
308
 
337
309
  def load_package_file_bytes(root, spec)
@@ -346,21 +318,6 @@ module Pray
346
318
  file_bytes
347
319
  end
348
320
 
349
- def load_export_bodies(file_bytes, spec, selected_exports)
350
- export_bodies = {}
351
- selected_exports.each do |export_name|
352
- entry = spec.exports[export_name]
353
- raise Error.resolution("package #{spec.name} is missing export #{export_name}") unless entry
354
- next unless entry.kind == "fragment"
355
-
356
- bytes = file_bytes[entry.path]
357
- raise Error.integrity("package file missing for export #{export_name}: #{entry.path}") unless bytes
358
-
359
- export_bodies[export_name] = Hashing.normalize_line_endings(bytes.force_encoding(Encoding::UTF_8))
360
- end
361
- export_bodies
362
- end
363
-
364
321
  def build_skill_file_index(spec)
365
322
  index = {}
366
323
  spec.exports.each do |export_name, export|
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pray
4
+ module ResolveExports
5
+ module_function
6
+
7
+ def select_exports(declaration, spec)
8
+ unless declaration.exports.empty?
9
+ declaration.exports.each do |export|
10
+ unless spec.exports.key?(export)
11
+ raise Error.resolution("package #{declaration.name} does not export #{export}")
12
+ end
13
+ end
14
+ return declaration.exports
15
+ end
16
+
17
+ roles = declaration.roles || []
18
+ return spec.exports.keys.sort if roles.empty? && declaration.file.nil?
19
+
20
+ effective_roles = roles.dup
21
+ effective_roles << "file" if declaration.file && !effective_roles.include?("file")
22
+
23
+ selected = []
24
+ effective_roles.each do |role|
25
+ compatible = if role == "fragment"
26
+ fragment_role_exports(spec)
27
+ else
28
+ spec.exports.filter_map do |name, export|
29
+ name if Destination.export_kind_matches_role?(export.kind, role)
30
+ end
31
+ end
32
+ case compatible.length
33
+ when 1
34
+ selected << compatible.first unless selected.include?(compatible.first)
35
+ when 0
36
+ raise Error.resolution(
37
+ "package #{declaration.name} has no export compatible with #{role}"
38
+ )
39
+ else
40
+ raise Error.resolution(
41
+ "package #{declaration.name} has multiple exports compatible with #{role}; set export: \"name\""
42
+ )
43
+ end
44
+ end
45
+ selected
46
+ end
47
+
48
+ def load_export_bodies(file_bytes, spec, selected_exports)
49
+ export_bodies = {}
50
+ selected_exports.each do |export_name|
51
+ entry = spec.exports[export_name]
52
+ raise Error.resolution("package #{spec.name} is missing export #{export_name}") unless entry
53
+ next unless %w[fragment file].include?(entry.kind)
54
+
55
+ bytes = file_bytes[entry.path]
56
+ unless bytes
57
+ raise Error.integrity("package file missing for export #{export_name}: #{entry.path}")
58
+ end
59
+
60
+ text = bytes.dup.force_encoding(Encoding::UTF_8)
61
+ unless text.valid_encoding?
62
+ if entry.kind == "fragment"
63
+ raise Error.integrity("package file is not valid utf-8 for export #{export_name}")
64
+ end
65
+ next
66
+ end
67
+ export_bodies[export_name] = Hashing.normalize_line_endings(text)
68
+ end
69
+ export_bodies
70
+ end
71
+
72
+ def fragment_role_exports(spec)
73
+ fragments = spec.exports.filter_map { |name, export| name if export.kind == "fragment" }
74
+ return fragments unless fragments.empty?
75
+
76
+ spec.exports.filter_map { |name, export| name if export.kind == "file" }
77
+ end
78
+ end
79
+ end
@@ -102,7 +102,10 @@ module Pray
102
102
  end
103
103
 
104
104
  def verify_checksum!(header)
105
- stored = parse_octal(header.byteslice(148, 6), "checksum")
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/verify.rb CHANGED
@@ -165,6 +165,8 @@ module Pray
165
165
  )
166
166
  end
167
167
 
168
+ VerifyProvisioned.push_findings(project, report)
169
+
168
170
  [report, rendered_targets, fresh_targets]
169
171
  end
170
172
 
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pray
4
+ module VerifyProvisioned
5
+ module_function
6
+
7
+ def push_findings(project, report)
8
+ push_exclusive_file_export_findings(project, report)
9
+ Render.planned_provisioned_files(project).each do |file|
10
+ path_text = file.path.to_s.tr("\\", "/")
11
+ absolute = File.join(project.project_root, file.path)
12
+ if File.symlink?(absolute)
13
+ report.findings << VerificationFinding.new(
14
+ kind: "verify_error",
15
+ message: "Provisioned file `#{path_text}` is a symbolic link. Remove the link or choose another destination."
16
+ )
17
+ next
18
+ end
19
+ unless File.file?(absolute)
20
+ report.findings << VerificationFinding.new(
21
+ kind: "verify_error",
22
+ message: "Provisioned file `#{path_text}` from `#{file.package}` is missing. Run `pray install` to materialize it."
23
+ )
24
+ next
25
+ end
26
+ destination_bytes = File.binread(absolute)
27
+ expected_bytes = Render.expected_provisioned_bytes(file.source, project.manifest.symbols || {})
28
+ next if Hashing.sha256_prefixed(destination_bytes) == Hashing.sha256_prefixed(expected_bytes.b)
29
+
30
+ report.findings << VerificationFinding.new(
31
+ kind: "package_integrity",
32
+ message: "Provisioned file `#{path_text}` no longer matches package `#{file.package}`. Run `pray install` to restore it."
33
+ )
34
+ end
35
+ end
36
+
37
+ def push_exclusive_file_export_findings(project, report)
38
+ project.packages.each do |package|
39
+ destination = package.declaration.file
40
+ next unless destination
41
+
42
+ has_file_export = package.selected_exports.any? do |name|
43
+ export = package.spec.exports[name]
44
+ export && export.kind == "file"
45
+ end
46
+ next if has_file_export
47
+
48
+ report.findings << VerificationFinding.new(
49
+ kind: "verify_error",
50
+ message: "Package `#{package.declaration.name}` declares file: \"#{destination}\" but has no selected file export."
51
+ )
52
+ end
53
+ end
54
+ end
55
+ 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.9.2"
5
- GENERATED_BY = "pray 1.9.2"
4
+ VERSION = "1.11.0"
5
+ GENERATED_BY = "pray 1.11.0"
6
6
  end
data/lib/pray.rb CHANGED
@@ -20,10 +20,16 @@ require_relative "pray/project_context"
20
20
  require_relative "pray/environment"
21
21
  require_relative "pray/resolve_context"
22
22
  require_relative "pray/resolve_source"
23
+ require_relative "pray/resolve_exports"
23
24
  require_relative "pray/invocation"
24
25
  require_relative "pray/resolve"
26
+ require_relative "pray/compose_dest"
27
+ require_relative "pray/render_patch"
25
28
  require_relative "pray/render"
29
+ require_relative "pray/render_dest"
30
+ require_relative "pray/render_layout"
26
31
  require_relative "pray/verify_position"
32
+ require_relative "pray/verify_provisioned"
27
33
  require_relative "pray/verify"
28
34
  require_relative "pray/resource_limits"
29
35
  require_relative "pray/http_body"
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.9.2
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
@@ -113,6 +114,7 @@ files:
113
114
  - lib/pray/cli/parse_auth.rb
114
115
  - lib/pray/cli/parse_trust.rb
115
116
  - lib/pray/cli/suggest.rb
117
+ - lib/pray/compose_dest.rb
116
118
  - lib/pray/confess.rb
117
119
  - lib/pray/config.rb
118
120
  - lib/pray/constraint.rb
@@ -146,8 +148,13 @@ files:
146
148
  - lib/pray/registry_install.rb
147
149
  - lib/pray/registry_integrity.rb
148
150
  - lib/pray/render.rb
151
+ - lib/pray/render_content.rb
152
+ - lib/pray/render_dest.rb
153
+ - lib/pray/render_layout.rb
154
+ - lib/pray/render_patch.rb
149
155
  - lib/pray/resolve.rb
150
156
  - lib/pray/resolve_context.rb
157
+ - lib/pray/resolve_exports.rb
151
158
  - lib/pray/resolve_source.rb
152
159
  - lib/pray/resource_limits.rb
153
160
  - lib/pray/serve.rb
@@ -164,6 +171,7 @@ files:
164
171
  - lib/pray/trust_ops.rb
165
172
  - lib/pray/verify.rb
166
173
  - lib/pray/verify_position.rb
174
+ - lib/pray/verify_provisioned.rb
167
175
  - lib/pray/version.rb
168
176
  - pray-cli.gemspec
169
177
  homepage: https://pray.kisko.dev