linear-cli 0.4.5 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Readme.adoc +1 -1
- data/lib/linear/cli/sub_commands.rb +58 -9
- data/lib/linear/cli/version.rb +1 -1
- data/lib/linear/commands/issue/create.rb +6 -3
- data/lib/linear/commands/issue/develop.rb +37 -0
- data/lib/linear/commands/issue.rb +10 -3
- data/lib/linear/models/issue.rb +1 -0
- data/lib/linear/models/label.rb +16 -1
- data/lib/linear/models/team.rb +18 -12
- data/linear-cli.gemspec +2 -0
- metadata +31 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d1133e666c4896922694d8dd9a8f3c9eeb99de63da400ae14ba6c06eaea6df1
|
4
|
+
data.tar.gz: a73559f2a87113d05cc089cac0b8ddb48b124da991b9e2fac8bed68d11537501
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3df7cfa7fccf9cefc7a2a9bf3204f22c55265612380baadf01df2115305bf5737c5a777259a2ad154cb60890f4078ab67a85aac84f9e2dcf185f2d0d2cf6d529
|
7
|
+
data.tar.gz: 41ddafcb546b3760a93b7880ebc877d4d6813d9f93545e1052063f97042df62d5d5427f96151d2e374f934ec266c283b862072181486a51b6112d751ad61a72b
|
data/Readme.adoc
CHANGED
@@ -15,21 +15,25 @@ module Rubyists
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
18
|
+
def choose_a_team!(teams)
|
19
|
+
prompt.on(:keypress) do |event|
|
20
|
+
prompt.trigger(:keydown) if event.value == 'j'
|
21
|
+
prompt.trigger(:keyup) if event.value == 'k'
|
22
|
+
end
|
23
|
+
key = prompt.select('Choose a team', teams.to_h { |t| [t.name, t.key] })
|
24
|
+
Rubyists::Linear::Team.find key
|
25
|
+
end
|
26
|
+
|
27
|
+
def ask_for_team
|
19
28
|
teams = Rubyists::Linear::Team.mine
|
20
29
|
if teams.size == 1
|
21
30
|
logger.info('Only one team found, using it', team: teams.first.name)
|
22
31
|
teams.first
|
23
32
|
elsif teams.empty?
|
24
|
-
logger.error('No teams found for you. Please join a team or pass an existing team
|
25
|
-
raise SmellsBad, 'No team given and none found for you'
|
33
|
+
logger.error('No teams found for you. Please join a team or pass an existing team ID.')
|
34
|
+
raise SmellsBad, 'No team given and none found for you (try joining a team or use a team id from `lc teams --no-mine`)' # rubocop:disable Layout/LineLength
|
26
35
|
else
|
27
|
-
|
28
|
-
prompt.trigger(:keydown) if event.value == 'j'
|
29
|
-
prompt.trigger(:keyup) if event.value == 'k'
|
30
|
-
end
|
31
|
-
key = prompt.select('Choose a team', teams.to_h { |t| [t.name, t.key] })
|
32
|
-
Rubyists::Linear::Team.find key
|
36
|
+
choose_a_team! teams
|
33
37
|
end
|
34
38
|
end
|
35
39
|
|
@@ -58,8 +62,53 @@ module Rubyists
|
|
58
62
|
def labels_for(team, labels = nil)
|
59
63
|
return Rubyists::Linear::Label.find_all_by_name(labels.map(&:strip)) if labels
|
60
64
|
|
65
|
+
prompt.on(:keypress) do |event|
|
66
|
+
prompt.trigger(:keydown) if event.value == 'j'
|
67
|
+
prompt.trigger(:keyup) if event.value == 'k'
|
68
|
+
end
|
61
69
|
prompt.multi_select('Labels:', team.labels.to_h { |t| [t.name, t] })
|
62
70
|
end
|
71
|
+
|
72
|
+
def cut_branch!(branch_name)
|
73
|
+
if current_branch != default_branch
|
74
|
+
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
|
75
|
+
end
|
76
|
+
git.branch(branch_name)
|
77
|
+
end
|
78
|
+
|
79
|
+
def branch_for(branch_name)
|
80
|
+
logger.trace('Looking for branch', branch_name:)
|
81
|
+
existing = git.branches[branch_name]
|
82
|
+
return cut_branch!(branch_name) unless existing
|
83
|
+
|
84
|
+
logger.trace('Branch found', branch: existing&.name)
|
85
|
+
existing
|
86
|
+
end
|
87
|
+
|
88
|
+
def current_branch
|
89
|
+
git.current_branch
|
90
|
+
end
|
91
|
+
|
92
|
+
# Horrible way to do this, but it is working for now
|
93
|
+
def pull_or_push_new_branch!(branch_name)
|
94
|
+
git.pull
|
95
|
+
rescue Git::FailedError
|
96
|
+
prompt.warn("Upstream branch not found, pushing local #{branch_name} to origin")
|
97
|
+
git.push('origin', branch_name)
|
98
|
+
`git branch --set-upstream-to=origin/#{branch_name} #{branch_name}`
|
99
|
+
prompt.ok("Set upstream to origin/#{branch_name}")
|
100
|
+
end
|
101
|
+
|
102
|
+
def git
|
103
|
+
@git ||= Git.open('.')
|
104
|
+
rescue Git::Repository::NoRepositoryError => e
|
105
|
+
logger.error('Your current directory is not a git repository!', error: e)
|
106
|
+
exit 121
|
107
|
+
end
|
108
|
+
|
109
|
+
def default_branch
|
110
|
+
@default_branch ||= Git.default_branch git.repo.path
|
111
|
+
end
|
63
112
|
end
|
64
113
|
end
|
65
114
|
end
|
data/lib/linear/cli/version.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'semantic_logger'
|
4
|
+
require_relative '../issue'
|
4
5
|
|
5
6
|
module Rubyists
|
6
7
|
# Namespace for Linear
|
@@ -14,12 +15,13 @@ module Rubyists
|
|
14
15
|
class Create
|
15
16
|
include SemanticLogger::Loggable
|
16
17
|
include Rubyists::Linear::CLI::CommonOptions
|
17
|
-
include Rubyists::Linear::CLI::Issue # for #gimme_da_issue and other methods
|
18
|
-
desc 'Create a
|
18
|
+
include Rubyists::Linear::CLI::Issue # for #gimme_da_issue! and other Issue methods
|
19
|
+
desc 'Create a new issue'
|
19
20
|
option :title, type: :string, aliases: ['-t'], desc: 'Issue Title'
|
20
|
-
option :team, type: :string, aliases: ['-T'], desc: 'Team Identifier'
|
21
21
|
option :description, type: :string, aliases: ['-d'], desc: 'Issue Description'
|
22
|
+
option :team, type: :string, aliases: ['-T'], desc: 'Team Identifier'
|
22
23
|
option :labels, type: :array, aliases: ['-l'], desc: 'Labels for the issue (Comma separated list)'
|
24
|
+
option :develop, type: :boolean, aliases: ['-D', '--dev'], desc: 'Start development after creating the issue'
|
23
25
|
|
24
26
|
def call(**options)
|
25
27
|
logger.debug('Creating issue', options:)
|
@@ -27,6 +29,7 @@ module Rubyists
|
|
27
29
|
logger.debug('Issue created', issue:)
|
28
30
|
prompt.yes?('Do you want to take this issue?') && gimme_da_issue!(issue.id, User.me)
|
29
31
|
display issue, options
|
32
|
+
Rubyists::Linear::CLI::Issue::Develop.new.call(issue_id: issue.id, **options) if options[:develop]
|
30
33
|
end
|
31
34
|
end
|
32
35
|
end
|
@@ -0,0 +1,37 @@
|
|
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
|
+
Develop = Class.new Dry::CLI::Command
|
15
|
+
# The Develop class is a Dry::CLI::Command to start/update development status of an issue
|
16
|
+
class Develop
|
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 'Start or update development status of an issue'
|
21
|
+
argument :issue_id, required: true, desc: 'The Issue (i.e. ISS-1)'
|
22
|
+
|
23
|
+
def call(issue_id:, **options)
|
24
|
+
logger.debug('Developing issue', options:)
|
25
|
+
issue = gimme_da_issue!(issue_id, Rubyists::Linear::User.me)
|
26
|
+
branch_name = issue.branchName
|
27
|
+
branch = branch_for(branch_name)
|
28
|
+
branch.checkout
|
29
|
+
prompt.ok "Checked out branch #{branch_name}"
|
30
|
+
pull_or_push_new_branch!(branch_name)
|
31
|
+
prompt.ok 'Ready to develop!'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -13,8 +13,9 @@ module Rubyists
|
|
13
13
|
# Aliases for Issue commands
|
14
14
|
ALIASES = {
|
15
15
|
create: %w[c new add], # aliases for the create command
|
16
|
+
develop: %w[d dev], # aliases for the create command
|
16
17
|
list: %w[l ls], # aliases for the list command
|
17
|
-
show: %w[s view v
|
18
|
+
show: %w[s view v], # aliases for the show command
|
18
19
|
issue: %w[i issues] # aliases for the main issue command itself
|
19
20
|
}.freeze
|
20
21
|
|
@@ -28,10 +29,16 @@ module Rubyists
|
|
28
29
|
end
|
29
30
|
|
30
31
|
def gimme_da_issue!(issue_id, me) # rubocop:disable Naming/MethodParameterName
|
32
|
+
logger.trace('Looking up issue', issue_id:, me:)
|
31
33
|
issue = Rubyists::Linear::Issue.find(issue_id)
|
32
|
-
|
34
|
+
if issue.assignee && issue.assignee[:id] == me.id
|
35
|
+
prompt.say("You are already assigned #{issue_id}")
|
36
|
+
return issue
|
37
|
+
end
|
38
|
+
|
39
|
+
prompt.say("Assigning issue #{issue_id} to ya")
|
33
40
|
updated = issue.assign! me
|
34
|
-
logger.
|
41
|
+
logger.trace 'Issue taken', issue: updated
|
35
42
|
updated
|
36
43
|
end
|
37
44
|
end
|
data/lib/linear/models/issue.rb
CHANGED
data/lib/linear/models/label.rb
CHANGED
@@ -12,14 +12,29 @@ module Rubyists
|
|
12
12
|
class Label
|
13
13
|
include SemanticLogger::Loggable
|
14
14
|
|
15
|
+
PLURAL = :issueLabels
|
15
16
|
Base = fragment('BaseLabel', 'IssueLabel') do
|
16
17
|
id
|
17
18
|
description
|
18
19
|
name
|
20
|
+
isGroup
|
19
21
|
createdAt
|
20
22
|
updatedAt
|
21
23
|
end
|
22
24
|
|
25
|
+
def self.base_fragment # rubocop:disable Metrics/AbcSize
|
26
|
+
define_method(:team) { updated_data[:team] }
|
27
|
+
define_method(:team=) { |val| updated_data[:team] = val }
|
28
|
+
define_method(:parent) { updated_data[:parent] }
|
29
|
+
define_method(:parent=) { |val| updated_data[:parent] = val }
|
30
|
+
|
31
|
+
fragment('LabelWithTeams', 'IssueLabel') do
|
32
|
+
___ Base
|
33
|
+
parent { ___ Base }
|
34
|
+
team { ___ Team::Base }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
23
38
|
def self.find_all_by_name(names)
|
24
39
|
q = query do
|
25
40
|
issueLabels(filter: { name: { in: names } }) do
|
@@ -46,7 +61,7 @@ module Rubyists
|
|
46
61
|
end
|
47
62
|
|
48
63
|
def full
|
49
|
-
format('%<to_s>-10s %<description>s', description
|
64
|
+
format('%<to_s>-10s %<description>s', description:, to_s:)
|
50
65
|
end
|
51
66
|
|
52
67
|
def display(_options)
|
data/lib/linear/models/team.rb
CHANGED
@@ -13,7 +13,13 @@ module Rubyists
|
|
13
13
|
include SemanticLogger::Loggable
|
14
14
|
|
15
15
|
# TODO: Make this configurable
|
16
|
-
|
16
|
+
BaseFilter = { # rubocop:disable Naming/ConstantName
|
17
|
+
and: [
|
18
|
+
{ name: { notEndsWith: ' Releases' } },
|
19
|
+
{ name: { notEndsWith: '-ios' } },
|
20
|
+
{ name: { notEndsWith: '-android' } }
|
21
|
+
]
|
22
|
+
}.freeze
|
17
23
|
|
18
24
|
Base = fragment('BaseTeam', 'Team') do
|
19
25
|
description
|
@@ -51,26 +57,26 @@ module Rubyists
|
|
51
57
|
team_id = id
|
52
58
|
query do
|
53
59
|
team(id: team_id) do
|
54
|
-
labels do
|
55
|
-
nodes { ___ Label
|
60
|
+
labels(first: 100, filter: BaseFilter) do
|
61
|
+
nodes { ___ Label.base_fragment }
|
56
62
|
end
|
57
63
|
end
|
58
64
|
end
|
59
65
|
end
|
60
66
|
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
labels.reject { |label| IgnoredLabels.detect { |i| label.name.match? i } }
|
67
|
+
def label_groups
|
68
|
+
@label_groups ||= []
|
65
69
|
end
|
66
70
|
|
67
|
-
def labels
|
68
|
-
return @labels if @labels
|
71
|
+
def labels # rubocop:disable Metrics/CyclomaticComplexity
|
72
|
+
return @labels if @labels
|
73
|
+
|
74
|
+
@labels = Api.query(label_query).dig(:team, :labels, :nodes)&.map do |label|
|
75
|
+
label_groups << Label.new(label) if label[:isGroup]
|
76
|
+
next if label[:isGroup] || label[:parent]
|
69
77
|
|
70
|
-
all = Api.query(label_query).dig(:team, :labels, :nodes)&.map do |label|
|
71
78
|
Label.new label
|
72
|
-
end
|
73
|
-
@labels = label_filter(all)
|
79
|
+
end&.compact
|
74
80
|
end
|
75
81
|
|
76
82
|
def members
|
data/linear-cli.gemspec
CHANGED
@@ -36,8 +36,10 @@ Gem::Specification.new do |spec|
|
|
36
36
|
spec.add_dependency 'base64', '~> 0.2'
|
37
37
|
spec.add_dependency 'dry-cli', '~> 1.0'
|
38
38
|
spec.add_dependency 'dry-cli-completion', '~> 1.0'
|
39
|
+
spec.add_dependency 'git', '~> 1.5'
|
39
40
|
spec.add_dependency 'gqli', '~> 1.2'
|
40
41
|
spec.add_dependency 'httpx', '~> 1.2'
|
42
|
+
spec.add_dependency 'octokit', '~> 5.0'
|
41
43
|
spec.add_dependency 'semantic_logger', '~> 4.0'
|
42
44
|
spec.add_dependency 'sequel', '~> 5.0'
|
43
45
|
spec.add_dependency 'sqlite3', '~> 1.7'
|
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.
|
4
|
+
version: 0.5.0
|
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-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: base64
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: git
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.5'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.5'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: gqli
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +94,20 @@ dependencies:
|
|
80
94
|
- - "~>"
|
81
95
|
- !ruby/object:Gem::Version
|
82
96
|
version: '1.2'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: octokit
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '5.0'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '5.0'
|
83
111
|
- !ruby/object:Gem::Dependency
|
84
112
|
name: semantic_logger
|
85
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -177,6 +205,7 @@ files:
|
|
177
205
|
- lib/linear/cli/watcher.rb
|
178
206
|
- lib/linear/commands/issue.rb
|
179
207
|
- lib/linear/commands/issue/create.rb
|
208
|
+
- lib/linear/commands/issue/develop.rb
|
180
209
|
- lib/linear/commands/issue/list.rb
|
181
210
|
- lib/linear/commands/issue/take.rb
|
182
211
|
- lib/linear/commands/team.rb
|