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.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +47 -0
  3. data/LICENSE.md +21 -0
  4. data/README.md +125 -0
  5. data/SECURITY.md +28 -0
  6. data/bin/polyrun +108 -0
  7. data/bin/pray +26 -0
  8. data/lib/pray/archive.rb +68 -0
  9. data/lib/pray/auth_client.rb +97 -0
  10. data/lib/pray/cli/commands/auth.rb +42 -0
  11. data/lib/pray/cli/commands/distribution.rb +17 -0
  12. data/lib/pray/cli/commands/init.rb +62 -0
  13. data/lib/pray/cli/commands/meta.rb +15 -0
  14. data/lib/pray/cli/commands/packages.rb +107 -0
  15. data/lib/pray/cli/commands/trust.rb +73 -0
  16. data/lib/pray/cli/commands/workflow.rb +126 -0
  17. data/lib/pray/cli/help.rb +168 -0
  18. data/lib/pray/cli/helpers.rb +166 -0
  19. data/lib/pray/cli/parse.rb +136 -0
  20. data/lib/pray/cli/parse_auth.rb +91 -0
  21. data/lib/pray/cli/parse_trust.rb +152 -0
  22. data/lib/pray/cli/suggest.rb +57 -0
  23. data/lib/pray/cli.rb +117 -0
  24. data/lib/pray/confess.rb +113 -0
  25. data/lib/pray/config.rb +52 -0
  26. data/lib/pray/constraint.rb +80 -0
  27. data/lib/pray/destination.rb +158 -0
  28. data/lib/pray/dotenv.rb +46 -0
  29. data/lib/pray/environment.rb +41 -0
  30. data/lib/pray/error.rb +55 -0
  31. data/lib/pray/format_manifest.rb +255 -0
  32. data/lib/pray/format_serialize.rb +135 -0
  33. data/lib/pray/git_sources.rb +228 -0
  34. data/lib/pray/hashing.rb +59 -0
  35. data/lib/pray/invocation.rb +110 -0
  36. data/lib/pray/literal.rb +382 -0
  37. data/lib/pray/lockfile.rb +259 -0
  38. data/lib/pray/lockfile_serialize.rb +125 -0
  39. data/lib/pray/manifest.rb +126 -0
  40. data/lib/pray/manifest_formatter.rb +56 -0
  41. data/lib/pray/manifest_json.rb +128 -0
  42. data/lib/pray/manifest_parser.rb +152 -0
  43. data/lib/pray/manifest_parser_blocks.rb +216 -0
  44. data/lib/pray/manifest_parser_helpers.rb +208 -0
  45. data/lib/pray/materialize.rb +120 -0
  46. data/lib/pray/package_spec.rb +245 -0
  47. data/lib/pray/path_safety.rb +41 -0
  48. data/lib/pray/plan.rb +83 -0
  49. data/lib/pray/project_context.rb +67 -0
  50. data/lib/pray/publish.rb +162 -0
  51. data/lib/pray/registry.rb +355 -0
  52. data/lib/pray/render.rb +360 -0
  53. data/lib/pray/resolve.rb +396 -0
  54. data/lib/pray/resolve_context.rb +19 -0
  55. data/lib/pray/serve.rb +130 -0
  56. data/lib/pray/serve_federation.rb +109 -0
  57. data/lib/pray/session.rb +90 -0
  58. data/lib/pray/ssh_agent.rb +115 -0
  59. data/lib/pray/statement_surface.rb +262 -0
  60. data/lib/pray/substitute.rb +50 -0
  61. data/lib/pray/sync.rb +219 -0
  62. data/lib/pray/terminal.rb +30 -0
  63. data/lib/pray/trust.rb +201 -0
  64. data/lib/pray/trust_feed.rb +110 -0
  65. data/lib/pray/trust_ops.rb +202 -0
  66. data/lib/pray/verify.rb +257 -0
  67. data/lib/pray/version.rb +6 -0
  68. data/lib/pray-cli.rb +4 -0
  69. data/lib/pray.rb +46 -0
  70. data/pray-cli.gemspec +48 -0
  71. metadata +187 -0
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "parse_trust"
4
+ require_relative "parse_auth"
5
+
6
+ module Pray
7
+ module CLI
8
+ def parse_command(arguments)
9
+ arguments = arguments.dup
10
+ flags = {
11
+ check: extract_flag!(arguments, "--check"),
12
+ strict: extract_flag!(arguments, "--strict"),
13
+ semantic: extract_flag!(arguments, "--semantic"),
14
+ locked: false,
15
+ frozen: false,
16
+ offline: false,
17
+ targets: []
18
+ }
19
+
20
+ arguments.each do |argument|
21
+ case argument
22
+ when "--locked" then flags[:locked] = true
23
+ when "--frozen" then flags[:locked] = flags[:frozen] = true
24
+ when "--offline" then flags[:offline] = true
25
+ end
26
+ end
27
+ arguments.reject! { |argument| %w[--locked --frozen --offline].include?(argument) }
28
+
29
+ command = arguments.shift
30
+ raise Error.usage("pray requires a command; run pray --help") unless command
31
+
32
+ case command
33
+ when "manifest" then [:manifest]
34
+ when "init"
35
+ targets = []
36
+ while (argument = arguments.shift)
37
+ if argument == "--targets"
38
+ targets = arguments.shift.to_s.split(",").map(&:strip).reject(&:empty?)
39
+ end
40
+ end
41
+ [:init, targets]
42
+ when "prayer"
43
+ raise Error.unsupported("prayer requires init") unless arguments.shift == "init"
44
+
45
+ [:prayer_init]
46
+ when "repo"
47
+ raise Error.unsupported("repo requires init") unless arguments.shift == "init"
48
+
49
+ [:repo_init]
50
+ when "install" then [:install, flags]
51
+ when "add" then [:add, parse_add_arguments(arguments)]
52
+ when "remove" then [:remove, arguments.shift]
53
+ when "update" then [:update, arguments]
54
+ when "unlock" then [:unlock, arguments.shift]
55
+ when "render" then [:render, flags]
56
+ when "plan" then [:plan, arguments]
57
+ when "apply" then [:apply]
58
+ when "verify" then [:verify, flags]
59
+ when "drift" then [:drift, flags]
60
+ when "format", "fmt" then [:format]
61
+ when "package" then [:package]
62
+ when "publish" then [:publish, parse_publish_arguments(arguments)]
63
+ when "login" then [:login, parse_login_arguments(arguments)]
64
+ when "serve" then [:serve, parse_serve_arguments(arguments)]
65
+ when "confess" then [:confess, parse_confess_arguments(arguments)]
66
+ when "list" then [:list]
67
+ when "outdated" then [:outdated, arguments]
68
+ when "explain" then [:explain, arguments.shift]
69
+ when "vendor" then [:vendor]
70
+ when "clean" then [:clean]
71
+ when "tree" then [:tree]
72
+ when "sync" then [:sync, parse_sync_arguments(arguments)]
73
+ when "trust" then parse_trust_command(arguments)
74
+ when "version", "-V", "--version" then [:version]
75
+ else
76
+ raise Error.usage(Suggest.unknown_command_message(command))
77
+ end
78
+ end
79
+
80
+ def extract_flag!(arguments, flag)
81
+ found = arguments.include?(flag)
82
+ arguments.reject! { |argument| argument == flag }
83
+ found
84
+ end
85
+
86
+ def parse_add_arguments(arguments)
87
+ name = arguments.shift
88
+ raise Error.unsupported("add requires a package name") unless name
89
+
90
+ constraint = nil
91
+ path = nil
92
+ while (argument = arguments.shift)
93
+ case argument
94
+ when "--path"
95
+ path = arguments.shift
96
+ else
97
+ constraint ||= argument
98
+ end
99
+ end
100
+ {name: name, constraint: constraint, path: path}
101
+ end
102
+
103
+ def parse_publish_arguments(arguments)
104
+ roots = []
105
+ servers = []
106
+ while (argument = arguments.shift)
107
+ case argument
108
+ when "--root"
109
+ roots << arguments.shift
110
+ when "--server"
111
+ servers << arguments.shift
112
+ else
113
+ raise Error.unsupported("unexpected publish argument: #{argument}")
114
+ end
115
+ end
116
+ raise Error.unsupported("publish requires at least one --root PATH or --server URL") if roots.empty? && servers.empty?
117
+
118
+ {roots: roots, servers: servers}
119
+ end
120
+
121
+ def parse_serve_arguments(arguments)
122
+ options = {root: ".", host: "127.0.0.1", port: 7429, stdio: false}
123
+ while (argument = arguments.shift)
124
+ case argument
125
+ when "--root" then options[:root] = arguments.shift
126
+ when "--host" then options[:host] = arguments.shift
127
+ when "--port" then options[:port] = Integer(arguments.shift)
128
+ when "--stdio" then options[:stdio] = true
129
+ else
130
+ raise Error.unsupported("unexpected serve argument: #{argument}")
131
+ end
132
+ end
133
+ options
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pray
4
+ module CLI
5
+ def parse_login_arguments(arguments)
6
+ servers = []
7
+ email = nil
8
+ passkey_key = nil
9
+ credential_id = nil
10
+ ssh_agent = false
11
+ public_key = nil
12
+ while (argument = arguments.shift)
13
+ case argument
14
+ when "--server" then servers << arguments.shift
15
+ when "--email" then email = arguments.shift
16
+ when "--passkey-key" then passkey_key = arguments.shift
17
+ when "--credential-id" then credential_id = arguments.shift
18
+ when "--ssh-agent" then ssh_agent = true
19
+ when "--public-key" then public_key = arguments.shift
20
+ else
21
+ raise Error.unsupported("unknown login argument: #{argument}")
22
+ end
23
+ end
24
+ raise Error.unsupported("login requires --server URL") if servers.empty?
25
+ raise Error.unsupported("login requires --email EMAIL") if email.to_s.empty?
26
+
27
+ passkey = passkey_key || credential_id
28
+ if passkey && ssh_agent
29
+ raise Error.unsupported("login accepts either passkey or ssh-agent mode, not both")
30
+ end
31
+ if passkey_key
32
+ raise Error.unsupported("passkey login requires --credential-id") if credential_id.to_s.empty?
33
+
34
+ {servers: servers, email: email, mode: :passkey, passkey_key: passkey_key,
35
+ credential_id: credential_id}
36
+ elsif ssh_agent
37
+ raise Error.unsupported("ssh-agent login requires --public-key") if public_key.to_s.empty?
38
+
39
+ {servers: servers, email: email, mode: :ssh_agent, public_key: public_key}
40
+ else
41
+ raise Error.unsupported("login requires --passkey-key/--credential-id or --ssh-agent")
42
+ end
43
+ end
44
+
45
+ def parse_confess_arguments(arguments)
46
+ package = nil
47
+ from_lock = nil
48
+ version = nil
49
+ accepted = false
50
+ rejected = false
51
+ note = nil
52
+ url = nil
53
+ while (argument = arguments.shift)
54
+ case argument
55
+ when "--from-lock" then from_lock = arguments.shift
56
+ when "--version" then version = arguments.shift
57
+ when "--accepted" then accepted = true
58
+ when "--rejected" then rejected = true
59
+ when "--note" then note = arguments.shift
60
+ when "--url" then url = arguments.shift
61
+ when /\A-/ then raise Error.unsupported("unknown confess argument: #{argument}")
62
+ else package = argument
63
+ end
64
+ end
65
+ if accepted == rejected
66
+ raise Error.unsupported("confess requires exactly one of --accepted or --rejected")
67
+ end
68
+ if package && from_lock
69
+ raise Error.unsupported("confess accepts either a package name or --from-lock")
70
+ end
71
+ raise Error.unsupported("confess requires a package name") if package.nil? && from_lock.nil?
72
+
73
+ {package: package, from_lock: from_lock, version: version, accepted: accepted,
74
+ rejected: rejected, note: note, url: url}
75
+ end
76
+
77
+ def parse_sync_arguments(arguments)
78
+ root = "."
79
+ peers = []
80
+ while (argument = arguments.shift)
81
+ case argument
82
+ when "--root" then root = arguments.shift
83
+ when "--peer" then peers << arguments.shift
84
+ else
85
+ raise Error.unsupported("unknown sync argument: #{argument}")
86
+ end
87
+ end
88
+ {root: root, peers: peers}
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pray
4
+ module CLI
5
+ def parse_trust_command(arguments)
6
+ subcommand = arguments.shift
7
+ raise Error.unsupported("trust requires a subcommand") unless subcommand
8
+
9
+ case subcommand
10
+ when "list" then [:trust_list, parse_trust_list_arguments(arguments)]
11
+ when "show"
12
+ raise Error.unsupported("unknown trust show argument: #{arguments.first}") if arguments.any?
13
+
14
+ [:trust_show]
15
+ when "add-key" then [:trust_add_key, parse_trust_key_arguments(arguments, "add-key")]
16
+ when "remove-key", "revoke"
17
+ [:trust_remove_key, parse_trust_key_arguments(arguments, "remove-key")]
18
+ when "set-signed" then [:trust_set_signed, parse_trust_set_signed_arguments(arguments)]
19
+ when "set-allow" then [:trust_set_allow, parse_trust_set_allow_arguments(arguments)]
20
+ when "import-repo" then [:trust_import_repo, parse_trust_import_repo_arguments(arguments)]
21
+ when "import-registry"
22
+ [:trust_import_registry, parse_trust_import_registry_arguments(arguments)]
23
+ when "check"
24
+ source = arguments.shift
25
+ raise Error.unsupported("trust check accepts at most one SOURCE argument") if arguments.any?
26
+
27
+ [:trust_check, source]
28
+ else
29
+ raise Error.unsupported("unknown trust command: #{subcommand}")
30
+ end
31
+ end
32
+
33
+ def parse_trust_list_arguments(arguments)
34
+ scope = :all
35
+ source_url = nil
36
+ arguments.each do |argument|
37
+ case argument
38
+ when "--global" then scope = :global
39
+ when "--local" then scope = :local
40
+ when /\A-/ then raise Error.unsupported("unknown trust list argument: #{argument}")
41
+ else source_url = argument
42
+ end
43
+ end
44
+ {scope: scope, source_url: source_url}
45
+ end
46
+
47
+ def parse_trust_key_arguments(arguments, label)
48
+ key = arguments.shift
49
+ raise Error.unsupported("trust #{label} requires KEY") unless key
50
+
51
+ match_prefix = nil
52
+ while (argument = arguments.shift)
53
+ if argument == "--match-prefix"
54
+ match_prefix = arguments.shift
55
+ raise Error.unsupported("--match-prefix requires VALUE") unless match_prefix
56
+ else
57
+ raise Error.unsupported("unknown trust #{label} argument: #{argument}")
58
+ end
59
+ end
60
+ {key: key, match_prefix: match_prefix}
61
+ end
62
+
63
+ def parse_trust_set_signed_arguments(arguments)
64
+ match_prefix = nil
65
+ enabled = true
66
+ while (argument = arguments.shift)
67
+ case argument
68
+ when "--match-prefix"
69
+ match_prefix = arguments.shift
70
+ raise Error.unsupported("--match-prefix requires VALUE") unless match_prefix
71
+ when "--enabled"
72
+ value = arguments.shift
73
+ raise Error.unsupported("--enabled requires true|false") unless value
74
+
75
+ enabled = parse_bool_flag(value)
76
+ else
77
+ raise Error.unsupported("unknown trust set-signed argument: #{argument}")
78
+ end
79
+ end
80
+ raise Error.unsupported("trust set-signed requires --match-prefix PREFIX") unless match_prefix
81
+
82
+ {match_prefix: match_prefix, enabled: enabled}
83
+ end
84
+
85
+ def parse_trust_set_allow_arguments(arguments)
86
+ match_prefix = nil
87
+ allow = true
88
+ while (argument = arguments.shift)
89
+ case argument
90
+ when "--match-prefix"
91
+ match_prefix = arguments.shift
92
+ raise Error.unsupported("--match-prefix requires VALUE") unless match_prefix
93
+ when "--allow"
94
+ value = arguments.shift
95
+ raise Error.unsupported("--allow requires true|false") unless value
96
+
97
+ allow = parse_bool_flag(value)
98
+ else
99
+ raise Error.unsupported("unknown trust set-allow argument: #{argument}")
100
+ end
101
+ end
102
+ raise Error.unsupported("trust set-allow requires --match-prefix PREFIX") unless match_prefix
103
+
104
+ {match_prefix: match_prefix, allow: allow}
105
+ end
106
+
107
+ def parse_trust_import_repo_arguments(arguments)
108
+ source_url = arguments.shift
109
+ raise Error.unsupported("trust import-repo requires SOURCE_URL") unless source_url
110
+
111
+ match_prefix = nil
112
+ while (argument = arguments.shift)
113
+ if argument == "--match-prefix"
114
+ match_prefix = arguments.shift
115
+ raise Error.unsupported("--match-prefix requires VALUE") unless match_prefix
116
+ else
117
+ raise Error.unsupported("unknown trust import-repo argument: #{argument}")
118
+ end
119
+ end
120
+ {source_url: source_url, match_prefix: match_prefix}
121
+ end
122
+
123
+ def parse_trust_import_registry_arguments(arguments)
124
+ source_url = arguments.shift
125
+ raise Error.unsupported("trust import-registry requires SOURCE_URL") unless source_url
126
+
127
+ match_prefix = nil
128
+ include_host_key = source_url.start_with?("pray+ssh://", "ssh+pray://")
129
+ while (argument = arguments.shift)
130
+ case argument
131
+ when "--match-prefix"
132
+ match_prefix = arguments.shift
133
+ raise Error.unsupported("--match-prefix requires VALUE") unless match_prefix
134
+ when "--host-key" then include_host_key = true
135
+ when "--no-host-key" then include_host_key = false
136
+ else
137
+ raise Error.unsupported("unknown trust import-registry argument: #{argument}")
138
+ end
139
+ end
140
+ {source_url: source_url, match_prefix: match_prefix, include_host_key: include_host_key}
141
+ end
142
+
143
+ def parse_bool_flag(value)
144
+ case value.downcase
145
+ when "true", "1", "yes", "on" then true
146
+ when "false", "0", "no", "off" then false
147
+ else
148
+ raise Error.unsupported("expected true|false, got #{value}")
149
+ end
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Pray
4
+ module CLI
5
+ module Suggest
6
+ TOP_LEVEL_COMMANDS = %w[
7
+ add apply clean confess drift explain fmt format help init install list login manifest
8
+ outdated package plan prayer publish remove render repo serve sync tree trust unlock
9
+ update vendor verify version
10
+ ].freeze
11
+
12
+ module_function
13
+
14
+ def unknown_command_message(command)
15
+ message = "unknown command: #{command}"
16
+ suggestion = suggest_command(command, TOP_LEVEL_COMMANDS)
17
+ suggestion ? "#{message}\nDid you mean `#{suggestion}`?" : message
18
+ end
19
+
20
+ def suggest_command(input, candidates)
21
+ maximum_distance = (input.length <= 3) ? 1 : 2
22
+ candidates
23
+ .map { |candidate| [candidate, levenshtein_distance(input, candidate)] }
24
+ .select { |(_, distance)| distance <= maximum_distance }
25
+ .min_by { |(_, distance)| distance }
26
+ &.first
27
+ end
28
+
29
+ def levenshtein_distance(left, right)
30
+ left_chars = left.chars
31
+ right_chars = right.chars
32
+ left_length = left_chars.length
33
+ right_length = right_chars.length
34
+ return right_length if left_length.zero?
35
+ return left_length if right_length.zero?
36
+
37
+ previous_row = (0..right_length).to_a
38
+ current_row = Array.new(right_length + 1, 0)
39
+
40
+ left_chars.each_with_index do |left_character, left_index|
41
+ current_row[0] = left_index + 1
42
+ right_chars.each_with_index do |right_character, right_index|
43
+ substitution_cost = (left_character == right_character) ? 0 : 1
44
+ current_row[right_index + 1] = [
45
+ previous_row[right_index + 1] + 1,
46
+ current_row[right_index] + 1,
47
+ previous_row[right_index] + substitution_cost
48
+ ].min
49
+ end
50
+ previous_row, current_row = current_row, previous_row
51
+ end
52
+
53
+ previous_row[right_length]
54
+ end
55
+ end
56
+ end
57
+ end
data/lib/pray/cli.rb ADDED
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "fileutils"
4
+
5
+ require_relative "invocation"
6
+ require_relative "cli/parse"
7
+ require_relative "cli/help"
8
+ require_relative "cli/suggest"
9
+ require_relative "cli/helpers"
10
+ require_relative "cli/commands/init"
11
+ require_relative "cli/commands/workflow"
12
+ require_relative "cli/commands/packages"
13
+ require_relative "cli/commands/distribution"
14
+ require_relative "cli/commands/trust"
15
+ require_relative "cli/commands/auth"
16
+ require_relative "cli/commands/meta"
17
+
18
+ module Pray
19
+ module CLI
20
+ MANIFEST_PATH = "Prayfile"
21
+ LOCKFILE_PATH = "Prayfile.lock"
22
+
23
+ def run(arguments)
24
+ arguments = arguments.dup
25
+ if arguments.delete("--no-input")
26
+ ENV["PRAY_NO_INPUT"] = "1"
27
+ end
28
+ arguments = Invocation.initialize(arguments)
29
+ return if maybe_print_help(arguments)
30
+
31
+ command = parse_command(arguments)
32
+ dispatch(command)
33
+ end
34
+
35
+ def maybe_print_help(arguments)
36
+ if arguments.empty?
37
+ Help.print_concise_help
38
+ return true
39
+ end
40
+
41
+ if arguments.length == 1 && %w[help -h --help].include?(arguments[0])
42
+ Help.print_concise_help
43
+ return true
44
+ end
45
+
46
+ if arguments[0] == "help"
47
+ target = arguments[1] || ""
48
+ if target.empty? || %w[-h --help].include?(target)
49
+ Help.print_concise_help
50
+ return true
51
+ end
52
+ return true if Help.print_command_help(target)
53
+
54
+ raise Error.usage(Suggest.unknown_command_message(target))
55
+ end
56
+
57
+ help_position = arguments.index { |argument| %w[--help -h].include?(argument) }
58
+ if help_position
59
+ if help_position.zero?
60
+ Help.print_concise_help
61
+ return true
62
+ end
63
+ command = arguments[0]
64
+ return true if Help.print_command_help(command)
65
+
66
+ raise Error.usage("unknown command: #{command}")
67
+ end
68
+
69
+ false
70
+ end
71
+
72
+ def dispatch(command)
73
+ case command
74
+ in [:manifest] then manifest_command
75
+ in [:init, targets] then init_command(targets)
76
+ in [:prayer_init] then prayer_init_command
77
+ in [:repo_init] then repo_init_command
78
+ in [:install, flags] then install_command(flags)
79
+ in [:add, add_args] then add_command(**add_args)
80
+ in [:remove, name] then remove_command(name)
81
+ in [:update, arguments] then update_command(arguments)
82
+ in [:unlock, name] then unlock_command(name)
83
+ in [:render, flags] then render_command(flags)
84
+ in [:plan, arguments] then plan_command(arguments)
85
+ in [:apply] then install_command({locked: false, frozen: false, offline: false})
86
+ in [:verify, flags] then verify_command(flags)
87
+ in [:drift, flags] then drift_command(flags)
88
+ in [:format] then format_command
89
+ in [:package] then package_command
90
+ in [:publish, publish_args] then publish_command(**publish_args)
91
+ in [:unsupported, name] then raise Error.unsupported("#{name} is not implemented yet in pray-cli Ruby")
92
+ in [:serve, serve_args] then serve_command(**serve_args)
93
+ in [:login, login_args] then login_command(**login_args)
94
+ in [:confess, confess_args] then confess_command(**confess_args)
95
+ in [:sync, sync_args] then sync_command(**sync_args)
96
+ in [:list] then list_command
97
+ in [:outdated, arguments] then outdated_command(arguments)
98
+ in [:explain, name] then explain_command(name)
99
+ in [:vendor] then raise Error.unsupported("vendor is not implemented yet in pray-cli Ruby")
100
+ in [:clean] then clean_command
101
+ in [:tree] then tree_command
102
+ in [:trust_list, options] then trust_list_command(**options)
103
+ in [:trust_show] then trust_show_command
104
+ in [:trust_add_key, options] then trust_add_key_command(**options)
105
+ in [:trust_remove_key, options] then trust_remove_key_command(**options)
106
+ in [:trust_set_signed, options] then trust_set_signed_command(**options)
107
+ in [:trust_set_allow, options] then trust_set_allow_command(**options)
108
+ in [:trust_import_repo, options] then trust_import_repo_command(**options)
109
+ in [:trust_import_registry, options] then trust_import_registry_command(**options)
110
+ in [:trust_check, source] then trust_check_command(source)
111
+ in [:version] then version_command
112
+ end
113
+ end
114
+
115
+ extend self
116
+ end
117
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "session"
5
+
6
+ module Pray
7
+ ConfessionSubmission = Struct.new(
8
+ :package, :version, :status, :note, :lockfile,
9
+ :distribution_point, :signer, :timestamp, :signature,
10
+ keyword_init: true
11
+ )
12
+
13
+ module Confess
14
+ module_function
15
+
16
+ def submit(package:, from_lock:, version:, accepted:, rejected:, note:, url:,
17
+ project_root: Dir.pwd)
18
+ raise Error.unsupported("confess requires exactly one of --accepted or --rejected") if accepted == rejected
19
+
20
+ manifest_path = File.join(project_root, "Prayfile")
21
+ project = Resolve.resolve_project(manifest_path)
22
+ lockfile_path = File.join(project_root, "Prayfile.lock")
23
+ lockfile = File.file?(lockfile_path) ? Lockfile.read_lockfile(lockfile_path) : nil
24
+
25
+ package_name, resolved_version, package_resolution =
26
+ resolve_target(project, lockfile, package, from_lock, version)
27
+ source_url = resolve_source_url(project, package_resolution, package_name, url)
28
+ if source_url.start_with?("pray+ssh://", "ssh+pray://")
29
+ raise Error.unsupported("pray_ssh confession submit is not implemented yet in pray-cli Ruby")
30
+ end
31
+
32
+ confession = ConfessionSubmission.new(
33
+ package: package_name,
34
+ version: resolved_version,
35
+ status: accepted ? "accepted" : "rejected",
36
+ note: note,
37
+ lockfile: lockfile&.file_hash,
38
+ distribution_point: source_url,
39
+ signer: Session.current_signer(project_root),
40
+ timestamp: Time.now.to_i.to_s,
41
+ signature: nil
42
+ )
43
+ confession.signature = Hashing.sha256_prefixed(JSON.generate(confession_to_hash(confession)))
44
+ Registry.http_post(
45
+ Registry.join_url(source_url, "v1/confessions"),
46
+ "application/json",
47
+ JSON.generate(confession_to_hash(confession))
48
+ )
49
+ puts "Confession submitted for #{confession.package} #{confession.version}"
50
+ end
51
+
52
+ def resolve_target(project, lockfile, package, from_lock, version)
53
+ if from_lock
54
+ raise Error.resolution("confess --from-lock requires an existing lockfile") unless lockfile
55
+
56
+ span = lockfile.managed_span.find { |record| record.id == from_lock }
57
+ raise Error.resolution("lockfile span #{from_lock} not found") unless span
58
+
59
+ package_resolution = project.packages.find { |entry| entry.declaration.name == span.package }
60
+ raise Error.resolution("package #{span.package} not found") unless package_resolution
61
+
62
+ locked = lockfile.package.find { |entry| entry.name == span.package }
63
+ raise Error.resolution("lockfile package #{span.package} not found") unless locked
64
+
65
+ if version && version != locked.version
66
+ raise Error.resolution(
67
+ "lockfile span #{from_lock} version #{locked.version} does not match requested version #{version}"
68
+ )
69
+ end
70
+ [span.package, version || locked.version, package_resolution]
71
+ else
72
+ raise Error.unsupported("confess requires a package name") unless package
73
+
74
+ package_resolution = project.packages.find { |entry| entry.declaration.name == package }
75
+ raise Error.resolution("package #{package} not found") unless package_resolution
76
+
77
+ if version && version != package_resolution.spec.version
78
+ raise Error.resolution(
79
+ "package #{package} version #{version} does not match resolved version #{package_resolution.spec.version}"
80
+ )
81
+ end
82
+ [package, version || package_resolution.spec.version, package_resolution]
83
+ end
84
+ end
85
+
86
+ def resolve_source_url(project, package_resolution, package_name, url)
87
+ return url if url
88
+
89
+ source_name = package_resolution.declaration.source
90
+ raise Error.resolution("package #{package_name} is missing a source") unless source_name
91
+
92
+ source = project.manifest.sources.find { |entry| entry.name == source_name }
93
+ raise Error.resolution("unknown source: #{source_name}") unless source
94
+
95
+ source.url
96
+ end
97
+
98
+ def confession_to_hash(confession)
99
+ hash = {
100
+ "package" => confession.package,
101
+ "version" => confession.version,
102
+ "status" => confession.status
103
+ }
104
+ hash["note"] = confession.note if confession.note
105
+ hash["lockfile"] = confession.lockfile if confession.lockfile
106
+ hash["distribution_point"] = confession.distribution_point if confession.distribution_point
107
+ hash["signer"] = confession.signer if confession.signer
108
+ hash["timestamp"] = confession.timestamp if confession.timestamp
109
+ hash["signature"] = confession.signature if confession.signature
110
+ hash
111
+ end
112
+ end
113
+ end