abt-cli 0.0.10 → 0.0.11

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7b24bcf0b7e31cf18b1f86381f6dabba6434e8dfbd4a077f5447afdd20f08d7e
4
- data.tar.gz: a66846cb48a109ea21bb7366c1432c8f7d647b5384f42565c90fa609c51d8b6b
3
+ metadata.gz: 0cc34f7579a887a49ae83db56c8b3808a0e00b09c55631f9b4224228477a0762
4
+ data.tar.gz: 9c26a06c3af53ebe55ca5e53f93b5d6d73854962daebef31d131000657be1c63
5
5
  SHA512:
6
- metadata.gz: b3610428c6ded90f53c49396d41b07fc54134dd88cb8019133c35dab230058b5506d93cfab0dc4b811b81161e45e24c1b0502dd7e03a8f3a65248db864886fb3
7
- data.tar.gz: 6affbc39d2572a73b700367d8cdabbe84275faf663b66b0c020c1683a3ffc7f584d43b828674cc2d32f1f05a8e2695443f13c64a2d1727e7fdb7708f93a0bcf5
6
+ metadata.gz: 4fe10297a130b747d93dc1398bdf404996d5c07987680deac59a5365d6893ab3d0466ebd764ef087f5eb2409b99a7733dc55a869432d81286f3460ec510d247d
7
+ data.tar.gz: 3684a1d3113f9df06637a6c7294f334b48e2c8cd5e54d4334c1ec1884571519f6e5aedeec327d8292f5364d1251453d6a7a8131074f91b2f94935b7a3586ddb9
@@ -32,6 +32,19 @@ module Abt
32
32
  set(key, value)
33
33
  end
34
34
 
35
+ def full_keys
36
+ if scope == 'local' && !self.class.local_available?
37
+ raise StandardError, 'Local configuration is not available outside a git repository'
38
+ end
39
+
40
+ `git config --#{scope} --get-regexp --name-only ^#{namespace}`.lines.map(&:strip)
41
+ end
42
+
43
+ def keys
44
+ offset = namespace.length + 1
45
+ full_keys.map { |key| key[offset..-1] }
46
+ end
47
+
35
48
  def local
36
49
  @local ||= begin
37
50
  if scope == 'local'
@@ -13,7 +13,7 @@ module Abt
13
13
  'Print Harvest time entry data for Asana task as json. Used by harvest start script.'
14
14
  end
15
15
 
16
- def call # rubocop:disable Metrics/MethodLength
16
+ def call
17
17
  ensure_current_is_valid!
18
18
 
19
19
  body = {
@@ -21,9 +21,7 @@ module Abt
21
21
  external_reference: {
22
22
  id: task_gid.to_i,
23
23
  group_id: project_gid.to_i,
24
- permalink: task['permalink_url'],
25
- service: 'app.asana.com',
26
- service_icon_url: 'https://proxy.harvestfiles.com/production_harvestapp_public/uploads/platform_icons/app.asana.com.png'
24
+ permalink: task['permalink_url']
27
25
  }
28
26
  }
29
27
 
@@ -13,6 +13,11 @@ module Abt
13
13
  'Pick Asana project for current git repository'
14
14
  end
15
15
 
16
+ def initialize(cli:, **)
17
+ @config = Configuration.new(cli: cli)
18
+ @cli = cli
19
+ end
20
+
16
21
  def call
17
22
  cli.abort 'Must be run inside a git repository' unless config.local_available?
18
23
 
@@ -67,8 +67,10 @@ module Abt
67
67
  end
68
68
 
69
69
  def clear_global
70
- git.global['workspaceGid'] = nil
71
- git.global['accessToken'] = nil
70
+ git.global.keys.each do |key|
71
+ cli.puts 'Deleting configuration: ' + key
72
+ git.global[key] = nil
73
+ end
72
74
  end
73
75
 
74
76
  def access_token
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir.glob("#{File.expand_path(__dir__)}/devops/*.rb").sort.each { |file| require file }
4
+ Dir.glob("#{File.expand_path(__dir__)}/devops/commands/*.rb").sort.each { |file| require file }
5
+
6
+ module Abt
7
+ module Providers
8
+ module Devops
9
+ def self.command_names
10
+ Commands.constants.sort.map { |constant_name| Helpers.const_to_command(constant_name) }
11
+ end
12
+
13
+ def self.command_class(name)
14
+ const_name = Helpers.command_to_const(name)
15
+ Commands.const_get(const_name) if Commands.const_defined?(const_name)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Devops
6
+ class Api
7
+ VERBS = %i[get post put].freeze
8
+
9
+ attr_reader :organization_name, :project_name, :username, :access_token
10
+
11
+ def initialize(organization_name:, project_name:, username:, access_token:)
12
+ @organization_name = organization_name
13
+ @project_name = project_name
14
+ @username = username
15
+ @access_token = access_token
16
+ end
17
+
18
+ VERBS.each do |verb|
19
+ define_method(verb) do |*args|
20
+ request(verb, *args)
21
+ end
22
+ end
23
+
24
+ def get_paged(path, query = {})
25
+ result = request(:get, path, query)
26
+ result['value']
27
+
28
+ # TODO: Loop if necessary
29
+ end
30
+
31
+ def request(*args)
32
+ response = connection.public_send(*args)
33
+
34
+ if response.success?
35
+ Oj.load(response.body)
36
+ else
37
+ error_class = Abt::HttpError.error_class_for_status(response.status)
38
+ encoded_response_body = response.body.force_encoding('utf-8')
39
+ raise error_class, "Code: #{response.status}, body: #{encoded_response_body}"
40
+ end
41
+ end
42
+
43
+ def base_url
44
+ "https://#{organization_name}.visualstudio.com/#{project_name}"
45
+ end
46
+
47
+ def api_endpoint
48
+ "#{base_url}/_apis"
49
+ end
50
+
51
+ def url_for_work_item(work_item)
52
+ "#{base_url}/_workitems/edit/#{work_item['id']}"
53
+ end
54
+
55
+ def connection
56
+ @connection ||= Faraday.new(api_endpoint) do |connection|
57
+ connection.basic_auth username, access_token
58
+ connection.headers['Content-Type'] = 'application/json'
59
+ connection.headers['Accept'] = 'application/json; api-version=6.0'
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Devops
6
+ class BaseCommand
7
+ attr_reader :arg_str, :organization_name, :project_name, :board_id, :work_item_id, :cli, :config
8
+
9
+ def initialize(arg_str:, cli:)
10
+ @arg_str = arg_str
11
+
12
+ @config = Configuration.new(cli: cli)
13
+ @cli = cli
14
+
15
+ if arg_str.nil?
16
+ use_current_args
17
+ else
18
+ use_arg_str(arg_str)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def sanitize_work_item(work_item)
25
+ return nil if work_item.nil?
26
+
27
+ work_item.merge(
28
+ 'id' => work_item['id'].to_s,
29
+ 'name' => work_item['fields']['System.Title'],
30
+ 'url' => api.url_for_work_item(work_item)
31
+ )
32
+ end
33
+
34
+ def same_args_as_config?
35
+ organization_name == config.organization_name &&
36
+ project_name == config.project_name &&
37
+ board_id == config.board_id &&
38
+ work_item_id == config.work_item_id
39
+ end
40
+
41
+ def print_board(organization_name, project_name, board)
42
+ arg_str = "#{organization_name}/#{project_name}/#{board['id']}"
43
+
44
+ cli.print_provider_command('devops', arg_str, board['name'])
45
+ # cli.warn board['url'] if board.key?('url') && cli.output.isatty # TODO: Web URL
46
+ end
47
+
48
+ def print_work_item(organization, project, board, work_item)
49
+ arg_str = "#{organization}/#{project}/#{board['id']}/#{work_item['id']}"
50
+
51
+ cli.print_provider_command('devops', arg_str, work_item['name'])
52
+ cli.warn work_item['url'] if work_item.key?('url') && cli.output.isatty
53
+ end
54
+
55
+ def use_current_args
56
+ @organization_name = config.organization_name
57
+ @project_name = config.project_name
58
+ @board_id = config.board_id
59
+ @work_item_id = config.work_item_id
60
+ end
61
+
62
+ def use_arg_str(arg_str)
63
+ args = arg_str.to_s.split('/')
64
+
65
+ if args.length < 3
66
+ cli.abort 'Argument format is <organization>/<project>/<board-id>[/<work-item-id>]'
67
+ end
68
+
69
+ (@organization_name, @project_name, @board_id, @work_item_id) = args
70
+ end
71
+
72
+ def api
73
+ Abt::Providers::Devops::Api.new(organization_name: organization_name,
74
+ project_name: project_name,
75
+ username: config.username_for_organization(organization_name),
76
+ access_token: config.access_token_for_organization(organization_name))
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Devops
6
+ module Commands
7
+ class Clear < BaseCommand
8
+ def self.command
9
+ 'clear devops'
10
+ end
11
+
12
+ def self.description
13
+ 'Clear DevOps config for current git repository'
14
+ end
15
+
16
+ def call
17
+ cli.warn 'Clearing configuration'
18
+ config.clear_local
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Devops
6
+ module Commands
7
+ class ClearGlobal < BaseCommand
8
+ def self.command
9
+ 'clear-global devops'
10
+ end
11
+
12
+ def self.description
13
+ 'Clear all global configuration'
14
+ end
15
+
16
+ def call
17
+ cli.warn 'Clearing global DevOps configuration'
18
+ config.clear_global
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Devops
6
+ module Commands
7
+ class Current < BaseCommand
8
+ def self.command
9
+ 'current devops[:<organization-name>/<project-name>/<board-id>[/<work-item-id>]]'
10
+ end
11
+
12
+ def self.description
13
+ 'Get or set DevOps configuration for current git repository'
14
+ end
15
+
16
+ def call
17
+ if same_args_as_config? || !config.local_available?
18
+ show_current_configuration
19
+ else
20
+ cli.warn 'Updating configuration'
21
+ update_configuration
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def show_current_configuration
28
+ if organization_name.nil?
29
+ cli.warn 'No organization selected'
30
+ elsif project_name.nil?
31
+ cli.warn 'No project selected'
32
+ elsif board_id.nil?
33
+ cli.warn 'No board selected'
34
+ elsif work_item_id.nil?
35
+ print_board(organization_name, project_name, board)
36
+ else
37
+ print_work_item(organization_name, project_name, board, work_item)
38
+ end
39
+ end
40
+
41
+ def update_configuration
42
+ ensure_board_is_valid!
43
+
44
+ if work_item_id.nil?
45
+ update_board_config
46
+ config.work_item_id = nil
47
+
48
+ print_board(organization_name, project_name, board)
49
+ else
50
+ ensure_work_item_is_valid!
51
+
52
+ update_board_config
53
+ config.work_item_id = work_item_id
54
+
55
+ print_work_item(organization_name, project_name, board, work_item)
56
+ end
57
+ end
58
+
59
+ def update_board_config
60
+ config.organization_name = organization_name
61
+ config.project_name = project_name
62
+ config.board_id = board_id
63
+ end
64
+
65
+ def ensure_board_is_valid!
66
+ if board.nil?
67
+ cli.abort 'Board could not be found, ensure that settings for organization, project, and board are correct'
68
+ end
69
+ end
70
+
71
+ def ensure_work_item_is_valid!
72
+ cli.abort "No such work item: ##{work_item_id}" if work_item.nil?
73
+ end
74
+
75
+ def board
76
+ @board ||= begin
77
+ cli.warn 'Fetching board...'
78
+ api.get("work/boards/#{board_id}")
79
+ rescue HttpError::NotFoundError
80
+ nil
81
+ end
82
+ end
83
+
84
+ def work_item
85
+ @work_item ||= begin
86
+ cli.warn 'Fetching work item...'
87
+ work_item = api.get_paged('wit/workitems', ids: work_item_id)[0]
88
+ sanitize_work_item(work_item)
89
+ rescue HttpError::NotFoundError
90
+ nil
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Devops
6
+ module Commands
7
+ class HarvestTimeEntryData < BaseCommand
8
+ def self.command
9
+ 'harvest-time-entry-data devops[:<organization-name>/<project-name>/<board-id>/<work-item-id>]'
10
+ end
11
+
12
+ def self.description
13
+ 'Print Harvest time entry data for DevOps work item as json. Used by harvest start script.'
14
+ end
15
+
16
+ def call
17
+ body = {
18
+ notes: notes,
19
+ external_reference: {
20
+ id: work_item['id'],
21
+ group_id: 'AzureDevOpsWorkItem',
22
+ permalink: work_item['url']
23
+ }
24
+ }
25
+
26
+ cli.puts Oj.dump(body, mode: :json)
27
+ end
28
+
29
+ private
30
+
31
+ def notes
32
+ [
33
+ 'Azure DevOps',
34
+ work_item['fields']['System.WorkItemType'],
35
+ "##{work_item['id']}",
36
+ '-',
37
+ work_item['name']
38
+ ].join(' ')
39
+ end
40
+
41
+ def work_item
42
+ @work_item ||= begin
43
+ work_item = api.get_paged('wit/workitems', ids: work_item_id)[0]
44
+ sanitize_work_item(work_item)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Devops
6
+ module Commands
7
+ class Init < BaseCommand
8
+ AZURE_DEV_URL_REGEX = %r{^https://dev\.azure\.com/(?<organization>[^/]+)/(?<project>[^/]+)}.freeze
9
+ VS_URL_REGEX = %r{^https://(?<organization>[^.]+)\.visualstudio\.com/(?<project>[^/]+)}.freeze
10
+
11
+ def self.command
12
+ 'init devops'
13
+ end
14
+
15
+ def self.description
16
+ 'Pick DevOps board for current git repository'
17
+ end
18
+
19
+ def call
20
+ cli.abort 'Must be run inside a git repository' unless config.local_available?
21
+
22
+ @organization_name = config.organization_name = organization_name_from_url
23
+ @project_name = config.project_name = project_name_from_url
24
+
25
+ board = cli.prompt_choice 'Select a project work board', boards
26
+
27
+ config.board_id = board['id']
28
+
29
+ print_board(organization_name, project_name, board)
30
+ end
31
+
32
+ private
33
+
34
+ def boards
35
+ @boards ||= api.get_paged('work/boards')
36
+ end
37
+
38
+ def project_name_from_url
39
+ if (match = AZURE_DEV_URL_REGEX.match(project_url)) ||
40
+ (match = VS_URL_REGEX.match(project_url))
41
+ match[:project]
42
+ end
43
+ end
44
+
45
+ def organization_name_from_url
46
+ if (match = AZURE_DEV_URL_REGEX.match(project_url)) ||
47
+ (match = VS_URL_REGEX.match(project_url))
48
+ match[:organization]
49
+ end
50
+ end
51
+
52
+ def project_url
53
+ @project_url ||= begin
54
+ loop do
55
+ url = cli.prompt([
56
+ 'Please provide the URL for the devops project',
57
+ 'For instance https://{organization}.visualstudio.com/{project} or https://dev.azure.com/{organization}/{project}',
58
+ '',
59
+ 'Enter URL'
60
+ ].join("\n"))
61
+
62
+ break url if AZURE_DEV_URL_REGEX =~ url || VS_URL_REGEX =~ url
63
+
64
+ cli.warn 'Invalid URL'
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Devops
6
+ module Commands
7
+ class Pick < BaseCommand
8
+ def self.command
9
+ 'pick devops[:<organization-name>/<project-name>/<board-id>]'
10
+ end
11
+
12
+ def self.description
13
+ 'Pick work item for current git repository'
14
+ end
15
+
16
+ def call
17
+ cli.abort 'Must be run inside a git repository' unless config.local_available?
18
+
19
+ cli.warn "#{project_name} - #{board['name']}"
20
+
21
+ work_item = select_work_item
22
+
23
+ # We might have gotten org, project, board as arg str
24
+ config.organization_name = organization_name
25
+ config.project_name = project_name
26
+ config.board_id = board_id
27
+ config.work_item_id = work_item['id']
28
+
29
+ print_work_item(organization_name, project_name, board, work_item)
30
+ end
31
+
32
+ private
33
+
34
+ def select_work_item
35
+ loop do
36
+ column = cli.prompt_choice 'Which column?', columns
37
+ cli.warn 'Fetching work items...'
38
+ work_items = work_items_in_column(column)
39
+
40
+ if work_items.length.zero?
41
+ cli.warn 'Section is empty'
42
+ next
43
+ end
44
+
45
+ work_item = cli.prompt_choice 'Select a work item', work_items, true
46
+ return work_item if work_item
47
+ end
48
+ end
49
+
50
+ def work_items_in_column(column)
51
+ wiql = <<~WIQL
52
+ SELECT [System.Id]
53
+ FROM WorkItems
54
+ WHERE [System.BoardColumn] = '#{column['name']}'
55
+ ORDER BY [Microsoft.VSTS.Common.BacklogPriority] ASC
56
+ WIQL
57
+
58
+ response = api.post('wit/wiql', Oj.dump({ query: wiql }, mode: :json))
59
+ ids = response['workItems'].map { |work_item| work_item['id'] }
60
+ work_items = api.get_paged('wit/workitems', ids: ids.join(','))
61
+
62
+ work_items.map { |work_item| sanitize_work_item(work_item) }
63
+ end
64
+
65
+ def columns
66
+ board['columns']
67
+ end
68
+
69
+ def board
70
+ @board ||= api.get("work/boards/#{board_id}")
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Devops
6
+ module Commands
7
+ class Share < BaseCommand
8
+ def self.command
9
+ 'share devops[:<organization-name>/<project-name>/<board-id>[/<work-item-id>]]'
10
+ end
11
+
12
+ def self.description
13
+ 'Print DevOps config string'
14
+ end
15
+
16
+ def call
17
+ if organization_name.nil?
18
+ cli.warn 'No organization selected'
19
+ elsif project_name.nil?
20
+ cli.warn 'No project selected'
21
+ elsif board_id.nil?
22
+ cli.warn 'No board selected'
23
+ else
24
+ args = [organization_name, project_name, board_id, work_item_id].compact
25
+ cli.print_provider_command('devops', args.join('/'))
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Abt
4
+ module Providers
5
+ module Devops
6
+ class Configuration
7
+ attr_accessor :cli
8
+
9
+ def initialize(cli:)
10
+ @cli = cli
11
+ @git = GitConfig.new(namespace: 'abt.devops')
12
+ end
13
+
14
+ def local_available?
15
+ GitConfig.local_available?
16
+ end
17
+
18
+ def organization_name
19
+ local_available? ? git['organizationName'] : nil
20
+ end
21
+
22
+ def project_name
23
+ local_available? ? git['projectName'] : nil
24
+ end
25
+
26
+ def board_id
27
+ local_available? ? git['boardId'] : nil
28
+ end
29
+
30
+ def work_item_id
31
+ local_available? ? git['workItemId'] : nil
32
+ end
33
+
34
+ def organization_name=(value)
35
+ return if organization_name == value
36
+
37
+ clear_local
38
+ git['organizationName'] = value unless value.nil?
39
+ end
40
+
41
+ def project_name=(value)
42
+ return if project_name == value
43
+
44
+ git['projectName'] = value unless value.nil?
45
+ git['boardId'] = nil
46
+ git['workItemId'] = nil
47
+ end
48
+
49
+ def board_id=(value)
50
+ return if board_id == value
51
+
52
+ git['boardId'] = value unless value.nil?
53
+ git['workItemId'] = nil
54
+ end
55
+
56
+ def work_item_id=(value)
57
+ git['workItemId'] = value
58
+ end
59
+
60
+ def clear_local
61
+ cli.abort 'No local configuration was found' unless local_available?
62
+
63
+ git['organizationName'] = nil
64
+ git['projectName'] = nil
65
+ git['boardId'] = nil
66
+ git['workItemId'] = nil
67
+ end
68
+
69
+ def clear_global
70
+ git.global.keys.each do |key|
71
+ cli.puts 'Deleting configuration: ' + key
72
+ git.global[key] = nil
73
+ end
74
+ end
75
+
76
+ def username_for_organization(organization_name)
77
+ username_key = "organizations.#{organization_name}.username"
78
+
79
+ return git.global[username_key] unless git.global[username_key].nil?
80
+
81
+ git.global[username_key] = cli.prompt([
82
+ "Please provide your username for the DevOps organization (#{organization_name}).",
83
+ '',
84
+ 'Enter username'
85
+ ].join("\n"))
86
+ end
87
+
88
+ def access_token_for_organization(organization_name)
89
+ access_token_key = "organizations.#{organization_name}.accessToken"
90
+
91
+ return git.global[access_token_key] unless git.global[access_token_key].nil?
92
+
93
+ git.global[access_token_key] = cli.prompt([
94
+ "Please provide your personal access token for the DevOps organization (#{organization_name}).",
95
+ 'If you don\'t have one, follow the guide here: https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate',
96
+ '',
97
+ 'The token MUST have "Read" permission for Work Items',
98
+ 'Future features will likely require "Write" or "Manage"',
99
+ '',
100
+ 'Enter access token'
101
+ ].join("\n"))
102
+ end
103
+
104
+ private
105
+
106
+ attr_reader :git
107
+ end
108
+ end
109
+ end
110
+ end
@@ -44,9 +44,10 @@ module Abt
44
44
  end
45
45
 
46
46
  def clear_global
47
- git.global['userId'] = nil
48
- git.global['accountId'] = nil
49
- git.global['accessToken'] = nil
47
+ git.global.keys.each do |key|
48
+ cli.puts 'Deleting configuration: ' + key
49
+ git.global[key] = nil
50
+ end
50
51
  end
51
52
 
52
53
  def access_token
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Abt
4
- VERSION = '0.0.10'
4
+ VERSION = '0.0.11'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: abt-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jesper Sørensen
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-27 00:00:00.000000000 Z
11
+ date: 2021-01-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-inflector
@@ -100,6 +100,17 @@ files:
100
100
  - "./lib/abt/providers/asana/commands/start.rb"
101
101
  - "./lib/abt/providers/asana/commands/tasks.rb"
102
102
  - "./lib/abt/providers/asana/configuration.rb"
103
+ - "./lib/abt/providers/devops.rb"
104
+ - "./lib/abt/providers/devops/api.rb"
105
+ - "./lib/abt/providers/devops/base_command.rb"
106
+ - "./lib/abt/providers/devops/commands/clear.rb"
107
+ - "./lib/abt/providers/devops/commands/clear_global.rb"
108
+ - "./lib/abt/providers/devops/commands/current.rb"
109
+ - "./lib/abt/providers/devops/commands/harvest_time_entry_data.rb"
110
+ - "./lib/abt/providers/devops/commands/init.rb"
111
+ - "./lib/abt/providers/devops/commands/pick.rb"
112
+ - "./lib/abt/providers/devops/commands/share.rb"
113
+ - "./lib/abt/providers/devops/configuration.rb"
103
114
  - "./lib/abt/providers/harvest.rb"
104
115
  - "./lib/abt/providers/harvest/api.rb"
105
116
  - "./lib/abt/providers/harvest/base_command.rb"