afterlife 1.7.3 → 1.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 04142460aa12c27177dd71d0450a10930e27370ab91b753f107ace751735ebbd
4
- data.tar.gz: a9f0f37abc39566e8c7a5660b5a86108a7e1dcc88f87936e882f0364f00687c7
3
+ metadata.gz: 6f862660ef3c576008d2054a427dcd03fbbbd8a1fe070158507f1eeaa32d288c
4
+ data.tar.gz: 9cc64fe488e5c3da7213bd8ca3b61d86539207dfef6a42fc5e043cb5047d3246
5
5
  SHA512:
6
- metadata.gz: c503b855bd1257b9266c010ca4364fc894d26aefaccb65f2debaba46859bf7af51e661265f148bb0119ea1432e3edc7fd21d5f89ba546e8be719c7f26e1c960c
7
- data.tar.gz: 9e046b5c9bb75f1ffd71edf220935260423c5d4d7d5fffabaedffc82a849944e0850458ac1e90885ccb96ad5072d802cac06bfa1851139f27295287386b42f1f
6
+ metadata.gz: 7a81d5767dadaf2ef615236856124f26fb17bdaaf198a5fef2edbfe520ca0aacb8196deab2345605f5d591ecfa35dbffd516db9061c98628b2aecf808986c8df
7
+ data.tar.gz: 8666f13424d39b7b5bc045e7b04c0b71de3a5842d3c519963e399fd62fc465ca6242711ad3ed79a1d4ce25e4b5db70065a6ff7fb2774bc1b9954c5851745fe9e
data/README.md CHANGED
@@ -40,12 +40,29 @@ To not do this blindly, you can run just `afterlife clickup get-commits <revisio
40
40
  clickup extract-ids` to review if the tasks being moved are correct. But the `update-status` in the
41
41
  full command will print the list of moved or ignored tasks anyway.
42
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
+
43
55
  ## Development
44
56
 
45
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.
46
58
 
47
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).
48
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
+
49
66
  ## Contributing
50
67
 
51
68
  Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/afterlife.
data/lib/afterlife/cli.rb CHANGED
@@ -50,6 +50,9 @@ module Afterlife
50
50
  desc 'clickup', 'ClickUp integration commands'
51
51
  subcommand 'clickup', Clickup::Cli
52
52
 
53
+ desc 'propagate', 'Propagate commands'
54
+ subcommand 'propagate', Propagate::Cli
55
+
53
56
  map %w[-v --version] => :version
54
57
  desc 'version', 'Prints afterlife version'
55
58
  def version
@@ -12,16 +12,20 @@ module Afterlife
12
12
  end
13
13
 
14
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'
15
17
  def extract_ids(text = nil)
16
18
  text ||= $stdin.read
17
- ids = GetIdsFromText.call(text)
19
+ ids = GetIdsFromText.call(text, options[:link])
18
20
  ids.each { |id| puts id }
19
21
  end
20
22
 
21
23
  desc 'get-commits FROM_REVISION [TO_REVISION]',
22
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.'
23
27
  def get_commits(from_revision, to_revision = 'HEAD')
24
- messages = GetRangeCommits.call(from_revision, to_revision)
28
+ messages = GetRangeCommits.call(from_revision, to_revision, short: options[:short])
25
29
  puts messages
26
30
  rescue StandardError => e
27
31
  fatal!(e.message)
@@ -29,10 +33,14 @@ module Afterlife
29
33
 
30
34
  desc 'update-status TARGET_STATUS TASK_ID [TASK_ID...]',
31
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'
32
40
  def update_status(target_status, *task_ids)
33
41
  task_ids = $stdin.read.strip.split("\n") if task_ids.empty?
34
42
 
35
- UpdateTaskStatus.call(task_ids, target_status)
43
+ UpdateTaskStatus.call(task_ids, target_status, force: options[:force], dry_run: options[:dry_run])
36
44
  rescue StandardError => e
37
45
  fatal!(e.message)
38
46
  end
@@ -10,7 +10,7 @@ module Afterlife
10
10
  /CU-([a-z0-9]+)/i,
11
11
  ].freeze
12
12
 
13
- def self.call(text)
13
+ def self.call(text, convert_to_link = false) # rubocop:disable Metrics/MethodLength
14
14
  ids = Set.new
15
15
 
16
16
  ID_PATTERNS.each do |pattern|
@@ -19,7 +19,11 @@ module Afterlife
19
19
  end
20
20
  end
21
21
 
22
- ids.to_a
22
+ if convert_to_link
23
+ ids.map { |id| "https://app.clickup.com/t/#{id}" }
24
+ else
25
+ ids.to_a
26
+ end
23
27
  end
24
28
  end
25
29
  end
@@ -5,9 +5,13 @@ require 'English'
5
5
  module Afterlife
6
6
  module Clickup
7
7
  class GetRangeCommits
8
- def self.call(from_revision, to_revision = 'HEAD')
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)
9
12
  range = "#{from_revision}..#{to_revision}"
10
- commit_messages = `git log --pretty=format:"%s%n%b" #{range}`.strip
13
+ params = short ? SHORT_PARAMS : DEFAULT_PARAMS
14
+ commit_messages = `git log #{params} #{range}`.strip
11
15
  fail "Error getting commits for range #{range}" unless $CHILD_STATUS.success?
12
16
 
13
17
  commit_messages
@@ -3,27 +3,34 @@
3
3
  module Afterlife
4
4
  module Clickup
5
5
  class UpdateTaskStatus
6
- attr_reader :target_status, :precedence_checker, :api
6
+ attr_reader :target_status, :precedence_checker, :api, :force, :dry_run
7
7
 
8
- def self.call(task_ids, target_status)
9
- new.call(task_ids, target_status)
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
10
  end
11
11
 
12
- def initialize
12
+ def initialize(force: false, dry_run: false)
13
+ @force = force
14
+ @dry_run = dry_run
13
15
  token = ENV['CLICKUP_TOKEN'] || fail('CLICKUP_TOKEN required, put `export CLICKUP_TOKEN=...` or the equivalent in your shell') # rubocop:disable Layout/LineLength
14
16
  @api = Api.new(token: token)
15
17
  @precedence_checker = StatusPrecedenceChecker.new
16
18
  end
17
19
 
18
- def call(task_ids, target_status)
20
+ def call(task_ids, target_status) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
19
21
  if task_ids.empty?
20
22
  puts 'No tasks to update'
21
23
  return []
22
24
  end
23
25
 
26
+ puts 'WARNING: Forcing status updates...' if force
27
+ puts 'WARNING: Dry run, no tasks will be updated' if dry_run
24
28
  puts "Updating #{task_ids.length} tasks to '#{target_status}'"
25
29
  moved_task_ids = task_ids.map { |task_id| move_task(task_id, target_status) }.compact
26
- puts "Moved #{moved_task_ids.length} tasks, with ids: #{moved_task_ids.join(', ')}"
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
27
34
  moved_task_ids
28
35
  end
29
36
 
@@ -34,11 +41,12 @@ module Afterlife
34
41
 
35
42
  return puts "WARNING: Task #{task_id} not found" if current_status.nil?
36
43
 
37
- should_skip_update = precedence_checker.should_skip_update?(current_status, target_status)
38
- return puts "Skipping task #{task_id} because it's already at '#{current_status}'" if should_skip_update
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
39
47
 
40
- api.update_task(task_id, { status: target_status })
41
- task_id
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 }
42
50
  end
43
51
  end
44
52
  end
@@ -11,6 +11,7 @@ module Afterlife
11
11
  CLOUD_PROFILES = {
12
12
  staging: PROFILE_TESTING,
13
13
  qa: PROFILE_TESTING,
14
+ dev: PROFILE_TESTING,
14
15
  sandbox: PROFILE_PRODUCTION,
15
16
  production: PROFILE_PRODUCTION,
16
17
  }.freeze
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Deploy
5
+ class DockerDeployment < Deployment
6
+ def setup
7
+ Afterlife.current_repo.env.set('PLATFORM' => 'linux/amd64') if deploy_stage?
8
+
9
+ Afterlife.current_repo.env.set!(
10
+ 'REGISTRY' => registry,
11
+ 'TAG' => tag,
12
+ )
13
+ end
14
+
15
+ def run
16
+ Exec.run(commands)
17
+ end
18
+
19
+ def confirmation_message
20
+ 'You are about to deploy the current directory'
21
+ end
22
+
23
+ private
24
+
25
+ def commands
26
+ [
27
+ authenticate_command,
28
+ build_command,
29
+ ].flatten.compact
30
+ end
31
+
32
+ # commands
33
+
34
+ def authenticate_command
35
+ return if options['no-auth'] || local_stage?
36
+
37
+ AwsAuth.new(registry).commands
38
+ end
39
+
40
+ def build_command
41
+ return if options['no-build']
42
+
43
+ <<-BASH
44
+ docker buildx bake --allow=ssh -f docker-bake.hcl #{targets.join(' ')} #{local_stage? ? '--load' : '--push'}
45
+ BASH
46
+ end
47
+
48
+ def targets
49
+ repo.conf.dig(:deploy, :targets) || %w[app]
50
+ end
51
+
52
+ def tag
53
+ repo.conf.dig(:deploy, :tag) || repo.current_revision
54
+ end
55
+
56
+ # utils
57
+
58
+ def local_stage?
59
+ Afterlife.current_stage.name.to_sym == :local
60
+ end
61
+
62
+ def deploy_stage?
63
+ %i[qa production staging dev].include?(Afterlife.current_stage.name.to_sym)
64
+ end
65
+
66
+ # Priority:
67
+ # 1. Uses AFTERLIFE_DEPLOY_REGISTRY from the ENV if it's defined. It can be
68
+ # either in real system ENV, or defined in the env section of the repo
69
+ # .afterlife.yml config
70
+ # 2. Uses repo .afterlife.yml config at deploy.registry
71
+ # 3. Uses stages.$current_stage.registry if exists in ~/.afterlife/config.yml
72
+ def registry
73
+ @registry ||= Afterlife.current_repo.variable('deploy.registry') || Afterlife.current_stage.registry
74
+ end
75
+
76
+ class AwsAuth
77
+ attr_reader :registry
78
+
79
+ def initialize(registry)
80
+ @registry = registry
81
+ end
82
+
83
+ def commands
84
+ [docker_login]
85
+ end
86
+
87
+ def docker_login
88
+ <<-BASH
89
+ echo "#{aws_ecr_token}" | docker login --username AWS --password-stdin #{registry}
90
+ BASH
91
+ end
92
+
93
+ def aws_ecr_token
94
+ @aws_ecr_token ||= Exec.result("aws ecr get-login-password --region #{region}")
95
+ end
96
+
97
+ def region
98
+ @region ||= registry.gsub(/[^.]+\.dkr\.ecr\.([^.]+)\.amazonaws\.com/, '\1')
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -2,16 +2,7 @@
2
2
 
3
3
  module Afterlife
4
4
  module Deploy
5
- class KubernetesDeployment < Deployment
6
- def setup
7
- Afterlife.current_repo.env.set('PLATFORM' => 'linux/amd64') if deploy_stage?
8
-
9
- Afterlife.current_repo.env.set!(
10
- 'REGISTRY' => registry,
11
- 'TAG' => repo.current_revision,
12
- )
13
- end
14
-
5
+ class KubernetesDeployment < DockerDeployment
15
6
  def run
16
7
  Exec.run(commands)
17
8
  end
@@ -24,7 +15,9 @@ module Afterlife
24
15
 
25
16
  def commands
26
17
  [
18
+ # from DockerDeployment
27
19
  authenticate_command,
20
+ # from DockerDeployment
28
21
  build_command,
29
22
  set_image_command,
30
23
  apply_kubernetes_settings,
@@ -33,20 +26,6 @@ module Afterlife
33
26
 
34
27
  # commands
35
28
 
36
- def authenticate_command
37
- return if options['no-auth'] || local_stage?
38
-
39
- AwsAuth.new(registry).commands
40
- end
41
-
42
- def build_command
43
- return if options['no-build']
44
-
45
- <<-BASH
46
- docker buildx bake --allow=ssh -f docker-bake.hcl #{targets.join(' ')} #{local_stage? ? '--load' : '--push'}
47
- BASH
48
- end
49
-
50
29
  def set_image_command
51
30
  <<-BASH
52
31
  cd #{kubelocation} &&
@@ -58,10 +37,6 @@ module Afterlife
58
37
  BASH
59
38
  end
60
39
 
61
- def targets
62
- repo.conf.dig(:deploy, :targets) || %w[app]
63
- end
64
-
65
40
  def registry_image_name(target)
66
41
  "#{registry}/#{full_image_name(target)}:#{repo.current_revision}"
67
42
  end
@@ -82,6 +57,7 @@ module Afterlife
82
57
  end
83
58
 
84
59
  def assert_correct_context
60
+ return if Afterlife.current_stage.name == 'local'
85
61
  return if current_context == expected_context
86
62
 
87
63
  fail Error, "kubectl context should be '#{expected_context}' but you are in '#{current_context}'"
@@ -97,14 +73,6 @@ module Afterlife
97
73
 
98
74
  # utils
99
75
 
100
- def local_stage?
101
- Afterlife.current_stage.name.to_sym == :local
102
- end
103
-
104
- def deploy_stage?
105
- %i[qa production staging].include?(Afterlife.current_stage.name.to_sym)
106
- end
107
-
108
76
  def kubelocation
109
77
  ".afterlife/#{Afterlife.current_stage.name}"
110
78
  end
@@ -114,42 +82,6 @@ module Afterlife
114
82
  fail Error, 'deploy.image_name for kubernetes deployments' unless result
115
83
  end
116
84
  end
117
-
118
- # Priority:
119
- # 1. Uses AFTERLIFE_DEPLOY_REGISTRY from the ENV if it's defined. It can be
120
- # either in real system ENV, or defined in the env section of the repo
121
- # .afterlife.yml config
122
- # 2. Uses repo .afterlife.yml config at deploy.registry
123
- # 3. Uses stages.$current_stage.registry if exists in ~/.afterlife/config.yml
124
- def registry
125
- @registry ||= Afterlife.current_repo.variable('deploy.registry') || Afterlife.current_stage.registry
126
- end
127
-
128
- class AwsAuth
129
- attr_reader :registry
130
-
131
- def initialize(registry)
132
- @registry = registry
133
- end
134
-
135
- def commands
136
- [docker_login]
137
- end
138
-
139
- def docker_login
140
- <<-BASH
141
- echo "#{aws_ecr_token}" | docker login --username AWS --password-stdin #{registry}
142
- BASH
143
- end
144
-
145
- def aws_ecr_token
146
- @aws_ecr_token ||= Exec.result("aws ecr get-login-password --region #{region}")
147
- end
148
-
149
- def region
150
- @region ||= registry.gsub(/[^.]+\.dkr\.ecr\.([^.]+)\.amazonaws\.com/, '\1')
151
- end
152
- end
153
85
  end
154
86
  end
155
87
  end
@@ -8,6 +8,7 @@ module Afterlife
8
8
  cdn: 'CdnDeployment',
9
9
  cdn_stenciljs: 'CdnStenciljsDeployment',
10
10
  custom: 'CustomDeployment',
11
+ docker: 'DockerDeployment',
11
12
  kubernetes: 'KubernetesDeployment',
12
13
  }.freeze
13
14
 
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Propagate
5
+ class Cli < Thor
6
+ include BaseCli
7
+ def self.exit_on_failure?
8
+ true
9
+ end
10
+
11
+ desc 'propagate-qa-to-sandbox', 'Propagate QA branch to Sandbox branch (fetch, reset, push)'
12
+ option :yes, type: :boolean, aliases: '-y',
13
+ desc: 'Skip confirmation prompt'
14
+ def propagate_qa_to_sandbox
15
+ confirmation_message = <<~MSG
16
+ This command will perform the following actions:
17
+
18
+ 1. Fetch the latest changes from origin/qa
19
+ 2. Switch to the sandbox branch
20
+ 3. Reset sandbox branch to match origin/qa exactly
21
+ 4. Force push the changes to origin/sandbox
22
+ 5. Return to your original branch
23
+
24
+ This operation will completely overwrite the sandbox branch with the current state of QA.
25
+ Are you sure you want to proceed?
26
+ MSG
27
+
28
+ sure?(confirmation_message) unless options[:yes]
29
+
30
+ PropagateQaToSandbox.call
31
+ rescue StandardError => e
32
+ fatal!(e.message)
33
+ rescue Interrupt
34
+ log_interrupted
35
+ end
36
+
37
+ private
38
+
39
+ def repo
40
+ Afterlife.current_repo
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Propagate
5
+ class PropagateQaToSandbox
6
+ def self.call(repo_path = nil)
7
+ new(repo_path).call
8
+ end
9
+
10
+ def initialize(repo_path = nil)
11
+ @repo_path = repo_path || Afterlife.current_repo.full_path
12
+ @original_branch = nil
13
+ end
14
+
15
+ def call
16
+ log_info "Starting QA to Sandbox propagation in #{@repo_path}"
17
+
18
+ @original_branch = current_branch
19
+ log_info "Current branch: #{@original_branch}"
20
+
21
+ fetch_qa_branch
22
+ switch_to_sandbox
23
+ reset_sandbox_to_qa
24
+ push_sandbox_changes
25
+
26
+ log_success 'Successfully propagated QA to Sandbox'
27
+
28
+ return_to_original_branch if @original_branch != 'sandbox'
29
+ rescue StandardError => e
30
+ log_error "Error during propagation: #{e.message}"
31
+ return_to_original_branch if @original_branch && @original_branch != current_branch
32
+ raise
33
+ end
34
+
35
+ private
36
+
37
+ def fetch_qa_branch
38
+ log_info 'Fetching latest changes from remote...'
39
+ `cd #{@repo_path} && git fetch origin qa`
40
+ fail 'Failed to fetch origin/qa' unless $CHILD_STATUS.success?
41
+
42
+ log_success 'Successfully fetched origin/qa'
43
+ end
44
+
45
+ def switch_to_sandbox
46
+ log_info 'Switching to sandbox branch...'
47
+ `cd #{@repo_path} && git checkout sandbox`
48
+ fail 'Failed to switch to sandbox branch' unless $CHILD_STATUS.success?
49
+
50
+ log_success 'Successfully switched to sandbox branch'
51
+ end
52
+
53
+ def reset_sandbox_to_qa
54
+ log_info 'Resetting sandbox to origin/qa...'
55
+ `cd #{@repo_path} && git reset --hard origin/qa`
56
+ fail 'Failed to reset sandbox to origin/qa' unless $CHILD_STATUS.success?
57
+
58
+ log_success 'Successfully reset sandbox to origin/qa'
59
+ end
60
+
61
+ def push_sandbox_changes
62
+ log_info 'About to push changes to remote sandbox...'
63
+ log_info 'This will force push and overwrite the remote sandbox branch'
64
+
65
+ unless yes?('Do you want to continue? [y/n]')
66
+ log_info 'Push cancelled by user. Reverting changes...'
67
+ revert_sandbox_changes
68
+ return
69
+ end
70
+
71
+ log_info 'Pushing changes to remote sandbox...'
72
+ `cd #{@repo_path} && git push origin sandbox --force-with-lease`
73
+ fail 'Failed to push to origin/sandbox' unless $CHILD_STATUS.success?
74
+
75
+ create_and_push_tag
76
+
77
+ log_success 'Successfully pushed to origin/sandbox'
78
+ end
79
+
80
+ def revert_sandbox_changes
81
+ log_info 'Reverting sandbox to previous state...'
82
+ `cd #{@repo_path} && git reset --hard HEAD@{1}`
83
+ if $CHILD_STATUS.success?
84
+ log_success 'Successfully reverted sandbox changes'
85
+ else
86
+ log_error 'Failed to revert changes. Manual intervention may be required.'
87
+ fail 'Failed to revert sandbox changes'
88
+ end
89
+ end
90
+
91
+ def return_to_original_branch
92
+ log_info "Returning to original branch: #{@original_branch}"
93
+ `cd #{@repo_path} && git checkout #{@original_branch}`
94
+ fail "Failed to return to original branch #{@original_branch}" unless $CHILD_STATUS.success?
95
+
96
+ log_success "Successfully returned to #{@original_branch}"
97
+ end
98
+
99
+ def current_branch
100
+ `cd #{@repo_path} && git rev-parse --abbrev-ref HEAD`.chomp
101
+ end
102
+
103
+ def create_and_push_tag
104
+ tag_name = generate_tag_name
105
+ log_info "Creating tag: #{tag_name}"
106
+
107
+ `cd #{@repo_path} && git tag #{tag_name}`
108
+ fail "Failed to create tag #{tag_name}" unless $CHILD_STATUS.success?
109
+
110
+ log_info 'Pushing tag to remote...'
111
+ `cd #{@repo_path} && git push origin #{tag_name}`
112
+ fail "Failed to push tag #{tag_name}" unless $CHILD_STATUS.success?
113
+
114
+ log_success "Successfully created and pushed tag: #{tag_name}"
115
+ end
116
+
117
+ def generate_tag_name
118
+ now = Time.now
119
+ year = now.year.to_s[2..3] # Last two digits of year
120
+ month = format('%02d', now.month)
121
+ day = format('%02d', now.day)
122
+ "#{year}#{month}#{day}"
123
+ end
124
+
125
+ def log_info(message)
126
+ puts "\e[36m#{message}\e[0m"
127
+ end
128
+
129
+ def log_success(message)
130
+ puts "\e[32m#{message}\e[0m"
131
+ end
132
+
133
+ def log_error(message)
134
+ puts "\e[31m#{message}\e[0m"
135
+ end
136
+
137
+ def fail(message)
138
+ fail StandardError, message
139
+ end
140
+
141
+ def yes?(question)
142
+ print "#{question} "
143
+ response = $stdin.gets.chomp.downcase
144
+ %w[y yes].include?(response)
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Propagate
5
+ end
6
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Afterlife
4
- VERSION = '1.7.3'
4
+ VERSION = '1.8.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: afterlife
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.3
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Genaro Madrid
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-07-29 00:00:00.000000000 Z
11
+ date: 2025-11-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -110,10 +110,14 @@ files:
110
110
  - lib/afterlife/deploy/cli.rb
111
111
  - lib/afterlife/deploy/custom_deployment.rb
112
112
  - lib/afterlife/deploy/deployment.rb
113
+ - lib/afterlife/deploy/docker_deployment.rb
113
114
  - lib/afterlife/deploy/kubernetes_deployment.rb
114
115
  - lib/afterlife/environment.rb
115
116
  - lib/afterlife/exec.rb
116
117
  - lib/afterlife/notify.rb
118
+ - lib/afterlife/propagate.rb
119
+ - lib/afterlife/propagate/cli.rb
120
+ - lib/afterlife/propagate/propagate_qa_to_sandbox.rb
117
121
  - lib/afterlife/release.rb
118
122
  - lib/afterlife/release/change_version.rb
119
123
  - lib/afterlife/release/cli.rb
@@ -147,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
147
151
  - !ruby/object:Gem::Version
148
152
  version: '0'
149
153
  requirements: []
150
- rubygems_version: 3.5.16
154
+ rubygems_version: 3.2.33
151
155
  signing_key:
152
156
  specification_version: 4
153
157
  summary: Devops utils