afterlife 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c8dfb530dade840c8e03042de3ba5508eec1002830c0b978ab42aa1f88b83fab
4
+ data.tar.gz: 39452459ace3d25eb5270e0e83295e0919f97b11813128b98a6deb39b407431b
5
+ SHA512:
6
+ metadata.gz: 30fb609ce63cd5038050a2afae03741fd9d8739d4d014cfbd85ca09a0c1f44a9eb3521b85866b64763bfeea287891ea6d00c84491bf39f4bbf6cea152b8ef8d5
7
+ data.tar.gz: e94831df012dfe22f833e277b0fbc5b6c11ae5c9c85d9897068d14b05171c4ab7fd542b2f578d0c95fd17feef9419e0c7abb8aeb50421b29e9e93322bd17fa47
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,46 @@
1
+ require:
2
+ - rubocop-rspec
3
+ - rubocop-rake
4
+
5
+ AllCops:
6
+ NewCops: enable
7
+ TargetRubyVersion: 2.7
8
+
9
+ Lint/ConstantDefinitionInBlock:
10
+ Enabled: false
11
+
12
+ Layout/IndentationConsistency:
13
+ Enabled: false
14
+
15
+ Layout/HashAlignment:
16
+ Enabled: false
17
+
18
+ Layout/SpaceAroundEqualsInParameterDefault:
19
+ Enabled: false
20
+
21
+ Style/HashAsLastArrayItem:
22
+ EnforcedStyle: no_braces
23
+
24
+ Style/OptionalBooleanParameter:
25
+ Enabled: false
26
+
27
+ Style/Documentation:
28
+ Enabled: false
29
+
30
+ Layout/EmptyLinesAroundBlockBody:
31
+ Enabled: false
32
+
33
+ Style/TrailingCommaInArguments:
34
+ EnforcedStyleForMultiline: consistent_comma
35
+
36
+ Style/TrailingCommaInArrayLiteral:
37
+ EnforcedStyleForMultiline: consistent_comma
38
+
39
+ Style/TrailingCommaInHashLiteral:
40
+ EnforcedStyleForMultiline: consistent_comma
41
+
42
+ Layout/AccessModifierIndentation:
43
+ EnforcedStyle: outdent
44
+
45
+ Style/SignalException:
46
+ EnforcedStyle: semantic
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ afterlife
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.0.5
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2023-01-13
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in afterlife.gemspec
6
+ gemspec
7
+
8
+ gem 'pry'
9
+
10
+ gem 'rake'
11
+
12
+ gem 'rspec'
13
+
14
+ gem 'rubocop'
15
+ gem 'rubocop-rake'
16
+ gem 'rubocop-rspec'
data/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # Afterlife
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/afterlife`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text abovxe, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ 1. Execute `bin/install` in the repository root
10
+ 2. Put a `env.yml` on ~/.afterlife/env.yml based on the AWS credentials found on mifiel's bitwarden vault
11
+ 3. Install AWS CLI following the [official guide](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html).
12
+
13
+ ## Usage
14
+ Run `afterlife help` for a list of all available commands
15
+
16
+
17
+ ## About CDN
18
+ Logs can be found on `~/.afterlife/logs/cdn.log`
19
+
20
+ 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.
21
+
22
+ ## Development
23
+
24
+ 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.
25
+
26
+ 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).
27
+
28
+ ## Contributing
29
+
30
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/afterlife.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ desc 'start a console'
13
+ task :console do
14
+ exec 'pry -r afterlife -I ./lib'
15
+ end
16
+
17
+ task default: %i[spec rubocop]
data/afterlife.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/afterlife/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'afterlife'
7
+ spec.version = Afterlife::VERSION
8
+ spec.authors = ['Genaro Madrid']
9
+ spec.email = ['genmadrid@gmail.com']
10
+
11
+ spec.summary = 'Devops tools for Mifiel'
12
+ spec.description = 'Afterlife helps you setup your development environment and deploy code easily'
13
+ spec.required_ruby_version = '>= 2.6'
14
+
15
+ spec.metadata['source_code_uri'] = 'https://bitbucket.org/volabit/afterlife'
16
+ spec.metadata['changelog_uri'] = 'https://bitbucket.org/volabit/afterlife/src/master/CHANGELOG.md'
17
+
18
+ # Specify which files should be added to the gem when it is released.
19
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
+ spec.files = Dir.chdir(__dir__) do
21
+ `git ls-files -z`.split("\x0").reject do |f|
22
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
23
+ end
24
+ end
25
+ spec.bindir = 'exe'
26
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
27
+ spec.require_paths = ['lib']
28
+
29
+ spec.add_dependency 'activesupport'
30
+ spec.add_dependency 'git'
31
+ spec.add_dependency 'puma'
32
+ spec.add_dependency 'rackup'
33
+ spec.add_dependency 'thor'
34
+ spec.add_dependency 'zeitwerk'
35
+
36
+ spec.metadata['rubygems_mfa_required'] = 'true'
37
+ end
data/config.ru ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require 'rack/directory'
5
+
6
+ logger_file = File.open(Afterlife::Cdn.log_path, 'a')
7
+ logger_file.sync = true
8
+
9
+ class AssetHeaders
10
+ def initialize(app)
11
+ @app = app
12
+ end
13
+
14
+ def call(env)
15
+ response = @app.call(env)
16
+ response[1]['access-control-allow-origin'] = '*'
17
+ response
18
+ end
19
+ end
20
+
21
+ use AssetHeaders
22
+ use Rack::CommonLogger, Logger.new(logger_file)
23
+ run do |env|
24
+ Rack::Directory.new(Afterlife::Cdn.local_path).call(env)
25
+ end
data/exe/afterlife ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ lib_dir = File.expand_path('../lib', __dir__)
5
+ require "#{lib_dir}/afterlife"
6
+
7
+ Afterlife.root = Pathname(File.expand_path('..', __dir__))
8
+ Afterlife::Cli.start
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+
5
+ module Afterlife
6
+ module BaseCli
7
+ def log_success(message)
8
+ say set_color(message, :green)
9
+ say "\n"
10
+ end
11
+
12
+ def log_error(message)
13
+ say set_color(message, :red)
14
+ say "\n"
15
+ end
16
+
17
+ def log_info(message)
18
+ say set_color(message, :cyan)
19
+ say "\n"
20
+ end
21
+
22
+ def log_interrupted
23
+ say ''
24
+ fatal! 'Interrupted'
25
+ end
26
+
27
+ def sure?(msg)
28
+ say msg
29
+ return true if yes?('Are you sure? [y/n]')
30
+
31
+ fatal! 'Good bye'
32
+ end
33
+
34
+ def fatal!(msg)
35
+ log_error "ERROR: #{msg}"
36
+ exit 1
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Bump
5
+ module Semver
6
+ module_function
7
+
8
+ DEFAULT_PRERELEASE_NAME = 'rc'
9
+ VERSION_REGEX = /(\d+\.\d+\.\d+(?:-(?:.+))?)/.freeze
10
+
11
+ def calculate_next(part, pre = nil) # rubocop:disable Metrics/MethodLength
12
+ x, y, z = release
13
+ case part.to_sym
14
+ when :major
15
+ "#{x + 1}.0.0"
16
+ when :minor
17
+ "#{x}.#{y + 1}.0"
18
+ when :patch
19
+ "#{x}.#{y}.#{z + 1}"
20
+ when :pre
21
+ name, ver = parse_prerelease(pre)
22
+ "#{x}.#{y}.#{z}-#{name}.#{ver + 1}"
23
+ end
24
+ end
25
+
26
+ def parse_prerelease(name)
27
+ curr_name, curr_ver = curr_prerelease
28
+
29
+ if name
30
+ # if the user specifies a name different than the current one, reseting the current version
31
+ curr_ver = -1 if name != curr_name
32
+ curr_name = name
33
+ end
34
+
35
+ [curr_name, curr_ver]
36
+ end
37
+
38
+ def release
39
+ version_chunks[0].split('.').map(&:to_i)
40
+ end
41
+
42
+ def curr_prerelease
43
+ return [DEFAULT_PRERELEASE_NAME, -1] unless version_chunks[1]
44
+
45
+ name, ver = version_chunks[1].split('.')
46
+ [name, ver.to_i]
47
+ end
48
+
49
+ def version_chunks
50
+ @version_chunks ||= version.split('-')
51
+ end
52
+
53
+ def version
54
+ result = %x(git describe --tags `git rev-list --tags --max-count=1`).chomp
55
+ # remove the first 'v' if exists, all mifiel tags always have it
56
+ result[0] == 'v' ? result[1..] : result
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Bump
5
+ end
6
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Afterlife
6
+ module Cdn
7
+ class Cli < Thor
8
+ include BaseCli
9
+ def self.exit_on_failure?
10
+ true
11
+ end
12
+
13
+ desc 'start', 'Start the CDN server'
14
+ option :port,
15
+ type: :numeric,
16
+ aliases: '-p',
17
+ default: 5000
18
+ option :foreground,
19
+ type: :boolean,
20
+ aliases: '-f'
21
+ option 'no-link', type: :boolean
22
+ def start
23
+ Cdn.link unless options['no-link']
24
+ say_status 'Starting', "http://localhost:#{options[:port]}"
25
+ Cdn::Server.start(options.symbolize_keys.slice(:port, :foreground))
26
+ log_success "The server started at http://localhost:#{options[:port]}"
27
+ rescue Afterlife::Error => e
28
+ log_error(e)
29
+ end
30
+
31
+ desc 'stop', 'stop the CDN server'
32
+ def stop
33
+ say_status 'Stopping', nil
34
+ Server.kill
35
+ log_success 'The server stopped'
36
+ rescue Afterlife::Error => e
37
+ log_error(e)
38
+ end
39
+
40
+ desc 'restart', 'restart the CDN server'
41
+ def restart
42
+ invoke :stop if Server.running?
43
+ invoke :start
44
+ end
45
+
46
+ desc 'logs', 'tail the logs of the CDN server'
47
+ def logs
48
+ exec("tail -f #{Afterlife::Cdn.log_path}")
49
+ end
50
+
51
+ desc 'link', 'create symlinks in .cdn directory. ' \
52
+ 'Usually you dont need to run this script by itself'
53
+ def link
54
+ Cdn.link
55
+ log_success('Repos successfully linked to local CDN path')
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rackup'
4
+
5
+ module Afterlife
6
+ module Cdn
7
+ class Server
8
+ attr_reader :instance
9
+
10
+ def self.pid_file
11
+ Afterlife.local_path.join('pids/server.pid')
12
+ end
13
+
14
+ def self.kill
15
+ fail Afterlife::Error, 'The server is not running' unless running?
16
+
17
+ Process.kill('INT', pid_file.read.to_i)
18
+ end
19
+
20
+ def self.start(args)
21
+ fail Afterlife::Error, 'A server is already running' if running?
22
+
23
+ new(args).start
24
+ end
25
+
26
+ def self.running?
27
+ pid_file.exist?
28
+ end
29
+
30
+ def initialize(args)
31
+ @instance = Rackup::Server.new(
32
+ environment: ENV['RACK_ENV'] || 'development',
33
+ quiet: true, # we setup log in config.ru
34
+ Port: args[:port],
35
+ config: Afterlife.root.join('config.ru').to_s,
36
+ pid: Server.pid_file.to_s,
37
+ daemonize: !args[:foreground],
38
+ )
39
+ end
40
+
41
+ def start
42
+ instance.start
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ module Cdn
5
+ module_function
6
+
7
+ def url(repo = nil)
8
+ return Afterlife.current_stage.cdn_url unless repo
9
+
10
+ "#{url}/#{repo.full_name}"
11
+ end
12
+
13
+ def log_path
14
+ Afterlife.local_path.join('logs/cdn.log')
15
+ end
16
+
17
+ def local_path
18
+ Afterlife.local_path.join('cdn')
19
+ end
20
+
21
+ def link
22
+ FileUtils.rm_rf(Afterlife::Cdn.local_path)
23
+ FileUtils.mkdir_p(Afterlife::Cdn.local_path)
24
+ Repo.all.each do |repo|
25
+ dest = Afterlife::Cdn.local_path.join(repo.full_name)
26
+ FileUtils.mkdir_p(File.dirname(dest))
27
+ FileUtils.ln_sf(repo.dist_path, dest)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+
5
+ module Afterlife
6
+ class Cli < Thor
7
+ include BaseCli
8
+ def self.exit_on_failure?
9
+ true
10
+ end
11
+
12
+ package_name 'Afterlife'
13
+ class_option :quiet, type: :boolean, group: :runtime
14
+ class_option :verbose, type: :boolean, group: :runtime
15
+
16
+ desc 'init', 'Init afterlife'
17
+ def init
18
+ FileUtils.mkdir_p(Afterlife.local_path.join('logs'))
19
+ FileUtils.mkdir_p(Afterlife.local_path.join('pids'))
20
+ FileUtils.mkdir_p(Afterlife::Cdn.local_path)
21
+ FileUtils.touch(Afterlife::Config.file)
22
+ log_success 'afterlife is ready!'
23
+ end
24
+
25
+ desc 'config <path>', 'get config options'
26
+ def config(path) # rubocop:disable Metrics/MethodLength
27
+ path = path.split('.').map(&:to_sym)
28
+ result = repo.conf.dig(*path)
29
+ case result
30
+ when Hash
31
+ say JSON.generate(result)
32
+ when Array
33
+ result.each do |item|
34
+ say item
35
+ end
36
+ else
37
+ say result
38
+ end
39
+ end
40
+
41
+ desc 'cdn', 'CDN stuff'
42
+ subcommand 'cdn', Cdn::Cli
43
+
44
+ desc 'deploy <stage>', 'Deploy current directory'
45
+ option 'no-build', type: :boolean
46
+ option 'no-install', type: :boolean
47
+ option 'dry-run', type: :boolean
48
+ option 'skip-after-hooks', type: :boolean
49
+ option :yes, type: :boolean
50
+ # this only has logic for CDN deployments
51
+ # TODO: Add logic for different deployment types
52
+ def deploy(stage) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
53
+ Afterlife.cli = self
54
+ Afterlife.current_stage = Afterlife.config.stages[stage.to_sym]
55
+ fill_env
56
+ ask_confirmation(repo.confirmation_message) unless options['yes']
57
+ install_dependencies unless options['no-install']
58
+ build unless options['no-build']
59
+
60
+ say_status 'Deploying', repo.dist_path
61
+ output = Deploy.call
62
+ say_status 'Deployed', output
63
+ rescue Deploy::Error => e
64
+ fatal!(e.message)
65
+ rescue Interrupt
66
+ log_interrupted
67
+ end
68
+
69
+ desc 'release', 'Releases management'
70
+ subcommand 'release', Release::Cli
71
+
72
+ map %w[-v --version] => :version
73
+ desc 'version', 'Prints afterlife version'
74
+ def version
75
+ say Afterlife::VERSION
76
+ end
77
+
78
+ private
79
+
80
+ def fill_env
81
+ repo.env.merge!(
82
+ 'DIST_PATH' => repo.dist_path.to_s,
83
+ 'CURRENT_BRANCH' => repo.current_branch,
84
+ 'AFTERLIFE_STAGE' => Afterlife.current_stage.name,
85
+ )
86
+ end
87
+
88
+ def install_dependencies
89
+ return unless repo.install_dependencies_command
90
+
91
+ say_status 'Installing', '*' * 20
92
+ Exec.run(repo.install_dependencies_command)
93
+ end
94
+
95
+ def build
96
+ return unless repo.build_command
97
+
98
+ say_status 'Building', '*' * 20
99
+ Exec.run(repo.build_command)
100
+ end
101
+
102
+ def ask_confirmation(message)
103
+ if message
104
+ say message
105
+ else
106
+ say set_color("You're about to deploy the contents of '#{repo.dist_path}' to")
107
+ say set_color(Cdn.url(repo), :bold)
108
+ say set_color("\nWARNING: Please be aware that this may be a destructive operation", :yellow)
109
+ end
110
+ exit 1 unless yes?('Are you sure? [y/n]')
111
+ end
112
+
113
+ def repo
114
+ Afterlife.current_repo
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ class Config
5
+ def self.file
6
+ Afterlife.local_path.join('config.yml')
7
+ end
8
+
9
+ def respond_to_missing?(sym, include_all)
10
+ config.key?(sym) || super
11
+ end
12
+
13
+ def method_missing(sym, *args, &block)
14
+ config.key?(sym) ? config[sym] : super
15
+ end
16
+
17
+ def stages
18
+ @stages ||= config.delete(:stages)&.to_h do |name, val|
19
+ [name, Stage.new(val.merge(name: name.to_s))]
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def config
26
+ @config ||= YAML.load_file(Config.file).deep_symbolize_keys
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ class Deploy
5
+ class CdnDeployment < Deployment
6
+ def run
7
+ Exec.run(commands)
8
+ Cdn.url(repo)
9
+ end
10
+
11
+ def setup
12
+ repo.env.merge!(
13
+ 'AWS_PROFILE' => Afterlife.config.credentials.dig(:aws, :profile),
14
+ 'AWS_BUCKET' => Afterlife.current_stage.bucket,
15
+ 'AWS_REGION' => Afterlife.current_stage.region,
16
+ 'S3_FULL_PATH' => s3_full_path,
17
+ )
18
+ end
19
+
20
+ private
21
+
22
+ def commands
23
+ [
24
+ repo_command || javascripts,
25
+ revision,
26
+ ].flatten
27
+ end
28
+
29
+ def javascripts
30
+ # by default we use no-cache, if something else is needed
31
+ # do it in the deploy.command option
32
+ <<-BASH
33
+ aws s3 sync
34
+ %<DIST_PATH>s
35
+ %<S3_FULL_PATH>s
36
+ --exclude '*.ts'
37
+ --exclude '*.tsx'
38
+ --cache-control 'no-cache'
39
+ BASH
40
+ end
41
+
42
+ def revision
43
+ <<-BASH
44
+ echo "#{repo.current_revision}" | aws s3 cp --content-type text/plain - %<S3_FULL_PATH>s/revision
45
+ BASH
46
+ end
47
+
48
+ def s3_full_path
49
+ "s3://#{Afterlife.current_stage.bucket}/#{repo.full_name}"
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Afterlife
4
+ class Deploy
5
+ class Deployment
6
+ def self.run
7
+ deployment = new.tap(&:setup)
8
+ deployment.run
9
+ end
10
+
11
+ def run
12
+ fail NotImplementedError
13
+ end
14
+
15
+ def repo_command
16
+ Array(repo.conf.dig(:deploy, :command))
17
+ end
18
+
19
+ def setup; end
20
+
21
+ protected
22
+
23
+ def repo
24
+ Afterlife.current_repo
25
+ end
26
+ end
27
+ end
28
+ end