pray-cli 1.16.0 → 1.17.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/apply_report.rb +49 -0
- data/lib/pray/cli/commands/init.rb +1 -20
- data/lib/pray/cli/commands/init_prayer.rb +76 -0
- data/lib/pray/cli/commands/init_prayer_manifest.rb +102 -0
- data/lib/pray/cli/commands/packages.rb +13 -2
- data/lib/pray/cli/commands/workflow.rb +5 -0
- data/lib/pray/cli/help.rb +52 -15
- data/lib/pray/cli/helpers.rb +13 -5
- data/lib/pray/cli/parse.rb +21 -1
- data/lib/pray/cli.rb +3 -1
- data/lib/pray/destination.rb +32 -2
- data/lib/pray/distribution.rb +94 -0
- data/lib/pray/format_serialize.rb +1 -4
- data/lib/pray/local_prayer.rb +38 -0
- data/lib/pray/lockfile.rb +8 -2
- data/lib/pray/manifest.rb +6 -3
- data/lib/pray/manifest_json.rb +1 -0
- data/lib/pray/manifest_parser_blocks.rb +15 -15
- data/lib/pray/materialize.rb +7 -1
- data/lib/pray/package_spec.rb +25 -13
- data/lib/pray/package_spec_render.rb +4 -3
- data/lib/pray/package_spec_version.rb +33 -0
- data/lib/pray/plan.rb +2 -1
- data/lib/pray/publish.rb +27 -2
- data/lib/pray/render.rb +6 -36
- data/lib/pray/render_managed.rb +64 -0
- data/lib/pray/render_patch.rb +63 -12
- data/lib/pray/resolve.rb +5 -36
- data/lib/pray/resolve_local.rb +39 -0
- data/lib/pray/resolve_source.rb +5 -0
- data/lib/pray/serve.rb +15 -4
- data/lib/pray/serve_range.rb +36 -0
- data/lib/pray/torrent_manifest.rb +78 -0
- data/lib/pray/upstream_drift.rb +42 -0
- data/lib/pray/upstream_merge.rb +25 -0
- data/lib/pray/upstream_refresh.rb +28 -2
- data/lib/pray/verify.rb +2 -2
- data/lib/pray/version.rb +2 -2
- data/lib/pray.rb +10 -0
- metadata +12 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f7499680372bceb27c55d6d182225ea43870e4bfd94a431a085e42198ff90557
|
|
4
|
+
data.tar.gz: 32af3f08b8ea05718970b3283f04e81552ff8355ad0ca260d3b1c61b3ab05619
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1ea412c96b4c0d3cb766f75bec70dcd9e29c368594bf67d4438f8464703dbe4cd781dff6b093eefa8f46269de3886d09ff4bf27c8c02503a6e61650413439667
|
|
7
|
+
data.tar.gz: 389eb906dbdae17bf789290a450e10c5a6ccae9064f707d4f494e444e4efdaf62283103adf3f0bc335d705a99292efb1885c87da4c1daab40677a6a0fbd9e0d0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 1.17.0 (2026-09-16)
|
|
4
|
+
|
|
5
|
+
- Locate local prayers through a path source of any directory name (RFC 0117). `pray prayer init` adds `source "local"` when needed and declares `pray "local/project"` once. Version is optional until `pray package` or `pray publish`. `.agents/project.md` remains a compose shortcut for one local file.
|
|
6
|
+
- Copy upstream files into an empty path-fork tree on `pray install` and `pray update` (RFC 0116). Extra listed overlay files stay. `pray outdated` lists fork files that differ from the locked upstream. An identity-only fork uses empty `spec.files`; refresh lists content paths, not the prayspec file.
|
|
7
|
+
- Refuse a local path with `file:` and a local path inside `tree`. Compose still embeds local fragments. Package `file:` and package `tree` stay.
|
|
8
|
+
- Name dest versus lock on `pray verify`, dest versus a fresh render on `pray drift`, and dest write on `pray render`. Dry-run of a write is `pray plan`. Listed local compose files stay on `pray install`.
|
|
9
|
+
- Write `.praytorrent.json` when `v1/distribution.json` lists `protocols: ["torrent"]` (RFC 0062). `pray repo init` writes empty protocols.
|
|
10
|
+
- Parse `spec.maintainers` as a string array, and accept `spec.pray_version` as an alias for `spec.prayfile_version`.
|
|
11
|
+
- Re-embed a changed local compose source on `pray install`, `pray update`, and `pray plan`, and print its `sha256` as `checked` or `was`. List local checksum drift under `pray outdated`.
|
|
12
|
+
|
|
3
13
|
## 1.16.0 (2026-09-15)
|
|
4
14
|
|
|
5
15
|
- Rewrite a two-component pessimistic Prayfile pin on `pray update --latest` (`~> 2.2` to `~> 2.4` when registry latest is 2.4.0), matching the Rust and TypeScript CLIs. Install a newer version that the current constraint already allows.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module ApplyReport
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def local_summary_lines(previous, project)
|
|
8
|
+
previous_checksums = previous_local_checksums(previous)
|
|
9
|
+
project.local_files.filter_map do |local|
|
|
10
|
+
next if local.content.empty? && local.optional
|
|
11
|
+
|
|
12
|
+
checksum = local.source_checksum
|
|
13
|
+
previous_checksum = previous_checksums[local.manifest_path]
|
|
14
|
+
if previous_checksum.nil?
|
|
15
|
+
"Installing #{local.manifest_path} (#{checksum})"
|
|
16
|
+
elsif previous_checksum == checksum
|
|
17
|
+
"Using #{local.manifest_path} (#{checksum} checked)"
|
|
18
|
+
else
|
|
19
|
+
"Updating #{local.manifest_path} (#{checksum} was #{previous_checksum})"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def outdated_local_lines(previous, project)
|
|
25
|
+
previous_checksums = previous_local_checksums(previous)
|
|
26
|
+
project.local_files.filter_map do |local|
|
|
27
|
+
next if local.content.empty? && local.optional
|
|
28
|
+
|
|
29
|
+
checksum = local.source_checksum
|
|
30
|
+
previous_checksum = previous_checksums[local.manifest_path]
|
|
31
|
+
if previous_checksum && previous_checksum != checksum
|
|
32
|
+
"#{local.manifest_path} #{previous_checksum} -> #{checksum}"
|
|
33
|
+
elsif previous && previous_checksum.nil?
|
|
34
|
+
"#{local.manifest_path} (new) -> #{checksum}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def previous_local_checksums(previous)
|
|
40
|
+
return {} unless previous
|
|
41
|
+
|
|
42
|
+
previous.managed_span.each_with_object({}) do |span, checksums|
|
|
43
|
+
next unless span.package == LOCAL_EMBED_PACKAGE
|
|
44
|
+
|
|
45
|
+
checksums[span.export] = span.source_checksum
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -25,26 +25,6 @@ module Pray
|
|
|
25
25
|
File.write(path, "#{lines.join("\n")}\n")
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
def prayer_init_command
|
|
29
|
-
root = Dir.pwd
|
|
30
|
-
package_name = File.basename(root)
|
|
31
|
-
package_name = "prayer-package" if package_name.strip.empty?
|
|
32
|
-
prayspec_path = File.join(root, "#{package_name}.prayspec")
|
|
33
|
-
raise Error.manifest("package spec already exists: #{prayspec_path}") if File.exist?(prayspec_path)
|
|
34
|
-
|
|
35
|
-
File.write(
|
|
36
|
-
prayspec_path,
|
|
37
|
-
<<~SPEC
|
|
38
|
-
Package::Specification.new do |spec|
|
|
39
|
-
spec.name = "#{package_name}"
|
|
40
|
-
spec.version = "0.1.0"
|
|
41
|
-
spec.summary = "Prayer package"
|
|
42
|
-
spec.files = []
|
|
43
|
-
end
|
|
44
|
-
SPEC
|
|
45
|
-
)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
28
|
def repo_init_command
|
|
49
29
|
distribution_root = repo_distribution_root(Dir.pwd)
|
|
50
30
|
index_path = File.join(distribution_root, "v1", "index.json")
|
|
@@ -53,6 +33,7 @@ module Pray
|
|
|
53
33
|
FileUtils.mkdir_p(File.join(distribution_root, "v1", "packages"))
|
|
54
34
|
FileUtils.mkdir_p(File.join(distribution_root, "v1", "artifacts"))
|
|
55
35
|
Publish.write_registry_index(distribution_root, RegistryIndex.new)
|
|
36
|
+
Distribution.write_settings(distribution_root)
|
|
56
37
|
end
|
|
57
38
|
|
|
58
39
|
def repo_distribution_root(root)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Pray
|
|
6
|
+
module CLI
|
|
7
|
+
def prayer_init_command(name = nil, directory = nil)
|
|
8
|
+
if File.exist?(manifest_path)
|
|
9
|
+
scaffold_local_prayer(name, directory)
|
|
10
|
+
else
|
|
11
|
+
scaffold_standalone_package(name)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def scaffold_local_prayer(name, directory)
|
|
16
|
+
requested = name.to_s.strip
|
|
17
|
+
package_name = LocalPrayer.validate_name!(requested.empty? ? DEFAULT_LOCAL_PRAYER_NAME : requested)
|
|
18
|
+
source_name, source_directory = resolve_local_prayer_directory(directory)
|
|
19
|
+
root = File.join(project_root, source_directory, package_name)
|
|
20
|
+
prayspec_path = File.join(root, "#{package_name}.prayspec")
|
|
21
|
+
raise Error.manifest("package spec already exists: #{prayspec_path}") if File.exist?(prayspec_path)
|
|
22
|
+
raise Error.manifest("prayer directory already exists: #{root}") if File.exist?(root)
|
|
23
|
+
|
|
24
|
+
FileUtils.mkdir_p(File.join(root, "exports"))
|
|
25
|
+
File.write(prayspec_path, local_prayspec(source_name, package_name))
|
|
26
|
+
File.write(File.join(root, "README.md"), "# #{package_name}\n")
|
|
27
|
+
File.write(File.join(root, "exports", "#{package_name}.md"), "# #{package_name}\n")
|
|
28
|
+
declare_local_prayer("#{source_name}/#{package_name}")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def scaffold_standalone_package(name)
|
|
32
|
+
root = Dir.pwd
|
|
33
|
+
package_name = if name.nil? || name.strip.empty?
|
|
34
|
+
basename = File.basename(root)
|
|
35
|
+
basename.strip.empty? ? "prayer-package" : basename
|
|
36
|
+
else
|
|
37
|
+
LocalPrayer.validate_name!(name)
|
|
38
|
+
end
|
|
39
|
+
prayspec_path = File.join(root, "#{package_name}.prayspec")
|
|
40
|
+
raise Error.manifest("package spec already exists: #{prayspec_path}") if File.exist?(prayspec_path)
|
|
41
|
+
|
|
42
|
+
File.write(prayspec_path, standalone_prayspec(package_name))
|
|
43
|
+
readme = File.join(root, "README.md")
|
|
44
|
+
File.write(readme, "# #{package_name}\n") unless File.exist?(readme)
|
|
45
|
+
FileUtils.mkdir_p(File.join(root, "exports"))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def local_prayspec(source_name, name)
|
|
49
|
+
<<~SPEC
|
|
50
|
+
Package::Specification.new do |spec|
|
|
51
|
+
spec.name = "#{source_name}/#{name}"
|
|
52
|
+
spec.summary = "Describe this package"
|
|
53
|
+
spec.files = ["README.md", "exports/#{name}.md"]
|
|
54
|
+
spec.exports = {
|
|
55
|
+
"#{name}" => {
|
|
56
|
+
type: "fragment",
|
|
57
|
+
path: "exports/#{name}.md"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
SPEC
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def standalone_prayspec(name)
|
|
65
|
+
<<~SPEC
|
|
66
|
+
Package::Specification.new do |spec|
|
|
67
|
+
spec.name = "#{name}"
|
|
68
|
+
spec.version = "0.1.0"
|
|
69
|
+
spec.summary = "Describe this package"
|
|
70
|
+
spec.files = ["README.md"]
|
|
71
|
+
spec.exports = {}
|
|
72
|
+
end
|
|
73
|
+
SPEC
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Pray
|
|
4
|
+
module CLI
|
|
5
|
+
def resolve_local_prayer_directory(requested)
|
|
6
|
+
manifest = Pray.parse_manifest(Pray.read_manifest_text(manifest_path))
|
|
7
|
+
path_sources = path_sources_for(manifest)
|
|
8
|
+
if requested
|
|
9
|
+
directory = requested.to_s.strip
|
|
10
|
+
raise Error.usage("--path requires a directory") if directory.empty?
|
|
11
|
+
|
|
12
|
+
matching = path_sources.find { |source| source.url == directory }
|
|
13
|
+
return [matching.name, matching.url] if matching
|
|
14
|
+
if path_sources.length == 1
|
|
15
|
+
raise Error.usage("path source already uses #{path_sources.first.url}")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
name = ensure_path_source(directory)
|
|
19
|
+
return [name, directory]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
case path_sources.length
|
|
23
|
+
when 0
|
|
24
|
+
name = ensure_path_source(DEFAULT_PATH_SOURCE_DIRECTORY)
|
|
25
|
+
[name, DEFAULT_PATH_SOURCE_DIRECTORY]
|
|
26
|
+
when 1
|
|
27
|
+
[path_sources.first.name, path_sources.first.url]
|
|
28
|
+
else
|
|
29
|
+
raise Error.usage("say which directory with --path")
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def declare_local_prayer(package_name)
|
|
34
|
+
manifest_text = Pray.read_manifest_text(manifest_path)
|
|
35
|
+
manifest = Pray.parse_manifest(manifest_text)
|
|
36
|
+
return if manifest.packages.any? { |package| package.name == package_name }
|
|
37
|
+
|
|
38
|
+
statement = "pray \"#{package_name}\""
|
|
39
|
+
updated = insert_into_first_compose(manifest_text, statement) ||
|
|
40
|
+
insert_manifest_statement(manifest_text, statement)
|
|
41
|
+
Transaction.write_file(manifest_path, updated)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def path_sources_for(manifest)
|
|
45
|
+
manifest.sources.select { |source| source.kind == "path" }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def ensure_path_source(directory)
|
|
49
|
+
manifest_text = Pray.read_manifest_text(manifest_path)
|
|
50
|
+
manifest = Pray.parse_manifest(manifest_text)
|
|
51
|
+
existing = manifest.sources.find { |source| source.kind == "path" && source.url == directory }
|
|
52
|
+
return existing.name if existing
|
|
53
|
+
|
|
54
|
+
name = unused_source_name(manifest, directory)
|
|
55
|
+
statement = "source \"#{name}\", path: \"#{directory}\""
|
|
56
|
+
Transaction.write_file(manifest_path, insert_source_statement(manifest_text, statement))
|
|
57
|
+
name
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def unused_source_name(manifest, directory)
|
|
61
|
+
taken = manifest.sources.map(&:name)
|
|
62
|
+
return DEFAULT_PATH_SOURCE_NAME unless taken.include?(DEFAULT_PATH_SOURCE_NAME)
|
|
63
|
+
|
|
64
|
+
fallback = directory.to_s.split(%r{[/\\]}).reject(&:empty?).last || DEFAULT_PATH_SOURCE_NAME
|
|
65
|
+
raise Error.manifest("source name #{fallback} is already used") if taken.include?(fallback)
|
|
66
|
+
|
|
67
|
+
fallback
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def insert_source_statement(text, statement)
|
|
71
|
+
lines = text.lines.map(&:chomp)
|
|
72
|
+
last_source = lines.rindex { |line| line.lstrip.start_with?("source ") }
|
|
73
|
+
insertion_index = if last_source
|
|
74
|
+
last_source + 1
|
|
75
|
+
else
|
|
76
|
+
prayfile = lines.index { |line| line.lstrip.start_with?("prayfile ") }
|
|
77
|
+
prayfile ? prayfile + 1 : 0
|
|
78
|
+
end
|
|
79
|
+
lines.insert(insertion_index, statement)
|
|
80
|
+
join_manifest_lines(lines)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def insert_into_first_compose(text, statement)
|
|
84
|
+
lines = text.lines.map(&:chomp)
|
|
85
|
+
index = lines.index do |line|
|
|
86
|
+
trimmed = line.lstrip
|
|
87
|
+
trimmed.start_with?("compose ") && trimmed.end_with?(" do")
|
|
88
|
+
end
|
|
89
|
+
return unless index
|
|
90
|
+
|
|
91
|
+
indent = lines[index][/\A\s*/].to_s
|
|
92
|
+
lines.insert(index + 1, "#{indent} #{statement}")
|
|
93
|
+
join_manifest_lines(lines)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def join_manifest_lines(lines)
|
|
97
|
+
output = lines.join("\n")
|
|
98
|
+
output += "\n" unless output.end_with?("\n")
|
|
99
|
+
output
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -36,7 +36,7 @@ module Pray
|
|
|
36
36
|
project = resolve_current_project
|
|
37
37
|
lines = ["Package list"]
|
|
38
38
|
project.packages.each do |package|
|
|
39
|
-
lines << "#{package.declaration.name} #{package.spec.
|
|
39
|
+
lines << "#{package.declaration.name} #{package.spec.recorded_version} source=#{package_source_summary(package)} exports=#{format_list(package.selected_exports)}"
|
|
40
40
|
end
|
|
41
41
|
puts lines.join("\n")
|
|
42
42
|
end
|
|
@@ -54,6 +54,7 @@ module Pray
|
|
|
54
54
|
def package_command
|
|
55
55
|
project = resolve_current_project
|
|
56
56
|
project.packages.each do |package|
|
|
57
|
+
package.spec.require_release_version!
|
|
57
58
|
output_path = Archive.package_archive_path(package.declaration.name, package.spec.version)
|
|
58
59
|
Archive.write_package_archive(package, output_path)
|
|
59
60
|
end
|
|
@@ -72,7 +73,7 @@ module Pray
|
|
|
72
73
|
lines = ["Package explanation"]
|
|
73
74
|
lines << "name: #{package.declaration.name}"
|
|
74
75
|
lines << "constraint: #{package.declaration.constraint}"
|
|
75
|
-
lines << "resolved version: #{package.spec.
|
|
76
|
+
lines << "resolved version: #{package.spec.recorded_version}"
|
|
76
77
|
if package.registry_latest_version
|
|
77
78
|
lines << "registry latest: #{package.registry_latest_version}"
|
|
78
79
|
end
|
|
@@ -102,6 +103,16 @@ module Pray
|
|
|
102
103
|
else
|
|
103
104
|
Plan.package_summary_lines(previous_lockfile, latest_lockfile, project).each { |line| puts line }
|
|
104
105
|
end
|
|
106
|
+
local_lines = ApplyReport.outdated_local_lines(previous_lockfile, project)
|
|
107
|
+
unless local_lines.empty?
|
|
108
|
+
puts "Outdated local files"
|
|
109
|
+
local_lines.each { |line| puts line }
|
|
110
|
+
end
|
|
111
|
+
fork_lines = Upstream.path_fork_drift_lines(project, previous_lockfile, ResolveOptions.new)
|
|
112
|
+
unless fork_lines.empty?
|
|
113
|
+
puts "Outdated path forks"
|
|
114
|
+
fork_lines.each { |line| puts line }
|
|
115
|
+
end
|
|
105
116
|
end
|
|
106
117
|
end
|
|
107
118
|
end
|
|
@@ -5,6 +5,7 @@ require "fileutils"
|
|
|
5
5
|
module Pray
|
|
6
6
|
module CLI
|
|
7
7
|
def install_command(flags)
|
|
8
|
+
previous_lockfile = File.exist?(lockfile_path) ? Pray.read_lockfile(lockfile_path) : nil
|
|
8
9
|
Pray.materialize_project(
|
|
9
10
|
manifest_path: manifest_path,
|
|
10
11
|
frozen: flags[:frozen],
|
|
@@ -14,6 +15,10 @@ module Pray
|
|
|
14
15
|
ignore_locked_versions: flags[:ignore_locked_versions],
|
|
15
16
|
unlocked_packages: flags[:unlocked_packages]
|
|
16
17
|
)
|
|
18
|
+
return if flags[:frozen] || flags[:locked]
|
|
19
|
+
|
|
20
|
+
project = resolve_current_project
|
|
21
|
+
ApplyReport.local_summary_lines(previous_lockfile, project).each { |line| puts line }
|
|
17
22
|
end
|
|
18
23
|
|
|
19
24
|
def render_command(flags)
|
data/lib/pray/cli/help.rb
CHANGED
|
@@ -4,19 +4,19 @@ module Pray
|
|
|
4
4
|
module CLI
|
|
5
5
|
module Help
|
|
6
6
|
WORKFLOW_COMMANDS = [
|
|
7
|
-
"install [--locked|--frozen|--offline]
|
|
8
|
-
"plan [--remote]
|
|
7
|
+
"install [--locked|--frozen|--offline] keep locked versions, re-embed listed locals",
|
|
8
|
+
"plan [--remote] dry-run of install: dest after patch",
|
|
9
9
|
"apply apply the current plan",
|
|
10
|
-
"verify [--strict]
|
|
11
|
-
"drift [--semantic]
|
|
12
|
-
"render [--check]
|
|
10
|
+
"verify [--strict] dest managed spans versus Prayfile.lock",
|
|
11
|
+
"drift [--semantic] dest versus lock, then dest versus a fresh render",
|
|
12
|
+
"render [--check] write dest from current inputs",
|
|
13
13
|
"format, fmt rewrite Prayfile to recommended destination DSL"
|
|
14
14
|
].freeze
|
|
15
15
|
|
|
16
16
|
PACKAGE_COMMANDS = [
|
|
17
17
|
"add <name> [constraint] [--path PATH] declare a package in Prayfile",
|
|
18
18
|
"remove <name> remove a package from Prayfile",
|
|
19
|
-
"update [package]",
|
|
19
|
+
"update [package] re-resolve packages and git sources within constraints",
|
|
20
20
|
"unlock <package> clear a locked package pin",
|
|
21
21
|
"vendor copy resolved packages locally",
|
|
22
22
|
"clean remove local cache and vendor trees"
|
|
@@ -43,7 +43,7 @@ module Pray
|
|
|
43
43
|
|
|
44
44
|
META_COMMANDS = [
|
|
45
45
|
"init [--targets tool_a,tool_b] create a starter Prayfile",
|
|
46
|
-
"prayer init
|
|
46
|
+
"prayer init [name] [--path DIR] scaffold a local prayer",
|
|
47
47
|
"repo init scaffold a distribution root",
|
|
48
48
|
"manifest print canonical Prayfile JSON",
|
|
49
49
|
"package build a distributable prayer archive",
|
|
@@ -58,33 +58,49 @@ module Pray
|
|
|
58
58
|
|
|
59
59
|
COMMAND_HELP = {
|
|
60
60
|
"install" => <<~TEXT.strip,
|
|
61
|
-
|
|
61
|
+
keep locked package versions and re-embed listed local compose sources
|
|
62
62
|
|
|
63
63
|
Usage: pray install [--locked|--frozen|--offline]
|
|
64
64
|
|
|
65
|
+
Install renders dest and writes Prayfile.lock. Package versions stay at the lock.
|
|
66
|
+
Listed local compose files are re-embedded and their span checksums refresh.
|
|
67
|
+
A path package with spec.upstream copies upstream files when the path tree has no content yet.
|
|
68
|
+
Use pray update to re-resolve package versions and git sources.
|
|
69
|
+
Use pray plan to preview a write.
|
|
70
|
+
|
|
65
71
|
--locked require an existing lockfile
|
|
66
|
-
--frozen
|
|
72
|
+
--frozen fail if dest or lock would change
|
|
67
73
|
--offline use cache only
|
|
68
74
|
TEXT
|
|
69
75
|
"verify" => <<~TEXT.strip,
|
|
70
|
-
|
|
76
|
+
compare dest managed spans versus Prayfile.lock
|
|
71
77
|
|
|
72
78
|
Usage: pray verify [--strict]
|
|
73
79
|
|
|
80
|
+
Read-only integrity check. It does not compare dest to a fresh render from current sources.
|
|
81
|
+
A changed local compose file is clean here when dest and lock still match.
|
|
82
|
+
Use pray drift to compare dest with a fresh render. Use pray plan to preview a write.
|
|
83
|
+
|
|
74
84
|
Without --strict, orphan-marker warnings print to stderr but exit 0.
|
|
75
85
|
With --strict, any finding fails with exit code 6.
|
|
76
86
|
TEXT
|
|
77
87
|
"drift" => <<~TEXT.strip,
|
|
78
|
-
|
|
88
|
+
compare dest versus Prayfile.lock, then dest versus a fresh render
|
|
79
89
|
|
|
80
90
|
Usage: pray drift [--semantic]
|
|
81
91
|
|
|
92
|
+
Read-only finding report from current packages and listed local files. Not a line diff.
|
|
82
93
|
Exits with code 6 when drift is found.
|
|
94
|
+
Use pray plan to preview a write. Use pray verify for dest versus lock alone.
|
|
83
95
|
TEXT
|
|
84
96
|
"render" => <<~TEXT.strip,
|
|
85
|
-
|
|
97
|
+
write dest from current inputs
|
|
86
98
|
|
|
87
99
|
Usage: pray render [--check]
|
|
100
|
+
|
|
101
|
+
Without --check, render writes dest. It does not write Prayfile.lock.
|
|
102
|
+
Use pray plan to preview a write.
|
|
103
|
+
--check writes nothing. It fails unless dest already matches a fresh render from current inputs.
|
|
88
104
|
TEXT
|
|
89
105
|
"format" => <<~TEXT.strip,
|
|
90
106
|
rewrite Prayfile to recommended destination DSL
|
|
@@ -99,18 +115,25 @@ module Pray
|
|
|
99
115
|
pray fmt
|
|
100
116
|
TEXT
|
|
101
117
|
"update" => <<~TEXT.strip,
|
|
102
|
-
|
|
118
|
+
re-resolve package versions and git sources within current constraints
|
|
103
119
|
|
|
104
120
|
Usage: pray update [package] [--latest] [--latest --dry-run]
|
|
105
121
|
|
|
122
|
+
With no package name, update re-resolves the whole graph within constraints
|
|
123
|
+
and refreshes git source revisions. A package name unlocks that package only.
|
|
124
|
+
Path-fork packages can refresh from upstream.
|
|
125
|
+
Listed local compose files are re-embedded by pray install, not by update.
|
|
126
|
+
|
|
106
127
|
--latest adjusts constraints to allow the latest package versions.
|
|
107
128
|
--latest also rewrites exact spec.upstream pins in path packages, then refreshes those trees.
|
|
108
129
|
--latest --dry-run prints the planned rewrite and does not write.
|
|
109
130
|
TEXT
|
|
110
131
|
"plan" => <<~TEXT.strip,
|
|
111
|
-
preview install
|
|
132
|
+
preview what install or apply would write after patch
|
|
112
133
|
|
|
113
134
|
Usage: pray plan [--remote]
|
|
135
|
+
|
|
136
|
+
This is the dry-run of a write. It does not write dest or the lock.
|
|
114
137
|
TEXT
|
|
115
138
|
"apply" => "materialize the current resolution plan\n\nUsage: pray apply",
|
|
116
139
|
"add" => <<~TEXT.strip,
|
|
@@ -159,6 +182,8 @@ module Pray
|
|
|
159
182
|
show constraint vs resolved versions
|
|
160
183
|
|
|
161
184
|
Usage: pray outdated [--remote]
|
|
185
|
+
|
|
186
|
+
Path-fork files that differ from the locked upstream are listed.
|
|
162
187
|
TEXT
|
|
163
188
|
"explain" => <<~TEXT.strip,
|
|
164
189
|
show why a package was selected
|
|
@@ -171,7 +196,19 @@ module Pray
|
|
|
171
196
|
|
|
172
197
|
Usage: pray init [--targets tool_a,tool_b]
|
|
173
198
|
TEXT
|
|
174
|
-
"prayer" =>
|
|
199
|
+
"prayer" => <<~TEXT.strip,
|
|
200
|
+
scaffold a local prayer under a path source
|
|
201
|
+
|
|
202
|
+
Usage: pray prayer init [name] [--path DIR]
|
|
203
|
+
|
|
204
|
+
With a Prayfile, writes <name>/ under the path source directory without a version.
|
|
205
|
+
The default directory is prayers/. Use --path to choose another.
|
|
206
|
+
Declares pray "<source>/<name>" once, such as pray "local/project", inside compose when a compose block exists.
|
|
207
|
+
The default name is project. The name v1 is reserved for the distribution layout.
|
|
208
|
+
Without a Prayfile, writes a versioned package spec in the current directory.
|
|
209
|
+
Add spec.version before pray package or pray publish.
|
|
210
|
+
A compose file such as .agents/project.md remains a shortcut for one local file.
|
|
211
|
+
TEXT
|
|
175
212
|
"repo" => "scaffold a distribution root\n\nUsage: pray repo init",
|
|
176
213
|
"manifest" => "print canonical Prayfile JSON\n\nUsage: pray manifest",
|
|
177
214
|
"package" => "build a distributable prayer archive\n\nUsage: pray package",
|
data/lib/pray/cli/helpers.rb
CHANGED
|
@@ -80,11 +80,19 @@ module Pray
|
|
|
80
80
|
|
|
81
81
|
def remove_manifest_statement(text, name)
|
|
82
82
|
lines = text.lines.map(&:chomp)
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
prefixes = [
|
|
84
|
+
%(pray "#{name}"),
|
|
85
|
+
%(pray '#{name}'),
|
|
86
|
+
%(use "#{name}"),
|
|
87
|
+
%(include "#{name}"),
|
|
88
|
+
%(agent "#{name}"),
|
|
89
|
+
%(agent '#{name}'),
|
|
90
|
+
%(package "#{name}"),
|
|
91
|
+
%(package '#{name}')
|
|
92
|
+
]
|
|
85
93
|
index = lines.index do |line|
|
|
86
94
|
trimmed = line.lstrip
|
|
87
|
-
trimmed.start_with?(
|
|
95
|
+
prefixes.any? { |prefix| trimmed.start_with?(prefix) }
|
|
88
96
|
end
|
|
89
97
|
if index
|
|
90
98
|
lines.delete_at(index)
|
|
@@ -141,14 +149,14 @@ module Pray
|
|
|
141
149
|
|
|
142
150
|
def render_tree_node(package, package_map, depth, ancestry, lines)
|
|
143
151
|
indent = " " * depth
|
|
144
|
-
lines << "#{indent}#{package.declaration.name} #{package.spec.
|
|
152
|
+
lines << "#{indent}#{package.declaration.name} #{package.spec.recorded_version}"
|
|
145
153
|
return unless ancestry.add?(package.declaration.name)
|
|
146
154
|
|
|
147
155
|
package.spec.dependencies.each do |dependency|
|
|
148
156
|
resolved = package_map[dependency.name]
|
|
149
157
|
if resolved
|
|
150
158
|
if ancestry.include?(resolved.declaration.name)
|
|
151
|
-
lines << "#{indent} #{resolved.declaration.name} #{resolved.spec.
|
|
159
|
+
lines << "#{indent} #{resolved.declaration.name} #{resolved.spec.recorded_version} (cycle)"
|
|
152
160
|
else
|
|
153
161
|
render_tree_node(resolved, package_map, depth + 1, ancestry, lines)
|
|
154
162
|
end
|
data/lib/pray/cli/parse.rb
CHANGED
|
@@ -42,7 +42,7 @@ module Pray
|
|
|
42
42
|
when "prayer"
|
|
43
43
|
raise Error.unsupported("prayer requires init") unless arguments.shift == "init"
|
|
44
44
|
|
|
45
|
-
[:prayer_init]
|
|
45
|
+
[:prayer_init, *parse_prayer_init_arguments(arguments)]
|
|
46
46
|
when "repo"
|
|
47
47
|
raise Error.unsupported("repo requires init") unless arguments.shift == "init"
|
|
48
48
|
|
|
@@ -77,6 +77,26 @@ module Pray
|
|
|
77
77
|
end
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
+
def parse_prayer_init_arguments(arguments)
|
|
81
|
+
name = nil
|
|
82
|
+
directory = nil
|
|
83
|
+
while (argument = arguments.shift)
|
|
84
|
+
if argument == "--path"
|
|
85
|
+
directory = arguments.shift
|
|
86
|
+
if directory.nil? || directory.start_with?("-")
|
|
87
|
+
raise Error.usage("--path requires a directory")
|
|
88
|
+
end
|
|
89
|
+
elsif argument.start_with?("-")
|
|
90
|
+
raise Error.unsupported("unexpected prayer argument: #{argument}")
|
|
91
|
+
elsif name.nil?
|
|
92
|
+
name = argument
|
|
93
|
+
else
|
|
94
|
+
raise Error.unsupported("unexpected prayer argument: #{argument}")
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
[name, directory]
|
|
98
|
+
end
|
|
99
|
+
|
|
80
100
|
def extract_flag!(arguments, flag)
|
|
81
101
|
found = arguments.include?(flag)
|
|
82
102
|
arguments.reject! { |argument| argument == flag }
|
data/lib/pray/cli.rb
CHANGED
|
@@ -9,6 +9,8 @@ require_relative "cli/help"
|
|
|
9
9
|
require_relative "cli/suggest"
|
|
10
10
|
require_relative "cli/helpers"
|
|
11
11
|
require_relative "cli/commands/init"
|
|
12
|
+
require_relative "cli/commands/init_prayer_manifest"
|
|
13
|
+
require_relative "cli/commands/init_prayer"
|
|
12
14
|
require_relative "cli/commands/workflow"
|
|
13
15
|
require_relative "cli/commands/update_latest"
|
|
14
16
|
require_relative "cli/commands/packages"
|
|
@@ -82,7 +84,7 @@ module Pray
|
|
|
82
84
|
case command
|
|
83
85
|
in [:manifest] then manifest_command
|
|
84
86
|
in [:init, targets] then init_command(targets)
|
|
85
|
-
in [:prayer_init] then prayer_init_command
|
|
87
|
+
in [:prayer_init, name, directory] then prayer_init_command(name, directory)
|
|
86
88
|
in [:repo_init] then repo_init_command
|
|
87
89
|
in [:install, flags] then install_command(flags)
|
|
88
90
|
in [:add, add_args] then add_command(**add_args)
|
data/lib/pray/destination.rb
CHANGED
|
@@ -16,8 +16,7 @@ module Pray
|
|
|
16
16
|
|
|
17
17
|
def local_path_form?(value)
|
|
18
18
|
value.start_with?(".", "/") ||
|
|
19
|
-
value.end_with?(".md", ".txt", ".markdown")
|
|
20
|
-
!value.include?("/")
|
|
19
|
+
value.end_with?(".md", ".txt", ".markdown")
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
def destination_target_name(mode, path)
|
|
@@ -106,6 +105,7 @@ module Pray
|
|
|
106
105
|
if existing.position == "after" && local.position != "after"
|
|
107
106
|
existing.position = local.position
|
|
108
107
|
end
|
|
108
|
+
existing.file ||= local.file
|
|
109
109
|
end
|
|
110
110
|
|
|
111
111
|
def bind_package_entry(target, package_name)
|
|
@@ -154,5 +154,35 @@ module Pray
|
|
|
154
154
|
else false
|
|
155
155
|
end
|
|
156
156
|
end
|
|
157
|
+
|
|
158
|
+
def local_compose_embed?(manifest, local)
|
|
159
|
+
return false if local.file
|
|
160
|
+
|
|
161
|
+
manifest.targets.none? do |target|
|
|
162
|
+
target.mode == "tree" &&
|
|
163
|
+
target.entries.any? { |entry| entry.kind == "local" && entry.path == local.path }
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def try_apply_local_pray(manifest, path, file_dest, other_package_signal, destination_index)
|
|
168
|
+
return false unless local_path_form?(path)
|
|
169
|
+
|
|
170
|
+
if file_dest
|
|
171
|
+
return false if other_package_signal
|
|
172
|
+
raise Error.parse("manifest", "a local file cannot use file:")
|
|
173
|
+
end
|
|
174
|
+
return false if other_package_signal
|
|
175
|
+
unless destination_index
|
|
176
|
+
raise Error.parse("manifest", "local pray paths are only valid inside compose")
|
|
177
|
+
end
|
|
178
|
+
mode = manifest.targets[destination_index].mode
|
|
179
|
+
unless mode == "compose"
|
|
180
|
+
raise Error.parse("manifest", "local pray paths are only valid inside compose")
|
|
181
|
+
end
|
|
182
|
+
local = ManifestLocal.new(path: path, bound: true)
|
|
183
|
+
bind_local_entry(manifest.targets[destination_index], local.path)
|
|
184
|
+
upsert_local(manifest, local)
|
|
185
|
+
true
|
|
186
|
+
end
|
|
157
187
|
end
|
|
158
188
|
end
|