Module: Pray::CLI

Extended by:
CLI
Included in:
CLI
Defined in:
lib/pray/cli.rb,
lib/pray/cli/help.rb,
lib/pray/cli/parse.rb,
lib/pray/cli/helpers.rb,
lib/pray/cli/suggest.rb,
lib/pray/cli/parse_auth.rb,
lib/pray/cli/parse_trust.rb,
lib/pray/cli/commands/auth.rb,
lib/pray/cli/commands/init.rb,
lib/pray/cli/commands/meta.rb,
lib/pray/cli/commands/trust.rb,
lib/pray/cli/commands/packages.rb,
lib/pray/cli/commands/workflow.rb,
lib/pray/cli/commands/distribution.rb

Defined Under Namespace

Modules: Help, Suggest

Constant Summary collapse

MANIFEST_PATH =
"Prayfile"
LOCKFILE_PATH =
"Prayfile.lock"

Instance Method Summary collapse

Instance Method Details

#add_command(name:, constraint: nil, path: nil) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/pray/cli/commands/packages.rb', line 7

def add_command(name:, constraint: nil, path: nil)
  manifest_path_value = manifest_path
  manifest_text = Pray.read_manifest_text(manifest_path_value)
  manifest = Pray.parse_manifest(manifest_text)
  if manifest.packages.any? { |package| package.name == name }
    raise Error.manifest("package #{name} already exists")
  end

  declaration = Pray.format_package_declaration(
    ManifestPackage.new(name: name, constraint: constraint || "*", path: path)
  )
  File.write(manifest_path_value, insert_manifest_statement(manifest_text, declaration))
end

#build_lockfile(project, rendered) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/pray/cli/helpers.rb', line 31

def build_lockfile(project, rendered)
  LockfileIO.build_lockfile(
    project.manifest_hash,
    project.environment,
    project.project_root,
    project.manifest.sources,
    project.manifest.targets,
    rendered,
    project.packages,
    project.source_revisions,
    project.source_host_keys
  )
end

#canonical_marker_line(line) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
# File 'lib/pray/cli/helpers.rb', line 107

def canonical_marker_line(line)
  trimmed = line.strip
  remainder = trimmed.delete_prefix("<!--").strip
  return nil unless remainder.start_with?("pray:")

  content = remainder.delete_prefix("pray:").strip.delete_suffix("-->").strip
  return "<!-- pray:0 ignore-comments -->" if content == "0 ignore-comments"
  return "<!-- pray:#{content} -->" if content.match?(/\A[a-z0-9]+\z/)

  nil
end

#clean_commandObject



9
10
11
12
13
# File 'lib/pray/cli/commands/meta.rb', line 9

def clean_command
  remove_path_if_exists(".pray/cache")
  remove_path_if_exists(".pray/vendor")
  remove_path_if_exists(".pray/state.json")
end

#confess_command(package:, from_lock:, version:, accepted:, rejected:, note:, url:) ⇒ Object



24
25
26
27
28
29
# File 'lib/pray/cli/commands/auth.rb', line 24

def confess_command(package:, from_lock:, version:, accepted:, rejected:, note:, url:)
  Confess.submit(
    package: package, from_lock: from_lock, version: version,
    accepted: accepted, rejected: rejected, note: note, url: url
  )
end

#default_output_for_target(target) ⇒ Object



23
24
25
26
27
28
29
# File 'lib/pray/cli/helpers.rb', line 23

def default_output_for_target(target)
  case target
  when "tool_a" then "INSTRUCTIONS"
  when "tool_b" then "TOOL_B"
  else target.upcase
  end
end

#dispatch(command) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/pray/cli.rb', line 72

def dispatch(command)
  case command
  in [:manifest] then manifest_command
  in [:init, targets] then init_command(targets)
  in [:prayer_init] then prayer_init_command
  in [:repo_init] then repo_init_command
  in [:install, flags] then install_command(flags)
  in [:add, add_args] then add_command(**add_args)
  in [:remove, name] then remove_command(name)
  in [:update, arguments] then update_command(arguments)
  in [:unlock, name] then unlock_command(name)
  in [:render, flags] then render_command(flags)
  in [:plan, arguments] then plan_command(arguments)
  in [:apply] then install_command({locked: false, frozen: false, offline: false})
  in [:verify, flags] then verify_command(flags)
  in [:drift, flags] then drift_command(flags)
  in [:format] then format_command
  in [:package] then package_command
  in [:publish, publish_args] then publish_command(**publish_args)
  in [:unsupported, name] then raise Error.unsupported("#{name} is not implemented yet in pray-cli Ruby")
  in [:serve, serve_args] then serve_command(**serve_args)
  in [:login, ] then (**)
  in [:confess, confess_args] then confess_command(**confess_args)
  in [:sync, sync_args] then sync_command(**sync_args)
  in [:list] then list_command
  in [:outdated, arguments] then outdated_command(arguments)
  in [:explain, name] then explain_command(name)
  in [:vendor] then raise Error.unsupported("vendor is not implemented yet in pray-cli Ruby")
  in [:clean] then clean_command
  in [:tree] then tree_command
  in [:trust_list, options] then trust_list_command(**options)
  in [:trust_show] then trust_show_command
  in [:trust_add_key, options] then trust_add_key_command(**options)
  in [:trust_remove_key, options] then trust_remove_key_command(**options)
  in [:trust_set_signed, options] then trust_set_signed_command(**options)
  in [:trust_set_allow, options] then trust_set_allow_command(**options)
  in [:trust_import_repo, options] then trust_import_repo_command(**options)
  in [:trust_import_registry, options] then trust_import_registry_command(**options)
  in [:trust_check, source] then trust_check_command(source)
  in [:version] then version_command
  end
end

#drift_command(flags) ⇒ Object



34
35
36
37
38
39
40
41
42
43
# File 'lib/pray/cli/commands/workflow.rb', line 34

def drift_command(flags)
  project = resolve_current_project
  lockfile = ensure_existing_lockfile(lockfile_path)
  report = if flags[:semantic]
    Verify.inspect_project(project, lockfile)
  else
    Verify.drift_project(project, lockfile)
  end
  puts format_drift_report(report) unless report.clean?
end

#ensure_existing_lockfile(path) ⇒ Object



45
46
47
48
49
# File 'lib/pray/cli/helpers.rb', line 45

def ensure_existing_lockfile(path)
  raise Error.verify("missing Prayfile.lock; run pray install first") unless File.exist?(path)

  Pray.read_lockfile(path)
end

#ensure_lockfile_current(project, rendered, existing) ⇒ Object



51
52
53
54
55
56
# File 'lib/pray/cli/helpers.rb', line 51

def ensure_lockfile_current(project, rendered, existing)
  current = build_lockfile(project, rendered)
  return if Pray.lockfiles_equivalent?(current, existing)

  raise Error.verify("lockfile needs update; rerun pray install to refresh Prayfile.lock")
end

#ensure_rendered_outputs_current(project, rendered) ⇒ Object



58
59
60
61
62
63
64
65
66
# File 'lib/pray/cli/helpers.rb', line 58

def ensure_rendered_outputs_current(project, rendered)
  rendered.each do |target|
    path = File.join(project.project_root, target.path)
    on_disk = File.read(path)
    if on_disk != target.content
      raise Error.render("#{path} is stale; rerun pray install to regenerate it or pray plan to inspect the diff")
    end
  end
end

#explain_command(name) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/pray/cli/commands/packages.rb', line 62

def explain_command(name)
  raise Error.resolution("explain requires a package name") unless name

  project = resolve_current_project
  package = project.packages.find { |entry| entry.declaration.name == name }
  raise Error.resolution("package #{name} not found") unless package

  lockfile = File.exist?(lockfile_path) ? Pray.read_lockfile(lockfile_path) : nil
  lockfile_package = lockfile&.package&.find { |entry| entry.name == name }

  lines = ["Package explanation"]
  lines << "name: #{package.declaration.name}"
  lines << "constraint: #{package.declaration.constraint}"
  lines << "resolved version: #{package.spec.version}"
  if package.registry_latest_version
    lines << "registry latest: #{package.registry_latest_version}"
  end
  lines << "source: #{package_source_summary(package)}"
  lines << "exports: #{format_list(package.selected_exports)}"
  lines << "dependencies: #{format_list(package.spec.dependencies.map(&:name))}"
  lines << "tree hash: #{package.tree_hash}"
  lines << "artifact hash: #{package.artifact_hash}"
  if lockfile_package
    lines << "lockfile version: #{lockfile_package.version}"
    lines << "lockfile path: #{lockfile_package.path}"
    lines << "lockfile exports: #{format_list(lockfile_package.exports)}"
  else
    lines << "lockfile record: missing"
  end
  puts lines.join("\n")
end

#extract_flag!(arguments, flag) ⇒ Object



80
81
82
83
84
# File 'lib/pray/cli/parse.rb', line 80

def extract_flag!(arguments, flag)
  found = arguments.include?(flag)
  arguments.reject! { |argument| argument == flag }
  found
end

#format_commandObject



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/pray/cli/commands/workflow.rb', line 45

def format_command
  path = manifest_path
  original = Pray.read_manifest_text(path)
  manifest = Pray.parse_manifest(original)
  project = begin
    resolve_current_project(ResolveOptions.new(offline: true))
  rescue Error
    resolve_current_project
  end
  hints = FormatManifest.classify_format_hints(project)
  formatted = FormatManifest.format_recommended(manifest, hints)
  File.write(path, formatted) if formatted != original

  return unless File.exist?(lockfile_path)

  lockfile = Pray.read_lockfile(lockfile_path)
  lockfile.target.each do |target|
    target.outputs.each do |output|
      next unless File.exist?(output)

      original_output = File.read(output)
      formatted_output = format_marker_comments(Hashing.normalize_line_endings(original_output))
      File.write(output, formatted_output) if formatted_output != original_output
    end
  end
end

#format_drift_report(report) ⇒ Object



137
138
139
# File 'lib/pray/cli/helpers.rb', line 137

def format_drift_report(report)
  Verify.format_drift_report(report)
end

#format_list(values) ⇒ Object



129
130
131
# File 'lib/pray/cli/helpers.rb', line 129

def format_list(values)
  values.empty? ? "none" : values.join(", ")
end

#format_marker_comments(text) ⇒ Object



98
99
100
101
102
103
104
105
# File 'lib/pray/cli/helpers.rb', line 98

def format_marker_comments(text)
  lines = text.split("\n", -1).map do |line|
    canonical_marker_line(line) || line
  end
  output = lines.join("\n")
  output += "\n" unless output.end_with?("\n")
  output
end

#format_verification_report(report) ⇒ Object



133
134
135
# File 'lib/pray/cli/helpers.rb', line 133

def format_verification_report(report)
  Verify.format_verification_report(report)
end

#init_command(targets) ⇒ Object



13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/pray/cli/commands/init.rb', line 13

def init_command(targets)
  path = manifest_path
  raise Error.manifest("Prayfile already exists") if File.exist?(path)

  targets = ["tool_a"] if targets.empty?
  lines = ['prayfile "1"']
  targets.each do |target|
    output = default_output_for_target(target)
    lines << "target :#{target} do"
    lines << "  output \"#{output}.md\""
    lines << "end"
  end
  File.write(path, "#{lines.join("\n")}\n")
end

#insert_manifest_statement(text, statement) ⇒ Object



68
69
70
71
72
73
74
75
76
77
78
# File 'lib/pray/cli/helpers.rb', line 68

def insert_manifest_statement(text, statement)
  lines = text.lines.map(&:chomp)
  insertion_index = lines.index do |line|
    trimmed = line.lstrip
    trimmed.start_with?("local ", "render ")
  end || lines.length
  lines.insert(insertion_index, statement)
  output = lines.join("\n")
  output += "\n" unless output.end_with?("\n")
  output
end

#install_command(flags) ⇒ Object



7
8
9
10
11
12
13
14
15
# File 'lib/pray/cli/commands/workflow.rb', line 7

def install_command(flags)
  Pray.materialize_project(
    manifest_path: manifest_path,
    frozen: flags[:frozen],
    locked: flags[:locked],
    offline: flags[:offline],
    refresh: flags[:refresh]
  )
end

#list_commandObject



35
36
37
38
39
40
41
42
# File 'lib/pray/cli/commands/packages.rb', line 35

def list_command
  project = resolve_current_project
  lines = ["Package list"]
  project.packages.each do |package|
    lines << "#{package.declaration.name} #{package.spec.version} source=#{package_source_summary(package)} exports=#{format_list(package.selected_exports)}"
  end
  puts lines.join("\n")
end

#lockfile_pathObject



11
12
13
# File 'lib/pray/cli/helpers.rb', line 11

def lockfile_path
  Invocation.lockfile_path
end

#login_command(servers:, email:, mode:, passkey_key: nil, credential_id: nil, public_key: nil) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/pray/cli/commands/auth.rb', line 5

def (servers:, email:, mode:, passkey_key: nil, credential_id: nil, public_key: nil)
  session_root = Dir.pwd
  servers.each do |server_url|
    session = case mode
    when :passkey
      AuthClient.(
        server_url, credential_id, passkey_key, session_root, email: email
      )
    when :ssh_agent
      AuthClient.(
        server_url, public_key, session_root, email: email
      )
    else
      raise Error.unsupported("unknown login mode: #{mode}")
    end
    puts "logged in as #{session.email} via #{session.kind} on #{server_url}"
  end
end

#manifest_commandObject



8
9
10
11
# File 'lib/pray/cli/commands/init.rb', line 8

def manifest_command
  manifest = Pray.parse_manifest(Pray.read_manifest_text(manifest_path))
  puts JSON.pretty_generate(ManifestJson.manifest_fields(manifest.canonicalized))
end

#manifest_pathObject



7
8
9
# File 'lib/pray/cli/helpers.rb', line 7

def manifest_path
  Invocation.manifest_path
end

#maybe_print_help(arguments) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/pray/cli.rb', line 35

def maybe_print_help(arguments)
  if arguments.empty?
    Help.print_concise_help
    return true
  end

  if arguments.length == 1 && %w[help -h --help].include?(arguments[0])
    Help.print_concise_help
    return true
  end

  if arguments[0] == "help"
    target = arguments[1] || ""
    if target.empty? || %w[-h --help].include?(target)
      Help.print_concise_help
      return true
    end
    return true if Help.print_command_help(target)

    raise Error.usage(Suggest.unknown_command_message(target))
  end

  help_position = arguments.index { |argument| %w[--help -h].include?(argument) }
  if help_position
    if help_position.zero?
      Help.print_concise_help
      return true
    end
    command = arguments[0]
    return true if Help.print_command_help(command)

    raise Error.usage("unknown command: #{command}")
  end

  false
end

#merge_selected_package_update(previous, updated, selected_package) ⇒ Object



115
116
117
118
119
120
121
122
123
124
# File 'lib/pray/cli/commands/workflow.rb', line 115

def merge_selected_package_update(previous, updated, selected_package)
  merged = updated.dup
  merged.package = updated.package.map do |package|
    next package if package.name == selected_package

    previous_package = previous.package.find { |entry| entry.name == package.name }
    previous_package ? package.dup.tap { |copy| copy.version = previous_package.version } : package
  end
  merged
end

#outdated_command(_arguments) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/pray/cli/commands/packages.rb', line 94

def outdated_command(_arguments)
  previous_lockfile = File.exist?(lockfile_path) ? Pray.read_lockfile(lockfile_path) : nil
  project = resolve_current_project
  rendered = Render.render_project(project)
  latest_lockfile = build_lockfile(project, rendered)
  puts "Outdated packages"
  if previous_lockfile && Pray.lockfiles_equivalent?(latest_lockfile, previous_lockfile)
    puts "All packages up to date"
  else
    Plan.package_summary_lines(previous_lockfile, latest_lockfile, project).each { |line| puts line }
  end
end

#package_commandObject



54
55
56
57
58
59
60
# File 'lib/pray/cli/commands/packages.rb', line 54

def package_command
  project = resolve_current_project
  project.packages.each do |package|
    output_path = Archive.package_archive_path(package.declaration.name, package.spec.version)
    Archive.write_package_archive(package, output_path)
  end
end

#package_source_summary(package) ⇒ Object



119
120
121
122
123
124
125
126
127
# File 'lib/pray/cli/helpers.rb', line 119

def package_source_summary(package)
  if package.declaration.path
    "path:#{package.declaration.path}"
  elsif package.declaration.source
    "source:#{package.declaration.source}"
  else
    "root:#{package.root}"
  end
end

#parse_add_arguments(arguments) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/pray/cli/parse.rb', line 86

def parse_add_arguments(arguments)
  name = arguments.shift
  raise Error.unsupported("add requires a package name") unless name

  constraint = nil
  path = nil
  while (argument = arguments.shift)
    case argument
    when "--path"
      path = arguments.shift
    else
      constraint ||= argument
    end
  end
  {name: name, constraint: constraint, path: path}
end

#parse_bool_flag(value) ⇒ Object



143
144
145
146
147
148
149
150
# File 'lib/pray/cli/parse_trust.rb', line 143

def parse_bool_flag(value)
  case value.downcase
  when "true", "1", "yes", "on" then true
  when "false", "0", "no", "off" then false
  else
    raise Error.unsupported("expected true|false, got #{value}")
  end
end

#parse_command(arguments) ⇒ Object



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/pray/cli/parse.rb', line 8

def parse_command(arguments)
  arguments = arguments.dup
  flags = {
    check: extract_flag!(arguments, "--check"),
    strict: extract_flag!(arguments, "--strict"),
    semantic: extract_flag!(arguments, "--semantic"),
    locked: false,
    frozen: false,
    offline: false,
    targets: []
  }

  arguments.each do |argument|
    case argument
    when "--locked" then flags[:locked] = true
    when "--frozen" then flags[:locked] = flags[:frozen] = true
    when "--offline" then flags[:offline] = true
    end
  end
  arguments.reject! { |argument| %w[--locked --frozen --offline].include?(argument) }

  command = arguments.shift
  raise Error.usage("pray requires a command; run pray --help") unless command

  case command
  when "manifest" then [:manifest]
  when "init"
    targets = []
    while (argument = arguments.shift)
      if argument == "--targets"
        targets = arguments.shift.to_s.split(",").map(&:strip).reject(&:empty?)
      end
    end
    [:init, targets]
  when "prayer"
    raise Error.unsupported("prayer requires init") unless arguments.shift == "init"

    [:prayer_init]
  when "repo"
    raise Error.unsupported("repo requires init") unless arguments.shift == "init"

    [:repo_init]
  when "install" then [:install, flags]
  when "add" then [:add, parse_add_arguments(arguments)]
  when "remove" then [:remove, arguments.shift]
  when "update" then [:update, arguments]
  when "unlock" then [:unlock, arguments.shift]
  when "render" then [:render, flags]
  when "plan" then [:plan, arguments]
  when "apply" then [:apply]
  when "verify" then [:verify, flags]
  when "drift" then [:drift, flags]
  when "format", "fmt" then [:format]
  when "package" then [:package]
  when "publish" then [:publish, parse_publish_arguments(arguments)]
  when "login" then [:login, (arguments)]
  when "serve" then [:serve, parse_serve_arguments(arguments)]
  when "confess" then [:confess, parse_confess_arguments(arguments)]
  when "list" then [:list]
  when "outdated" then [:outdated, arguments]
  when "explain" then [:explain, arguments.shift]
  when "vendor" then [:vendor]
  when "clean" then [:clean]
  when "tree" then [:tree]
  when "sync" then [:sync, parse_sync_arguments(arguments)]
  when "trust" then parse_trust_command(arguments)
  when "version", "-V", "--version" then [:version]
  else
    raise Error.usage(Suggest.unknown_command_message(command))
  end
end

#parse_confess_arguments(arguments) ⇒ Object



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/pray/cli/parse_auth.rb', line 45

def parse_confess_arguments(arguments)
  package = nil
  from_lock = nil
  version = nil
  accepted = false
  rejected = false
  note = nil
  url = nil
  while (argument = arguments.shift)
    case argument
    when "--from-lock" then from_lock = arguments.shift
    when "--version" then version = arguments.shift
    when "--accepted" then accepted = true
    when "--rejected" then rejected = true
    when "--note" then note = arguments.shift
    when "--url" then url = arguments.shift
    when /\A-/ then raise Error.unsupported("unknown confess argument: #{argument}")
    else package = argument
    end
  end
  if accepted == rejected
    raise Error.unsupported("confess requires exactly one of --accepted or --rejected")
  end
  if package && from_lock
    raise Error.unsupported("confess accepts either a package name or --from-lock")
  end
  raise Error.unsupported("confess requires a package name") if package.nil? && from_lock.nil?

  {package: package, from_lock: from_lock, version: version, accepted: accepted,
   rejected: rejected, note: note, url: url}
end

#parse_login_arguments(arguments) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/pray/cli/parse_auth.rb', line 5

def (arguments)
  servers = []
  email = nil
  passkey_key = nil
  credential_id = nil
  ssh_agent = false
  public_key = nil
  while (argument = arguments.shift)
    case argument
    when "--server" then servers << arguments.shift
    when "--email" then email = arguments.shift
    when "--passkey-key" then passkey_key = arguments.shift
    when "--credential-id" then credential_id = arguments.shift
    when "--ssh-agent" then ssh_agent = true
    when "--public-key" then public_key = arguments.shift
    else
      raise Error.unsupported("unknown login argument: #{argument}")
    end
  end
  raise Error.unsupported("login requires --server URL") if servers.empty?
  raise Error.unsupported("login requires --email EMAIL") if email.to_s.empty?

  passkey = passkey_key || credential_id
  if passkey && ssh_agent
    raise Error.unsupported("login accepts either passkey or ssh-agent mode, not both")
  end
  if passkey_key
    raise Error.unsupported("passkey login requires --credential-id") if credential_id.to_s.empty?

    {servers: servers, email: email, mode: :passkey, passkey_key: passkey_key,
     credential_id: credential_id}
  elsif ssh_agent
    raise Error.unsupported("ssh-agent login requires --public-key") if public_key.to_s.empty?

    {servers: servers, email: email, mode: :ssh_agent, public_key: public_key}
  else
    raise Error.unsupported("login requires --passkey-key/--credential-id or --ssh-agent")
  end
end

#parse_publish_arguments(arguments) ⇒ Object



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/pray/cli/parse.rb', line 103

def parse_publish_arguments(arguments)
  roots = []
  servers = []
  while (argument = arguments.shift)
    case argument
    when "--root"
      roots << arguments.shift
    when "--server"
      servers << arguments.shift
    else
      raise Error.unsupported("unexpected publish argument: #{argument}")
    end
  end
  raise Error.unsupported("publish requires at least one --root PATH or --server URL") if roots.empty? && servers.empty?

  {roots: roots, servers: servers}
end

#parse_serve_arguments(arguments) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/pray/cli/parse.rb', line 121

def parse_serve_arguments(arguments)
  options = {root: ".", host: "127.0.0.1", port: 7429, stdio: false}
  while (argument = arguments.shift)
    case argument
    when "--root" then options[:root] = arguments.shift
    when "--host" then options[:host] = arguments.shift
    when "--port" then options[:port] = Integer(arguments.shift)
    when "--stdio" then options[:stdio] = true
    else
      raise Error.unsupported("unexpected serve argument: #{argument}")
    end
  end
  options
end

#parse_sync_arguments(arguments) ⇒ Object



77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/pray/cli/parse_auth.rb', line 77

def parse_sync_arguments(arguments)
  root = "."
  peers = []
  while (argument = arguments.shift)
    case argument
    when "--root" then root = arguments.shift
    when "--peer" then peers << arguments.shift
    else
      raise Error.unsupported("unknown sync argument: #{argument}")
    end
  end
  {root: root, peers: peers}
end

#parse_trust_command(arguments) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/pray/cli/parse_trust.rb', line 5

def parse_trust_command(arguments)
  subcommand = arguments.shift
  raise Error.unsupported("trust requires a subcommand") unless subcommand

  case subcommand
  when "list" then [:trust_list, parse_trust_list_arguments(arguments)]
  when "show"
    raise Error.unsupported("unknown trust show argument: #{arguments.first}") if arguments.any?

    [:trust_show]
  when "add-key" then [:trust_add_key, parse_trust_key_arguments(arguments, "add-key")]
  when "remove-key", "revoke"
    [:trust_remove_key, parse_trust_key_arguments(arguments, "remove-key")]
  when "set-signed" then [:trust_set_signed, parse_trust_set_signed_arguments(arguments)]
  when "set-allow" then [:trust_set_allow, parse_trust_set_allow_arguments(arguments)]
  when "import-repo" then [:trust_import_repo, parse_trust_import_repo_arguments(arguments)]
  when "import-registry"
    [:trust_import_registry, parse_trust_import_registry_arguments(arguments)]
  when "check"
    source = arguments.shift
    raise Error.unsupported("trust check accepts at most one SOURCE argument") if arguments.any?

    [:trust_check, source]
  else
    raise Error.unsupported("unknown trust command: #{subcommand}")
  end
end

#parse_trust_import_registry_arguments(arguments) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/pray/cli/parse_trust.rb', line 123

def parse_trust_import_registry_arguments(arguments)
  source_url = arguments.shift
  raise Error.unsupported("trust import-registry requires SOURCE_URL") unless source_url

  match_prefix = nil
  include_host_key = source_url.start_with?("pray+ssh://", "ssh+pray://")
  while (argument = arguments.shift)
    case argument
    when "--match-prefix"
      match_prefix = arguments.shift
      raise Error.unsupported("--match-prefix requires VALUE") unless match_prefix
    when "--host-key" then include_host_key = true
    when "--no-host-key" then include_host_key = false
    else
      raise Error.unsupported("unknown trust import-registry argument: #{argument}")
    end
  end
  {source_url: source_url, match_prefix: match_prefix, include_host_key: include_host_key}
end

#parse_trust_import_repo_arguments(arguments) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/pray/cli/parse_trust.rb', line 107

def parse_trust_import_repo_arguments(arguments)
  source_url = arguments.shift
  raise Error.unsupported("trust import-repo requires SOURCE_URL") unless source_url

  match_prefix = nil
  while (argument = arguments.shift)
    if argument == "--match-prefix"
      match_prefix = arguments.shift
      raise Error.unsupported("--match-prefix requires VALUE") unless match_prefix
    else
      raise Error.unsupported("unknown trust import-repo argument: #{argument}")
    end
  end
  {source_url: source_url, match_prefix: match_prefix}
end

#parse_trust_key_arguments(arguments, label) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/pray/cli/parse_trust.rb', line 47

def parse_trust_key_arguments(arguments, label)
  key = arguments.shift
  raise Error.unsupported("trust #{label} requires KEY") unless key

  match_prefix = nil
  while (argument = arguments.shift)
    if argument == "--match-prefix"
      match_prefix = arguments.shift
      raise Error.unsupported("--match-prefix requires VALUE") unless match_prefix
    else
      raise Error.unsupported("unknown trust #{label} argument: #{argument}")
    end
  end
  {key: key, match_prefix: match_prefix}
end

#parse_trust_list_arguments(arguments) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/pray/cli/parse_trust.rb', line 33

def parse_trust_list_arguments(arguments)
  scope = :all
  source_url = nil
  arguments.each do |argument|
    case argument
    when "--global" then scope = :global
    when "--local" then scope = :local
    when /\A-/ then raise Error.unsupported("unknown trust list argument: #{argument}")
    else source_url = argument
    end
  end
  {scope: scope, source_url: source_url}
end

#parse_trust_set_allow_arguments(arguments) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/pray/cli/parse_trust.rb', line 85

def parse_trust_set_allow_arguments(arguments)
  match_prefix = nil
  allow = true
  while (argument = arguments.shift)
    case argument
    when "--match-prefix"
      match_prefix = arguments.shift
      raise Error.unsupported("--match-prefix requires VALUE") unless match_prefix
    when "--allow"
      value = arguments.shift
      raise Error.unsupported("--allow requires true|false") unless value

      allow = parse_bool_flag(value)
    else
      raise Error.unsupported("unknown trust set-allow argument: #{argument}")
    end
  end
  raise Error.unsupported("trust set-allow requires --match-prefix PREFIX") unless match_prefix

  {match_prefix: match_prefix, allow: allow}
end

#parse_trust_set_signed_arguments(arguments) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/pray/cli/parse_trust.rb', line 63

def parse_trust_set_signed_arguments(arguments)
  match_prefix = nil
  enabled = true
  while (argument = arguments.shift)
    case argument
    when "--match-prefix"
      match_prefix = arguments.shift
      raise Error.unsupported("--match-prefix requires VALUE") unless match_prefix
    when "--enabled"
      value = arguments.shift
      raise Error.unsupported("--enabled requires true|false") unless value

      enabled = parse_bool_flag(value)
    else
      raise Error.unsupported("unknown trust set-signed argument: #{argument}")
    end
  end
  raise Error.unsupported("trust set-signed requires --match-prefix PREFIX") unless match_prefix

  {match_prefix: match_prefix, enabled: enabled}
end

#plan_command(_arguments) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/pray/cli/commands/workflow.rb', line 72

def plan_command(_arguments)
  project = Invocation.resolve_current_project_with_git_refresh_fallback(
    ResolveOptions.new,
    allow_git_refresh_fallback: true
  )
  rendered = Render.render_project(project)
  lockfile = build_lockfile(project, rendered)
  previous_lockfile = File.exist?(lockfile_path) ? Pray.read_lockfile(lockfile_path) : nil
  preview = Plan.build_materialization_preview(
    project, rendered, lockfile, lockfile_path, previous_lockfile
  )
  Plan.print_materialization_report(preview, :plan)
end

#prayer_init_commandObject



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/pray/cli/commands/init.rb', line 28

def prayer_init_command
  root = Dir.pwd
  package_name = File.basename(root)
  package_name = "prayer-package" if package_name.strip.empty?
  prayspec_path = File.join(root, "#{package_name}.prayspec")
  raise Error.manifest("package spec already exists: #{prayspec_path}") if File.exist?(prayspec_path)

  File.write(
    prayspec_path,
    <<~SPEC
      Package::Specification.new do |spec|
        spec.name = "#{package_name}"
        spec.version = "0.1.0"
        spec.summary = "Prayer package"
        spec.files = []
      end
    SPEC
  )
end

#project_rootObject



15
16
17
# File 'lib/pray/cli/helpers.rb', line 15

def project_root
  Invocation.project_root
end

#publish_command(roots:, servers:) ⇒ Object



5
6
7
8
9
# File 'lib/pray/cli/commands/distribution.rb', line 5

def publish_command(roots:, servers:)
  project = resolve_current_project
  roots.each { |root| Publish.publish_to_root(project, root) }
  servers.each { |server| Publish.publish_to_server(project, server) }
end

#remove_command(name) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/pray/cli/commands/packages.rb', line 21

def remove_command(name)
  raise Error.manifest("remove requires a package name") unless name

  manifest_path_value = manifest_path
  manifest_text = Pray.read_manifest_text(manifest_path_value)
  manifest = Pray.parse_manifest(manifest_text)
  unless manifest.packages.any? { |package| package.name == name }
    raise Error.manifest("package #{name} not found")
  end

  File.write(manifest_path_value, remove_manifest_statement(manifest_text, name))
  install_command({locked: false, frozen: false, offline: false})
end

#remove_manifest_statement(text, name) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/pray/cli/helpers.rb', line 80

def remove_manifest_statement(text, name)
  lines = text.lines.map(&:chomp)
  package_prefix = "agent \"#{name}\""
  alternate_prefix = "agent '#{name}'"
  index = lines.index do |line|
    trimmed = line.lstrip
    trimmed.start_with?(package_prefix, alternate_prefix)
  end
  if index
    lines.delete_at(index)
    lines.delete_at(index) if index < lines.length && lines[index].strip.empty?
    lines.delete_at(index - 1) if index.positive? && lines[index - 1].strip.empty?
  end
  output = lines.join("\n")
  output += "\n" unless output.end_with?("\n")
  output
end

#remove_path_if_exists(path) ⇒ Object



160
161
162
163
164
# File 'lib/pray/cli/helpers.rb', line 160

def remove_path_if_exists(path)
  return unless File.exist?(path)

  File.directory?(path) ? FileUtils.rm_rf(path) : File.delete(path)
end

#render_command(flags) ⇒ Object



17
18
19
20
21
22
23
24
25
# File 'lib/pray/cli/commands/workflow.rb', line 17

def render_command(flags)
  project = resolve_current_project
  rendered = Render.render_project(project)
  if flags[:check]
    ensure_rendered_outputs_current(project, rendered)
  else
    Render.write_rendered_targets(project, rendered)
  end
end

#render_tree_node(package, package_map, depth, ancestry, lines) ⇒ Object



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/pray/cli/helpers.rb', line 141

def render_tree_node(package, package_map, depth, ancestry, lines)
  indent = "  " * depth
  lines << "#{indent}#{package.declaration.name} #{package.spec.version}"
  return unless ancestry.add?(package.declaration.name)

  package.spec.dependencies.each do |dependency|
    resolved = package_map[dependency.name]
    if resolved
      if ancestry.include?(resolved.declaration.name)
        lines << "#{indent}  #{resolved.declaration.name} #{resolved.spec.version} (cycle)"
      else
        render_tree_node(resolved, package_map, depth + 1, ancestry, lines)
      end
    else
      lines << "#{indent}  #{dependency.name} (missing)"
    end
  end
end

#repo_distribution_root(root) ⇒ Object



58
59
60
# File 'lib/pray/cli/commands/init.rb', line 58

def repo_distribution_root(root)
  (File.basename(root) == "prayers") ? root : File.join(root, "prayers")
end

#repo_init_commandObject



48
49
50
51
52
53
54
55
56
# File 'lib/pray/cli/commands/init.rb', line 48

def repo_init_command
  distribution_root = repo_distribution_root(Dir.pwd)
  index_path = File.join(distribution_root, "v1", "index.json")
  raise Error.manifest("distribution repo already exists") if File.exist?(index_path)

  FileUtils.mkdir_p(File.join(distribution_root, "v1", "packages"))
  FileUtils.mkdir_p(File.join(distribution_root, "v1", "artifacts"))
  Publish.write_registry_index(distribution_root, RegistryIndex.new)
end

#resolve_current_project(options = ResolveOptions.new) ⇒ Object



19
20
21
# File 'lib/pray/cli/helpers.rb', line 19

def resolve_current_project(options = ResolveOptions.new)
  Invocation.resolve_current_project(options)
end

#run(arguments) ⇒ Object



23
24
25
26
27
28
29
30
31
32
33
# File 'lib/pray/cli.rb', line 23

def run(arguments)
  arguments = arguments.dup
  if arguments.delete("--no-input")
    ENV["PRAY_NO_INPUT"] = "1"
  end
  arguments = Invocation.initialize(arguments)
  return if maybe_print_help(arguments)

  command = parse_command(arguments)
  dispatch(command)
end

#serve_command(root:, host:, port:, stdio:) ⇒ Object



11
12
13
14
15
# File 'lib/pray/cli/commands/distribution.rb', line 11

def serve_command(root:, host:, port:, stdio:)
  raise Error.unsupported("serve --stdio is not implemented yet in pray-cli Ruby") if stdio

  Serve.run_server(root: root, host: host, port: port)
end

#sync_command(root:, peers:) ⇒ Object



31
32
33
34
35
36
37
38
39
40
# File 'lib/pray/cli/commands/auth.rb', line 31

def sync_command(root:, peers:)
  peer_sources = if peers.empty?
    Sync.load_sync_peers(root).map { |peer| peer["url"] }
  else
    peers
  end
  summary = Sync.synchronize_registry(root, peer_sources)
  puts "Synchronized #{summary[:packages]} package(s) from #{summary[:peers]} peer(s); " \
       "learned #{summary[:known_peers]} peer(s)"
end

#tree_commandObject



44
45
46
47
48
49
50
51
52
# File 'lib/pray/cli/commands/packages.rb', line 44

def tree_command
  project = resolve_current_project
  package_map = project.packages.to_h { |package| [package.declaration.name, package] }
  lines = ["Dependency tree"]
  project.packages.each do |package|
    render_tree_node(package, package_map, 0, Set.new, lines)
  end
  puts lines.join("\n")
end

#trust_add_key_command(key, match_prefix: nil) ⇒ Object



13
14
15
# File 'lib/pray/cli/commands/trust.rb', line 13

def trust_add_key_command(key, match_prefix: nil)
  Trust.add_allowed_signing_key(key, match_prefix: match_prefix)
end

#trust_check_command(source = nil) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/pray/cli/commands/trust.rb', line 52

def trust_check_command(source = nil)
  source_description, hits = Trust.check_compromised(source)
  if hits.empty?
    puts "no compromised trusted signing keys detected (checked against #{source_description})"
    return
  end

  hits.each do |key, scopes, matches|
    puts "[compromised] #{key}"
    puts "  scopes: #{scopes.join(", ")}"
    matches.each do |entry|
      puts "  reason: #{entry.reason}" if entry.reason
      puts "  reference: #{entry.reference}" if entry.reference
      puts "  reported_at: #{entry.reported_at}" if entry.reported_at
    end
  end
  raise Error.integrity(
    "found #{hits.length} compromised trusted key(s) in #{source_description}"
  )
end

#trust_import_registry_command(source_url, match_prefix: nil, include_host_key: false) ⇒ Object



44
45
46
47
48
49
50
# File 'lib/pray/cli/commands/trust.rb', line 44

def trust_import_registry_command(source_url, match_prefix: nil, include_host_key: false)
  result = Trust.import_registry(
    source_url, match_prefix: match_prefix, include_host_key: include_host_key
  )
  puts "imported #{result[:publishers_added]} publisher fingerprint(s) and " \
       "#{result[:host_keys_added]} host key(s) for #{match_prefix || source_url}"
end

#trust_import_repo_command(source_url, match_prefix: nil) ⇒ Object



29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/pray/cli/commands/trust.rb', line 29

def trust_import_repo_command(source_url, match_prefix: nil)
  clone_url = source_url.delete_prefix("git+")
  repository = GitSources.git_source_cache_directory(Dir.pwd, clone_url)
  unless File.directory?(File.join(repository, ".git"))
    raise Error.resolution(
      "no cached git repository for #{clone_url} at #{repository}"
    )
  end

  added = Trust.import_repo(
    clone_url, repository, match_prefix: match_prefix || clone_url
  )
  puts "imported #{added} key(s) from #{repository}"
end

#trust_list_command(scope: :all, source_url: nil) ⇒ Object



5
6
7
# File 'lib/pray/cli/commands/trust.rb', line 5

def trust_list_command(scope: :all, source_url: nil)
  puts Trust.list_policy(scope: scope, source_url: source_url)
end

#trust_remove_key_command(key, match_prefix: nil) ⇒ Object



17
18
19
# File 'lib/pray/cli/commands/trust.rb', line 17

def trust_remove_key_command(key, match_prefix: nil)
  Trust.remove_allowed_signing_key(key, match_prefix: match_prefix)
end

#trust_set_allow_command(match_prefix:, allow:) ⇒ Object



25
26
27
# File 'lib/pray/cli/commands/trust.rb', line 25

def trust_set_allow_command(match_prefix:, allow:)
  Trust.set_allow(match_prefix, allow)
end

#trust_set_signed_command(match_prefix:, enabled:) ⇒ Object



21
22
23
# File 'lib/pray/cli/commands/trust.rb', line 21

def trust_set_signed_command(match_prefix:, enabled:)
  Trust.set_require_signed_commit(match_prefix, enabled)
end

#trust_show_commandObject



9
10
11
# File 'lib/pray/cli/commands/trust.rb', line 9

def trust_show_command
  puts Trust.show_policy_toml
end

#unlock_command(name) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/pray/cli/commands/workflow.rb', line 98

def unlock_command(name)
  raise Error.manifest("unlock requires a package name") unless name

  project = resolve_current_project
  unless project.manifest.packages.any? { |entry| entry.name == name }
    raise Error.manifest("package #{name} not found")
  end

  previous_lockfile = Pray.read_lockfile(lockfile_path)
  rendered = Render.render_project(project)
  updated_lockfile = build_lockfile(project, rendered)
  merged_lockfile = merge_selected_package_update(previous_lockfile, updated_lockfile, name)
  Pray.write_lockfile(lockfile_path, merged_lockfile)
  Render.write_rendered_targets(project, rendered)
  puts "Unlocked #{name}"
end

#update_command(arguments) ⇒ Object



86
87
88
89
90
91
92
93
94
95
96
# File 'lib/pray/cli/commands/workflow.rb', line 86

def update_command(arguments)
  package = arguments.reject { |argument| argument.start_with?("--") }.first
  offline = arguments.include?("--offline")
  if package
    manifest = Pray.parse_manifest(Pray.read_manifest_text(manifest_path))
    unless manifest.packages.any? { |entry| entry.name == package }
      raise Error.manifest("package #{package} not found")
    end
  end
  install_command({locked: false, frozen: false, offline: offline, refresh: true})
end

#verify_command(flags) ⇒ Object



27
28
29
30
31
32
# File 'lib/pray/cli/commands/workflow.rb', line 27

def verify_command(flags)
  project = resolve_current_project
  lockfile = ensure_existing_lockfile(lockfile_path)
  report = Verify.verify_project(project, lockfile, strict: flags[:strict])
  puts format_verification_report(report) unless report.clean?
end

#version_commandObject



5
6
7
# File 'lib/pray/cli/commands/meta.rb', line 5

def version_command
  puts "pray #{Pray::VERSION}"
end