linear-cli 0.7.6 → 0.7.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -2
- data/Readme.adoc +7 -1
- data/changelog/0.7.7/added_ability_to_attach_project_to_command.yml +4 -0
- data/changelog/0.7.7/added_issue_pr_command.yml +4 -0
- data/changelog/0.7.7/added_lcomment_alias_to_add_comments_to_issues.yml +4 -0
- data/changelog/0.7.7/tag.yml +1 -0
- data/exe/lc +5 -1
- data/exe/lclose +1 -0
- data/exe/lcls +1 -0
- data/exe/lcomment +1 -0
- data/exe/lcreate +1 -0
- data/exe/{lc.sh → scripts/lc.sh} +4 -3
- data/exe/scripts/lcls.sh +2 -0
- data/exe/scripts/lcomment.sh +2 -0
- data/lib/linear/api.rb +1 -1
- data/lib/linear/cli/caller.rb +6 -1
- data/lib/linear/cli/sub_commands.rb +5 -67
- data/lib/linear/cli/version.rb +1 -1
- data/lib/linear/cli/what_for.rb +143 -0
- data/lib/linear/cli.rb +8 -1
- data/lib/linear/commands/issue/create.rb +1 -0
- data/lib/linear/commands/issue/pr.rb +38 -0
- data/lib/linear/commands/issue/update.rb +5 -4
- data/lib/linear/commands/issue.rb +42 -23
- data/lib/linear/models/base_model/class_methods.rb +41 -9
- data/lib/linear/models/base_model/method_magic.rb +5 -7
- data/lib/linear/models/issue/class_methods.rb +44 -0
- data/lib/linear/models/issue.rb +23 -38
- data/lib/linear/models/project.rb +47 -0
- data/lib/linear/models/team.rb +6 -9
- data/linear-cli.gemspec +1 -1
- metadata +17 -10
- data/exe/lclose +0 -4
- data/exe/lcls +0 -4
- data/exe/lcls.sh +0 -2
- data/exe/lcreate +0 -4
- /data/exe/{lclose.sh → scripts/lclose.sh} +0 -0
- /data/exe/{lcreate.sh → scripts/lcreate.sh} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 040dce1ff59ddd000240b8a03105928d775841be91d1fe50f974249e2ddc21fc
|
4
|
+
data.tar.gz: 0de33b286614b981e63ad75b14d1728e825d44e6ec2265c625a00888cdb32cf2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f45d0fc3280fda121430d10078e49a5a870753a8fa4788943934a586134101b58c40cacbd8fb3d83bd140d36db70bf19e82a65e3a87770bcf6a6ab48aa8bbbe
|
7
|
+
data.tar.gz: 9333ae8a1cb9bab180035dba641c23a5315783bf3d6bd0e790f0b21eb3da0a8ae149c1264ff4619ef75caaff1ec51bf8fdf4cd7c1f36659552d8cb685b7f4332
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,12 @@
|
|
2
2
|
|
3
3
|
## [Unreleased]
|
4
4
|
|
5
|
+
## [0.7.7] - 2024-02-06
|
6
|
+
### Added
|
7
|
+
- Added ability to attach project to command (@bougyman)
|
8
|
+
- Added issue pr command (@bougyman)
|
9
|
+
- Added lcomment alias to add comments to issues (@bougyman)
|
10
|
+
|
5
11
|
## [0.7.5] - 2024-02-05
|
6
12
|
### Fixed
|
7
13
|
- Fixed problem when choosing from multiple completed states (@bougyman)
|
@@ -45,8 +51,9 @@
|
|
45
51
|
### Added
|
46
52
|
- Added new changelog management system (changelog-rb) (@bougyman)
|
47
53
|
|
48
|
-
[Unreleased]: https://github.com/rubyists/linear-cli/compare/0.7.
|
49
|
-
[0.7.
|
54
|
+
[Unreleased]: https://github.com/rubyists/linear-cli/compare/0.7.7...HEAD
|
55
|
+
[0.7.7]: https://github.com/rubyists/linear-cli/compare/v0.7.5...0.7.7
|
56
|
+
[0.7.5]: https://github.com/rubyists/linear-cli/compare/v0.7.3...v0.7.5
|
50
57
|
[0.7.3]: https://github.com/rubyists/linear-cli/compare/v0.7.2...v0.7.3
|
51
58
|
[0.7.2]: https://github.com/rubyists/linear-cli/compare/v0.7.1...v0.7.2
|
52
59
|
[0.7.1]: https://github.com/rubyists/linear-cli/compare/v0.7.0...v0.7.1
|
data/Readme.adoc
CHANGED
@@ -133,8 +133,13 @@ at a time. You can also use the 'u' alias for 'update', and as always, the 'i' a
|
|
133
133
|
|
134
134
|
[source,sh]
|
135
135
|
----
|
136
|
-
$ lc issue update --comment "Here is a comment" CRY-1234
|
136
|
+
$ lc issue update --comment "Here is a comment" CRY-1234 <1>
|
137
|
+
$ lc issue update --comment - CRY-14 CRY-15 <2>
|
138
|
+
$ lcomment CRY-1234 CRY-3 <3>
|
137
139
|
----
|
140
|
+
<1> This will use the provided comment
|
141
|
+
<2> This will prompt for a comment (use '-' to prompt)
|
142
|
+
<3> This will always prompt you for a comment ('lcomment' is an alias for 'lc issue update --comment -')
|
138
143
|
|
139
144
|
===== Close one or many issues
|
140
145
|
|
@@ -152,4 +157,5 @@ Some command aliases are available to make things easier to type.
|
|
152
157
|
$ lcls
|
153
158
|
$ lcreate --description "This is a new issue" --labels Bug,Feature --team CRY
|
154
159
|
$ lclose --reason "This issue sucks" CRY-1234 CRY-456
|
160
|
+
$ lcancel --reason "These should never have been here" --trash CRY-1234 CRY-456
|
155
161
|
----
|
@@ -0,0 +1 @@
|
|
1
|
+
date: 2024-02-06
|
data/exe/lc
CHANGED
@@ -1,4 +1,8 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
|
4
|
+
require 'pathname'
|
5
|
+
script_dir = Pathname(__dir__).join('scripts')
|
6
|
+
basename = File.basename(__FILE__)
|
7
|
+
script = script_dir.join(basename).exist? ? script_dir.join(basename) : script_dir.join("#{basename}.sh")
|
8
|
+
exec script.to_s, *ARGV
|
data/exe/lclose
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
lc
|
data/exe/lcls
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
lc
|
data/exe/lcomment
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
lc
|
data/exe/lcreate
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
lc
|
data/exe/{lc.sh → scripts/lc.sh}
RENAMED
@@ -9,9 +9,10 @@ then
|
|
9
9
|
linear-cli "$@" 2>&1|sed 's/linear-cli/lc/g'
|
10
10
|
exit 0
|
11
11
|
fi
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
linear-cli "$@"
|
13
|
+
result=$?
|
14
|
+
if [ $result -gt 1 ]; then
|
15
|
+
printf "lc: linear-cli failed %s\n" $result >&2
|
15
16
|
lc "$@" --help 2>&1
|
16
17
|
exit 1
|
17
18
|
fi
|
data/exe/scripts/lcls.sh
ADDED
data/lib/linear/api.rb
CHANGED
data/lib/linear/cli/caller.rb
CHANGED
@@ -9,8 +9,13 @@ module Rubyists
|
|
9
9
|
# Global options for all commands
|
10
10
|
mod.instance_eval do
|
11
11
|
option :output, type: :string, default: 'text', values: %w[text json], desc: 'Output format'
|
12
|
-
option :debug,
|
12
|
+
option :debug,
|
13
|
+
type: :integer,
|
14
|
+
aliases: ['-D'],
|
15
|
+
default: 0,
|
16
|
+
desc: 'Debug level (greater than 0 to see backtraces)'
|
13
17
|
end
|
18
|
+
|
14
19
|
Caller.class_eval do
|
15
20
|
# Wraps the :call method so the debug option is honored, and we can trace the call
|
16
21
|
# as well as handle any exceptions that are raised
|
@@ -1,10 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
# This is where all the _for methods live
|
4
|
+
require_relative 'what_for'
|
5
|
+
|
3
6
|
module Rubyists
|
4
7
|
module Linear
|
5
8
|
module CLI
|
6
9
|
# The SubCommands module should be included in all commands with subcommands
|
7
10
|
module SubCommands
|
11
|
+
include CLI::WhatFor
|
12
|
+
|
8
13
|
def self.included(mod)
|
9
14
|
mod.instance_eval do
|
10
15
|
def const_added(const)
|
@@ -41,73 +46,6 @@ module Rubyists
|
|
41
46
|
@prompt ||= CLI.prompt
|
42
47
|
end
|
43
48
|
|
44
|
-
def team_for(key = nil)
|
45
|
-
return Rubyists::Linear::Team.find(key) if key
|
46
|
-
|
47
|
-
ask_for_team
|
48
|
-
end
|
49
|
-
|
50
|
-
def reason_for(reason = nil, four: nil)
|
51
|
-
return reason if reason
|
52
|
-
|
53
|
-
question = four ? "Reason for #{four}:" : 'Reason:'
|
54
|
-
prompt.ask(question)
|
55
|
-
end
|
56
|
-
|
57
|
-
def cancelled_state_for(thingy)
|
58
|
-
states = thingy.cancelled_states
|
59
|
-
return states.first if states.size == 1
|
60
|
-
|
61
|
-
selection = prompt.select('Choose a cancelled state', states.to_h { |s| [s.name, s.id] })
|
62
|
-
Rubyists::Linear::WorkflowState.find selection
|
63
|
-
end
|
64
|
-
|
65
|
-
def completed_state_for(thingy)
|
66
|
-
states = thingy.completed_states
|
67
|
-
return states.first if states.size == 1
|
68
|
-
|
69
|
-
selection = prompt.select('Choose a completed state', states.to_h { |s| [s.name, s.id] })
|
70
|
-
Rubyists::Linear::WorkflowState.find selection
|
71
|
-
end
|
72
|
-
|
73
|
-
def description_for(description = nil)
|
74
|
-
return description if description
|
75
|
-
|
76
|
-
prompt.multiline('Description:').map(&:chomp).join('\\n')
|
77
|
-
end
|
78
|
-
|
79
|
-
def title_for(title = nil)
|
80
|
-
return title if title
|
81
|
-
|
82
|
-
prompt.ask('Title:')
|
83
|
-
end
|
84
|
-
|
85
|
-
def labels_for(team, labels = nil)
|
86
|
-
return Rubyists::Linear::Label.find_all_by_name(labels.map(&:strip)) if labels
|
87
|
-
|
88
|
-
prompt.on(:keypress) do |event|
|
89
|
-
prompt.trigger(:keydown) if event.value == 'j'
|
90
|
-
prompt.trigger(:keyup) if event.value == 'k'
|
91
|
-
end
|
92
|
-
prompt.multi_select('Labels:', team.labels.to_h { |t| [t.name, t] })
|
93
|
-
end
|
94
|
-
|
95
|
-
def cut_branch!(branch_name)
|
96
|
-
if current_branch != default_branch
|
97
|
-
prompt.yes?("You are not on the default branch (#{default_branch}). Do you want to checkout #{default_branch} and create a new branch?") && git.checkout(default_branch) # rubocop:disable Layout/LineLength
|
98
|
-
end
|
99
|
-
git.branch(branch_name)
|
100
|
-
end
|
101
|
-
|
102
|
-
def branch_for(branch_name)
|
103
|
-
logger.trace('Looking for branch', branch_name:)
|
104
|
-
existing = git.branches[branch_name]
|
105
|
-
return cut_branch!(branch_name) unless existing
|
106
|
-
|
107
|
-
logger.trace('Branch found', branch: existing&.name)
|
108
|
-
existing
|
109
|
-
end
|
110
|
-
|
111
49
|
def current_branch
|
112
50
|
git.current_branch
|
113
51
|
end
|
data/lib/linear/cli/version.rb
CHANGED
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rubyists
|
4
|
+
module Linear
|
5
|
+
module CLI
|
6
|
+
# Module for the _for methods
|
7
|
+
module WhatFor
|
8
|
+
def editor_for(prefix)
|
9
|
+
file = Tempfile.open(prefix, Rubyists::Linear.tmpdir)
|
10
|
+
TTY::Editor.open(file.path)
|
11
|
+
file.close
|
12
|
+
File.readlines(file.path).map(&:chomp).join('\\n')
|
13
|
+
ensure
|
14
|
+
file&.close
|
15
|
+
end
|
16
|
+
|
17
|
+
def comment_for(issue, comment)
|
18
|
+
return comment unless comment.nil? || comment == '-'
|
19
|
+
|
20
|
+
comment = prompt.ask("Comment for #{issue.identifier} - #{issue.title} (- to open an editor)", default: '-')
|
21
|
+
return comment unless comment == '-'
|
22
|
+
|
23
|
+
editor_for %w[comment .md]
|
24
|
+
end
|
25
|
+
|
26
|
+
def team_for(key = nil)
|
27
|
+
return Rubyists::Linear::Team.find(key) if key
|
28
|
+
|
29
|
+
ask_for_team
|
30
|
+
end
|
31
|
+
|
32
|
+
def reason_for(reason = nil, four: nil)
|
33
|
+
return reason if reason && reason != '-'
|
34
|
+
|
35
|
+
question = four ? "Reason for #{TTY::Markdown.parse(four)}" : 'Reason'
|
36
|
+
answer = prompt.ask("#{question} (- to open an editor):", default: '-')
|
37
|
+
return answer unless answer == '-'
|
38
|
+
|
39
|
+
editor_for %w[reason .md]
|
40
|
+
end
|
41
|
+
|
42
|
+
def cancelled_state_for(thingy)
|
43
|
+
states = thingy.cancelled_states
|
44
|
+
return states.first if states.size == 1
|
45
|
+
|
46
|
+
selection = prompt.select('Choose a cancelled state', states.to_h { |s| [s.name, s.id] })
|
47
|
+
Rubyists::Linear::WorkflowState.find selection
|
48
|
+
end
|
49
|
+
|
50
|
+
def completed_state_for(thingy)
|
51
|
+
states = thingy.completed_states
|
52
|
+
return states.first if states.size == 1
|
53
|
+
|
54
|
+
selection = prompt.select('Choose a completed state', states.to_h { |s| [s.name, s.id] })
|
55
|
+
Rubyists::Linear::WorkflowState.find selection
|
56
|
+
end
|
57
|
+
|
58
|
+
def description_for(description = nil)
|
59
|
+
return description if description
|
60
|
+
|
61
|
+
prompt.multiline('Description:').map(&:chomp).join('\\n')
|
62
|
+
end
|
63
|
+
|
64
|
+
def title_for(title = nil)
|
65
|
+
return title if title
|
66
|
+
|
67
|
+
prompt.ask('Title:')
|
68
|
+
end
|
69
|
+
|
70
|
+
def ask_for_projects(projects, search: true)
|
71
|
+
prompt.warn("No project found matching #{search}.") if search
|
72
|
+
return projects.first if projects.size == 1
|
73
|
+
|
74
|
+
prompt.select('Project:', projects.to_h { |p| [p.name, p] })
|
75
|
+
end
|
76
|
+
|
77
|
+
def project_scores(projects, search_term)
|
78
|
+
projects.select { |p| p.match_score?(search_term).positive? }.sort_by { |p| p.match_score?(search_term) }
|
79
|
+
end
|
80
|
+
|
81
|
+
def project_for(team, project = nil)
|
82
|
+
projects = team.projects
|
83
|
+
return nil if projects.empty?
|
84
|
+
|
85
|
+
possibles = project_scores(projects, project)
|
86
|
+
return ask_for_projects(projects, search: project) if possibles.empty?
|
87
|
+
|
88
|
+
first = possibles.first
|
89
|
+
return first if first.match_score?(project) == 100
|
90
|
+
|
91
|
+
selections = possibles + (projects - possibles)
|
92
|
+
prompt.select('Project:', selections.to_h { |p| [p.name, p] }) if possibles.size.positive?
|
93
|
+
end
|
94
|
+
|
95
|
+
def pr_title_for(issue)
|
96
|
+
proposed = [pr_type_for(issue)]
|
97
|
+
proposed_scope = pr_scope_for(issue.title)
|
98
|
+
proposed << "(#{proposed_scope})" if proposed_scope
|
99
|
+
summary = issue.title.sub(/(?:#{ALLOWED_PR_TYPES})(\([^)]+\))? /, '')
|
100
|
+
proposed << ": #{issue.identifier} - #{summary}"
|
101
|
+
prompt.ask("Title for PR for #{issue.identifier} - #{summary}", default: proposed.join)
|
102
|
+
end
|
103
|
+
|
104
|
+
def pr_description_for(issue)
|
105
|
+
tmpfile = Tempfile.new([issue.identifier, '.md'], Rubyists::Linear.tmpdir)
|
106
|
+
# TODO: Look up templates
|
107
|
+
proposed = "# Context\n\n#{issue.description}\n\n## Issue\n\n#{issue.identifier}\n\n# Solution\n\n# Testing\n\n# Notes\n\n" # rubocop:disable Layout/LineLength
|
108
|
+
tmpfile.write(proposed) && tmpfile.close
|
109
|
+
desc = TTY::Editor.open(tmpfile.path)
|
110
|
+
return tmpfile if desc
|
111
|
+
|
112
|
+
File.open(tmpfile.path, 'w+') do |file|
|
113
|
+
file.puts prompt.ask("Description for PR for #{issue.identifier} - #{issue.title}", default: proposed)
|
114
|
+
end
|
115
|
+
tmpfile
|
116
|
+
end
|
117
|
+
|
118
|
+
def pr_type_for(issue)
|
119
|
+
proposed_type = issue.title.match(/^(#{ALLOWED_PR_TYPES})/i)
|
120
|
+
return proposed_type[1].downcase if proposed_type
|
121
|
+
|
122
|
+
prompt.select('What type of PR is this?', %w[fix feature chore refactor test docs style ci perf security])
|
123
|
+
end
|
124
|
+
|
125
|
+
def pr_scope_for(title)
|
126
|
+
proposed_scope = title.match(/^\w+\(([^\)]+)\)/)
|
127
|
+
return proposed_scope[1].downcase if proposed_scope
|
128
|
+
|
129
|
+
scope = prompt.ask('What is the scope of this PR?', default: 'none')
|
130
|
+
return nil if scope.empty? && scope == 'none'
|
131
|
+
|
132
|
+
scope
|
133
|
+
end
|
134
|
+
|
135
|
+
def labels_for(team, labels = nil)
|
136
|
+
return Rubyists::Linear::Label.find_all_by_name(labels.map(&:strip)) if labels
|
137
|
+
|
138
|
+
prompt.multi_select('Labels:', team.labels.to_h { |t| [t.name, t] })
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
data/lib/linear/cli.rb
CHANGED
@@ -16,7 +16,14 @@ module Rubyists
|
|
16
16
|
extend Dry::CLI::Registry
|
17
17
|
|
18
18
|
def self.prompt
|
19
|
-
@prompt
|
19
|
+
return @prompt if @prompt
|
20
|
+
|
21
|
+
@prompt = TTY::Prompt.new
|
22
|
+
@prompt.on(:keypress) do |event|
|
23
|
+
@prompt.trigger(:keydown) if event.value == 'j'
|
24
|
+
@prompt.trigger(:keyup) if event.value == 'k'
|
25
|
+
end
|
26
|
+
@prompt
|
20
27
|
end
|
21
28
|
|
22
29
|
def self.register_sub!(command, sub_file, klass)
|
@@ -21,6 +21,7 @@ module Rubyists
|
|
21
21
|
option :description, type: :string, aliases: ['-d'], desc: 'Issue Description'
|
22
22
|
option :team, type: :string, aliases: ['-T'], desc: 'Team Identifier'
|
23
23
|
option :labels, type: :array, aliases: ['-l'], desc: 'Labels for the issue (Comma separated list)'
|
24
|
+
option :project, type: :string, aliases: ['-p'], desc: 'Project Identifier'
|
24
25
|
option :develop, type: :boolean, aliases: ['-D', '--dev'], desc: 'Start development after creating the issue'
|
25
26
|
|
26
27
|
def call(**options)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'semantic_logger'
|
4
|
+
require 'git'
|
5
|
+
require_relative '../issue'
|
6
|
+
|
7
|
+
module Rubyists
|
8
|
+
# Namespace for Linear
|
9
|
+
module Linear
|
10
|
+
M :issue, :user, :label
|
11
|
+
# Namespace for CLI
|
12
|
+
module CLI
|
13
|
+
module Issue
|
14
|
+
Pr = Class.new Dry::CLI::Command
|
15
|
+
# The Develop class is a Dry::CLI::Command to start/update development status of an issue
|
16
|
+
class Pr
|
17
|
+
include SemanticLogger::Loggable
|
18
|
+
include Rubyists::Linear::CLI::CommonOptions
|
19
|
+
include Rubyists::Linear::CLI::Issue # for #gimme_da_issue! and other Issue methods
|
20
|
+
desc 'Create a PR for an issue and push it to the remote'
|
21
|
+
argument :issue_id, required: true, desc: 'The Issue (i.e. CRY-1)'
|
22
|
+
option :title, required: false, desc: 'The title of the PR'
|
23
|
+
option :description, required: false, desc: 'The description of the PR'
|
24
|
+
|
25
|
+
def call(issue_id:, **options)
|
26
|
+
logger.debug('Creating PR for issue issue', options:)
|
27
|
+
issue = gimme_da_issue!(issue_id, me: Rubyists::Linear::User.me)
|
28
|
+
branch_name = issue.branchName
|
29
|
+
branch = branch_for(branch_name)
|
30
|
+
branch.checkout
|
31
|
+
prompt.ok "Checked out branch #{branch_name}"
|
32
|
+
issue_pr(issue, **options)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -19,11 +19,11 @@ module Rubyists
|
|
19
19
|
include Rubyists::Linear::CLI::Issue # for #gimme_da_issue! and other Issue methods
|
20
20
|
desc 'Update an issue'
|
21
21
|
argument :issue_ids, type: :array, required: true, desc: 'Issue IDs (i.e. CRY-1)'
|
22
|
-
option :comment, type: :string, aliases: ['-m'], desc: 'Comment to add to the issue'
|
23
|
-
option :
|
22
|
+
option :comment, type: :string, aliases: ['-m'], desc: 'Comment to add to the issue. - open an editor'
|
23
|
+
option :project, type: :string, aliases: ['-p'], desc: 'Project to move the issue to. - select from a list'
|
24
24
|
option :cancel, type: :boolean, default: false, desc: 'Cancel the issue'
|
25
25
|
option :close, type: :boolean, default: false, desc: 'Close the issue'
|
26
|
-
option :reason, type: :string, aliases: ['--butwhy'], desc: 'Reason for closing the issue'
|
26
|
+
option :reason, type: :string, aliases: ['--butwhy'], desc: 'Reason for closing the issue. - open an editor'
|
27
27
|
option :trash,
|
28
28
|
type: :boolean,
|
29
29
|
default: false,
|
@@ -31,7 +31,8 @@ module Rubyists
|
|
31
31
|
|
32
32
|
example [
|
33
33
|
'--comment "This is a comment" CRY-1 CRY2 # Add a comment to multiple issues',
|
34
|
-
'--
|
34
|
+
'--comment - CRY-1 CRY2 # Add a comment to multiple issues, open an editor',
|
35
|
+
'--project "Manhattan" CRY-3 CRY-4 # Move tickets to a different project',
|
35
36
|
'--close CRY-2 # Close an issue. Will be prompted for a reason',
|
36
37
|
'--close --reason "Done" CRY-1 CRY-2 # Close multiple issues with a reason',
|
37
38
|
'--cancel --trash --reason "Garbage" CRY-2 # Cancel an issue, and throw it in the trash'
|
@@ -4,6 +4,8 @@
|
|
4
4
|
# as well as other helpers which are used in multiple commands and subcommands
|
5
5
|
# This is also where the #prompt method is defined, which is used to display messages to the user and get input
|
6
6
|
require_relative '../cli/sub_commands'
|
7
|
+
require 'tty-editor'
|
8
|
+
require 'git'
|
7
9
|
|
8
10
|
module Rubyists
|
9
11
|
module Linear
|
@@ -15,6 +17,7 @@ module Rubyists
|
|
15
17
|
include CLI::SubCommands
|
16
18
|
|
17
19
|
DESCRIPTION = 'Manage issues'
|
20
|
+
ALLOWED_PR_TYPES = 'bug|fix|sec(urity)|feat(ure)|chore|refactor|test|docs|style|ci|perf'
|
18
21
|
|
19
22
|
# Aliases for Issue commands
|
20
23
|
ALIASES = {
|
@@ -22,20 +25,21 @@ module Rubyists
|
|
22
25
|
develop: %w[d dev], # aliases for the develop command
|
23
26
|
list: %w[l ls], # aliases for the list command
|
24
27
|
update: %w[u], # aliases for the close command
|
28
|
+
pr: %w[pull-request], # aliases for the pr command
|
25
29
|
issue: %w[i issues] # aliases for the main issue command itself
|
26
30
|
}.freeze
|
27
31
|
|
28
32
|
def issue_comment(issue, comment)
|
29
|
-
issue.add_comment(comment)
|
30
|
-
prompt.ok
|
33
|
+
issue.add_comment comment_for(issue, comment)
|
34
|
+
prompt.ok "Comment added to #{issue.identifier}"
|
31
35
|
end
|
32
36
|
|
33
37
|
def cancel_issue(issue, **options)
|
34
38
|
reason = reason_for(options[:reason], four: "cancelling #{issue.identifier} - #{issue.title}")
|
35
|
-
issue_comment
|
39
|
+
issue_comment issue, reason
|
36
40
|
cancel_state = cancel_state_for(issue)
|
37
|
-
issue.close!
|
38
|
-
prompt.ok
|
41
|
+
issue.close! state: cancel_state, trash: options[:trash]
|
42
|
+
prompt.ok "#{issue.identifier} was cancelled"
|
39
43
|
end
|
40
44
|
|
41
45
|
def close_issue(issue, **options)
|
@@ -43,46 +47,61 @@ module Rubyists
|
|
43
47
|
doing = cancelled ? 'cancelling' : 'closing'
|
44
48
|
done = cancelled ? 'cancelled' : 'closed'
|
45
49
|
workflow_state = cancelled ? cancelled_state_for(issue) : completed_state_for(issue)
|
46
|
-
reason = reason_for(options[:reason], four: "#{doing}
|
47
|
-
issue_comment
|
48
|
-
issue.close!
|
49
|
-
prompt.ok
|
50
|
+
reason = reason_for(options[:reason], four: "#{doing} *#{issue.identifier} - #{issue.title}*")
|
51
|
+
issue_comment issue, reason
|
52
|
+
issue.close! state: workflow_state, trash: options[:trash]
|
53
|
+
prompt.ok "#{issue.identifier} was #{done}"
|
50
54
|
end
|
51
55
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
56
|
+
def create_pr!(title:, body:)
|
57
|
+
return `gh pr create -a @me --title "#{title}" --body-file "#{body.path}"` if body.respond_to?(:path)
|
58
|
+
|
59
|
+
`gh pr create -a @me --title "#{title}" --body "#{body}"`
|
60
|
+
end
|
61
|
+
|
62
|
+
def issue_pr(issue, **options)
|
63
|
+
title = options[:title] || pr_title_for(issue)
|
64
|
+
body = options[:description] || pr_description_for(issue)
|
65
|
+
create_pr!(title:, body:)
|
66
|
+
end
|
67
|
+
|
68
|
+
def attach_project(issue, project_search)
|
69
|
+
project = project_for(issue.team, project_search)
|
70
|
+
issue.attach_to_project project
|
71
|
+
prompt.ok "#{issue.identifier} was attached to #{project.name}"
|
55
72
|
end
|
56
73
|
|
57
74
|
def update_issue(issue, **options)
|
58
75
|
issue_comment(issue, options[:comment]) if options[:comment]
|
59
76
|
return close_issue(issue, **options) if options[:close]
|
60
77
|
return issue_pr(issue) if options[:pr]
|
78
|
+
return attach_project(issue, options[:project]) if options[:project]
|
61
79
|
return if options[:comment]
|
62
80
|
|
63
|
-
prompt.warn
|
64
|
-
prompt.ok
|
81
|
+
prompt.warn 'No action taken, no options specified'
|
82
|
+
prompt.ok 'Issue was not updated'
|
65
83
|
end
|
66
84
|
|
67
85
|
def make_da_issue!(**options)
|
68
86
|
# These *_for methods are defined in Rubyists::Linear::CLI::SubCommands
|
69
|
-
title = title_for
|
70
|
-
description = description_for
|
71
|
-
team = team_for
|
72
|
-
labels = labels_for
|
73
|
-
|
87
|
+
title = title_for(options[:title])
|
88
|
+
description = description_for(options[:description])
|
89
|
+
team = team_for(options[:team])
|
90
|
+
labels = labels_for(team, options[:labels])
|
91
|
+
project = project_for(team, options[:project])
|
92
|
+
Rubyists::Linear::Issue.create(title:, description:, team:, labels:, project:)
|
74
93
|
end
|
75
94
|
|
76
95
|
def gimme_da_issue!(issue_id, me: Rubyists::Linear::User.me) # rubocop:disable Naming/MethodParameterName
|
77
96
|
logger.trace('Looking up issue', issue_id:, me:)
|
78
97
|
issue = Rubyists::Linear::Issue.find(issue_id)
|
79
|
-
if issue.assignee && issue.assignee
|
80
|
-
prompt.say
|
98
|
+
if issue.assignee && issue.assignee.id == me.id
|
99
|
+
prompt.say "You are already assigned #{issue_id}"
|
81
100
|
return issue
|
82
101
|
end
|
83
102
|
|
84
|
-
prompt.say
|
85
|
-
updated = issue.assign!
|
103
|
+
prompt.say "Assigning issue #{issue_id} to ya"
|
104
|
+
updated = issue.assign!(me)
|
86
105
|
logger.trace 'Issue taken', issue: updated
|
87
106
|
updated
|
88
107
|
end
|
@@ -5,27 +5,55 @@ module Rubyists
|
|
5
5
|
class BaseModel
|
6
6
|
# Class methods for Linear models.
|
7
7
|
module ClassMethods
|
8
|
-
def
|
8
|
+
def setter!(relation, klass)
|
9
|
+
define_method "#{relation}=" do |val|
|
10
|
+
hash = val.is_a?(Hash) ? val : val.updated_data
|
11
|
+
updated_data[relation] = hash
|
12
|
+
instance_variable_set("@#{relation}", Rubyists::Linear.const_get(klass).new(hash))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def getter!(relation)
|
9
17
|
define_method relation do
|
10
18
|
return instance_variable_get("@#{relation}") if instance_variable_defined?("@#{relation}")
|
11
|
-
return unless (val = data[relation])
|
12
19
|
|
13
|
-
|
20
|
+
return unless (val = updated_data[relation])
|
21
|
+
|
22
|
+
send("#{relation}=", val)
|
14
23
|
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def many_to_one(relation, klass = nil)
|
27
|
+
klass ||= relation.to_s.camelize.to_sym
|
28
|
+
getter! relation
|
29
|
+
setter! relation, klass
|
30
|
+
end
|
31
|
+
|
32
|
+
alias one_to_one many_to_one
|
15
33
|
|
34
|
+
def many_setter!(relation, klass)
|
16
35
|
define_method "#{relation}=" do |val|
|
17
|
-
|
18
|
-
|
19
|
-
|
36
|
+
vals = if val&.key?(:nodes)
|
37
|
+
val[:nodes]
|
38
|
+
else
|
39
|
+
Array(val)
|
40
|
+
end
|
41
|
+
updated_data[relation] = vals.map { |v| v.is_a?(Hash) ? v : v.updated_data }
|
42
|
+
new_relations = vals.map { |v| v.is_a?(Hash) ? Rubyists::Linear.const_get(klass).new(v) : v }
|
43
|
+
instance_variable_set("@#{relation}", new_relations)
|
20
44
|
end
|
21
45
|
end
|
22
46
|
|
23
|
-
|
47
|
+
def one_to_many(relation, klass = nil)
|
48
|
+
klass ||= relation.to_s.singularize.camelize.to_sym
|
49
|
+
getter! relation
|
50
|
+
many_setter! relation, klass
|
51
|
+
end
|
24
52
|
|
25
53
|
def find(id_val)
|
26
54
|
camel_name = just_name.camelize :lower
|
27
|
-
|
28
|
-
query_data = Api.query(query { __node(camel_name, id: id_val) { ___
|
55
|
+
ff = full_fragment
|
56
|
+
query_data = Api.query(query { __node(camel_name, id: id_val) { ___ ff } })
|
29
57
|
new query_data[camel_name.to_sym]
|
30
58
|
end
|
31
59
|
|
@@ -63,6 +91,10 @@ module Rubyists
|
|
63
91
|
const_get(:Base)
|
64
92
|
end
|
65
93
|
|
94
|
+
def full_fragment
|
95
|
+
base_fragment
|
96
|
+
end
|
97
|
+
|
66
98
|
def basic_filter
|
67
99
|
return const_get(:BASIC_FILTER) if const_defined?(:BASIC_FILTER)
|
68
100
|
|
@@ -5,17 +5,15 @@ module Rubyists
|
|
5
5
|
class BaseModel
|
6
6
|
# Methods for Linear models.
|
7
7
|
module MethodMagic
|
8
|
-
def self.included(base) # rubocop:disable Metrics/
|
8
|
+
def self.included(base) # rubocop:disable Metrics/AbcSize
|
9
9
|
base.instance_eval do
|
10
10
|
base.base_fragment.__nodes.each do |node|
|
11
11
|
sym = node.__name.to_sym
|
12
|
-
define_method
|
13
|
-
|
14
|
-
|
12
|
+
define_method(sym) { updated_data[sym] } unless instance_methods.include? sym
|
13
|
+
esym = :"#{sym}="
|
14
|
+
next if instance_methods.include? esym
|
15
15
|
|
16
|
-
define_method
|
17
|
-
updated_data[sym] = value
|
18
|
-
end
|
16
|
+
define_method(esym) { |value| updated_data[sym] = value }
|
19
17
|
end
|
20
18
|
end
|
21
19
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rubyists
|
4
|
+
# Namespace for Linear
|
5
|
+
module Linear
|
6
|
+
M :user, :team
|
7
|
+
# The Issue class represents a Linear issue.
|
8
|
+
class Issue
|
9
|
+
# Class methods for Issue
|
10
|
+
module ClassMethods
|
11
|
+
def base_fragment
|
12
|
+
@base_fragment ||= fragment('BaseIssue', 'Issue') do
|
13
|
+
___ Base
|
14
|
+
assignee { ___ User.base_fragment }
|
15
|
+
team { ___ Team.base_fragment }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def full_fragment
|
20
|
+
@full_fragment ||= fragment('FullIssue', 'Issue') do
|
21
|
+
___ Base
|
22
|
+
assignee { ___ User.full_fragment }
|
23
|
+
team { ___ Team.full_fragment }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_all(*slugs)
|
28
|
+
slugs.flatten.map { |slug| find(slug) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def create(title:, description:, team:, project:, labels: [])
|
32
|
+
team_id = team.id
|
33
|
+
label_ids = labels.map(&:id)
|
34
|
+
input = { title:, description:, teamId: team_id }
|
35
|
+
input[:labelIds] = label_ids unless label_ids.empty?
|
36
|
+
input[:projectId] = project.id if project
|
37
|
+
m = mutation { issueCreate(input:) { issue { ___ Issue.base_fragment } } }
|
38
|
+
query_data = Api.query(m)
|
39
|
+
new query_data.dig(:issueCreate, :issue)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/linear/models/issue.rb
CHANGED
@@ -5,13 +5,15 @@ require 'gqli'
|
|
5
5
|
module Rubyists
|
6
6
|
# Namespace for Linear
|
7
7
|
module Linear
|
8
|
-
M :base_model
|
8
|
+
M :base_model
|
9
9
|
Issue = Class.new(BaseModel)
|
10
|
+
M 'issue/class_methods'
|
10
11
|
# The Issue class represents a Linear issue.
|
11
|
-
class Issue
|
12
|
+
class Issue
|
12
13
|
include SemanticLogger::Loggable
|
13
|
-
|
14
|
-
|
14
|
+
extend ClassMethods
|
15
|
+
many_to_one :assignee, :User
|
16
|
+
many_to_one :team, :Team
|
15
17
|
|
16
18
|
BASIC_FILTER = { completedAt: { null: true } }.freeze
|
17
19
|
|
@@ -25,38 +27,6 @@ module Rubyists
|
|
25
27
|
updatedAt
|
26
28
|
end
|
27
29
|
|
28
|
-
class << self
|
29
|
-
def base_fragment
|
30
|
-
@base_fragment ||= fragment('IssueWithTeams', 'Issue') do
|
31
|
-
___ Base
|
32
|
-
assignee { ___ User.base_fragment }
|
33
|
-
team { ___ Team.base_fragment }
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
def find(slug)
|
38
|
-
q = query { issue(id: slug) { ___ Issue.base_fragment } }
|
39
|
-
data = Api.query(q)
|
40
|
-
raise NotFoundError, "Issue not found: #{slug}" if data.nil?
|
41
|
-
|
42
|
-
new(data[:issue])
|
43
|
-
end
|
44
|
-
|
45
|
-
def find_all(*slugs)
|
46
|
-
slugs.flatten.map { |slug| find(slug) }
|
47
|
-
end
|
48
|
-
|
49
|
-
def create(title:, description:, team:, labels: [])
|
50
|
-
team_id = team.id
|
51
|
-
label_ids = labels.map(&:id)
|
52
|
-
input = { title:, description:, teamId: team_id }
|
53
|
-
input[:labelIds] = label_ids unless label_ids.empty?
|
54
|
-
m = mutation { issueCreate(input:) { issue { ___ Issue.base_fragment } } }
|
55
|
-
query_data = Api.query(m)
|
56
|
-
new query_data.dig(:issueCreate, :issue)
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
30
|
def comment_fragment
|
61
31
|
@comment_fragment ||= fragment('Comment', 'Comment') do
|
62
32
|
id
|
@@ -65,6 +35,21 @@ module Rubyists
|
|
65
35
|
end
|
66
36
|
end
|
67
37
|
|
38
|
+
def update!(input)
|
39
|
+
id_for_this = identifier
|
40
|
+
m = mutation { issueUpdate(id: id_for_this, input:) { issue { ___ Issue.full_fragment } } }
|
41
|
+
query_data = Api.query(m)
|
42
|
+
updated = query_data.dig(:issueUpdate, :issue)
|
43
|
+
raise SmellsBad, "Unknown response for issue update: #{data} (should have :issueUpdate key)" if updated.nil?
|
44
|
+
|
45
|
+
@data = @updated_data = updated
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def attach_to_project(project)
|
50
|
+
update!({ projectId: project.id })
|
51
|
+
end
|
52
|
+
|
68
53
|
# Reference for this mutation:
|
69
54
|
# https://studio.apollographql.com/public/Linear-API/variant/current/schema/reference/inputs/CommentCreateInput
|
70
55
|
def add_comment(comment)
|
@@ -81,7 +66,7 @@ module Rubyists
|
|
81
66
|
id_for_this = identifier
|
82
67
|
input = { stateId: close_state.id }
|
83
68
|
input[:trash] = true if trash
|
84
|
-
mutation { issueUpdate(id: id_for_this, input:) { issue { ___ Issue.
|
69
|
+
mutation { issueUpdate(id: id_for_this, input:) { issue { ___ Issue.full_fragment } } }
|
85
70
|
end
|
86
71
|
|
87
72
|
def close!(state: nil, trash: false)
|
@@ -97,7 +82,7 @@ module Rubyists
|
|
97
82
|
|
98
83
|
def assign!(user)
|
99
84
|
this_id = identifier
|
100
|
-
m = mutation { issueUpdate(id: this_id, input: { assigneeId: user.id }) { issue { ___ Issue.
|
85
|
+
m = mutation { issueUpdate(id: this_id, input: { assigneeId: user.id }) { issue { ___ Issue.full_fragment } } }
|
101
86
|
query_data = Api.query(m)
|
102
87
|
updated = query_data.dig(:issueUpdate, :issue)
|
103
88
|
raise SmellsBad, "Unknown response for issue update: #{data} (should have :issueUpdate key)" if updated.nil?
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'gqli'
|
4
|
+
|
5
|
+
module Rubyists
|
6
|
+
# Namespace for Linear
|
7
|
+
module Linear
|
8
|
+
M :base_model
|
9
|
+
Project = Class.new(BaseModel)
|
10
|
+
# The Project class represents a Linear workflow state.
|
11
|
+
class Project
|
12
|
+
include SemanticLogger::Loggable
|
13
|
+
|
14
|
+
Base = fragment('BaseProject', 'Project') do
|
15
|
+
id
|
16
|
+
name
|
17
|
+
content
|
18
|
+
slugId
|
19
|
+
description
|
20
|
+
url
|
21
|
+
createdAt
|
22
|
+
updatedAt
|
23
|
+
end
|
24
|
+
|
25
|
+
def slug
|
26
|
+
File.basename(url).sub("-#{slugId}", '')
|
27
|
+
end
|
28
|
+
|
29
|
+
def match_score?(string)
|
30
|
+
downed = string.downcase
|
31
|
+
return 100 if downed.split.join('-') == slug || downed == name.downcase
|
32
|
+
return 75 if name.include?(string) || slug.include?(downed)
|
33
|
+
return 50 if description.downcase.include?(downed)
|
34
|
+
|
35
|
+
0
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_s
|
39
|
+
format('%<name>-12s %<url>s', name:, url:)
|
40
|
+
end
|
41
|
+
|
42
|
+
def inspection
|
43
|
+
format('name: "%<name>s" type: "%<url>s"', name:, url:)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/linear/models/team.rb
CHANGED
@@ -5,11 +5,12 @@ require 'gqli'
|
|
5
5
|
module Rubyists
|
6
6
|
# Namespace for Linear
|
7
7
|
module Linear
|
8
|
-
M :base_model, :issue, :
|
8
|
+
M :base_model, :issue, :project, :workflow_state, :user
|
9
9
|
Team = Class.new(BaseModel)
|
10
10
|
# The Issue class represents a Linear issue.
|
11
11
|
class Team
|
12
12
|
include SemanticLogger::Loggable
|
13
|
+
one_to_many :projects
|
13
14
|
|
14
15
|
# TODO: Make this configurable
|
15
16
|
BaseFilter = { # rubocop:disable Naming/ConstantName
|
@@ -29,15 +30,11 @@ module Rubyists
|
|
29
30
|
updatedAt
|
30
31
|
end
|
31
32
|
|
32
|
-
def self.
|
33
|
-
|
34
|
-
|
33
|
+
def self.full_fragment
|
34
|
+
@full_fragment ||= fragment('WholeTeam', 'Team') do
|
35
|
+
___ Base
|
36
|
+
projects { nodes { ___ Project.base_fragment } }
|
35
37
|
end
|
36
|
-
data = Api.query(q)
|
37
|
-
hash = data[:team]
|
38
|
-
raise NotFoundError, "Team not found: #{key}" unless hash
|
39
|
-
|
40
|
-
new hash
|
41
38
|
end
|
42
39
|
|
43
40
|
def self.mine
|
data/linear-cli.gemspec
CHANGED
@@ -29,7 +29,7 @@ Gem::Specification.new do |spec|
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
spec.bindir = 'exe'
|
32
|
-
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
32
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }.reject { |f| f.end_with?('.sh') }
|
33
33
|
spec.require_paths = ['lib']
|
34
34
|
|
35
35
|
# Uncomment to register a new dependency of your gem
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: linear-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.7.
|
4
|
+
version: 0.7.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tj (bougyman) Vanderpoel
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-02-
|
11
|
+
date: 2024-02-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: base64
|
@@ -197,13 +197,10 @@ email:
|
|
197
197
|
- tj@rubyists.com
|
198
198
|
executables:
|
199
199
|
- lc
|
200
|
-
- lc.sh
|
201
200
|
- lclose
|
202
|
-
- lclose.sh
|
203
201
|
- lcls
|
204
|
-
-
|
202
|
+
- lcomment
|
205
203
|
- lcreate
|
206
|
-
- lcreate.sh
|
207
204
|
- linear-cli
|
208
205
|
extensions: []
|
209
206
|
extra_rdoc_files: []
|
@@ -233,17 +230,23 @@ files:
|
|
233
230
|
- changelog/0.7.3/tag.yml
|
234
231
|
- changelog/0.7.5/fixed_problem_when_choosing_from_multiple_completed_states.yml
|
235
232
|
- changelog/0.7.5/tag.yml
|
233
|
+
- changelog/0.7.7/added_ability_to_attach_project_to_command.yml
|
234
|
+
- changelog/0.7.7/added_issue_pr_command.yml
|
235
|
+
- changelog/0.7.7/added_lcomment_alias_to_add_comments_to_issues.yml
|
236
|
+
- changelog/0.7.7/tag.yml
|
236
237
|
- changelog/unreleased/.gitkeep
|
237
238
|
- cinemas/listings.cinema
|
238
239
|
- exe/lc
|
239
|
-
- exe/lc.sh
|
240
240
|
- exe/lclose
|
241
|
-
- exe/lclose.sh
|
242
241
|
- exe/lcls
|
243
|
-
- exe/
|
242
|
+
- exe/lcomment
|
244
243
|
- exe/lcreate
|
245
|
-
- exe/lcreate.sh
|
246
244
|
- exe/linear-cli
|
245
|
+
- exe/scripts/lc.sh
|
246
|
+
- exe/scripts/lclose.sh
|
247
|
+
- exe/scripts/lcls.sh
|
248
|
+
- exe/scripts/lcomment.sh
|
249
|
+
- exe/scripts/lcreate.sh
|
247
250
|
- lib/linear.rb
|
248
251
|
- lib/linear/api.rb
|
249
252
|
- lib/linear/cli.rb
|
@@ -252,10 +255,12 @@ files:
|
|
252
255
|
- lib/linear/cli/sub_commands.rb
|
253
256
|
- lib/linear/cli/version.rb
|
254
257
|
- lib/linear/cli/watcher.rb
|
258
|
+
- lib/linear/cli/what_for.rb
|
255
259
|
- lib/linear/commands/issue.rb
|
256
260
|
- lib/linear/commands/issue/create.rb
|
257
261
|
- lib/linear/commands/issue/develop.rb
|
258
262
|
- lib/linear/commands/issue/list.rb
|
263
|
+
- lib/linear/commands/issue/pr.rb
|
259
264
|
- lib/linear/commands/issue/take.rb
|
260
265
|
- lib/linear/commands/issue/update.rb
|
261
266
|
- lib/linear/commands/team.rb
|
@@ -267,7 +272,9 @@ files:
|
|
267
272
|
- lib/linear/models/base_model/class_methods.rb
|
268
273
|
- lib/linear/models/base_model/method_magic.rb
|
269
274
|
- lib/linear/models/issue.rb
|
275
|
+
- lib/linear/models/issue/class_methods.rb
|
270
276
|
- lib/linear/models/label.rb
|
277
|
+
- lib/linear/models/project.rb
|
271
278
|
- lib/linear/models/team.rb
|
272
279
|
- lib/linear/models/user.rb
|
273
280
|
- lib/linear/models/workflow_state.rb
|
data/exe/lclose
DELETED
data/exe/lcls
DELETED
data/exe/lcls.sh
DELETED
data/exe/lcreate
DELETED
File without changes
|
File without changes
|