pray-cli 1.6.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 +7 -0
- data/CHANGELOG.md +47 -0
- data/LICENSE.md +21 -0
- data/README.md +125 -0
- data/SECURITY.md +28 -0
- data/bin/polyrun +108 -0
- data/bin/pray +26 -0
- data/lib/pray/archive.rb +68 -0
- data/lib/pray/auth_client.rb +97 -0
- data/lib/pray/cli/commands/auth.rb +42 -0
- data/lib/pray/cli/commands/distribution.rb +17 -0
- data/lib/pray/cli/commands/init.rb +62 -0
- data/lib/pray/cli/commands/meta.rb +15 -0
- data/lib/pray/cli/commands/packages.rb +107 -0
- data/lib/pray/cli/commands/trust.rb +73 -0
- data/lib/pray/cli/commands/workflow.rb +126 -0
- data/lib/pray/cli/help.rb +168 -0
- data/lib/pray/cli/helpers.rb +166 -0
- data/lib/pray/cli/parse.rb +136 -0
- data/lib/pray/cli/parse_auth.rb +91 -0
- data/lib/pray/cli/parse_trust.rb +152 -0
- data/lib/pray/cli/suggest.rb +57 -0
- data/lib/pray/cli.rb +117 -0
- data/lib/pray/confess.rb +113 -0
- data/lib/pray/config.rb +52 -0
- data/lib/pray/constraint.rb +80 -0
- data/lib/pray/destination.rb +158 -0
- data/lib/pray/dotenv.rb +46 -0
- data/lib/pray/environment.rb +41 -0
- data/lib/pray/error.rb +55 -0
- data/lib/pray/format_manifest.rb +255 -0
- data/lib/pray/format_serialize.rb +135 -0
- data/lib/pray/git_sources.rb +228 -0
- data/lib/pray/hashing.rb +59 -0
- data/lib/pray/invocation.rb +110 -0
- data/lib/pray/literal.rb +382 -0
- data/lib/pray/lockfile.rb +259 -0
- data/lib/pray/lockfile_serialize.rb +125 -0
- data/lib/pray/manifest.rb +126 -0
- data/lib/pray/manifest_formatter.rb +56 -0
- data/lib/pray/manifest_json.rb +128 -0
- data/lib/pray/manifest_parser.rb +152 -0
- data/lib/pray/manifest_parser_blocks.rb +216 -0
- data/lib/pray/manifest_parser_helpers.rb +208 -0
- data/lib/pray/materialize.rb +120 -0
- data/lib/pray/package_spec.rb +245 -0
- data/lib/pray/path_safety.rb +41 -0
- data/lib/pray/plan.rb +83 -0
- data/lib/pray/project_context.rb +67 -0
- data/lib/pray/publish.rb +162 -0
- data/lib/pray/registry.rb +355 -0
- data/lib/pray/render.rb +360 -0
- data/lib/pray/resolve.rb +396 -0
- data/lib/pray/resolve_context.rb +19 -0
- data/lib/pray/serve.rb +130 -0
- data/lib/pray/serve_federation.rb +109 -0
- data/lib/pray/session.rb +90 -0
- data/lib/pray/ssh_agent.rb +115 -0
- data/lib/pray/statement_surface.rb +262 -0
- data/lib/pray/substitute.rb +50 -0
- data/lib/pray/sync.rb +219 -0
- data/lib/pray/terminal.rb +30 -0
- data/lib/pray/trust.rb +201 -0
- data/lib/pray/trust_feed.rb +110 -0
- data/lib/pray/trust_ops.rb +202 -0
- data/lib/pray/verify.rb +257 -0
- data/lib/pray/version.rb +6 -0
- data/lib/pray-cli.rb +4 -0
- data/lib/pray.rb +46 -0
- data/pray-cli.gemspec +48 -0
- metadata +187 -0
data/lib/pray/literal.rb
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module Literal
|
|
5
|
+
LiteralValue = Struct.new(:kind, :value) do
|
|
6
|
+
def string? = kind == :string || kind == :symbol
|
|
7
|
+
def as_string = string? ? value : nil
|
|
8
|
+
def as_bool = (kind == :bool) ? value : nil
|
|
9
|
+
def as_integer = (kind == :integer) ? value : nil
|
|
10
|
+
def as_array = (kind == :array) ? value : nil
|
|
11
|
+
def as_map = (kind == :map) ? value : nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def split_top_level(input, separator)
|
|
17
|
+
output = []
|
|
18
|
+
start = 0
|
|
19
|
+
depth = 0
|
|
20
|
+
quote = nil
|
|
21
|
+
escaped = false
|
|
22
|
+
|
|
23
|
+
input.each_char.with_index do |character, index|
|
|
24
|
+
if quote
|
|
25
|
+
if escaped
|
|
26
|
+
escaped = false
|
|
27
|
+
elsif character == "\\"
|
|
28
|
+
escaped = true
|
|
29
|
+
elsif character == quote
|
|
30
|
+
quote = nil
|
|
31
|
+
end
|
|
32
|
+
next
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
case character
|
|
36
|
+
when '"', "'"
|
|
37
|
+
quote = character
|
|
38
|
+
when "[", "{", "("
|
|
39
|
+
depth += 1
|
|
40
|
+
when "]", "}", ")"
|
|
41
|
+
depth -= 1
|
|
42
|
+
else
|
|
43
|
+
if character == separator && depth.zero?
|
|
44
|
+
segment = input[start...index].strip
|
|
45
|
+
output << segment unless segment.empty?
|
|
46
|
+
start = index + 1
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
tail = input[start..].strip
|
|
52
|
+
output << tail unless tail.empty?
|
|
53
|
+
output
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def find_top_level(input, token)
|
|
57
|
+
depth = 0
|
|
58
|
+
quote = nil
|
|
59
|
+
escaped = false
|
|
60
|
+
index = 0
|
|
61
|
+
|
|
62
|
+
while index < input.length
|
|
63
|
+
character = input[index]
|
|
64
|
+
if quote
|
|
65
|
+
if escaped
|
|
66
|
+
escaped = false
|
|
67
|
+
elsif character == "\\"
|
|
68
|
+
escaped = true
|
|
69
|
+
elsif character == quote
|
|
70
|
+
quote = nil
|
|
71
|
+
end
|
|
72
|
+
index += 1
|
|
73
|
+
next
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
case character
|
|
77
|
+
when '"', "'"
|
|
78
|
+
quote = character
|
|
79
|
+
when "[", "{", "("
|
|
80
|
+
depth += 1
|
|
81
|
+
when "]", "}", ")"
|
|
82
|
+
depth -= 1
|
|
83
|
+
else
|
|
84
|
+
return index if depth.zero? && input[index..].start_with?(token)
|
|
85
|
+
end
|
|
86
|
+
index += 1
|
|
87
|
+
end
|
|
88
|
+
nil
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def is_balanced?(input)
|
|
92
|
+
depth = 0
|
|
93
|
+
quote = nil
|
|
94
|
+
escaped = false
|
|
95
|
+
|
|
96
|
+
input.each_char do |character|
|
|
97
|
+
if quote
|
|
98
|
+
if escaped
|
|
99
|
+
escaped = false
|
|
100
|
+
elsif character == "\\"
|
|
101
|
+
escaped = true
|
|
102
|
+
elsif character == quote
|
|
103
|
+
quote = nil
|
|
104
|
+
end
|
|
105
|
+
next
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
case character
|
|
109
|
+
when '"', "'"
|
|
110
|
+
quote = character
|
|
111
|
+
when "[", "{", "("
|
|
112
|
+
depth += 1
|
|
113
|
+
when "]", "}", ")"
|
|
114
|
+
depth -= 1
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
depth.zero? && quote.nil?
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def parse_literal(input)
|
|
122
|
+
parser = Parser.new(input)
|
|
123
|
+
value = parser.parse_value
|
|
124
|
+
parser.skip_whitespace
|
|
125
|
+
unless parser.finished?
|
|
126
|
+
raise Error.parse("literal", "unexpected trailing input near #{parser.remaining.inspect}")
|
|
127
|
+
end
|
|
128
|
+
value
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def parse_literal_map(input)
|
|
132
|
+
value = parse_literal(input)
|
|
133
|
+
raise Error.parse("literal", "expected map literal, found #{value.inspect}") unless value.kind == :map
|
|
134
|
+
|
|
135
|
+
value.value
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def parse_literal_array(input)
|
|
139
|
+
value = parse_literal(input)
|
|
140
|
+
raise Error.parse("literal", "expected array literal, found #{value.inspect}") unless value.kind == :array
|
|
141
|
+
|
|
142
|
+
value.value
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def prepare_parser_lines(text)
|
|
146
|
+
text.lines.map { |line| prepare_parser_line(line) }
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def prepare_parser_line(line)
|
|
150
|
+
strip_line_comment(line).rstrip
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def strip_line_comment(line)
|
|
154
|
+
quote = nil
|
|
155
|
+
escaped = false
|
|
156
|
+
line.each_char.with_index do |character, index|
|
|
157
|
+
if quote
|
|
158
|
+
if escaped
|
|
159
|
+
escaped = false
|
|
160
|
+
elsif character == "\\"
|
|
161
|
+
escaped = true
|
|
162
|
+
elsif character == quote
|
|
163
|
+
quote = nil
|
|
164
|
+
end
|
|
165
|
+
next
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
case character
|
|
169
|
+
when '"', "'"
|
|
170
|
+
quote = character
|
|
171
|
+
when "#"
|
|
172
|
+
return line[0...index]
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
line
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
class Parser
|
|
179
|
+
def initialize(input)
|
|
180
|
+
@input = input
|
|
181
|
+
@cursor = 0
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def finished? = @cursor >= @input.length
|
|
185
|
+
def remaining = @input[@cursor..]
|
|
186
|
+
|
|
187
|
+
def skip_whitespace
|
|
188
|
+
while (character = peek) && character.match?(/\s/)
|
|
189
|
+
@cursor += 1
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def peek = remaining[0]
|
|
194
|
+
|
|
195
|
+
def next_character
|
|
196
|
+
character = peek
|
|
197
|
+
@cursor += 1 if character
|
|
198
|
+
character
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def parse_value
|
|
202
|
+
skip_whitespace
|
|
203
|
+
case peek
|
|
204
|
+
when '"', "'"
|
|
205
|
+
parse_string
|
|
206
|
+
when ":"
|
|
207
|
+
parse_symbol
|
|
208
|
+
when "["
|
|
209
|
+
parse_array
|
|
210
|
+
when "{"
|
|
211
|
+
parse_map
|
|
212
|
+
when "-", "0".."9"
|
|
213
|
+
parse_integer_or_identifier
|
|
214
|
+
when nil
|
|
215
|
+
raise Error.parse("literal", "unexpected end of input")
|
|
216
|
+
else
|
|
217
|
+
parse_identifier
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def parse_string
|
|
222
|
+
quote = next_character
|
|
223
|
+
output = +""
|
|
224
|
+
escaped = false
|
|
225
|
+
while (character = next_character)
|
|
226
|
+
if escaped
|
|
227
|
+
output << case character
|
|
228
|
+
when "n" then "\n"
|
|
229
|
+
when "r" then "\r"
|
|
230
|
+
when "t" then "\t"
|
|
231
|
+
when "\\" then "\\"
|
|
232
|
+
when "\"", "'" then character
|
|
233
|
+
else character
|
|
234
|
+
end
|
|
235
|
+
escaped = false
|
|
236
|
+
next
|
|
237
|
+
end
|
|
238
|
+
if character == "\\"
|
|
239
|
+
escaped = true
|
|
240
|
+
next
|
|
241
|
+
end
|
|
242
|
+
if character == quote
|
|
243
|
+
return LiteralValue.new(kind: :string, value: output)
|
|
244
|
+
end
|
|
245
|
+
output << character
|
|
246
|
+
end
|
|
247
|
+
raise Error.parse("literal", "unterminated string literal")
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def parse_symbol
|
|
251
|
+
next_character
|
|
252
|
+
output = +""
|
|
253
|
+
while (character = peek) && character.match?(%r{[[:alnum:]_\-.\\/]})
|
|
254
|
+
output << character
|
|
255
|
+
next_character
|
|
256
|
+
end
|
|
257
|
+
raise Error.parse("literal", "empty symbol") if output.empty?
|
|
258
|
+
|
|
259
|
+
LiteralValue.new(kind: :symbol, value: output)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def parse_array
|
|
263
|
+
next_character
|
|
264
|
+
values = []
|
|
265
|
+
loop do
|
|
266
|
+
skip_whitespace
|
|
267
|
+
break if peek == "]" && next_character
|
|
268
|
+
|
|
269
|
+
values << parse_value
|
|
270
|
+
skip_whitespace
|
|
271
|
+
case peek
|
|
272
|
+
when ","
|
|
273
|
+
next_character
|
|
274
|
+
when "]"
|
|
275
|
+
next_character
|
|
276
|
+
break
|
|
277
|
+
else
|
|
278
|
+
raise Error.parse("literal", "expected ',' or ']'")
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
LiteralValue.new(kind: :array, value: values)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def parse_map
|
|
285
|
+
next_character
|
|
286
|
+
entries = {}
|
|
287
|
+
loop do
|
|
288
|
+
skip_whitespace
|
|
289
|
+
break if peek == "}" && next_character
|
|
290
|
+
|
|
291
|
+
key = parse_map_key
|
|
292
|
+
skip_whitespace
|
|
293
|
+
if remaining.start_with?("=>")
|
|
294
|
+
@cursor += 2
|
|
295
|
+
elsif peek == ":"
|
|
296
|
+
next_character
|
|
297
|
+
else
|
|
298
|
+
raise Error.parse("literal", "expected ':' or '=>' after map key")
|
|
299
|
+
end
|
|
300
|
+
entries[key] = parse_value
|
|
301
|
+
skip_whitespace
|
|
302
|
+
case peek
|
|
303
|
+
when ","
|
|
304
|
+
next_character
|
|
305
|
+
when "}"
|
|
306
|
+
next_character
|
|
307
|
+
break
|
|
308
|
+
else
|
|
309
|
+
raise Error.parse("literal", "expected ',' or '}'")
|
|
310
|
+
end
|
|
311
|
+
end
|
|
312
|
+
LiteralValue.new(kind: :map, value: entries)
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def parse_map_key
|
|
316
|
+
skip_whitespace
|
|
317
|
+
case peek
|
|
318
|
+
when '"', "'"
|
|
319
|
+
value = parse_string
|
|
320
|
+
value.value
|
|
321
|
+
when ":"
|
|
322
|
+
parse_symbol.value
|
|
323
|
+
when "a".."z", "A".."Z", "_"
|
|
324
|
+
parse_identifier_name
|
|
325
|
+
else
|
|
326
|
+
raise Error.parse("literal", "invalid map key")
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def parse_integer_or_identifier
|
|
331
|
+
start = @cursor
|
|
332
|
+
next_character if peek == "-"
|
|
333
|
+
while peek&.match?(/[0-9_]/)
|
|
334
|
+
next_character
|
|
335
|
+
end
|
|
336
|
+
if peek == "."
|
|
337
|
+
return parse_identifier_from(start)
|
|
338
|
+
end
|
|
339
|
+
text = @input[start...@cursor].delete("_")
|
|
340
|
+
LiteralValue.new(kind: :integer, value: Integer(text))
|
|
341
|
+
rescue ArgumentError => error
|
|
342
|
+
raise Error.parse("literal", error.message)
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
def parse_identifier
|
|
346
|
+
identifier = parse_identifier_name
|
|
347
|
+
case identifier
|
|
348
|
+
when "true" then LiteralValue.new(kind: :bool, value: true)
|
|
349
|
+
when "false" then LiteralValue.new(kind: :bool, value: false)
|
|
350
|
+
when "nil" then LiteralValue.new(kind: :null, value: nil)
|
|
351
|
+
else LiteralValue.new(kind: :string, value: identifier)
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def parse_identifier_name
|
|
356
|
+
raise Error.parse("literal", "expected identifier") unless identifier_start?(peek)
|
|
357
|
+
|
|
358
|
+
start = @cursor
|
|
359
|
+
next_character
|
|
360
|
+
while peek && identifier_continue?(peek)
|
|
361
|
+
next_character
|
|
362
|
+
end
|
|
363
|
+
@input[start...@cursor]
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def parse_identifier_from(start)
|
|
367
|
+
while peek && (identifier_continue?(peek) || peek == ".")
|
|
368
|
+
next_character
|
|
369
|
+
end
|
|
370
|
+
LiteralValue.new(kind: :string, value: @input[start...@cursor])
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def identifier_start?(character)
|
|
374
|
+
character && (character.match?(/[[:alpha:]]/) || character == "_")
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def identifier_continue?(character)
|
|
378
|
+
character.match?(%r{[[:alnum:]_\-.\\/]})
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
end
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "toml-rb"
|
|
4
|
+
|
|
5
|
+
require_relative "lockfile_serialize"
|
|
6
|
+
|
|
7
|
+
module Pray
|
|
8
|
+
LockSource = Struct.new(:name, :kind, :url, :revision, :host_key_fingerprint)
|
|
9
|
+
LockedPackage = Struct.new(
|
|
10
|
+
:name, :version, :source, :path, :tree_hash, :artifact_hash, :artifact,
|
|
11
|
+
:exports, :dependencies, :signer_fingerprint
|
|
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)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
LockedTarget = Struct.new(:name, :outputs)
|
|
19
|
+
ManagedSpanRecord = Struct.new(
|
|
20
|
+
:id, :target, :open_line, :close_line, :ideal_checksum, :package, :export,
|
|
21
|
+
:source_checksum, :silenced
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
Lockfile = Struct.new(
|
|
25
|
+
:prayfile_lock, :spec, :generated_by, :manifest_hash, :environment, :source, :package,
|
|
26
|
+
:target, :managed_span
|
|
27
|
+
) do
|
|
28
|
+
def initialize(
|
|
29
|
+
prayfile_lock: "1", spec: "0.1", generated_by: Pray::GENERATED_BY,
|
|
30
|
+
manifest_hash: "", environment: nil, source: [], package: [], target: [], managed_span: []
|
|
31
|
+
)
|
|
32
|
+
super
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def canonicalized
|
|
36
|
+
dup.tap do |copy|
|
|
37
|
+
copy.source = source.sort_by(&:name)
|
|
38
|
+
copy.package = package.sort_by { |entry| [entry.name, entry.source.to_s, entry.version] }
|
|
39
|
+
copy.target = target.sort_by(&:name)
|
|
40
|
+
copy.managed_span = managed_span.sort_by { |span| [span.target, span.open_line, span.id] }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def serialized
|
|
45
|
+
LockfileIO.lockfile_to_toml(canonicalized)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def file_hash
|
|
49
|
+
Hashing.sha256_prefixed(serialized)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def equivalent_to?(other)
|
|
53
|
+
canonicalized == other.canonicalized
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
module LockfileIO
|
|
58
|
+
module_function
|
|
59
|
+
|
|
60
|
+
def lockfile_to_toml(lockfile)
|
|
61
|
+
LockfileSerialize.lockfile_to_toml(lockfile)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def lockfile_to_hash(lockfile)
|
|
65
|
+
{
|
|
66
|
+
"prayfile_lock" => lockfile.prayfile_lock,
|
|
67
|
+
"spec" => lockfile.spec,
|
|
68
|
+
"generated_by" => lockfile.generated_by,
|
|
69
|
+
"manifest_hash" => lockfile.manifest_hash,
|
|
70
|
+
"source" => lockfile.source.map { |entry| source_to_hash(entry) },
|
|
71
|
+
"package" => lockfile.package.map { |entry| package_to_hash(entry) },
|
|
72
|
+
"target" => lockfile.target.map { |entry| {"name" => entry.name, "outputs" => entry.outputs} },
|
|
73
|
+
"managed_span" => lockfile.managed_span.map { |entry| managed_span_to_hash(entry) }
|
|
74
|
+
}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def source_to_hash(entry)
|
|
78
|
+
hash = {"name" => entry.name, "kind" => entry.kind, "url" => entry.url}
|
|
79
|
+
hash["revision"] = entry.revision if entry.revision
|
|
80
|
+
hash["host_key_fingerprint"] = entry.host_key_fingerprint if entry.host_key_fingerprint
|
|
81
|
+
hash
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def package_to_hash(entry)
|
|
85
|
+
hash = {
|
|
86
|
+
"name" => entry.name,
|
|
87
|
+
"version" => entry.version,
|
|
88
|
+
"path" => entry.path,
|
|
89
|
+
"tree_hash" => entry.tree_hash,
|
|
90
|
+
"artifact_hash" => entry.artifact_hash,
|
|
91
|
+
"artifact" => entry.artifact,
|
|
92
|
+
"exports" => entry.exports,
|
|
93
|
+
"dependencies" => entry.dependencies
|
|
94
|
+
}
|
|
95
|
+
hash["source"] = entry.source unless entry.source.nil?
|
|
96
|
+
hash["signer_fingerprint"] = entry.signer_fingerprint if entry.signer_fingerprint
|
|
97
|
+
hash
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def managed_span_to_hash(entry)
|
|
101
|
+
{
|
|
102
|
+
"id" => entry.id,
|
|
103
|
+
"target" => entry.target,
|
|
104
|
+
"open_line" => entry.open_line,
|
|
105
|
+
"close_line" => entry.close_line,
|
|
106
|
+
"ideal_checksum" => entry.ideal_checksum,
|
|
107
|
+
"package" => entry.package,
|
|
108
|
+
"export" => entry.export,
|
|
109
|
+
"source_checksum" => entry.source_checksum,
|
|
110
|
+
"silenced" => entry.silenced
|
|
111
|
+
}
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def parse_lockfile(text)
|
|
115
|
+
data = TomlRB.parse(text)
|
|
116
|
+
from_hash(data)
|
|
117
|
+
rescue TomlRB::ParseError => error
|
|
118
|
+
raise Error.parse("lockfile", error.message)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def read_lockfile(path)
|
|
122
|
+
parse_lockfile(File.read(path))
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def serialize_lockfile(lockfile)
|
|
126
|
+
lockfile_to_toml(lockfile.canonicalized)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def lockfile_hash(lockfile)
|
|
130
|
+
Hashing.sha256_prefixed(serialize_lockfile(lockfile))
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def write_lockfile(path, lockfile)
|
|
134
|
+
File.write(path, serialize_lockfile(lockfile))
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def write_lockfile_if_changed(path, lockfile)
|
|
138
|
+
serialized = serialize_lockfile(lockfile)
|
|
139
|
+
if File.exist?(path) && File.binread(path) == serialized
|
|
140
|
+
return
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
File.write(path, serialized)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def lockfiles_equivalent?(left, right)
|
|
147
|
+
left.equivalent_to?(right)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def build_lockfile(manifest_hash, environment, project_root, manifest_sources, manifest_targets, rendered, packages, source_revisions, source_host_keys)
|
|
151
|
+
Lockfile.new(
|
|
152
|
+
manifest_hash: manifest_hash,
|
|
153
|
+
environment: environment,
|
|
154
|
+
source: manifest_sources.map do |source|
|
|
155
|
+
LockSource.new(
|
|
156
|
+
name: source.name,
|
|
157
|
+
kind: source.kind,
|
|
158
|
+
url: source.url,
|
|
159
|
+
revision: source_revisions[source.name],
|
|
160
|
+
host_key_fingerprint: source_host_keys[source.name]
|
|
161
|
+
)
|
|
162
|
+
end,
|
|
163
|
+
package: packages.map do |package|
|
|
164
|
+
LockedPackage.new(
|
|
165
|
+
name: package.declaration.name,
|
|
166
|
+
version: package.spec.version,
|
|
167
|
+
source: package.declaration.source,
|
|
168
|
+
path: relative_lockfile_path(project_root, package.root),
|
|
169
|
+
tree_hash: package.tree_hash,
|
|
170
|
+
artifact_hash: package.artifact_hash,
|
|
171
|
+
artifact: normalize_lockfile_artifact(project_root, package.artifact, package.root),
|
|
172
|
+
exports: package.selected_exports,
|
|
173
|
+
dependencies: package.spec.dependencies.map(&:name),
|
|
174
|
+
signer_fingerprint: package.signer_fingerprint
|
|
175
|
+
)
|
|
176
|
+
end,
|
|
177
|
+
target: manifest_targets.map { |target| LockedTarget.new(name: target.name, outputs: target.outputs) },
|
|
178
|
+
managed_span: rendered.flat_map(&:managed_spans)
|
|
179
|
+
).canonicalized
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def from_hash(data)
|
|
183
|
+
Lockfile.new(
|
|
184
|
+
prayfile_lock: data["prayfile_lock"],
|
|
185
|
+
spec: data["spec"],
|
|
186
|
+
generated_by: data["generated_by"],
|
|
187
|
+
manifest_hash: data["manifest_hash"],
|
|
188
|
+
environment: data["environment"],
|
|
189
|
+
source: Array(data["source"]).map do |entry|
|
|
190
|
+
LockSource.new(
|
|
191
|
+
name: entry["name"],
|
|
192
|
+
kind: entry["kind"],
|
|
193
|
+
url: entry["url"],
|
|
194
|
+
revision: entry["revision"],
|
|
195
|
+
host_key_fingerprint: entry["host_key_fingerprint"]
|
|
196
|
+
)
|
|
197
|
+
end,
|
|
198
|
+
package: Array(data["package"]).map do |entry|
|
|
199
|
+
LockedPackage.new(
|
|
200
|
+
name: entry["name"],
|
|
201
|
+
version: entry["version"],
|
|
202
|
+
source: entry["source"],
|
|
203
|
+
path: entry["path"],
|
|
204
|
+
tree_hash: entry["tree_hash"],
|
|
205
|
+
artifact_hash: entry["artifact_hash"],
|
|
206
|
+
artifact: entry["artifact"],
|
|
207
|
+
exports: entry["exports"] || [],
|
|
208
|
+
dependencies: entry["dependencies"] || [],
|
|
209
|
+
signer_fingerprint: entry["signer_fingerprint"]
|
|
210
|
+
)
|
|
211
|
+
end,
|
|
212
|
+
target: Array(data["target"]).map { |entry| LockedTarget.new(name: entry["name"], outputs: entry["outputs"] || []) },
|
|
213
|
+
managed_span: Array(data["managed_span"]).map do |entry|
|
|
214
|
+
ManagedSpanRecord.new(
|
|
215
|
+
id: entry["id"],
|
|
216
|
+
target: entry["target"],
|
|
217
|
+
open_line: entry["open_line"],
|
|
218
|
+
close_line: entry["close_line"],
|
|
219
|
+
ideal_checksum: entry["ideal_checksum"],
|
|
220
|
+
package: entry["package"],
|
|
221
|
+
export: entry["export"],
|
|
222
|
+
source_checksum: entry["source_checksum"],
|
|
223
|
+
silenced: entry["silenced"]
|
|
224
|
+
)
|
|
225
|
+
end
|
|
226
|
+
)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def relative_lockfile_path(project_root, path)
|
|
230
|
+
absolute = Pathname(path).absolute? ? Pathname(path) : Pathname(project_root).join(path)
|
|
231
|
+
normalized_root = Pathname(project_root).cleanpath
|
|
232
|
+
normalized_absolute = absolute.cleanpath
|
|
233
|
+
relative = normalized_absolute.relative_path_from(normalized_root)
|
|
234
|
+
format_relative_lockfile_path(relative)
|
|
235
|
+
rescue ArgumentError
|
|
236
|
+
format_relative_lockfile_path(Pathname(path).cleanpath)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def format_relative_lockfile_path(relative)
|
|
240
|
+
text = relative.to_s.tr("\\", "/")
|
|
241
|
+
(text == "." || text.start_with?("./")) ? text : "./#{text}"
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def normalize_lockfile_artifact(project_root, artifact, package_root)
|
|
245
|
+
return artifact unless artifact.start_with?("path:")
|
|
246
|
+
|
|
247
|
+
path_text = artifact.delete_prefix("path:")
|
|
248
|
+
path = Pathname(path_text)
|
|
249
|
+
relative = if path.absolute?
|
|
250
|
+
relative_lockfile_path(project_root, path)
|
|
251
|
+
else
|
|
252
|
+
relative_lockfile_path(project_root, package_root)
|
|
253
|
+
end
|
|
254
|
+
"path:#{relative}"
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
extend LockfileIO
|
|
259
|
+
end
|