afterlife 1.7.2 → 1.7.4

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: acd1912f0524e2efc93ab47e2f43fed17487fc5b05cff9c56f25e275c4501790
4
- data.tar.gz: 3f658d550f54904680eb77ab7333bcde9bfeea2b5dbe4f19b634132ac45276e4
3
+ metadata.gz: 5b3027a754b5aeaaa15a7dbbba4b5aa895ce11fb48402dd9a4348f30b3dc8aa7
4
+ data.tar.gz: 2253a73fd40b0e247cafb3b35b0bd0d75fb9b78d722b7887a7800b30d0f8faa9
5
5
  SHA512:
6
- metadata.gz: a14e900ab52e37392e5a2ebe142257a72db3a0c79b47947975f28d1ac3c58f4c6085c22027935c3be4bd2fda9ac38b1464b04a8e7c60bc6ce8c141c723c9e1b8
7
- data.tar.gz: 316829b117b8b6f7b98d6582244e9bf269bc7e0ca59608c93dd50e54b1a10b25c0bed5272ab143d8261fb61349b97bdd236317ed41402710c55a68db2da3bbbd
6
+ metadata.gz: 01a9c292bef075d314f0ce97ff3deda29705247f9e6e0227c82645292fe0ffe262a4d18d62b8abd896b6376325bcb5071a61d90f992ee47e42dba0a0828bba64
7
+ data.tar.gz: 2f7763617dbd485d863afb4ea226caac63fa3493b14ab3a0c35c186d11ff22f01a8b2e895ba0b7d12ca5cc30db45e140a4be0814e557840cdffd3ff085016b99
@@ -0,0 +1,3 @@
1
+ {
2
+ "vscode-run-rspec-file.custom-command": "bundle exec rspec --color --fail-fast"
3
+ }
data/README.md CHANGED
@@ -20,12 +20,49 @@ Logs can be found on `~/.afterlife/logs/cdn.log`
20
20
 
21
21
  Running `afterlife cdn start` will run the CDN web server in the background. Running in foreground using `afterlife cdn start --foreground` will display errors and content normally found in logs in console instead.
22
22
 
23
+ # About Clickup module
24
+ Usage example:
25
+ ```sh
26
+ afterlife clickup get-commits <revision> | afterlife clickup extract-ids | afterlife clickup update-status 'ready for qa'
27
+ ```
28
+ This will output something like:
29
+ ```sh
30
+ Updating 1 tasks to 'trunk merged'
31
+ Moved 1 tasks, with ids: 86dxbp9d6
32
+ ```
33
+
34
+ The above line does the following:
35
+ 1. Prints all the commits betweed the specified <revision> and HEAD.
36
+ 2. Extracts the clickup ids from the previous output.
37
+ 3. Moves the ids extracted from the previous output to 'ready for qa' status.
38
+
39
+ To not do this blindly, you can run just `afterlife clickup get-commits <revision> | afterlife
40
+ clickup extract-ids` to review if the tasks being moved are correct. But the `update-status` in the
41
+ full command will print the list of moved or ignored tasks anyway.
42
+
43
+ Also, to easily run these commands, is recommended to setup an alias:
44
+
45
+ For bash or zsh:
46
+ ```bash
47
+ alias ci="afterlife clickup"
48
+ ```
49
+
50
+ For fish:
51
+ ```fish
52
+ alias ci 'afterlife clickup'
53
+ ```
54
+
23
55
  ## Development
24
56
 
25
57
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
26
58
 
27
59
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
28
60
 
61
+ ## Publishing
62
+ This gem is published on rubygems. To publish there, just:
63
+ 1. Do a version bump in `lib/afterlife/version.rb`.
64
+ 2. Execute `bundle exec rake release`. This will ask you for rubygems credentials. You'll access, ask Genaro for it.
65
+
29
66
  ## Contributing
30
67
 
31
68
  Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/afterlife.
data/afterlife.gemspec CHANGED
@@ -28,6 +28,8 @@ Gem::Specification.new do |spec|
28
28
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
29
  spec.require_paths = ['lib']
30
30
 
31
+ spec.add_dependency 'faraday'
32
+ spec.add_dependency 'racc' # rubocop dependency, cannot run `bundle exec rubocop` without it
31
33
  spec.add_dependency 'thor'
32
34
  spec.add_dependency 'zeitwerk'
33
35
 
data/lib/afterlife/cli.rb CHANGED
@@ -47,6 +47,9 @@ module Afterlife
47
47
  desc 'release', 'Releases management'
48
48
  subcommand 'release', Release::Cli
49
49
 
50
+ desc 'clickup', 'ClickUp integration commands'
51
+ subcommand 'clickup', Clickup::Cli
52
+
50
53
  map %w[-v --version] => :version
51
54
  desc 'version', 'Prints afterlife version'
52
55
  def version
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+
5
+ module Afterlife
6
+ module Clickup
7
+ class Api
8
+ BASE_URL = 'https://api.clickup.com'
9
+ attr_reader :connection
10
+
11
+ def initialize(token:)
12
+ @connection = Faraday.new(
13
+ url: BASE_URL,
14
+ headers: { 'Content-Type' => 'application/json', 'Authorization' => token },
15
+ ) do |faraday|
16
+ faraday.request :json # encode request bodies as JSON
17
+ faraday.response :json # decode response bodies as JSON
18
+ faraday.response :raise_error # raise an error if the response is not successful
19
+ end
20
+ end
21
+
22
+ def get_task(task_id)
23
+ connection.get("/api/v2/task/#{task_id}").body
24
+ rescue Faraday::Error
25
+ nil
26
+ end
27
+
28
+ def update_task(task_id, params)
29
+ connection.put("/api/v2/task/#{task_id}", params).body
30
+ rescue Faraday::Error => e
31
+ puts "Error updating task #{task_id}: #{e.message}"
32
+ nil
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Clickup
5
+ class Cli < Thor
6
+ include BaseCli
7
+
8
+ CLICKUP_STATUS_OPTIONS = StatusPrecedenceChecker::STATUS_PRECEDENCE.map { |s| "'#{s}'" }.join(', ')
9
+
10
+ def self.exit_on_failure?
11
+ true
12
+ end
13
+
14
+ desc 'extract-ids [TEXT]', 'Extract ClickUp task IDs from text or stdin'
15
+ option :link, type: :boolean, default: false, aliases: '-l',
16
+ desc: 'Convert task IDs to links'
17
+ def extract_ids(text = nil)
18
+ text ||= $stdin.read
19
+ ids = GetIdsFromText.call(text, options[:link])
20
+ ids.each { |id| puts id }
21
+ end
22
+
23
+ desc 'get-commits FROM_REVISION [TO_REVISION]',
24
+ 'Get commits between revisions. The revision can be a short or long version of the commit sha.'
25
+ option :short, type: :boolean, default: false, aliases: '-s',
26
+ desc: 'Only prints a short version of the commit, do not use it on CI.'
27
+ def get_commits(from_revision, to_revision = 'HEAD')
28
+ messages = GetRangeCommits.call(from_revision, to_revision, short: options[:short])
29
+ puts messages
30
+ rescue StandardError => e
31
+ fatal!(e.message)
32
+ end
33
+
34
+ desc 'update-status TARGET_STATUS TASK_ID [TASK_ID...]',
35
+ "Update status of ClickUp tasks. Possible values for TARGET_STATUS: #{CLICKUP_STATUS_OPTIONS}"
36
+ option :force, type: :boolean, default: false, aliases: '-f',
37
+ desc: 'Force update status of tasks even if they would not be updated due to status precedence'
38
+ option :dry_run, type: :boolean, default: false, aliases: '-d',
39
+ desc: 'Do not actually update the status of the tasks, just print what would be done'
40
+ def update_status(target_status, *task_ids)
41
+ task_ids = $stdin.read.strip.split("\n") if task_ids.empty?
42
+
43
+ UpdateTaskStatus.call(task_ids, target_status, force: options[:force], dry_run: options[:dry_run])
44
+ rescue StandardError => e
45
+ fatal!(e.message)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Clickup
5
+ class GetIdsFromText
6
+ ID_PATTERNS = [
7
+ /#CU-([a-z0-9]+)/i,
8
+ /#CU_([a-z0-9]+)/i,
9
+ /#([a-z0-9]{8,})/i,
10
+ /CU-([a-z0-9]+)/i,
11
+ ].freeze
12
+
13
+ def self.call(text, convert_to_link = false) # rubocop:disable Metrics/MethodLength
14
+ ids = Set.new
15
+
16
+ ID_PATTERNS.each do |pattern|
17
+ text.scan(pattern) do |match|
18
+ ids.add(match.first.downcase)
19
+ end
20
+ end
21
+
22
+ if convert_to_link
23
+ ids.map { |id| "https://app.clickup.com/t/#{id}" }
24
+ else
25
+ ids.to_a
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+
5
+ module Afterlife
6
+ module Clickup
7
+ class GetRangeCommits
8
+ SHORT_PARAMS = "--graph --decorate --pretty=format:'%C(yellow)%h %C(cyan)%ad %C(cyan)%d %Creset%s %Cgreen[%an]' --date=relative" # rubocop:disable Layout/LineLength
9
+ DEFAULT_PARAMS = "--pretty=format:'%s%n%b'"
10
+
11
+ def self.call(from_revision, to_revision = 'HEAD', short: nil)
12
+ range = "#{from_revision}..#{to_revision}"
13
+ params = short ? SHORT_PARAMS : DEFAULT_PARAMS
14
+ commit_messages = `git log #{params} #{range}`.strip
15
+ fail "Error getting commits for range #{range}" unless $CHILD_STATUS.success?
16
+
17
+ commit_messages
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Clickup
5
+ class StatusPrecedenceChecker
6
+ STATUS_PRECEDENCE = [
7
+ 'backlog',
8
+ 'defined',
9
+ 'planned',
10
+ 'in progress',
11
+ 'needs review',
12
+ 'needs rework',
13
+ 'blocked',
14
+ 'trunk merged',
15
+ 'ready for qa',
16
+ 'validated qa',
17
+ 'completed',
18
+ ].freeze
19
+
20
+ def should_skip_update?(current_status, target_status)
21
+ current_precedence = get_precedence(current_status)
22
+ target_precedence = get_precedence(target_status)
23
+
24
+ current_precedence >= target_precedence
25
+ end
26
+
27
+ private
28
+
29
+ def get_precedence(status)
30
+ return -1 if status.nil? || status.strip.empty?
31
+
32
+ result = STATUS_PRECEDENCE.index(status.strip.downcase) || -1
33
+
34
+ puts "WARNING: status precedence not found: #{status}" if result == -1
35
+
36
+ result
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Clickup
5
+ class UpdateTaskStatus
6
+ attr_reader :target_status, :precedence_checker, :api, :force, :dry_run
7
+
8
+ def self.call(task_ids, target_status, force: false, dry_run: false)
9
+ new(force: force, dry_run: dry_run).call(task_ids, target_status)
10
+ end
11
+
12
+ def initialize(force: false, dry_run: false)
13
+ @force = force
14
+ @dry_run = dry_run
15
+ token = ENV['CLICKUP_TOKEN'] || fail('CLICKUP_TOKEN required, put `export CLICKUP_TOKEN=...` or the equivalent in your shell') # rubocop:disable Layout/LineLength
16
+ @api = Api.new(token: token)
17
+ @precedence_checker = StatusPrecedenceChecker.new
18
+ end
19
+
20
+ def call(task_ids, target_status) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
21
+ if task_ids.empty?
22
+ puts 'No tasks to update'
23
+ return []
24
+ end
25
+
26
+ puts 'WARNING: Forcing status updates...' if force
27
+ puts 'WARNING: Dry run, no tasks will be updated' if dry_run
28
+ puts "Updating #{task_ids.length} tasks to '#{target_status}'"
29
+ moved_task_ids = task_ids.map { |task_id| move_task(task_id, target_status) }.compact
30
+ puts "#{dry_run ? 'Would have moved' : 'Moved'} #{moved_task_ids.length} tasks:"
31
+ moved_task_ids.each do |task|
32
+ puts "- #{task[:link]} (from '#{task[:previous_status]}' to '#{task[:new_status]}')"
33
+ end
34
+ moved_task_ids
35
+ end
36
+
37
+ private
38
+
39
+ def move_task(task_id, target_status)
40
+ current_status = api.get_task(task_id).dig('status', 'status')&.downcase
41
+
42
+ return puts "WARNING: Task #{task_id} not found" if current_status.nil?
43
+
44
+ should_skip_update = !force && precedence_checker.should_skip_update?(current_status, target_status)
45
+ task_link = "https://app.clickup.com/t/#{task_id}"
46
+ return puts "Skipping task #{task_link} because it's already at '#{current_status}'" if should_skip_update
47
+
48
+ api.update_task(task_id, { status: target_status }) unless dry_run
49
+ { id: task_id, link: task_link, previous_status: current_status, new_status: target_status }
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Clickup
5
+ end
6
+ end
@@ -43,7 +43,7 @@ module Afterlife
43
43
  return if options['no-build']
44
44
 
45
45
  <<-BASH
46
- docker buildx bake -f docker-bake.hcl #{targets.join(' ')} #{local_stage? ? '--load' : '--push'}
46
+ docker buildx bake --allow=ssh -f docker-bake.hcl #{targets.join(' ')} #{local_stage? ? '--load' : '--push'}
47
47
  BASH
48
48
  end
49
49
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Afterlife
4
- VERSION = '1.7.2'
4
+ VERSION = '1.7.4'
5
5
  end
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: afterlife
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.2
4
+ version: 1.7.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Genaro Madrid
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-10-07 00:00:00.000000000 Z
11
+ date: 2025-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: racc
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: thor
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -51,6 +79,7 @@ files:
51
79
  - ".rubocop.yml"
52
80
  - ".ruby-gemset"
53
81
  - ".ruby-version"
82
+ - ".vscode/settings.json.example"
54
83
  - CHANGELOG.md
55
84
  - Gemfile
56
85
  - README.md
@@ -65,6 +94,13 @@ files:
65
94
  - lib/afterlife/cdn/cli.rb
66
95
  - lib/afterlife/cdn/server.rb
67
96
  - lib/afterlife/cli.rb
97
+ - lib/afterlife/clickup.rb
98
+ - lib/afterlife/clickup/api.rb
99
+ - lib/afterlife/clickup/cli.rb
100
+ - lib/afterlife/clickup/get_ids_from_text.rb
101
+ - lib/afterlife/clickup/get_range_commits.rb
102
+ - lib/afterlife/clickup/status_precedence_checker.rb
103
+ - lib/afterlife/clickup/update_task_status.rb
68
104
  - lib/afterlife/config.rb
69
105
  - lib/afterlife/config/cli.rb
70
106
  - lib/afterlife/config/provider.rb
@@ -111,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
111
147
  - !ruby/object:Gem::Version
112
148
  version: '0'
113
149
  requirements: []
114
- rubygems_version: 3.2.33
150
+ rubygems_version: 3.5.16
115
151
  signing_key:
116
152
  specification_version: 4
117
153
  summary: Devops utils