space-architect 1.1.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 +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +284 -0
- data/exe/architect +13 -0
- data/exe/space +13 -0
- data/lib/space_architect/architect_mission.rb +436 -0
- data/lib/space_architect/atomic_write.rb +21 -0
- data/lib/space_architect/cli/architect.rb +388 -0
- data/lib/space_architect/cli/config.rb +61 -0
- data/lib/space_architect/cli/current.rb +22 -0
- data/lib/space_architect/cli/helpers.rb +117 -0
- data/lib/space_architect/cli/init.rb +35 -0
- data/lib/space_architect/cli/list.rb +30 -0
- data/lib/space_architect/cli/new.rb +43 -0
- data/lib/space_architect/cli/options.rb +12 -0
- data/lib/space_architect/cli/path.rb +22 -0
- data/lib/space_architect/cli/repo.rb +88 -0
- data/lib/space_architect/cli/shell.rb +137 -0
- data/lib/space_architect/cli/show.rb +27 -0
- data/lib/space_architect/cli/space.rb +35 -0
- data/lib/space_architect/cli/src.rb +32 -0
- data/lib/space_architect/cli/status.rb +39 -0
- data/lib/space_architect/cli/use.rb +23 -0
- data/lib/space_architect/cli.rb +102 -0
- data/lib/space_architect/config.rb +152 -0
- data/lib/space_architect/dispatcher.rb +21 -0
- data/lib/space_architect/errors.rb +14 -0
- data/lib/space_architect/git_client.rb +49 -0
- data/lib/space_architect/harness.rb +168 -0
- data/lib/space_architect/mise_client.rb +37 -0
- data/lib/space_architect/repo_reference.rb +19 -0
- data/lib/space_architect/repo_resolver.rb +167 -0
- data/lib/space_architect/shell_integration.rb +438 -0
- data/lib/space_architect/slugger.rb +16 -0
- data/lib/space_architect/space.rb +110 -0
- data/lib/space_architect/space_store.rb +319 -0
- data/lib/space_architect/state.rb +86 -0
- data/lib/space_architect/templates/architect.md.erb +48 -0
- data/lib/space_architect/templates/iteration.md.erb +66 -0
- data/lib/space_architect/terminal.rb +163 -0
- data/lib/space_architect/version.rb +5 -0
- data/lib/space_architect/warnings.rb +13 -0
- data/lib/space_architect/xdg.rb +33 -0
- data/lib/space_architect.rb +26 -0
- data/vendor/repo-tender/lib/space_architect/pristine/cli/clone.rb +55 -0
- data/vendor/repo-tender/lib/space_architect/pristine/cli/config.rb +66 -0
- data/vendor/repo-tender/lib/space_architect/pristine/cli/daemon.rb +347 -0
- data/vendor/repo-tender/lib/space_architect/pristine/cli/options.rb +21 -0
- data/vendor/repo-tender/lib/space_architect/pristine/cli/org.rb +200 -0
- data/vendor/repo-tender/lib/space_architect/pristine/cli/repo.rb +170 -0
- data/vendor/repo-tender/lib/space_architect/pristine/cli/status.rb +76 -0
- data/vendor/repo-tender/lib/space_architect/pristine/cli/sync.rb +149 -0
- data/vendor/repo-tender/lib/space_architect/pristine/cli.rb +137 -0
- data/vendor/repo-tender/lib/space_architect/pristine/cloner.rb +75 -0
- data/vendor/repo-tender/lib/space_architect/pristine/config/contract.rb +54 -0
- data/vendor/repo-tender/lib/space_architect/pristine/config/duration.rb +79 -0
- data/vendor/repo-tender/lib/space_architect/pristine/config/model.rb +49 -0
- data/vendor/repo-tender/lib/space_architect/pristine/config/store.rb +156 -0
- data/vendor/repo-tender/lib/space_architect/pristine/forge/client.rb +31 -0
- data/vendor/repo-tender/lib/space_architect/pristine/forge/github.rb +98 -0
- data/vendor/repo-tender/lib/space_architect/pristine/launchd/agent.rb +195 -0
- data/vendor/repo-tender/lib/space_architect/pristine/launchd/plist.rb +129 -0
- data/vendor/repo-tender/lib/space_architect/pristine/log_rotator.rb +46 -0
- data/vendor/repo-tender/lib/space_architect/pristine/paths.rb +72 -0
- data/vendor/repo-tender/lib/space_architect/pristine/scm/client.rb +87 -0
- data/vendor/repo-tender/lib/space_architect/pristine/scm/git.rb +232 -0
- data/vendor/repo-tender/lib/space_architect/pristine/scm/status.rb +24 -0
- data/vendor/repo-tender/lib/space_architect/pristine/shell.rb +90 -0
- data/vendor/repo-tender/lib/space_architect/pristine/state/lock.rb +59 -0
- data/vendor/repo-tender/lib/space_architect/pristine/state/store.rb +140 -0
- data/vendor/repo-tender/lib/space_architect/pristine/sync/engine.rb +464 -0
- data/vendor/repo-tender/lib/space_architect/pristine/sync/repo_plan.rb +215 -0
- data/vendor/repo-tender/lib/space_architect/pristine/ui/interactive_reporter.rb +280 -0
- data/vendor/repo-tender/lib/space_architect/pristine/ui/json_reporter.rb +39 -0
- data/vendor/repo-tender/lib/space_architect/pristine/ui/mode.rb +68 -0
- data/vendor/repo-tender/lib/space_architect/pristine/ui/plain_reporter.rb +53 -0
- data/vendor/repo-tender/lib/space_architect/pristine/ui/reporter.rb +48 -0
- data/vendor/repo-tender/lib/space_architect/pristine/version.rb +7 -0
- data/vendor/repo-tender/lib/space_architect/pristine.rb +37 -0
- metadata +307 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SpaceArchitect
|
|
4
|
+
module CLI
|
|
5
|
+
class New < Dry::CLI::Command
|
|
6
|
+
include GlobalOptions
|
|
7
|
+
include Helpers
|
|
8
|
+
|
|
9
|
+
desc "Create a new project space"
|
|
10
|
+
argument :title, required: true, desc: "Space title"
|
|
11
|
+
argument :repos, type: :array, required: false, desc: "Repo refs to clone"
|
|
12
|
+
option :git, type: :boolean, default: true, desc: "Initialize the space as a Git repository (use --no-git to skip)"
|
|
13
|
+
|
|
14
|
+
def call(title:, repos: [], git: true, **opts)
|
|
15
|
+
setup_terminal(**opts.slice(:color, :colors))
|
|
16
|
+
result = store.create(title, git: git).bind do |space|
|
|
17
|
+
terminal.success "Created #{space.id}"
|
|
18
|
+
|
|
19
|
+
repo_specs = Array(repos).compact
|
|
20
|
+
repo_specs.each { |spec| terminal.say "Queued #{spec}" }
|
|
21
|
+
|
|
22
|
+
next Success(space) if repo_specs.empty?
|
|
23
|
+
|
|
24
|
+
progress = RepoProgress.new(repo_specs.length)
|
|
25
|
+
terminal.with_spinner(-> { progress.message }) do
|
|
26
|
+
store.add_repos_to(space, repo_specs, reporter: progress)
|
|
27
|
+
end.fmap do |results|
|
|
28
|
+
results.each do |r|
|
|
29
|
+
terminal.success "Added #{r.fetch(:repo).fetch('full_name')}"
|
|
30
|
+
terminal.say terminal.path(r.fetch(:path))
|
|
31
|
+
end
|
|
32
|
+
space
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
render(result) do |space|
|
|
36
|
+
terminal.say terminal.path(space.path)
|
|
37
|
+
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SpaceArchitect
|
|
4
|
+
module CLI
|
|
5
|
+
module GlobalOptions
|
|
6
|
+
def self.included(base)
|
|
7
|
+
base.option :color, type: :string, default: "auto", desc: "Color output: auto, always, never"
|
|
8
|
+
base.option :colors, type: :string, desc: "Alias for --color"
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SpaceArchitect
|
|
4
|
+
module CLI
|
|
5
|
+
class Path < Dry::CLI::Command
|
|
6
|
+
include GlobalOptions
|
|
7
|
+
include Helpers
|
|
8
|
+
|
|
9
|
+
desc "Print the path for a space or the current space"
|
|
10
|
+
argument :identifier, required: false, desc: "Space ID or title slug"
|
|
11
|
+
|
|
12
|
+
def call(identifier: nil, **opts)
|
|
13
|
+
setup_terminal(**opts.slice(:color, :colors))
|
|
14
|
+
render(store.path_for(identifier)) do |path|
|
|
15
|
+
terminal.say terminal.path(path)
|
|
16
|
+
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SpaceArchitect
|
|
4
|
+
module CLI
|
|
5
|
+
module Repo
|
|
6
|
+
class Add < Dry::CLI::Command
|
|
7
|
+
include GlobalOptions
|
|
8
|
+
include Helpers
|
|
9
|
+
|
|
10
|
+
desc "Clone repos into the current space"
|
|
11
|
+
argument :repos, type: :array, required: false, desc: "REPO [REPO...]"
|
|
12
|
+
|
|
13
|
+
def call(repos: [], **opts)
|
|
14
|
+
setup_terminal(**opts.slice(:color, :colors))
|
|
15
|
+
handle_errors do
|
|
16
|
+
specs = Array(repos).compact
|
|
17
|
+
if specs.empty?
|
|
18
|
+
terminal.error("Usage: space repo add REPO [REPO...]")
|
|
19
|
+
CLI.record_outcome(Outcome.new(exit_code: 1))
|
|
20
|
+
next
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
progress = RepoProgress.new(specs.length)
|
|
24
|
+
add_result = terminal.with_spinner(-> { progress.message }) do
|
|
25
|
+
store.add_repos(specs, reporter: progress)
|
|
26
|
+
end
|
|
27
|
+
render(add_result) do |results|
|
|
28
|
+
results.each do |result|
|
|
29
|
+
terminal.success "Added #{result.fetch(:repo).fetch('full_name')}"
|
|
30
|
+
terminal.say terminal.path(result.fetch(:path))
|
|
31
|
+
end
|
|
32
|
+
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class RepoList < Dry::CLI::Command
|
|
39
|
+
include GlobalOptions
|
|
40
|
+
include Helpers
|
|
41
|
+
|
|
42
|
+
desc "List repos in the current space"
|
|
43
|
+
|
|
44
|
+
def call(**opts)
|
|
45
|
+
setup_terminal(**opts.slice(:color, :colors))
|
|
46
|
+
handle_errors do
|
|
47
|
+
render(store.repos) do |repos|
|
|
48
|
+
if repos.empty?
|
|
49
|
+
id = store.find.fmap(&:id).value_or("(unknown space)")
|
|
50
|
+
terminal.say "No repos found in #{id}"
|
|
51
|
+
next
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
rows = repos.map { |repo| [repo.fetch("full_name", repo["name"]), repo.fetch("path", "")] }
|
|
55
|
+
terminal.say terminal.table(["Repo", "Path"], rows)
|
|
56
|
+
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class Resolve < Dry::CLI::Command
|
|
63
|
+
include GlobalOptions
|
|
64
|
+
include Helpers
|
|
65
|
+
|
|
66
|
+
desc "Resolve repo refs without cloning"
|
|
67
|
+
argument :repos, type: :array, required: false, desc: "REPO [REPO...]"
|
|
68
|
+
|
|
69
|
+
def call(repos: [], **opts)
|
|
70
|
+
setup_terminal(**opts.slice(:color, :colors))
|
|
71
|
+
handle_errors do
|
|
72
|
+
specs = Array(repos).compact
|
|
73
|
+
if specs.empty?
|
|
74
|
+
terminal.error("Usage: space repo resolve REPO [REPO...]")
|
|
75
|
+
CLI.record_outcome(Outcome.new(exit_code: 1))
|
|
76
|
+
next
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
references = specs.map { |spec| RepoResolver.new(project_config).resolve(spec) }
|
|
80
|
+
terminal.say terminal.table(["Repo", "Clone URL"], references.map { |ref| [ref.full_name, ref.clone_url] })
|
|
81
|
+
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SpaceArchitect
|
|
4
|
+
module CLI
|
|
5
|
+
module Shell
|
|
6
|
+
class ShellInit < Dry::CLI::Command
|
|
7
|
+
include GlobalOptions
|
|
8
|
+
include Helpers
|
|
9
|
+
|
|
10
|
+
desc "Print shell integration script"
|
|
11
|
+
argument :shell_name, required: true, desc: "Shell name (e.g. fish)"
|
|
12
|
+
|
|
13
|
+
def call(shell_name:, **opts)
|
|
14
|
+
setup_terminal(**opts.slice(:color, :colors))
|
|
15
|
+
handle_errors do
|
|
16
|
+
terminal.say ShellIntegration.for(shell_name)
|
|
17
|
+
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class Fish < Dry::CLI::Command
|
|
23
|
+
include GlobalOptions
|
|
24
|
+
include Helpers
|
|
25
|
+
|
|
26
|
+
desc "Manage fish shell integration: install, uninstall, path"
|
|
27
|
+
argument :subcommand, required: false, desc: "install, uninstall, or path (default: install)"
|
|
28
|
+
option :force, type: :boolean, default: false, desc: "Overwrite or remove existing shell files"
|
|
29
|
+
|
|
30
|
+
def call(subcommand: "install", force: false, **opts)
|
|
31
|
+
setup_terminal(**opts.slice(:color, :colors))
|
|
32
|
+
handle_errors do
|
|
33
|
+
case subcommand
|
|
34
|
+
when "install"
|
|
35
|
+
result = ShellIntegration.install("fish", env: project_config.env, force: force)
|
|
36
|
+
terminal.success fish_install_message(result.fetch(:action), result.fetch(:path))
|
|
37
|
+
terminal.success fish_completions_install_message(result.fetch(:completions_action), result.fetch(:completions_path))
|
|
38
|
+
terminal.say "Restart fish to load the integration in this terminal: exec fish"
|
|
39
|
+
when "uninstall"
|
|
40
|
+
result = ShellIntegration.uninstall("fish", env: project_config.env, force: force)
|
|
41
|
+
terminal.success fish_uninstall_message(result.fetch(:action), result.fetch(:path))
|
|
42
|
+
terminal.success fish_completions_uninstall_message(result.fetch(:completions_action), result.fetch(:completions_path))
|
|
43
|
+
when "path"
|
|
44
|
+
terminal.say "Function: #{terminal.path(ShellIntegration.path_for('fish', env: project_config.env))}"
|
|
45
|
+
terminal.say "Completions: #{terminal.path(ShellIntegration.completions_path_for('fish', env: project_config.env))}"
|
|
46
|
+
else
|
|
47
|
+
err.puts "Usage: space shell fish [install|uninstall|path]"
|
|
48
|
+
CLI.record_outcome(Outcome.new(exit_code: 1))
|
|
49
|
+
next
|
|
50
|
+
end
|
|
51
|
+
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def fish_install_message(action, path)
|
|
58
|
+
case action
|
|
59
|
+
when :unchanged then "Fish integration already installed: #{terminal.path(path)}"
|
|
60
|
+
when :updated then "Updated fish integration: #{terminal.path(path)}"
|
|
61
|
+
else "Installed fish integration: #{terminal.path(path)}"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def fish_uninstall_message(action, path)
|
|
66
|
+
case action
|
|
67
|
+
when :missing then "Fish integration was not installed: #{terminal.path(path)}"
|
|
68
|
+
else "Removed fish integration: #{terminal.path(path)}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def fish_completions_install_message(action, path)
|
|
73
|
+
case action
|
|
74
|
+
when :unchanged then "Fish completions already installed: #{terminal.path(path)}"
|
|
75
|
+
when :updated then "Updated fish completions: #{terminal.path(path)}"
|
|
76
|
+
else "Installed fish completions: #{terminal.path(path)}"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def fish_completions_uninstall_message(action, path)
|
|
81
|
+
case action
|
|
82
|
+
when :missing then "Fish completions were not installed: #{terminal.path(path)}"
|
|
83
|
+
else "Removed fish completions: #{terminal.path(path)}"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
class Complete < Dry::CLI::Command
|
|
89
|
+
include GlobalOptions
|
|
90
|
+
include Helpers
|
|
91
|
+
|
|
92
|
+
desc "Print completion candidates"
|
|
93
|
+
argument :kind, required: true, desc: "Completion kind"
|
|
94
|
+
argument :extra, type: :array, required: false, desc: "Extra args for completion"
|
|
95
|
+
|
|
96
|
+
def call(kind:, extra: [], **opts)
|
|
97
|
+
setup_terminal(**opts.slice(:color, :colors))
|
|
98
|
+
handle_errors do
|
|
99
|
+
completion_candidates(kind, Array(extra)).each { |c| terminal.say c }
|
|
100
|
+
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def completion_candidates(kind, args)
|
|
107
|
+
case kind
|
|
108
|
+
when "spaces" then store.list.map { |space| "#{space.id}\t#{space.title}" }
|
|
109
|
+
when "statuses" then Space::VALID_STATUSES
|
|
110
|
+
when "config-keys" then SpaceArchitect::Config::EDITABLE_KEYS
|
|
111
|
+
when "config-values" then completion_values_for_config_key(args.first)
|
|
112
|
+
when "shells" then ["fish"]
|
|
113
|
+
when "color-modes" then %w[auto always never]
|
|
114
|
+
when "repo-subcommands" then %w[add list ls resolve]
|
|
115
|
+
when "config-subcommands" then %w[show path set]
|
|
116
|
+
when "fish-subcommands" then %w[install uninstall path]
|
|
117
|
+
else
|
|
118
|
+
raise SpaceArchitect::Error, "Usage: space shell complete #{completion_kinds.join('|')}"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def completion_values_for_config_key(key)
|
|
123
|
+
case key
|
|
124
|
+
when "git_clone_protocol" then SpaceArchitect::Config::VALID_GIT_CLONE_PROTOCOLS
|
|
125
|
+
when "default_provider" then %w[github.com gitlab.com]
|
|
126
|
+
else []
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def completion_kinds
|
|
131
|
+
%w[spaces statuses config-keys config-values shells color-modes repo-subcommands config-subcommands fish-subcommands]
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SpaceArchitect
|
|
4
|
+
module CLI
|
|
5
|
+
class Show < Dry::CLI::Command
|
|
6
|
+
include GlobalOptions
|
|
7
|
+
include Helpers
|
|
8
|
+
|
|
9
|
+
desc "Show metadata for a space or the current space"
|
|
10
|
+
argument :identifier, required: false, desc: "Space ID or title slug"
|
|
11
|
+
|
|
12
|
+
def call(identifier: nil, **opts)
|
|
13
|
+
setup_terminal(**opts.slice(:color, :colors))
|
|
14
|
+
render(store.find(identifier)) do |space|
|
|
15
|
+
terminal.say "ID: #{space.id}"
|
|
16
|
+
terminal.say "Title: #{space.title}"
|
|
17
|
+
terminal.say "Status: #{space.status}"
|
|
18
|
+
terminal.say "Path: #{terminal.path(space.path)}"
|
|
19
|
+
terminal.say "Created: #{space.data['created_at']}"
|
|
20
|
+
terminal.say "Updated: #{space.data['updated_at']}"
|
|
21
|
+
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
SpaceArchitect::CLI::Registry.register "space" do |prefix|
|
|
4
|
+
prefix.register "init", SpaceArchitect::CLI::Init
|
|
5
|
+
prefix.register "new", SpaceArchitect::CLI::New
|
|
6
|
+
prefix.register "list", SpaceArchitect::CLI::List
|
|
7
|
+
prefix.register "ls", SpaceArchitect::CLI::List
|
|
8
|
+
prefix.register "show", SpaceArchitect::CLI::Show
|
|
9
|
+
prefix.register "path", SpaceArchitect::CLI::Path
|
|
10
|
+
prefix.register "use", SpaceArchitect::CLI::Use
|
|
11
|
+
prefix.register "current", SpaceArchitect::CLI::Current
|
|
12
|
+
prefix.register "status", SpaceArchitect::CLI::Status
|
|
13
|
+
prefix.register "config" do |c|
|
|
14
|
+
c.register "show", SpaceArchitect::CLI::Config::Show
|
|
15
|
+
c.register "path", SpaceArchitect::CLI::Config::ConfigPath
|
|
16
|
+
c.register "set", SpaceArchitect::CLI::Config::Set
|
|
17
|
+
end
|
|
18
|
+
prefix.register "repo" do |r|
|
|
19
|
+
r.register "add", SpaceArchitect::CLI::Repo::Add
|
|
20
|
+
r.register "list", SpaceArchitect::CLI::Repo::RepoList
|
|
21
|
+
r.register "ls", SpaceArchitect::CLI::Repo::RepoList
|
|
22
|
+
r.register "resolve", SpaceArchitect::CLI::Repo::Resolve
|
|
23
|
+
end
|
|
24
|
+
prefix.register "repos" do |r|
|
|
25
|
+
r.register "add", SpaceArchitect::CLI::Repo::Add
|
|
26
|
+
r.register "list", SpaceArchitect::CLI::Repo::RepoList
|
|
27
|
+
r.register "ls", SpaceArchitect::CLI::Repo::RepoList
|
|
28
|
+
r.register "resolve", SpaceArchitect::CLI::Repo::Resolve
|
|
29
|
+
end
|
|
30
|
+
prefix.register "shell" do |s|
|
|
31
|
+
s.register "init", SpaceArchitect::CLI::Shell::ShellInit
|
|
32
|
+
s.register "fish", SpaceArchitect::CLI::Shell::Fish
|
|
33
|
+
s.register "complete", SpaceArchitect::CLI::Shell::Complete
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "space_architect/pristine/cli"
|
|
4
|
+
|
|
5
|
+
module SpaceArchitect
|
|
6
|
+
module CLI
|
|
7
|
+
# Exit-code bridge to the vendored Pristine (repo-tender) CLI engine.
|
|
8
|
+
# `architect src <args>` hands the raw remainder to Pristine's own dry-cli
|
|
9
|
+
# registry and translates Pristine's recorded Outcome into the host exit code.
|
|
10
|
+
# Pristine has its own Registry, Outcome, and :repo_tender_cli_* thread-locals;
|
|
11
|
+
# this is the seam between the two registries (NOT a re-registration). Pristine's
|
|
12
|
+
# top-level help/version interceptors call Kernel.exit, so we reproduce that
|
|
13
|
+
# interception here against the injected IO instead of delegating to them.
|
|
14
|
+
# dry-cli's internal exit on a bare group / unknown command propagates as
|
|
15
|
+
# SystemExit — same as the host's own bare groups (e.g. `space repo`) — and is
|
|
16
|
+
# intentionally NOT rescued (accepted behavior change).
|
|
17
|
+
def self.dispatch_src(rest, out = $stdout, err = $stderr)
|
|
18
|
+
if Pristine::CLI::TOP_LEVEL_HELP.include?(rest)
|
|
19
|
+
out.puts Dry::CLI::Usage.call(Pristine::CLI::Registry.get([]))
|
|
20
|
+
return 0
|
|
21
|
+
end
|
|
22
|
+
if Pristine::CLI::VERSION_REQUEST.include?(rest)
|
|
23
|
+
out.puts SpaceArchitect::Pristine::VERSION
|
|
24
|
+
return 0
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
Thread.current[:repo_tender_cli_outcome] = nil
|
|
28
|
+
Dry::CLI.new(Pristine::CLI::Registry).call(arguments: rest, out: out, err: err)
|
|
29
|
+
Pristine::CLI.last_outcome&.exit_code || 0
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SpaceArchitect
|
|
4
|
+
module CLI
|
|
5
|
+
class Status < Dry::CLI::Command
|
|
6
|
+
include GlobalOptions
|
|
7
|
+
include Helpers
|
|
8
|
+
|
|
9
|
+
desc "Set a space status: active, paused, done, archived"
|
|
10
|
+
argument :rest, type: :array, required: false, desc: "[SPACE] STATUS"
|
|
11
|
+
|
|
12
|
+
def call(rest: [], **opts)
|
|
13
|
+
setup_terminal(**opts.slice(:color, :colors))
|
|
14
|
+
handle_errors do
|
|
15
|
+
identifier, status_value = parse_status_args(Array(rest))
|
|
16
|
+
render(store.find(identifier)) do |space|
|
|
17
|
+
space.update_status(status_value)
|
|
18
|
+
terminal.success "#{space.id} is #{space.status}"
|
|
19
|
+
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def parse_status_args(args)
|
|
27
|
+
case args.length
|
|
28
|
+
when 1
|
|
29
|
+
[nil, args.first]
|
|
30
|
+
when 2
|
|
31
|
+
args
|
|
32
|
+
else
|
|
33
|
+
raise SpaceArchitect::Error, "Usage: space status [SPACE] STATUS"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SpaceArchitect
|
|
4
|
+
module CLI
|
|
5
|
+
class Use < Dry::CLI::Command
|
|
6
|
+
include GlobalOptions
|
|
7
|
+
include Helpers
|
|
8
|
+
|
|
9
|
+
desc "Remember a space in recent state and print its path"
|
|
10
|
+
argument :identifier, required: true, desc: "Space ID or title slug"
|
|
11
|
+
|
|
12
|
+
def call(identifier:, **opts)
|
|
13
|
+
setup_terminal(**opts.slice(:color, :colors))
|
|
14
|
+
render(store.use(identifier)) do |space|
|
|
15
|
+
terminal.success "Recent space: #{space.id}"
|
|
16
|
+
terminal.say terminal.path(space.path)
|
|
17
|
+
CLI.record_outcome(Outcome.new(exit_code: 0))
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/cli"
|
|
4
|
+
|
|
5
|
+
module SpaceArchitect
|
|
6
|
+
module CLI
|
|
7
|
+
Outcome = Data.define(:exit_code, :message) do
|
|
8
|
+
def initialize(exit_code:, message: nil) = super
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.record_outcome(o) = (Thread.current[:space_architect_outcome] = o)
|
|
12
|
+
def self.last_outcome = Thread.current[:space_architect_outcome]
|
|
13
|
+
|
|
14
|
+
module Registry
|
|
15
|
+
extend Dry::CLI::Registry
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
TOP_LEVEL_HELP = [[], ["--help"], ["-h"], ["help"]].freeze
|
|
19
|
+
VERSION_REQUEST = [["version"], ["--version"]].freeze
|
|
20
|
+
|
|
21
|
+
def self.call(argv, out = $stdout, err = $stderr)
|
|
22
|
+
Thread.current[:space_architect_outcome] = nil
|
|
23
|
+
|
|
24
|
+
if TOP_LEVEL_HELP.include?(argv)
|
|
25
|
+
out.puts Dry::CLI::Usage.call(Registry.get([]))
|
|
26
|
+
return 0
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if VERSION_REQUEST.include?(argv)
|
|
30
|
+
out.puts SpaceArchitect::VERSION
|
|
31
|
+
return 0
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
if argv.first == "src"
|
|
35
|
+
return dispatch_src(argv[1..], out, err)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
Dry::CLI.new(Registry).call(arguments: normalize_args(argv), out: out, err: err)
|
|
39
|
+
last_outcome&.exit_code || 0
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Move --color/--colors options to the end of the argument list so dry-cli's
|
|
43
|
+
# command routing is not confused by options before the subcommand name.
|
|
44
|
+
#
|
|
45
|
+
# Two passes:
|
|
46
|
+
# 1. Leading: extract two-token form (--color VALUE) and =-form from the
|
|
47
|
+
# front while args still look like options.
|
|
48
|
+
# 2. Non-leading: extract =-form (--color=VALUE / --colors=VALUE) from any
|
|
49
|
+
# position before the -- separator. The bare two-token form is ambiguous
|
|
50
|
+
# with a subcommand name in non-leading position and is left in place.
|
|
51
|
+
def self.normalize_args(argv)
|
|
52
|
+
args = argv.dup
|
|
53
|
+
extracted = []
|
|
54
|
+
|
|
55
|
+
# Pass 1: leading two-token and =-form (existing behavior, unchanged)
|
|
56
|
+
while (arg = args.first) && arg != "--" && arg.start_with?("-")
|
|
57
|
+
if %w[--color --colors].include?(arg)
|
|
58
|
+
extracted << args.shift
|
|
59
|
+
extracted << args.shift if args.first && !args.first.start_with?("-")
|
|
60
|
+
elsif arg.start_with?("--color=", "--colors=")
|
|
61
|
+
extracted << args.shift
|
|
62
|
+
else
|
|
63
|
+
break
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Pass 2: =-form from any non-leading position, stop at --
|
|
68
|
+
sep = args.index("--")
|
|
69
|
+
head = sep ? args[0, sep] : args
|
|
70
|
+
tail = sep ? args[sep..] : []
|
|
71
|
+
mid_color, head = head.partition { |a| a.start_with?("--color=", "--colors=") }
|
|
72
|
+
extracted += mid_color
|
|
73
|
+
args = head + tail
|
|
74
|
+
|
|
75
|
+
extracted.empty? ? args : args + extracted
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def self.run(argv, out = $stdout, err = $stderr)
|
|
79
|
+
Kernel.exit(call(argv, out, err))
|
|
80
|
+
rescue Interrupt
|
|
81
|
+
err.puts "interrupted"
|
|
82
|
+
Kernel.exit(130)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
require_relative "cli/options"
|
|
88
|
+
require_relative "cli/helpers"
|
|
89
|
+
require_relative "cli/init"
|
|
90
|
+
require_relative "cli/new"
|
|
91
|
+
require_relative "cli/list"
|
|
92
|
+
require_relative "cli/show"
|
|
93
|
+
require_relative "cli/path"
|
|
94
|
+
require_relative "cli/use"
|
|
95
|
+
require_relative "cli/current"
|
|
96
|
+
require_relative "cli/status"
|
|
97
|
+
require_relative "cli/config"
|
|
98
|
+
require_relative "cli/repo"
|
|
99
|
+
require_relative "cli/shell"
|
|
100
|
+
require_relative "cli/architect"
|
|
101
|
+
require_relative "cli/space"
|
|
102
|
+
require_relative "cli/src"
|