pray-cli 1.22.0 → 1.23.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 +10 -0
- data/lib/pray/constraint.rb +1 -1
- data/lib/pray/destination.rb +28 -8
- data/lib/pray/literal.rb +4 -5
- data/lib/pray/lockfile_serialize.rb +6 -1
- data/lib/pray/manifest_json.rb +3 -27
- data/lib/pray/manifest_parser.rb +25 -3
- data/lib/pray/manifest_parser_blocks.rb +7 -7
- data/lib/pray/publish.rb +1 -1
- data/lib/pray/render.rb +26 -32
- data/lib/pray/render_content.rb +4 -4
- data/lib/pray/render_project.rb +22 -0
- data/lib/pray/resolve.rb +17 -23
- data/lib/pray/resolve_local.rb +1 -0
- data/lib/pray/serve.rb +15 -1
- data/lib/pray/serve_federation.rb +3 -0
- data/lib/pray/source_path.rb +35 -0
- data/lib/pray/statement_surface.rb +3 -1
- data/lib/pray/trust.rb +4 -3
- data/lib/pray/version.rb +2 -2
- data/lib/pray.rb +1 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9fa10db14b7b45fdd5152a06e02fdf4312ca40fa575529ecd7448722caea437c
|
|
4
|
+
data.tar.gz: f53a2e0ca8b6d8a5d97a8a45903953a9b20c900864a354b8ea494e986eb9d815
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 21c44b12b0e8bf97d429491f4c32969a6700857c28b2b4b0981312dab4032279f7e52b4a4bd050984578469287554497e0ccff96a36ff6a3f99e69b97bbe1790
|
|
7
|
+
data.tar.gz: 11b795d9454a35f763cab755f82747c4470303912ef4269dd399d0b89266ad6833cf2246462b39113acfaccc494b904eba026d0e3813709a5e62735fcb74617e
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 1.23.0 (2026-10-26)
|
|
6
|
+
|
|
7
|
+
- Leave package catalog JSON bytes alone when publish reports a version unchanged. A load and rewrite round trip no longer strips trailing newlines or reorders keys such as `artifact_hash` relative to `yanked`.
|
|
8
|
+
- Refuse private, parent-relative, and symbolic-link paths from distribution readers. Reject symbolic-link source files and package paths.
|
|
9
|
+
- Treat `~> 1.4` as allowing later 1.x releases and keep `~> 1.4.0` within the 1.4 patch line.
|
|
10
|
+
- Render every legacy target output and enforce `max_bytes`.
|
|
11
|
+
- Refuse two packages or exports that claim the same provisioned destination, and name both owners in the error.
|
|
12
|
+
- Emit valid TOML and JSON escapes for lock and manifest values containing control characters.
|
|
13
|
+
- Reduce repeated parser and render scans.
|
|
14
|
+
|
|
5
15
|
## 1.22.0 (2026-10-22)
|
|
6
16
|
|
|
7
17
|
- Keep and create catalog `derived_metadata` on publish. Unchanged republishes and version appends no longer strip historical summaries, topics, or embeddings.
|
data/lib/pray/constraint.rb
CHANGED
|
@@ -74,7 +74,7 @@ module Pray
|
|
|
74
74
|
lower = numbers.join(".")
|
|
75
75
|
upper = case parts.length
|
|
76
76
|
when 1 then "#{numbers[0] + 1}.0.0"
|
|
77
|
-
when 2 then "#{numbers[0]
|
|
77
|
+
when 2 then "#{numbers[0] + 1}.0.0"
|
|
78
78
|
else "#{numbers[0]}.#{numbers[1] + 1}.0"
|
|
79
79
|
end
|
|
80
80
|
">= #{lower}, < #{upper}"
|
data/lib/pray/destination.rb
CHANGED
|
@@ -41,9 +41,15 @@ module Pray
|
|
|
41
41
|
target
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
-
def upsert_package(manifest, package)
|
|
45
|
-
|
|
44
|
+
def upsert_package(manifest, package, package_indices = nil)
|
|
45
|
+
existing_index = package_indices&.[](package.name)
|
|
46
|
+
existing = if existing_index
|
|
47
|
+
manifest.packages[existing_index]
|
|
48
|
+
elsif package_indices.nil?
|
|
49
|
+
manifest.packages.find { |candidate| candidate.name == package.name }
|
|
50
|
+
end
|
|
46
51
|
unless existing
|
|
52
|
+
package_indices[package.name] = manifest.packages.length if package_indices
|
|
47
53
|
manifest.packages << package
|
|
48
54
|
return
|
|
49
55
|
end
|
|
@@ -93,9 +99,15 @@ module Pray
|
|
|
93
99
|
end
|
|
94
100
|
end
|
|
95
101
|
|
|
96
|
-
def upsert_local(manifest, local)
|
|
97
|
-
|
|
102
|
+
def upsert_local(manifest, local, local_indices = nil)
|
|
103
|
+
existing_index = local_indices&.[](local.path)
|
|
104
|
+
existing = if existing_index
|
|
105
|
+
manifest.local[existing_index]
|
|
106
|
+
elsif local_indices.nil?
|
|
107
|
+
manifest.local.find { |candidate| candidate.path == local.path }
|
|
108
|
+
end
|
|
98
109
|
unless existing
|
|
110
|
+
local_indices[local.path] = manifest.local.length if local_indices
|
|
99
111
|
manifest.local << local
|
|
100
112
|
return
|
|
101
113
|
end
|
|
@@ -108,17 +120,25 @@ module Pray
|
|
|
108
120
|
existing.file ||= local.file
|
|
109
121
|
end
|
|
110
122
|
|
|
111
|
-
def bind_package_entry(target, package_name)
|
|
123
|
+
def bind_package_entry(target, package_name, package_names = nil)
|
|
112
124
|
entry = DestinationEntry.package(package_name)
|
|
113
|
-
|
|
125
|
+
exists = package_names ? package_names.key?(package_name) : target.entries.any? do |candidate|
|
|
126
|
+
candidate.kind == "package" && candidate.name == package_name
|
|
127
|
+
end
|
|
128
|
+
return if exists
|
|
114
129
|
|
|
115
130
|
target.entries << entry
|
|
131
|
+
package_names[package_name] = true if package_names
|
|
116
132
|
end
|
|
117
133
|
|
|
118
|
-
def bind_local_entry(target, path)
|
|
119
|
-
|
|
134
|
+
def bind_local_entry(target, path, local_paths = nil)
|
|
135
|
+
exists = local_paths ? local_paths.key?(path) : target.entries.any? do |candidate|
|
|
136
|
+
candidate.kind == "local" && candidate.path == path
|
|
137
|
+
end
|
|
138
|
+
return if exists
|
|
120
139
|
|
|
121
140
|
target.entries << DestinationEntry.local(path)
|
|
141
|
+
local_paths[path] = true if local_paths
|
|
122
142
|
end
|
|
123
143
|
|
|
124
144
|
def role_for_destination(mode)
|
data/lib/pray/literal.rb
CHANGED
|
@@ -81,7 +81,7 @@ module Pray
|
|
|
81
81
|
when "]", "}", ")"
|
|
82
82
|
depth -= 1
|
|
83
83
|
else
|
|
84
|
-
return index if depth.zero? && input[index
|
|
84
|
+
return index if depth.zero? && input[index, token.length] == token
|
|
85
85
|
end
|
|
86
86
|
index += 1
|
|
87
87
|
end
|
|
@@ -151,6 +151,7 @@ module Pray
|
|
|
151
151
|
end
|
|
152
152
|
|
|
153
153
|
def strip_line_comment(line)
|
|
154
|
+
return line unless line.include?("#")
|
|
154
155
|
quote = nil
|
|
155
156
|
escaped = false
|
|
156
157
|
line.each_char.with_index do |character, index|
|
|
@@ -190,7 +191,7 @@ module Pray
|
|
|
190
191
|
end
|
|
191
192
|
end
|
|
192
193
|
|
|
193
|
-
def peek =
|
|
194
|
+
def peek = @input[@cursor]
|
|
194
195
|
|
|
195
196
|
def next_character
|
|
196
197
|
character = peek
|
|
@@ -370,9 +371,7 @@ module Pray
|
|
|
370
371
|
LiteralValue.new(kind: :string, value: @input[start...@cursor])
|
|
371
372
|
end
|
|
372
373
|
|
|
373
|
-
def identifier_start?(character)
|
|
374
|
-
character && (character.match?(/[[:alpha:]]/) || character == "_")
|
|
375
|
-
end
|
|
374
|
+
def identifier_start?(character) = character && (character.match?(/[[:alpha:]]/) || character == "_")
|
|
376
375
|
|
|
377
376
|
def identifier_continue?(character)
|
|
378
377
|
character.match?(%r{[[:alnum:]_\-.\\/]})
|
|
@@ -144,7 +144,12 @@ module Pray
|
|
|
144
144
|
end
|
|
145
145
|
|
|
146
146
|
def format_string(value)
|
|
147
|
-
|
|
147
|
+
escapes = {8 => "\\b", 9 => "\\t", 10 => "\\n", 12 => "\\f", 13 => "\\r", 34 => "\\\"", 92 => "\\\\"}
|
|
148
|
+
escaped = value.each_codepoint.map do |codepoint|
|
|
149
|
+
escapes.fetch(codepoint) do
|
|
150
|
+
(codepoint < 32 || codepoint == 127) ? format("\\u%04X", codepoint) : codepoint.chr(Encoding::UTF_8)
|
|
151
|
+
end
|
|
152
|
+
end.join
|
|
148
153
|
"\"#{escaped}\""
|
|
149
154
|
end
|
|
150
155
|
|
data/lib/pray/manifest_json.rb
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
3
5
|
module Pray
|
|
4
6
|
module ManifestJson
|
|
5
7
|
module_function
|
|
6
8
|
|
|
7
9
|
def encode_compact(manifest)
|
|
8
|
-
|
|
10
|
+
JSON.generate(manifest_fields(manifest))
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
def manifest_fields(manifest)
|
|
@@ -109,31 +111,5 @@ module Pray
|
|
|
109
111
|
"header" => render.header
|
|
110
112
|
}
|
|
111
113
|
end
|
|
112
|
-
|
|
113
|
-
def encode_object(hash)
|
|
114
|
-
entries = hash.map { |key, value| "#{encode_string(key)}:#{encode_value(value)}" }
|
|
115
|
-
"{#{entries.join(",")}}"
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def encode_array(array)
|
|
119
|
-
"[#{array.map { |value| encode_value(value) }.join(",")}]"
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def encode_value(value)
|
|
123
|
-
case value
|
|
124
|
-
when Hash then encode_object(value)
|
|
125
|
-
when Array then encode_array(value)
|
|
126
|
-
when String then encode_string(value)
|
|
127
|
-
when TrueClass, FalseClass then value.to_s
|
|
128
|
-
when NilClass then "null"
|
|
129
|
-
when Integer then value.to_s
|
|
130
|
-
else
|
|
131
|
-
raise Error.manifest("unsupported JSON value: #{value.inspect}")
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
def encode_string(text)
|
|
136
|
-
"\"#{text.gsub("\\", "\\\\").gsub("\"", "\\\"").gsub("\n", "\\n").gsub("\r", "\\r").gsub("\t", "\\t")}\""
|
|
137
|
-
end
|
|
138
114
|
end
|
|
139
115
|
end
|
data/lib/pray/manifest_parser.rb
CHANGED
|
@@ -12,6 +12,10 @@ module Pray
|
|
|
12
12
|
@cursor = 0
|
|
13
13
|
@group_stack = []
|
|
14
14
|
@surface = StatementSurface::Reader.new
|
|
15
|
+
@package_indices = {}
|
|
16
|
+
@local_indices = {}
|
|
17
|
+
@target_package_names = {}
|
|
18
|
+
@target_local_paths = {}
|
|
15
19
|
end
|
|
16
20
|
|
|
17
21
|
def parse_root
|
|
@@ -59,7 +63,7 @@ module Pray
|
|
|
59
63
|
when /\Aagent (.+)\z/
|
|
60
64
|
apply_agent_package(manifest, Regexp.last_match(1))
|
|
61
65
|
when /\Apackage (.+)\z/
|
|
62
|
-
|
|
66
|
+
upsert_package(manifest, parse_package_with_groups(Regexp.last_match(1)))
|
|
63
67
|
when /\A(?:pray|use|include) (.+)\z/
|
|
64
68
|
apply_pray_statement(manifest, Regexp.last_match(1), nil)
|
|
65
69
|
when /\Alocal (.+)\z/
|
|
@@ -113,13 +117,13 @@ module Pray
|
|
|
113
117
|
|
|
114
118
|
def apply_agent_package(manifest, rest)
|
|
115
119
|
manifest.note_deprecated_keyword("agent")
|
|
116
|
-
|
|
120
|
+
upsert_package(manifest, parse_package_with_groups(rest))
|
|
117
121
|
end
|
|
118
122
|
|
|
119
123
|
def apply_unbound_local(manifest, rest)
|
|
120
124
|
local = parse_local_decl(rest)
|
|
121
125
|
local.bound = false
|
|
122
|
-
|
|
126
|
+
upsert_local(manifest, local)
|
|
123
127
|
end
|
|
124
128
|
|
|
125
129
|
def parse_package_with_groups(rest)
|
|
@@ -128,6 +132,24 @@ module Pray
|
|
|
128
132
|
package
|
|
129
133
|
end
|
|
130
134
|
|
|
135
|
+
def upsert_package(manifest, package)
|
|
136
|
+
Destination.upsert_package(manifest, package, @package_indices)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def upsert_local(manifest, local)
|
|
140
|
+
Destination.upsert_local(manifest, local, @local_indices)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def bind_package_entry(target, name)
|
|
144
|
+
names = (@target_package_names[target.object_id] ||= {})
|
|
145
|
+
Destination.bind_package_entry(target, name, names)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def bind_local_entry(target, path)
|
|
149
|
+
paths = (@target_local_paths[target.object_id] ||= {})
|
|
150
|
+
Destination.bind_local_entry(target, path, paths)
|
|
151
|
+
end
|
|
152
|
+
|
|
131
153
|
def next_statement
|
|
132
154
|
pending = @surface.next
|
|
133
155
|
return pending if pending
|
|
@@ -33,9 +33,9 @@ module Pray
|
|
|
33
33
|
return if statement == "end"
|
|
34
34
|
if (match = statement.match(/\Aagent (.+)\z/))
|
|
35
35
|
manifest.note_deprecated_keyword("agent")
|
|
36
|
-
|
|
36
|
+
upsert_package(manifest, parse_package_with_groups(match[1]))
|
|
37
37
|
elsif (match = statement.match(/\A(?:package|pray|use|include) (.+)\z/))
|
|
38
|
-
|
|
38
|
+
upsert_package(manifest, parse_package_with_groups(match[1]))
|
|
39
39
|
elsif /\Agroup (.+)\z/.match?(statement)
|
|
40
40
|
raise Error.parse("manifest", "nested group blocks are not supported")
|
|
41
41
|
else
|
|
@@ -95,8 +95,8 @@ module Pray
|
|
|
95
95
|
def bind_compose_local(manifest, index, rest)
|
|
96
96
|
local = parse_local_decl(rest)
|
|
97
97
|
local.bound = true
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
bind_local_entry(manifest.targets[index], local.path)
|
|
99
|
+
upsert_local(manifest, local)
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
def parse_file_block(manifest, rest)
|
|
@@ -154,7 +154,7 @@ module Pray
|
|
|
154
154
|
package.file = file_path
|
|
155
155
|
package.bound = true
|
|
156
156
|
package.roles << "file" unless package.roles.include?("file")
|
|
157
|
-
|
|
157
|
+
upsert_package(manifest, package)
|
|
158
158
|
end
|
|
159
159
|
|
|
160
160
|
def apply_pray_statement(manifest, rest, destination_index)
|
|
@@ -196,9 +196,9 @@ module Pray
|
|
|
196
196
|
package.bound = true
|
|
197
197
|
role = Destination.role_for_destination(mode)
|
|
198
198
|
package.roles << role if role && !package.roles.include?(role)
|
|
199
|
-
|
|
199
|
+
bind_package_entry(manifest.targets[destination_index], package.name)
|
|
200
200
|
end
|
|
201
|
-
|
|
201
|
+
upsert_package(manifest, package)
|
|
202
202
|
end
|
|
203
203
|
|
|
204
204
|
def parse_target_block(manifest, target_index)
|
data/lib/pray/publish.rb
CHANGED
|
@@ -34,7 +34,7 @@ module Pray
|
|
|
34
34
|
stored_artifact = stored_package_artifact(root, artifact_path, package, existing, distribution)
|
|
35
35
|
if stored_artifact && stored_publish_matches?(stored_artifact, package, existing)
|
|
36
36
|
package_names << package.declaration.name
|
|
37
|
-
|
|
37
|
+
# Intent: unchanged packages keep on-disk catalog bytes (newline, key order).
|
|
38
38
|
report_unchanged(package, dest_name, target)
|
|
39
39
|
stats[:unchanged] += 1
|
|
40
40
|
next
|
data/lib/pray/render.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "fileutils"
|
|
|
4
4
|
require "pathname"
|
|
5
5
|
require_relative "render_content"
|
|
6
6
|
require_relative "render_managed"
|
|
7
|
+
require_relative "render_project"
|
|
7
8
|
|
|
8
9
|
module Pray
|
|
9
10
|
RenderedTarget = Struct.new(:path, :content, :managed_spans) do
|
|
@@ -15,16 +16,6 @@ module Pray
|
|
|
15
16
|
module Render
|
|
16
17
|
module_function
|
|
17
18
|
|
|
18
|
-
def render_project(project)
|
|
19
|
-
rendered = []
|
|
20
|
-
project.manifest.targets.each do |target|
|
|
21
|
-
output = target.outputs.first
|
|
22
|
-
next unless output
|
|
23
|
-
rendered << render_target(project, target, output)
|
|
24
|
-
end
|
|
25
|
-
rendered
|
|
26
|
-
end
|
|
27
|
-
|
|
28
19
|
def write_rendered_targets(project, rendered, previous_lockfile = nil)
|
|
29
20
|
Transaction.run(project.project_root) { RenderDest.write_rendered_targets(project, rendered, previous_lockfile) }
|
|
30
21
|
end
|
|
@@ -56,7 +47,18 @@ module Pray
|
|
|
56
47
|
end
|
|
57
48
|
end
|
|
58
49
|
end
|
|
59
|
-
planned.sort_by
|
|
50
|
+
planned.sort_by! { |file| [file.path, file.package, file.export] }
|
|
51
|
+
planned.each_cons(2) do |left, right|
|
|
52
|
+
next unless left.path == right.path
|
|
53
|
+
next if left.package == right.package && left.export == right.export
|
|
54
|
+
|
|
55
|
+
raise Error.render(
|
|
56
|
+
"provisioned destination `#{left.path}` has multiple owners: " \
|
|
57
|
+
"package `#{left.package}` export `#{left.export}` and " \
|
|
58
|
+
"package `#{right.package}` export `#{right.export}`"
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
planned.uniq(&:path)
|
|
60
62
|
end
|
|
61
63
|
|
|
62
64
|
PlannedProvisionedFile = Struct.new(:path, :source, :package, :export, keyword_init: true)
|
|
@@ -74,12 +76,23 @@ module Pray
|
|
|
74
76
|
append_header(builder, project, target, output)
|
|
75
77
|
symbols = project.manifest.symbols || {}
|
|
76
78
|
managed_spans = []
|
|
79
|
+
locals_by_path = project.local_files.to_h { |local| [local.manifest_path, local] }
|
|
80
|
+
packages_by_name = project.packages.to_h { |package| [package.declaration.name, package] }
|
|
77
81
|
|
|
78
82
|
(target.entries || []).each do |entry|
|
|
79
83
|
if entry.kind == "local"
|
|
80
|
-
|
|
84
|
+
local = locals_by_path[entry.path]
|
|
85
|
+
append_managed_local(builder, managed_spans, local, target, output, symbols) if local
|
|
81
86
|
else
|
|
82
|
-
|
|
87
|
+
package = packages_by_name[entry.name]
|
|
88
|
+
next unless package
|
|
89
|
+
next unless Environment.package_matches_environment?(package.declaration.groups, project.environment)
|
|
90
|
+
|
|
91
|
+
package.selected_exports.each do |export|
|
|
92
|
+
next unless should_inline_export?(package, export)
|
|
93
|
+
|
|
94
|
+
append_managed_export(builder, managed_spans, package, export, target, output, symbols)
|
|
95
|
+
end
|
|
83
96
|
end
|
|
84
97
|
end
|
|
85
98
|
|
|
@@ -133,25 +146,6 @@ module Pray
|
|
|
133
146
|
builder.append_empty_line
|
|
134
147
|
end
|
|
135
148
|
|
|
136
|
-
def append_scoped_local(builder, managed_spans, project, target, output, path, symbols)
|
|
137
|
-
local = project.local_files.find { |candidate| candidate.manifest_path == path }
|
|
138
|
-
return unless local
|
|
139
|
-
|
|
140
|
-
append_managed_local(builder, managed_spans, local, target, output, symbols)
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def append_scoped_package(builder, managed_spans, project, target, output, package_name, symbols)
|
|
144
|
-
package = project.packages.find { |candidate| candidate.declaration.name == package_name }
|
|
145
|
-
return unless package
|
|
146
|
-
return unless Environment.package_matches_environment?(package.declaration.groups, project.environment)
|
|
147
|
-
|
|
148
|
-
package.selected_exports.each do |export|
|
|
149
|
-
next unless should_inline_export?(package, export)
|
|
150
|
-
|
|
151
|
-
append_managed_export(builder, managed_spans, package, export, target, output, symbols)
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
|
|
155
149
|
def should_inline_export?(package, export_name)
|
|
156
150
|
export = package.spec.exports[export_name]
|
|
157
151
|
export.nil? || %w[fragment file].include?(export.kind)
|
data/lib/pray/render_content.rb
CHANGED
|
@@ -3,16 +3,16 @@
|
|
|
3
3
|
module Pray
|
|
4
4
|
module Render
|
|
5
5
|
class ContentBuilder
|
|
6
|
+
attr_reader :next_line_number
|
|
7
|
+
|
|
6
8
|
def initialize
|
|
7
9
|
@content = +""
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def next_line_number
|
|
11
|
-
@content.count("\n") + 1
|
|
10
|
+
@next_line_number = 1
|
|
12
11
|
end
|
|
13
12
|
|
|
14
13
|
def append_line(line)
|
|
15
14
|
@content << line << "\n"
|
|
15
|
+
@next_line_number += 1
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def append_empty_line
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module Render
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def render_project(project)
|
|
8
|
+
project.manifest.targets.flat_map do |target|
|
|
9
|
+
target.outputs.map do |output|
|
|
10
|
+
rendered = render_target(project, target, output)
|
|
11
|
+
if target.max_bytes && rendered.content.bytesize > target.max_bytes
|
|
12
|
+
raise Error.render(
|
|
13
|
+
"target `#{target.name}` output `#{output}` is #{rendered.content.bytesize} bytes; " \
|
|
14
|
+
"max_bytes is #{target.max_bytes}"
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
rendered
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/pray/resolve.rb
CHANGED
|
@@ -154,8 +154,8 @@ module Pray
|
|
|
154
154
|
project_root, sources, git_sources, user_config, declaration, lockfile, options
|
|
155
155
|
)
|
|
156
156
|
spec_path = find_prayspec_file(root)
|
|
157
|
-
spec_text =
|
|
158
|
-
spec = Pray.parse_package_spec(spec_text)
|
|
157
|
+
spec_text = SourcePath.read(root, File.basename(spec_path), "package specification").force_encoding(Encoding::UTF_8)
|
|
158
|
+
spec = Pray.parse_package_spec(spec_text)
|
|
159
159
|
if spec.name != declaration.name
|
|
160
160
|
raise Error.resolution(
|
|
161
161
|
"package path #{root.inspect} declares #{spec.name.inspect}, expected #{declaration.name.inspect}"
|
|
@@ -195,9 +195,11 @@ module Pray
|
|
|
195
195
|
offline = options.offline
|
|
196
196
|
preferred_version = options.preferred_lock_version(lockfile, declaration.name)
|
|
197
197
|
if (local_path = user_config.local.package[declaration.name])
|
|
198
|
-
return [
|
|
198
|
+
return [SourcePath.declared_directory(project_root, local_path, "package directory"), nil]
|
|
199
|
+
end
|
|
200
|
+
if declaration.path
|
|
201
|
+
return [SourcePath.declared_directory(project_root, declaration.path, "package directory"), nil]
|
|
199
202
|
end
|
|
200
|
-
return [File.expand_path(declaration.path, project_root), nil] if declaration.path
|
|
201
203
|
return [ResolveTarball.package_root(project_root, declaration.tarball, offline: offline), nil] if declaration.tarball
|
|
202
204
|
source_name = ResolveSource.implied_source_name(declaration, sources)
|
|
203
205
|
if source_name
|
|
@@ -220,7 +222,8 @@ module Pray
|
|
|
220
222
|
case source.kind
|
|
221
223
|
when "path"
|
|
222
224
|
directory = LocalPrayer.path_source_package_directory(source_name, declaration.name)
|
|
223
|
-
|
|
225
|
+
relative = File.join(source.url, directory)
|
|
226
|
+
return [SourcePath.declared_directory(project_root, relative, "package directory"), nil]
|
|
224
227
|
when "registry", "static index"
|
|
225
228
|
resolved = Registry.resolve_registry_package_root(
|
|
226
229
|
project_root,
|
|
@@ -264,7 +267,8 @@ module Pray
|
|
|
264
267
|
|
|
265
268
|
raise Error.unsupported("remote sources are not implemented yet") if declaration.git || declaration.oci
|
|
266
269
|
|
|
267
|
-
|
|
270
|
+
slug = declaration.name.tr("/", "-")
|
|
271
|
+
[SourcePath.declared_directory(project_root, slug, "package directory"), nil]
|
|
268
272
|
end
|
|
269
273
|
|
|
270
274
|
def resolve_package_root(project_root, sources, git_sources, user_config, declaration, lockfile = nil)
|
|
@@ -272,9 +276,12 @@ module Pray
|
|
|
272
276
|
end
|
|
273
277
|
|
|
274
278
|
def find_prayspec_file(root)
|
|
275
|
-
files =
|
|
279
|
+
files = []
|
|
280
|
+
Dir.each_child(root) do |entry|
|
|
281
|
+
next unless File.extname(entry) == ".prayspec"
|
|
282
|
+
|
|
276
283
|
path = File.join(root, entry)
|
|
277
|
-
|
|
284
|
+
files << path if File.file?(path)
|
|
278
285
|
end
|
|
279
286
|
case files.length
|
|
280
287
|
when 1 then files.first
|
|
@@ -296,15 +303,7 @@ module Pray
|
|
|
296
303
|
end
|
|
297
304
|
|
|
298
305
|
def load_package_file_bytes(root, spec)
|
|
299
|
-
|
|
300
|
-
spec.files.each do |file|
|
|
301
|
-
path = File.join(root, file)
|
|
302
|
-
raise Error.integrity("package file missing: #{file}") unless File.exist?(path)
|
|
303
|
-
raise Error.integrity("package file is a directory: #{file}") if File.directory?(path)
|
|
304
|
-
|
|
305
|
-
file_bytes[file] = File.binread(path)
|
|
306
|
-
end
|
|
307
|
-
file_bytes
|
|
306
|
+
spec.files.to_h { |file| [file, SourcePath.read(root, file, "package file")] }
|
|
308
307
|
end
|
|
309
308
|
|
|
310
309
|
def build_skill_file_index(spec)
|
|
@@ -331,12 +330,7 @@ module Pray
|
|
|
331
330
|
end
|
|
332
331
|
|
|
333
332
|
def skill_relative_file(file, skill_prefix)
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
relative = file.delete_prefix(skill_prefix).delete_prefix("/")
|
|
337
|
-
return nil if relative.empty? || file == skill_prefix
|
|
338
|
-
|
|
339
|
-
relative
|
|
333
|
+
file.delete_prefix("#{skill_prefix.delete_suffix("/")}/") if file.start_with?("#{skill_prefix.delete_suffix("/")}/")
|
|
340
334
|
end
|
|
341
335
|
end
|
|
342
336
|
end
|
data/lib/pray/resolve_local.rb
CHANGED
|
@@ -11,6 +11,7 @@ module Pray
|
|
|
11
11
|
|
|
12
12
|
def resolve_local_file(project_root, declaration)
|
|
13
13
|
path = File.join(project_root, declaration.path)
|
|
14
|
+
SourcePath.reject_symlinks!(project_root, declaration.path, "local source")
|
|
14
15
|
unless File.exist?(path)
|
|
15
16
|
if declaration.optional
|
|
16
17
|
return ResolvedLocalFile.new(
|
data/lib/pray/serve.rb
CHANGED
|
@@ -118,7 +118,7 @@ module Pray
|
|
|
118
118
|
return html_response("<h1>Pray distribution</h1>")
|
|
119
119
|
end
|
|
120
120
|
|
|
121
|
-
file_path =
|
|
121
|
+
file_path = public_distribution_path(root, path)
|
|
122
122
|
return not_found unless file_path
|
|
123
123
|
return not_found unless File.file?(file_path)
|
|
124
124
|
return payload_too_large if File.size(file_path) > ResourceLimits::MAX_HTTP_RESPONSE_BYTES
|
|
@@ -129,6 +129,20 @@ module Pray
|
|
|
129
129
|
ranged_response(status, content_type, ranged_body, content_range)
|
|
130
130
|
end
|
|
131
131
|
|
|
132
|
+
def public_distribution_path(root, request_path)
|
|
133
|
+
relative = PathSafety.sanitize_relative_path(request_path)
|
|
134
|
+
exact = %w[v1/index.json v1/distribution.json v1/trust.json]
|
|
135
|
+
return nil unless exact.include?(relative) || relative.start_with?("v1/packages/", "v1/artifacts/")
|
|
136
|
+
|
|
137
|
+
file_path = PathSafety.join_under_root(root, relative)
|
|
138
|
+
return nil unless file_path
|
|
139
|
+
|
|
140
|
+
SourcePath.reject_symlinks!(root, relative, "distribution file")
|
|
141
|
+
file_path
|
|
142
|
+
rescue Error
|
|
143
|
+
nil
|
|
144
|
+
end
|
|
145
|
+
|
|
132
146
|
def content_type_for(path)
|
|
133
147
|
case File.extname(path)
|
|
134
148
|
when ".json" then "application/json"
|
|
@@ -51,6 +51,7 @@ module Pray
|
|
|
51
51
|
|
|
52
52
|
def package_response(root, path)
|
|
53
53
|
name = path.delete_prefix("/v1/sync/package/")
|
|
54
|
+
PathSafety.validate_registry_cache_identity!(name, "metadata")
|
|
54
55
|
metadata_path = Publish.registry_metadata_path(root, name)
|
|
55
56
|
return Serve.not_found unless File.file?(metadata_path)
|
|
56
57
|
|
|
@@ -61,6 +62,8 @@ module Pray
|
|
|
61
62
|
"versions" => metadata.versions.map { |version| transport_version(version) }
|
|
62
63
|
}
|
|
63
64
|
Serve.ok_response("application/json", JSON.pretty_generate(body))
|
|
65
|
+
rescue Error
|
|
66
|
+
Serve.not_found
|
|
64
67
|
end
|
|
65
68
|
|
|
66
69
|
def append_confession(root, body)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "pathname"
|
|
4
|
+
|
|
5
|
+
module Pray
|
|
6
|
+
module SourcePath
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def declared_directory(root, relative, label)
|
|
10
|
+
reject_symlinks!(root, relative, label)
|
|
11
|
+
File.expand_path(relative, root)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def reject_symlinks!(root, relative, label)
|
|
15
|
+
current = Pathname(relative).absolute? ? File::SEPARATOR : root
|
|
16
|
+
status = nil
|
|
17
|
+
Pathname(relative).each_filename do |component|
|
|
18
|
+
current = File.join(current, component)
|
|
19
|
+
status = File.lstat(current)
|
|
20
|
+
raise Error.integrity("#{label} uses a symbolic link: #{relative}") if status.symlink?
|
|
21
|
+
rescue Errno::ENOENT
|
|
22
|
+
return nil
|
|
23
|
+
end
|
|
24
|
+
status
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def read(root, relative, label)
|
|
28
|
+
status = reject_symlinks!(root, relative, label)
|
|
29
|
+
raise Error.integrity("#{label} missing: #{relative}") unless status
|
|
30
|
+
raise Error.integrity("#{label} is a directory: #{relative}") if status.directory?
|
|
31
|
+
|
|
32
|
+
File.binread(File.join(root, relative))
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -7,6 +7,7 @@ module Pray
|
|
|
7
7
|
def expand_statement_surface(statement)
|
|
8
8
|
trimmed = statement.strip
|
|
9
9
|
return [] if trimmed.empty?
|
|
10
|
+
return [normalize_keyword_call(trimmed)] unless trimmed.include?(";") || trimmed.include?("{")
|
|
10
11
|
|
|
11
12
|
Literal.split_top_level(trimmed, ";").flat_map { |segment| expand_one_surface(segment) }
|
|
12
13
|
end
|
|
@@ -15,7 +16,7 @@ module Pray
|
|
|
15
16
|
trimmed = statement.strip
|
|
16
17
|
return [] if trimmed.empty?
|
|
17
18
|
|
|
18
|
-
braced = expand_brace_block(trimmed)
|
|
19
|
+
braced = expand_brace_block(trimmed) if trimmed.include?("{")
|
|
19
20
|
return braced if braced
|
|
20
21
|
|
|
21
22
|
[normalize_keyword_call(trimmed)]
|
|
@@ -71,6 +72,7 @@ module Pray
|
|
|
71
72
|
def normalize_keyword_call(statement)
|
|
72
73
|
spaced = normalize_spaced_block_opener(statement)
|
|
73
74
|
return spaced if spaced
|
|
75
|
+
return statement unless statement.include?("(")
|
|
74
76
|
|
|
75
77
|
keyword = leading_identifier(statement)
|
|
76
78
|
return statement unless keyword
|
data/lib/pray/trust.rb
CHANGED
|
@@ -110,10 +110,11 @@ module Pray
|
|
|
110
110
|
end
|
|
111
111
|
|
|
112
112
|
def prepare_source_host_keys(sources)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
next unless source.kind == "pray_ssh"
|
|
113
|
+
ssh_sources = sources.select { |source| source.kind == "pray_ssh" }
|
|
114
|
+
return {} if ssh_sources.empty?
|
|
116
115
|
|
|
116
|
+
policy = load_policy_or_default
|
|
117
|
+
ssh_sources.each_with_object({}) do |source, host_keys|
|
|
117
118
|
rule = best_rule(policy, source.url)
|
|
118
119
|
fingerprint = rule.allowed_host_keys.first
|
|
119
120
|
host_keys[source.name] = fingerprint if fingerprint
|
data/lib/pray/version.rb
CHANGED
data/lib/pray.rb
CHANGED
|
@@ -26,6 +26,7 @@ require_relative "pray/dotenv"
|
|
|
26
26
|
require_relative "pray/project_context"
|
|
27
27
|
require_relative "pray/environment"
|
|
28
28
|
require_relative "pray/resolve_context"
|
|
29
|
+
require_relative "pray/source_path"
|
|
29
30
|
require_relative "pray/resolve_source"
|
|
30
31
|
require_relative "pray/resolve_exports"
|
|
31
32
|
require_relative "pray/dependency_graph"
|
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.23.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -182,6 +182,7 @@ files:
|
|
|
182
182
|
- lib/pray/render_layout.rb
|
|
183
183
|
- lib/pray/render_managed.rb
|
|
184
184
|
- lib/pray/render_patch.rb
|
|
185
|
+
- lib/pray/render_project.rb
|
|
185
186
|
- lib/pray/resolve.rb
|
|
186
187
|
- lib/pray/resolve_context.rb
|
|
187
188
|
- lib/pray/resolve_deps.rb
|
|
@@ -194,6 +195,7 @@ files:
|
|
|
194
195
|
- lib/pray/serve_federation.rb
|
|
195
196
|
- lib/pray/serve_range.rb
|
|
196
197
|
- lib/pray/session.rb
|
|
198
|
+
- lib/pray/source_path.rb
|
|
197
199
|
- lib/pray/ssh_agent.rb
|
|
198
200
|
- lib/pray/statement_surface.rb
|
|
199
201
|
- lib/pray/substitute.rb
|