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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/lib/pray/cache_clean.rb +76 -0
- data/lib/pray/cli/commands/meta.rb +9 -4
- data/lib/pray/cli/commands/workflow.rb +1 -1
- data/lib/pray/cli/help.rb +1 -1
- data/lib/pray/cli/helpers.rb +2 -1
- data/lib/pray/cli/parse.rb +19 -1
- data/lib/pray/cli.rb +2 -1
- data/lib/pray/compose_dest.rb +59 -0
- data/lib/pray/format_serialize.rb +6 -1
- data/lib/pray/lockfile.rb +32 -6
- data/lib/pray/lockfile_serialize.rb +15 -0
- data/lib/pray/manifest.rb +11 -10
- data/lib/pray/manifest_json.rb +4 -4
- data/lib/pray/manifest_parser.rb +1 -0
- data/lib/pray/manifest_parser_blocks.rb +5 -2
- data/lib/pray/manifest_parser_helpers.rb +34 -5
- data/lib/pray/materialize.rb +5 -3
- data/lib/pray/package_spec.rb +19 -0
- data/lib/pray/path_safety.rb +29 -0
- data/lib/pray/plan.rb +5 -11
- data/lib/pray/registry.rb +6 -13
- data/lib/pray/render.rb +43 -65
- data/lib/pray/render_content.rb +36 -0
- data/lib/pray/render_dest.rb +215 -0
- data/lib/pray/render_layout.rb +21 -0
- data/lib/pray/render_patch.rb +107 -0
- data/lib/pray/resolve.rb +5 -48
- data/lib/pray/resolve_exports.rb +79 -0
- data/lib/pray/tar_validation.rb +4 -1
- data/lib/pray/verify.rb +2 -0
- data/lib/pray/verify_provisioned.rb +55 -0
- data/lib/pray/version.rb +2 -2
- data/lib/pray.rb +6 -0
- metadata +9 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cce969add8d5bb73ee6fc4b9910d785a0565e24afaeb6d4b80d9383f9fbe828b
|
|
4
|
+
data.tar.gz: f96cac68f4cf12079056d0ef300c57ab0efa47f3c9ab1e9967a9fe03cb8a5c37
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a0ec1da337a61097a65ebd7dc091879a978bbb1f13d6eba41ee4149735e8d37b3d32a7634e5b79fbc426e7ef068cf49ac5437c71b9492fe09f5ab439b1c5645b
|
|
7
|
+
data.tar.gz: 0ea56f7dd07ddf9be173bc0dd3ed399b91d280c36aaea1f703d0a60b9676dba15398cef53c876f71b3c86347611b9dd5f10af6d716152a69e4bf488d7b82996c
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 1.11.0 (2026-09-04)
|
|
6
|
+
|
|
7
|
+
- Read the whole eight byte tar checksum field so `.praypkg` archives whose checksum is written as seven octal digits unpack instead of failing integrity.
|
|
8
|
+
- Share the source-keyed registry cache path with the Rust and TypeScript CLIs.
|
|
9
|
+
- Add `pray clean --unused` for lockfile-driven registry cache cleanup.
|
|
10
|
+
- Reject unsafe registry package and version path segments.
|
|
11
|
+
|
|
12
|
+
## 1.10.0 (2026-09-02)
|
|
13
|
+
|
|
14
|
+
- Refuse to overwrite exclusive `file:` and `tree:` destinations that already exist with other bytes.
|
|
15
|
+
- Fail when a provisioned or compose destination is a symbolic link.
|
|
16
|
+
- Record provisioned leaves in `Prayfile.lock` and delete a dropped leaf only when on-disk bytes still match the locked hash.
|
|
17
|
+
- Keep the previous lock when a provisioned destination write fails, so retry still has its ownership record.
|
|
18
|
+
- Reject destination paths that start with `~`.
|
|
19
|
+
- Print every provisioned path in `pray plan`.
|
|
20
|
+
- BREAKING: Drop unused `render` fields `section_markers` and `line_endings`; reject them at parse.
|
|
21
|
+
- Inline a UTF-8 `file` export into `compose` as a marked span. Exclusive `file:` stays unmarked.
|
|
22
|
+
- Write the Agent context banner on `AGENTS.md` by default. Other compose destinations opt in with `header: true`.
|
|
23
|
+
- Fail compose of JSON, binary, or an unknown file type and name `file:` as the unmarked path.
|
|
24
|
+
- Warn when Prayfile uses deprecated `skills`, export type `skill`, or `spec.skills`; prefer `tree` / `folder`. These forms will be removed in version 2.
|
|
25
|
+
|
|
3
26
|
## 1.9.2 (2026-09-01)
|
|
4
27
|
|
|
5
28
|
- Fix `.praypkg` unpack for git and registry installs when the Ruby process forces UTF-8 internal encoding.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Pray
|
|
6
|
+
module CacheClean
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def clean_unused_registry_cache(project_root)
|
|
10
|
+
lockfile = Pray.read_lockfile(File.join(project_root, "Prayfile.lock"))
|
|
11
|
+
validate_lockfile_for_cleanup!(lockfile)
|
|
12
|
+
registry_root = File.expand_path(".pray/cache/registry", project_root)
|
|
13
|
+
retained = lockfile.package.filter_map do |package|
|
|
14
|
+
package_path = File.expand_path(package.path, project_root)
|
|
15
|
+
package_path if PathSafety.path_under_root?(registry_root, package_path)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
begin
|
|
19
|
+
metadata = File.lstat(registry_root)
|
|
20
|
+
rescue Errno::ENOENT
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
if metadata.symlink? || !metadata.directory?
|
|
24
|
+
remove_path(registry_root)
|
|
25
|
+
return
|
|
26
|
+
end
|
|
27
|
+
prune_directory(registry_root, retained, remove_when_empty: false)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def validate_lockfile_for_cleanup!(lockfile)
|
|
31
|
+
validate_sha256_digest!("manifest_hash", lockfile.manifest_hash)
|
|
32
|
+
lockfile.package.each do |package|
|
|
33
|
+
if !package.path.is_a?(String) || package.path.empty?
|
|
34
|
+
raise Error.parse("lockfile", "package path must not be empty")
|
|
35
|
+
end
|
|
36
|
+
validate_sha256_digest!("package tree_hash", package.tree_hash)
|
|
37
|
+
validate_sha256_digest!("package artifact_hash", package.artifact_hash)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def validate_sha256_digest!(field, value)
|
|
42
|
+
digest = value.to_s.delete_prefix("sha256:")
|
|
43
|
+
return if value.is_a?(String) && value.start_with?("sha256:") && digest.match?(/\A[0-9a-f]{64}\z/)
|
|
44
|
+
|
|
45
|
+
raise Error.parse("lockfile", "#{field} must be a sha256 digest")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def prune_directory(path, retained, remove_when_empty:)
|
|
49
|
+
Dir.each_child(path) do |name|
|
|
50
|
+
entry = File.join(path, name)
|
|
51
|
+
protects_entry = retained.include?(entry)
|
|
52
|
+
leads_to_retained = retained.any? { |kept| PathSafety.path_under_root?(entry, kept) }
|
|
53
|
+
next if protects_entry
|
|
54
|
+
|
|
55
|
+
metadata = File.lstat(entry)
|
|
56
|
+
if leads_to_retained && metadata.directory? && !metadata.symlink?
|
|
57
|
+
prune_directory(entry, retained, remove_when_empty: true)
|
|
58
|
+
else
|
|
59
|
+
remove_path(entry)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
Dir.rmdir(path) if remove_when_empty && Dir.empty?(path)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def remove_path(path)
|
|
66
|
+
metadata = File.lstat(path)
|
|
67
|
+
if metadata.directory? && !metadata.symlink?
|
|
68
|
+
FileUtils.rm_rf(path)
|
|
69
|
+
else
|
|
70
|
+
File.unlink(path)
|
|
71
|
+
end
|
|
72
|
+
rescue Errno::ENOENT
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -6,10 +6,15 @@ module Pray
|
|
|
6
6
|
puts "pray #{Pray::VERSION}"
|
|
7
7
|
end
|
|
8
8
|
|
|
9
|
-
def clean_command
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
def clean_command(unused: false)
|
|
10
|
+
project_root = Invocation.project_root
|
|
11
|
+
if unused
|
|
12
|
+
CacheClean.clean_unused_registry_cache(project_root)
|
|
13
|
+
return
|
|
14
|
+
end
|
|
15
|
+
remove_path_if_exists(File.join(project_root, ".pray/cache"))
|
|
16
|
+
remove_path_if_exists(File.join(project_root, ".pray/vendor"))
|
|
17
|
+
remove_path_if_exists(File.join(project_root, ".pray/state.json"))
|
|
13
18
|
end
|
|
14
19
|
end
|
|
15
20
|
end
|
|
@@ -107,8 +107,8 @@ module Pray
|
|
|
107
107
|
rendered = Render.render_project(project)
|
|
108
108
|
updated_lockfile = build_lockfile(project, rendered)
|
|
109
109
|
merged_lockfile = merge_selected_package_update(previous_lockfile, updated_lockfile, name)
|
|
110
|
+
Render.write_rendered_targets(project, rendered, previous_lockfile)
|
|
110
111
|
Pray.write_lockfile(lockfile_path, merged_lockfile)
|
|
111
|
-
Render.write_rendered_targets(project, rendered)
|
|
112
112
|
puts "Unlocked #{name}"
|
|
113
113
|
end
|
|
114
114
|
|
data/lib/pray/cli/help.rb
CHANGED
|
@@ -117,7 +117,7 @@ module Pray
|
|
|
117
117
|
"remove" => "remove a package from Prayfile\n\nUsage: pray remove <name>",
|
|
118
118
|
"unlock" => "clear a locked package pin\n\nUsage: pray unlock <package>",
|
|
119
119
|
"vendor" => "copy resolved packages locally\n\nUsage: pray vendor",
|
|
120
|
-
"clean" => "remove local cache and vendor trees\n\nUsage: pray clean",
|
|
120
|
+
"clean" => "remove local cache and vendor trees, or only unused registry entries\n\nUsage: pray clean [--unused]",
|
|
121
121
|
"publish" => <<~TEXT.strip,
|
|
122
122
|
upload packages to a registry or local root
|
|
123
123
|
|
data/lib/pray/cli/helpers.rb
CHANGED
data/lib/pray/cli/parse.rb
CHANGED
|
@@ -67,7 +67,7 @@ module Pray
|
|
|
67
67
|
when "outdated" then [:outdated, arguments]
|
|
68
68
|
when "explain" then [:explain, arguments.shift]
|
|
69
69
|
when "vendor" then [:vendor]
|
|
70
|
-
when "clean" then [:clean]
|
|
70
|
+
when "clean" then [:clean, parse_clean_arguments(arguments, flags)]
|
|
71
71
|
when "tree" then [:tree]
|
|
72
72
|
when "sync" then [:sync, parse_sync_arguments(arguments)]
|
|
73
73
|
when "trust" then parse_trust_command(arguments)
|
|
@@ -83,6 +83,24 @@ module Pray
|
|
|
83
83
|
found
|
|
84
84
|
end
|
|
85
85
|
|
|
86
|
+
def parse_clean_arguments(arguments, flags)
|
|
87
|
+
if flags.values_at(:check, :strict, :semantic, :locked, :frozen, :offline).any?
|
|
88
|
+
raise Error.unsupported("unknown clean flag")
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
unused = false
|
|
92
|
+
arguments.each do |argument|
|
|
93
|
+
if argument == "--unused" && !unused
|
|
94
|
+
unused = true
|
|
95
|
+
elsif argument.start_with?("--")
|
|
96
|
+
raise Error.unsupported("unknown clean flag: #{argument}")
|
|
97
|
+
else
|
|
98
|
+
raise Error.unsupported("unexpected clean argument: #{argument}")
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
{unused: unused}
|
|
102
|
+
end
|
|
103
|
+
|
|
86
104
|
def parse_add_arguments(arguments)
|
|
87
105
|
name = arguments.shift
|
|
88
106
|
raise Error.unsupported("add requires a package name") unless name
|
data/lib/pray/cli.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
|
|
5
5
|
require_relative "invocation"
|
|
6
|
+
require_relative "cache_clean"
|
|
6
7
|
require_relative "cli/parse"
|
|
7
8
|
require_relative "cli/help"
|
|
8
9
|
require_relative "cli/suggest"
|
|
@@ -97,7 +98,7 @@ module Pray
|
|
|
97
98
|
in [:outdated, arguments] then outdated_command(arguments)
|
|
98
99
|
in [:explain, name] then explain_command(name)
|
|
99
100
|
in [:vendor] then raise Error.unsupported("vendor is not implemented yet in pray-cli Ruby")
|
|
100
|
-
in [:clean] then clean_command
|
|
101
|
+
in [:clean, options] then clean_command(**options)
|
|
101
102
|
in [:tree] then tree_command
|
|
102
103
|
in [:trust_list, options] then trust_list_command(**options)
|
|
103
104
|
in [:trust_show] then trust_show_command
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module ComposeDest
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
HTML_COMMENT_EXTENSIONS = %w[.md .markdown .html .htm].freeze
|
|
8
|
+
BINARY_EXTENSIONS = %w[
|
|
9
|
+
.png .jpg .jpeg .gif .webp .ico .pdf .zip .gz .tgz .tar .wasm .bin
|
|
10
|
+
.woff .woff2 .exe .dylib .so
|
|
11
|
+
].freeze
|
|
12
|
+
|
|
13
|
+
def compose_writes_header?(target, output, project_header)
|
|
14
|
+
return target.header unless target.header.nil?
|
|
15
|
+
|
|
16
|
+
project_header && agents_markdown?(output)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def header_text(target, output, project_header)
|
|
20
|
+
return unless compose_writes_header?(target, output, project_header)
|
|
21
|
+
|
|
22
|
+
name = File.basename(output)
|
|
23
|
+
guidance = if agents_markdown?(output)
|
|
24
|
+
"Do not edit managed blocks in `#{name}` or provisioned files under `.agents/`."
|
|
25
|
+
else
|
|
26
|
+
"Do not edit managed blocks in `#{name}`."
|
|
27
|
+
end
|
|
28
|
+
<<~HEADER.rstrip
|
|
29
|
+
<!-- pray:0 ignore-comments -->
|
|
30
|
+
|
|
31
|
+
# Agent context
|
|
32
|
+
|
|
33
|
+
#{guidance}
|
|
34
|
+
To change shared guidance, update `Prayfile` and run `pray install`.
|
|
35
|
+
HEADER
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def ensure_html_comment_dest!(output)
|
|
39
|
+
dest = output.tr("\\", "/")
|
|
40
|
+
extension = File.extname(output).downcase
|
|
41
|
+
return if HTML_COMMENT_EXTENSIONS.include?(extension)
|
|
42
|
+
if extension == ".json"
|
|
43
|
+
raise Error.render("compose cannot write JSON; use file: \"#{dest}\" for unmarked bytes")
|
|
44
|
+
end
|
|
45
|
+
if BINARY_EXTENSIONS.include?(extension)
|
|
46
|
+
raise Error.render(
|
|
47
|
+
"compose cannot write a binary file; use file: \"#{dest}\" for unmarked bytes"
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
raise Error.render(
|
|
51
|
+
"compose cannot write this file type; use file: \"#{dest}\" for unmarked bytes"
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def agents_markdown?(output)
|
|
56
|
+
File.basename(output) == "AGENTS.md"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -28,7 +28,12 @@ module Pray
|
|
|
28
28
|
case target.mode
|
|
29
29
|
when "compose"
|
|
30
30
|
path = target.outputs.first || ""
|
|
31
|
-
|
|
31
|
+
header = case target.header
|
|
32
|
+
when true then ", header: true"
|
|
33
|
+
when false then ", header: false"
|
|
34
|
+
else ""
|
|
35
|
+
end
|
|
36
|
+
lines << %(compose "#{path}"#{header} do)
|
|
32
37
|
target.entries.each do |entry|
|
|
33
38
|
lines << " #{format_destination_entry(entry, manifest)}"
|
|
34
39
|
end
|
data/lib/pray/lockfile.rb
CHANGED
|
@@ -20,14 +20,16 @@ module Pray
|
|
|
20
20
|
:id, :target, :open_line, :close_line, :ideal_checksum, :package, :export,
|
|
21
21
|
:source_checksum, :silenced
|
|
22
22
|
)
|
|
23
|
+
ProvisionedFileRecord = Struct.new(:path, :content_hash, :package, :export)
|
|
23
24
|
|
|
24
25
|
Lockfile = Struct.new(
|
|
25
26
|
:prayfile_lock, :spec, :generated_by, :manifest_hash, :environment, :source, :package,
|
|
26
|
-
:target, :managed_span
|
|
27
|
+
:target, :managed_span, :provisioned
|
|
27
28
|
) do
|
|
28
29
|
def initialize(
|
|
29
30
|
prayfile_lock: "1", spec: "0.1", generated_by: Pray::GENERATED_BY,
|
|
30
|
-
manifest_hash: "", environment: nil, source: [], package: [], target: [], managed_span: []
|
|
31
|
+
manifest_hash: "", environment: nil, source: [], package: [], target: [], managed_span: [],
|
|
32
|
+
provisioned: []
|
|
31
33
|
)
|
|
32
34
|
super
|
|
33
35
|
end
|
|
@@ -38,6 +40,7 @@ module Pray
|
|
|
38
40
|
copy.package = package.sort_by { |entry| [entry.name, entry.source.to_s, entry.version] }
|
|
39
41
|
copy.target = target.sort_by(&:name)
|
|
40
42
|
copy.managed_span = managed_span.sort_by { |span| [span.target, span.open_line, span.id] }
|
|
43
|
+
copy.provisioned = provisioned.sort_by { |entry| [entry.path, entry.package] }
|
|
41
44
|
end
|
|
42
45
|
end
|
|
43
46
|
|
|
@@ -70,7 +73,8 @@ module Pray
|
|
|
70
73
|
"source" => lockfile.source.map { |entry| source_to_hash(entry) },
|
|
71
74
|
"package" => lockfile.package.map { |entry| package_to_hash(entry) },
|
|
72
75
|
"target" => lockfile.target.map { |entry| {"name" => entry.name, "outputs" => entry.outputs} },
|
|
73
|
-
"managed_span" => lockfile.managed_span.map { |entry| managed_span_to_hash(entry) }
|
|
76
|
+
"managed_span" => lockfile.managed_span.map { |entry| managed_span_to_hash(entry) },
|
|
77
|
+
"provisioned" => lockfile.provisioned.map { |entry| provisioned_to_hash(entry) }
|
|
74
78
|
}
|
|
75
79
|
end
|
|
76
80
|
|
|
@@ -111,6 +115,15 @@ module Pray
|
|
|
111
115
|
}
|
|
112
116
|
end
|
|
113
117
|
|
|
118
|
+
def provisioned_to_hash(entry)
|
|
119
|
+
{
|
|
120
|
+
"path" => entry.path,
|
|
121
|
+
"content_hash" => entry.content_hash,
|
|
122
|
+
"package" => entry.package,
|
|
123
|
+
"export" => entry.export
|
|
124
|
+
}
|
|
125
|
+
end
|
|
126
|
+
|
|
114
127
|
def parse_lockfile(text)
|
|
115
128
|
data = TomlRB.parse(text)
|
|
116
129
|
from_hash(data)
|
|
@@ -147,8 +160,9 @@ module Pray
|
|
|
147
160
|
left.equivalent_to?(right)
|
|
148
161
|
end
|
|
149
162
|
|
|
150
|
-
def build_lockfile(manifest_hash, environment, project_root, manifest_sources, manifest_targets, rendered, packages, source_revisions, source_host_keys)
|
|
151
|
-
|
|
163
|
+
def build_lockfile(manifest_hash, environment, project_root, manifest_sources, manifest_targets, rendered, packages, source_revisions, source_host_keys, project = nil)
|
|
164
|
+
rendered = Render.layout_rendered_targets(project, rendered) if project
|
|
165
|
+
lockfile = Lockfile.new(
|
|
152
166
|
manifest_hash: manifest_hash,
|
|
153
167
|
environment: environment,
|
|
154
168
|
source: manifest_sources.map do |source|
|
|
@@ -176,7 +190,11 @@ module Pray
|
|
|
176
190
|
end,
|
|
177
191
|
target: manifest_targets.map { |target| LockedTarget.new(name: target.name, outputs: target.outputs) },
|
|
178
192
|
managed_span: rendered.flat_map(&:managed_spans)
|
|
179
|
-
)
|
|
193
|
+
)
|
|
194
|
+
if project
|
|
195
|
+
lockfile.provisioned = RenderDest.provisioned_records(project)
|
|
196
|
+
end
|
|
197
|
+
lockfile.canonicalized
|
|
180
198
|
end
|
|
181
199
|
|
|
182
200
|
def from_hash(data)
|
|
@@ -222,6 +240,14 @@ module Pray
|
|
|
222
240
|
source_checksum: entry["source_checksum"],
|
|
223
241
|
silenced: entry["silenced"]
|
|
224
242
|
)
|
|
243
|
+
end,
|
|
244
|
+
provisioned: Array(data["provisioned"]).map do |entry|
|
|
245
|
+
ProvisionedFileRecord.new(
|
|
246
|
+
path: entry["path"],
|
|
247
|
+
content_hash: entry["content_hash"],
|
|
248
|
+
package: entry["package"],
|
|
249
|
+
export: entry["export"]
|
|
250
|
+
)
|
|
225
251
|
end
|
|
226
252
|
)
|
|
227
253
|
end
|
|
@@ -22,6 +22,7 @@ module Pray
|
|
|
22
22
|
lockfile.package.each { |entry| append_section(lines, format_package(entry)) }
|
|
23
23
|
lockfile.target.each { |entry| append_section(lines, format_target(entry)) }
|
|
24
24
|
lockfile.managed_span.each { |entry| append_section(lines, format_managed_span(entry)) }
|
|
25
|
+
lockfile.provisioned.each { |entry| append_section(lines, format_provisioned(entry)) }
|
|
25
26
|
|
|
26
27
|
lines.pop while lines.last == ""
|
|
27
28
|
"#{lines.join("\n")}\n"
|
|
@@ -94,6 +95,20 @@ module Pray
|
|
|
94
95
|
lines
|
|
95
96
|
end
|
|
96
97
|
|
|
98
|
+
def format_provisioned(entry)
|
|
99
|
+
lines = ["[[provisioned]]"]
|
|
100
|
+
append_scalars(
|
|
101
|
+
lines,
|
|
102
|
+
[
|
|
103
|
+
["path", entry.path],
|
|
104
|
+
["content_hash", entry.content_hash],
|
|
105
|
+
["package", entry.package],
|
|
106
|
+
["export", entry.export]
|
|
107
|
+
]
|
|
108
|
+
)
|
|
109
|
+
lines
|
|
110
|
+
end
|
|
111
|
+
|
|
97
112
|
def append_scalars(lines, entries)
|
|
98
113
|
entries.each do |key, value|
|
|
99
114
|
lines << case value
|
data/lib/pray/manifest.rb
CHANGED
|
@@ -9,27 +9,25 @@ require_relative "path_safety"
|
|
|
9
9
|
|
|
10
10
|
module Pray
|
|
11
11
|
RenderPolicy = Struct.new(
|
|
12
|
-
:mode, :conflict, :churn, :header
|
|
12
|
+
:mode, :conflict, :churn, :header
|
|
13
13
|
) do
|
|
14
14
|
def self.default
|
|
15
15
|
new(
|
|
16
16
|
mode: "managed",
|
|
17
17
|
conflict: "fail",
|
|
18
18
|
churn: "minimal",
|
|
19
|
-
header: true
|
|
20
|
-
section_markers: true,
|
|
21
|
-
line_endings: "lf"
|
|
19
|
+
header: true
|
|
22
20
|
)
|
|
23
21
|
end
|
|
24
22
|
end
|
|
25
23
|
|
|
26
24
|
ManifestSource = Struct.new(:name, :kind, :url, :subdir, :rev, :tag)
|
|
27
25
|
ManifestTarget = Struct.new(
|
|
28
|
-
:name, :outputs, :skills, :commands, :rules, :max_bytes, :mode, :scoped, :entries
|
|
26
|
+
:name, :outputs, :skills, :commands, :rules, :max_bytes, :mode, :scoped, :entries, :header
|
|
29
27
|
) do
|
|
30
28
|
def initialize(
|
|
31
29
|
name:, outputs: [], skills: [], commands: [], rules: [], max_bytes: nil,
|
|
32
|
-
mode: "legacy", scoped: false, entries: []
|
|
30
|
+
mode: "legacy", scoped: false, entries: [], header: nil
|
|
33
31
|
)
|
|
34
32
|
super
|
|
35
33
|
end
|
|
@@ -73,7 +71,7 @@ module Pray
|
|
|
73
71
|
end
|
|
74
72
|
|
|
75
73
|
def note_deprecated_keyword(keyword)
|
|
76
|
-
return unless %w[target output agent].include?(keyword)
|
|
74
|
+
return unless %w[target output agent skills].include?(keyword)
|
|
77
75
|
self.deprecated_keywords ||= []
|
|
78
76
|
deprecated_keywords << keyword unless deprecated_keywords.include?(keyword)
|
|
79
77
|
end
|
|
@@ -82,7 +80,10 @@ module Pray
|
|
|
82
80
|
replacements = {
|
|
83
81
|
"target" => "compose` / `tree",
|
|
84
82
|
"output" => "compose",
|
|
85
|
-
"agent" => "pray"
|
|
83
|
+
"agent" => "pray",
|
|
84
|
+
"skills" => "tree` / `folder",
|
|
85
|
+
"skill" => "folder",
|
|
86
|
+
"spec.skills" => "a folder export"
|
|
86
87
|
}
|
|
87
88
|
(deprecated_keywords || []).filter_map do |keyword|
|
|
88
89
|
replacement = replacements[keyword]
|
|
@@ -127,12 +128,12 @@ module Pray
|
|
|
127
128
|
def validate_manifest_paths!(manifest)
|
|
128
129
|
manifest.targets.each do |target|
|
|
129
130
|
(target.outputs + target.skills + target.commands + target.rules).each do |path|
|
|
130
|
-
PathSafety.
|
|
131
|
+
PathSafety.validate_destination_path!(path)
|
|
131
132
|
end
|
|
132
133
|
end
|
|
133
134
|
manifest.packages.each do |package|
|
|
134
135
|
PathSafety.validate_project_relative_path!(package.path) if package.path
|
|
135
|
-
PathSafety.
|
|
136
|
+
PathSafety.validate_destination_path!(package.file) if package.file
|
|
136
137
|
end
|
|
137
138
|
manifest.local.each { |local| PathSafety.validate_project_relative_path!(local.path) }
|
|
138
139
|
end
|
data/lib/pray/manifest_json.rb
CHANGED
|
@@ -34,7 +34,7 @@ module Pray
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def target_fields(target)
|
|
37
|
-
{
|
|
37
|
+
fields = {
|
|
38
38
|
"name" => target.name,
|
|
39
39
|
"outputs" => target.outputs,
|
|
40
40
|
"skills" => target.skills,
|
|
@@ -45,6 +45,8 @@ module Pray
|
|
|
45
45
|
"scoped" => target.scoped || false,
|
|
46
46
|
"entries" => (target.entries || []).map { |entry| entry_fields(entry) }
|
|
47
47
|
}
|
|
48
|
+
fields["header"] = target.header unless target.header.nil?
|
|
49
|
+
fields
|
|
48
50
|
end
|
|
49
51
|
|
|
50
52
|
def entry_fields(entry)
|
|
@@ -93,9 +95,7 @@ module Pray
|
|
|
93
95
|
"mode" => render.mode,
|
|
94
96
|
"conflict" => render.conflict,
|
|
95
97
|
"churn" => render.churn,
|
|
96
|
-
"header" => render.header
|
|
97
|
-
"section_markers" => render.section_markers,
|
|
98
|
-
"line_endings" => render.line_endings
|
|
98
|
+
"header" => render.header
|
|
99
99
|
}
|
|
100
100
|
end
|
|
101
101
|
|
data/lib/pray/manifest_parser.rb
CHANGED
|
@@ -104,6 +104,7 @@ module Pray
|
|
|
104
104
|
if statement.start_with?("folder ", "skills ") && !statement.end_with?(" do")
|
|
105
105
|
raise Error.parse("manifest", "top-level folder/skills must use a tree block")
|
|
106
106
|
end
|
|
107
|
+
manifest.note_deprecated_keyword("skills") if statement.start_with?("skills ")
|
|
107
108
|
parse_destination_block(manifest, rest, "tree")
|
|
108
109
|
end
|
|
109
110
|
|
|
@@ -55,11 +55,13 @@ module Pray
|
|
|
55
55
|
end
|
|
56
56
|
|
|
57
57
|
header = rest.sub(/\s*do\z/, "").strip
|
|
58
|
-
values, = parse_call(header)
|
|
58
|
+
values, keywords = parse_call(header)
|
|
59
59
|
raise Error.parse("manifest", "destination missing path") if values.empty?
|
|
60
60
|
|
|
61
61
|
path = string_from_value(values.first)
|
|
62
|
-
|
|
62
|
+
target = Destination.new_destination_target(mode, path)
|
|
63
|
+
target.header = destination_header_keyword(mode, keywords)
|
|
64
|
+
manifest.targets << target
|
|
63
65
|
index = manifest.targets.length - 1
|
|
64
66
|
parse_destination_body(manifest, index, mode)
|
|
65
67
|
end
|
|
@@ -207,6 +209,7 @@ module Pray
|
|
|
207
209
|
raise Error.manifest("target index out of range") unless target
|
|
208
210
|
|
|
209
211
|
manifest.note_deprecated_keyword("output") if statement.start_with?("output ")
|
|
212
|
+
manifest.note_deprecated_keyword("skills") if statement.start_with?("skills ")
|
|
210
213
|
apply_target_statement(target, statement)
|
|
211
214
|
end
|
|
212
215
|
raise Error.parse("manifest", "missing 'end' for target block")
|
|
@@ -170,23 +170,52 @@ module Pray
|
|
|
170
170
|
|
|
171
171
|
def parse_render_policy(rest)
|
|
172
172
|
_, keywords = parse_call(rest)
|
|
173
|
+
keywords.each_key do |key|
|
|
174
|
+
unless %w[mode conflict churn header].include?(key)
|
|
175
|
+
raise Error.parse("manifest", "render does not accept #{key}")
|
|
176
|
+
end
|
|
177
|
+
end
|
|
173
178
|
conflict = keywords["conflict"]&.as_string || "fail"
|
|
174
179
|
unless conflict == "fail"
|
|
175
180
|
raise Error.unsupported(
|
|
176
181
|
"render conflict :#{conflict} is not implemented; only :fail is supported"
|
|
177
182
|
)
|
|
178
183
|
end
|
|
184
|
+
mode = keywords["mode"]&.as_string || "managed"
|
|
185
|
+
unless mode == "managed"
|
|
186
|
+
raise Error.unsupported("render mode :#{mode} is not implemented")
|
|
187
|
+
end
|
|
188
|
+
churn = keywords["churn"]&.as_string || "minimal"
|
|
189
|
+
unless churn == "minimal"
|
|
190
|
+
raise Error.unsupported("render churn :#{churn} is not implemented")
|
|
191
|
+
end
|
|
179
192
|
|
|
180
193
|
RenderPolicy.new(
|
|
181
|
-
mode:
|
|
194
|
+
mode: mode,
|
|
182
195
|
conflict: conflict,
|
|
183
|
-
churn:
|
|
184
|
-
header: keyword_bool(keywords, "header", true)
|
|
185
|
-
section_markers: keyword_bool(keywords, "section_markers", true),
|
|
186
|
-
line_endings: keywords["line_endings"]&.as_string || "lf"
|
|
196
|
+
churn: churn,
|
|
197
|
+
header: keyword_bool(keywords, "header", true)
|
|
187
198
|
)
|
|
188
199
|
end
|
|
189
200
|
|
|
201
|
+
def destination_header_keyword(mode, keywords)
|
|
202
|
+
label = (mode == "compose") ? "compose" : "tree"
|
|
203
|
+
keywords.each_key do |key|
|
|
204
|
+
unless key == "header"
|
|
205
|
+
raise Error.parse("manifest", "#{label} does not accept #{key}")
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
return if keywords["header"].nil?
|
|
209
|
+
unless mode == "compose"
|
|
210
|
+
raise Error.parse("manifest", "#{label} does not accept header")
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
value = keywords["header"].as_bool
|
|
214
|
+
raise Error.parse("manifest", "header must be true or false") if value.nil?
|
|
215
|
+
|
|
216
|
+
value
|
|
217
|
+
end
|
|
218
|
+
|
|
190
219
|
def keyword_bool(keywords, key, default)
|
|
191
220
|
value = keywords[key]
|
|
192
221
|
return default if value.nil?
|
data/lib/pray/materialize.rb
CHANGED
|
@@ -40,6 +40,7 @@ module Pray
|
|
|
40
40
|
end
|
|
41
41
|
rendered = Render.render_project(project)
|
|
42
42
|
lockfile_path = default_lockfile_path(project.project_root)
|
|
43
|
+
previous_lockfile = File.exist?(lockfile_path) ? Pray.read_lockfile(lockfile_path) : nil
|
|
43
44
|
next_lockfile = LockfileIO.build_lockfile(
|
|
44
45
|
project.manifest_hash,
|
|
45
46
|
project.environment,
|
|
@@ -49,7 +50,8 @@ module Pray
|
|
|
49
50
|
rendered,
|
|
50
51
|
project.packages,
|
|
51
52
|
project.source_revisions,
|
|
52
|
-
project.source_host_keys
|
|
53
|
+
project.source_host_keys,
|
|
54
|
+
project
|
|
53
55
|
)
|
|
54
56
|
|
|
55
57
|
if locked
|
|
@@ -61,7 +63,7 @@ module Pray
|
|
|
61
63
|
raise Error.verify("lockfile needs update; rerun install to refresh Prayfile.lock")
|
|
62
64
|
end
|
|
63
65
|
unless frozen
|
|
64
|
-
Render.write_rendered_targets(project, rendered)
|
|
66
|
+
Render.write_rendered_targets(project, rendered, existing)
|
|
65
67
|
end
|
|
66
68
|
if frozen
|
|
67
69
|
rendered.each do |target|
|
|
@@ -89,8 +91,8 @@ module Pray
|
|
|
89
91
|
end
|
|
90
92
|
end
|
|
91
93
|
|
|
94
|
+
Render.write_rendered_targets(project, rendered, previous_lockfile)
|
|
92
95
|
Pray.write_lockfile_if_changed(lockfile_path, next_lockfile)
|
|
93
|
-
Render.write_rendered_targets(project, rendered)
|
|
94
96
|
end
|
|
95
97
|
|
|
96
98
|
def default_manifest_path(working_directory = Dir.pwd)
|
data/lib/pray/package_spec.rb
CHANGED
|
@@ -24,6 +24,25 @@ module Pray
|
|
|
24
24
|
super
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
def deprecation_warnings
|
|
28
|
+
keywords = []
|
|
29
|
+
keywords << "spec.skills" unless skills.nil? || skills.empty?
|
|
30
|
+
keywords << "skill" if (exports || {}).values.any? { |export| export.kind == "skill" }
|
|
31
|
+
Manifest.new(deprecated_keywords: keywords).deprecation_warnings
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.warn_resolved_deprecations(packages)
|
|
35
|
+
seen = {}
|
|
36
|
+
packages.each do |package|
|
|
37
|
+
package.spec.deprecation_warnings.each do |warning|
|
|
38
|
+
next if seen[warning]
|
|
39
|
+
|
|
40
|
+
seen[warning] = true
|
|
41
|
+
warn(warning)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
27
46
|
def canonicalized
|
|
28
47
|
dup.tap do |copy|
|
|
29
48
|
copy.files = files.sort
|