ddenv 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d454d9e8eba496ccf1c073109663cd75eb360317650505ba195f46e35df21330
4
- data.tar.gz: ed0c07ba499cdb8ebbfdb0c24d4d57c64902eb4e5ec50599bed07c97961b1dff
3
+ metadata.gz: be59b284b001642fbf169fb71a0c6887b2a54c4747ef52c0cc695b6b6621f7eb
4
+ data.tar.gz: b140aa2607964e48d50555420db3d038cc1e9306ce46147e5657e97c4f2c06bd
5
5
  SHA512:
6
- metadata.gz: bd6afc63047346551aee1dbea9d9b29895e3f355dbbdfb3638fea312aa05daa658af804b282b6f9ac36d7d0b6b95849162bbbbec881dcd07e863c141aba33d91
7
- data.tar.gz: 3302364fce3c5cfee447a1cffecbf2ed5f691f1821e2b9db8a0a38ae362800cec2b8e0df7dc9f4bae28234ec2ee39c1eaa29f842cc2a7fd473ab09ee60f93584
6
+ metadata.gz: e59e6c7ef5f3e64ccd0962475e0137912009188b2cff521d39dc4bfacfb8645542ae432299aeefda5af817dfdb4b8999cc75186af8ef66ea79b8c15d947ac4f6
7
+ data.tar.gz: cdcffc2846efe163d628f6ac677533e12cd8501783d72fb8ee6f5f09cb2bd37d166ca7b37628a4b2d6321762b5f902533668ca137134458506135305710d2f36
data/.rubocop.yml CHANGED
@@ -14,3 +14,9 @@ Style/Documentation:
14
14
 
15
15
  Metrics:
16
16
  Enabled: false
17
+
18
+ Layout/LineLength:
19
+ Enabled: false
20
+
21
+ Naming/MemoizedInstanceVariableName:
22
+ EnforcedStyleForLeadingUnderscores: required
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.2] - 2024-04-29
4
+
5
+ - Fix unknown `Pathname` constant
6
+ - Add `bundle`, `node` and `npm` goals
7
+
8
+ ## [0.1.1] - 2024-04-29
9
+
10
+ - Add functioning `ddenv.yaml` with support for `homebrew` and `ruby` goals.
11
+
3
12
  ## [0.1.0] - 2024-04-28
4
13
 
5
14
  - Initial release
data/README.md CHANGED
@@ -1,39 +1,62 @@
1
- # Ddenv
2
-
3
- TODO: Delete this and the text below, and describe your gem
4
-
5
- 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/ddenv`. To experiment with that code, run `bin/console` for an interactive prompt.
6
-
7
- ## Installation
8
-
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10
-
11
- Install the gem and add to the application's Gemfile by executing:
12
-
13
- $ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
14
-
15
- If bundler is not being used to manage dependencies, install the gem by executing:
16
-
17
- $ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
18
-
19
- ## Usage
20
-
21
- TODO: Write usage instructions here
22
-
23
- ## Development
24
-
25
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
26
-
27
- 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
-
29
- ## Contributing
30
-
31
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ddenv. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/ddenv/blob/main/CODE_OF_CONDUCT.md).
32
-
33
- ## License
34
-
35
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
36
-
37
- ## Code of Conduct
38
-
39
- Everyone interacting in the Ddenv project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/ddenv/blob/main/CODE_OF_CONDUCT.md).
1
+ # ddenv
2
+
3
+ **ddenv** (*Denis’ Developer Environment*) is a tool for maintaining a local
4
+ environment for development.
5
+
6
+ > [!CAUTION]
7
+ > This software is a pre-alpha work in progress. Do not use just yet.
8
+
9
+ ## Requirements
10
+
11
+ - Homebrew
12
+ - fish shell
13
+
14
+ > [!NOTE]
15
+ > In the future, this will work for bash and zsh as well.
16
+
17
+ ## Quick start
18
+
19
+ Install it: `brew install denisdefreyne/ddenv/ddenv`.
20
+
21
+ Next, create a `ddenv.yaml` file which contains the list of dependencies to manage:
22
+
23
+ ```yaml
24
+ up:
25
+ - homebrew: overmind
26
+ - ruby: 3.3.0
27
+ - bundle
28
+ - node: 20.12.2
29
+ - npm
30
+ ```
31
+
32
+ Lastly, run `ddenv up`.
33
+
34
+ ```
35
+ % ddenv up
36
+ [✔] Installing Homebrew package overmind done (previously achieved)
37
+ [✔] Installing Homebrew package ruby-install done (previously achieved)
38
+ [✔] Installing Ruby v3.3.0 done (previously achieved)
39
+ [✔] Initializing Shadowenv done (previously achieved)
40
+ [✔] Creating .shadowenv.d done (previously achieved)
41
+ [✔] Adding .shadowenv.d to .gitignore done (previously achieved)
42
+ [✔] Trusting .shadowenv.d done (previously achieved)
43
+ [✔] Adding Ruby to shadowenv done (previously achieved)
44
+ [✔] Installing Ruby gem bundler done (previously achieved)
45
+ [✔] Installing bundle done (previously achieved)
46
+ [✔] Installing Homebrew package node-build done (previously achieved)
47
+ [✔] Installing Node v20.12.2 done (previously achieved)
48
+ [✔] Adding Node to shadowenv done (previously achieved)
49
+ [✔] Installing npm packages done (previously achieved)
50
+ %
51
+ ```
52
+
53
+ Now your local developer environment is ready to be used.
54
+
55
+ ## Goals
56
+
57
+ - <code>homebrew: <var>PACKAGENAME</var></code> installs the Homebrew package with the given name.
58
+ - <code>ruby</code> installs Ruby (with the version specified in the `.ruby-version`
59
+ file).
60
+ - <code>bundle</code> runs `bundle install`.
61
+ - <code>node: <var>VERSION</var></code> installs the give Node.js version.
62
+ - <code>npm</code> installs packages from package.json using npm.
data/TODO.md ADDED
@@ -0,0 +1,6 @@
1
+ # To do
2
+
3
+ - Add support for bash/zsh
4
+ - Add support for installing PostgreSQL (and set env variables)
5
+ - Add support for installing Redis (and set env variables)
6
+ - Add support for installing Python (and maintain venv)
data/ddenv.yaml ADDED
@@ -0,0 +1,6 @@
1
+ up:
2
+ - homebrew: overmind
3
+ - ruby
4
+ - bundle
5
+ - node: 20.12.2
6
+ - npm
data/lib/ddenv/cli.rb CHANGED
@@ -2,26 +2,30 @@
2
2
 
3
3
  module Ddenv
4
4
  class CLI
5
- def initialize(args)
6
- @args = args
5
+ ROOT_CMD = Cri::Command.new_basic_root.modify do
6
+ name "ddenv"
7
+ usage "ddenv [options] [command] [options]"
8
+ summary "manage developer environment"
9
+ description "Manages your local developer environment."
7
10
  end
8
11
 
9
- def call
10
- if @args.size != 1
11
- warn "usage: ddenv [command]"
12
- exit 64 # EX_USAGE
13
- end
12
+ ROOT_CMD.define_command("up") do
13
+ name "up"
14
+ usage "up [options]"
15
+ summary "bring up developer environment"
16
+ description "Brings up the local developer environment to be in line what is described in the ddenv.yaml configuration file."
14
17
 
15
- command_name = @args.first
16
- case command_name
17
- when "help"
18
- Ddenv::Commands::Help.new.call
19
- when "up"
18
+ run do |_opts, _args, _cmd|
20
19
  Ddenv::Commands::Up.new.call
21
- else
22
- warn "ddenv: unknown command: #{command_name}"
23
- exit 64 # EX_USAGE
24
20
  end
25
21
  end
22
+
23
+ def initialize(args)
24
+ @args = args
25
+ end
26
+
27
+ def call
28
+ ROOT_CMD.run(@args)
29
+ end
26
30
  end
27
31
  end
@@ -4,19 +4,26 @@ module Ddenv
4
4
  module Commands
5
5
  class Up < Ddenv::Command
6
6
  def call
7
- config = Ddenv::Config.read_from_file("dev.yml")
7
+ config = Ddenv::Config.read_from_file("ddenv.yaml")
8
+
9
+ all_goals = config.goals.flat_map(&:with_pre_and_post_goals).uniq
10
+
11
+ max_message_size = all_goals.map { _1.message.size }.max
12
+
13
+ all_goals.each do |goal|
14
+ spinner = TTY::Spinner.new("[:spinner] :message :title")
15
+ spinner.update(message: format("%-#{max_message_size}s", goal.message))
16
+ spinner.update(title: "checking")
17
+ spinner.auto_spin
8
18
 
9
- config.goals.each do |goal|
10
- puts goal.message
11
- puts " Checking..."
12
19
  if goal.achieved?
13
- puts " Already achieved."
20
+ spinner.update(title: "done (previously achieved)")
14
21
  else
15
- puts " Achieving..."
22
+ spinner.update(title: "working...")
16
23
  goal.achieve
17
- puts " Newly achieved."
24
+ spinner.update(title: "done (newly achieved)")
18
25
  end
19
- puts
26
+ spinner.success
20
27
  end
21
28
  end
22
29
  end
data/lib/ddenv/config.rb CHANGED
@@ -19,13 +19,22 @@ module Ddenv
19
19
  when Hash
20
20
  [element.keys.first, element.values.first]
21
21
  when String
22
- # TODO: for later
23
- # (when a goal has no extra details)
22
+ [element, nil]
24
23
  end
25
24
 
26
25
  case key
27
26
  when "homebrew"
28
27
  Goals::HomebrewPackageInstalled.new(value)
28
+ when "ruby"
29
+ Goals::RubyInstalled.new
30
+ when "node"
31
+ Goals::NodeInstalled.new(value)
32
+ when "bundle"
33
+ Goals::BundleInstalled.new
34
+ when "npm"
35
+ Goals::NpmPackesInstalled.new
36
+ else
37
+ raise "unknown key: #{key}"
29
38
  end
30
39
  end
31
40
  end
data/lib/ddenv/goal.rb CHANGED
@@ -15,5 +15,37 @@ module Ddenv
15
15
  def achieve
16
16
  raise SubclassResponsibilityError.new(self.class, __method__)
17
17
  end
18
+
19
+ def pre_goals
20
+ []
21
+ end
22
+
23
+ def post_goals
24
+ []
25
+ end
26
+
27
+ def with_pre_and_post_goals
28
+ [
29
+ *pre_goals.flat_map(&:with_pre_and_post_goals),
30
+ self,
31
+ *post_goals.flat_map(&:with_pre_and_post_goals)
32
+ ]
33
+ end
34
+
35
+ def props
36
+ []
37
+ end
38
+
39
+ def ==(other)
40
+ self.class == other.class && props == other.props
41
+ end
42
+
43
+ def eql?(other)
44
+ self == other
45
+ end
46
+
47
+ def hash
48
+ [self.class, props].hash
49
+ end
18
50
  end
19
51
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ddenv
4
+ module Goals
5
+ class BundleInstalled < Goal
6
+ def message
7
+ "Installing bundle"
8
+ end
9
+
10
+ def achieved?
11
+ cmd = TTY::Command.new(printer: :null)
12
+ cmd.run("shadowenv", "exec", "--", "bundle", "check")
13
+ true
14
+ rescue StandardError
15
+ false
16
+ end
17
+
18
+ def achieve
19
+ # TODO: use :pretty when passing --verbose
20
+ cmd = TTY::Command.new(printer: :null)
21
+ cmd.run("shadowenv", "exec", "--", "bundle", "install")
22
+ end
23
+
24
+ def pre_goals
25
+ [
26
+ GemInstalled.new("bundler")
27
+ ]
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ddenv
4
+ module Goals
5
+ class GemInstalled < Goal
6
+ def initialize(gem_name)
7
+ super()
8
+ @gem_name = gem_name
9
+ end
10
+
11
+ def message
12
+ "Installing Ruby gem #{@gem_name}"
13
+ end
14
+
15
+ def achieved?
16
+ cmd = TTY::Command.new(printer: :null)
17
+ stdout, _stderr = cmd.run("shadowenv", "exec", "--", "gem", "list", "-i", "^#{@gem_name}$")
18
+ stdout.chomp == "true"
19
+ end
20
+
21
+ def achieve
22
+ # TODO: use :pretty when passing --verbose
23
+ cmd = TTY::Command.new(printer: :null)
24
+ cmd.run("shadowenv", "exec", "--", "gem", "install", @gem_name)
25
+ end
26
+
27
+ def props
28
+ [@gem_name]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -3,6 +3,8 @@
3
3
  module Ddenv
4
4
  module Goals
5
5
  class HomebrewPackageInstalled < Goal
6
+ attr_reader :package_name
7
+
6
8
  def initialize(package_name)
7
9
  super()
8
10
  @package_name = package_name
@@ -21,6 +23,10 @@ module Ddenv
21
23
  cmd = TTY::Command.new(printer: :null)
22
24
  cmd.run("brew", "install", @package_name)
23
25
  end
26
+
27
+ def props
28
+ [@package_name]
29
+ end
24
30
  end
25
31
  end
26
32
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ddenv
4
+ module Goals
5
+ module NodeBuildInstalled
6
+ def self.new
7
+ Ddenv::Goals::HomebrewPackageInstalled.new("node-build")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ddenv
4
+ module Goals
5
+ class NodeInstalled < Goal
6
+ attr_reader :node_version
7
+
8
+ def initialize(node_version)
9
+ super()
10
+
11
+ @node_version = node_version.to_s
12
+ end
13
+
14
+ def message
15
+ "Installing Node v#{node_version}"
16
+ end
17
+
18
+ def achieved?
19
+ node_pathname.directory?
20
+ end
21
+
22
+ def achieve
23
+ # TODO: use :pretty when passing --verbose
24
+ cmd = TTY::Command.new(printer: :null)
25
+ cmd.run("node-build", node_version, node_pathname.to_s)
26
+ end
27
+
28
+ def pre_goals
29
+ [
30
+ NodeBuildInstalled.new
31
+ ]
32
+ end
33
+
34
+ def post_goals
35
+ [
36
+ NodeShadowenvCreated.new(node_version, node_pathname.to_s)
37
+ ]
38
+ end
39
+
40
+ private
41
+
42
+ def node_pathname
43
+ @_node_pathname ||= Pathname.new(Dir.home) / ".node-versions" / node_version
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ddenv
4
+ module Goals
5
+ class NodeShadowenvCreated < Goal
6
+ PATH = ".shadowenv.d/20_node.lisp"
7
+
8
+ def initialize(node_version, node_path)
9
+ super()
10
+
11
+ @node_version = node_version
12
+ @node_path = node_path
13
+ end
14
+
15
+ def message
16
+ "Adding Node to shadowenv"
17
+ end
18
+
19
+ def achieved?
20
+ File.file?(PATH) && File.read(PATH) == shadowenv_contents
21
+ end
22
+
23
+ def achieve
24
+ File.write(PATH, shadowenv_contents)
25
+
26
+ # TODO: use :pretty when passing --verbose
27
+ cmd = TTY::Command.new(printer: :null)
28
+ cmd.run("shadowenv", "trust")
29
+ end
30
+
31
+ def pre_goals
32
+ [
33
+ Ddenv::Goals::ShadowenvDirCreated.new
34
+ ]
35
+ end
36
+
37
+ def props
38
+ [@node_version, @node_path]
39
+ end
40
+
41
+ private
42
+
43
+ def shadowenv_contents
44
+ <<~CONTENTS
45
+ (provide "node" "#{@node_version}")
46
+
47
+ (env/prepend-to-pathlist "PATH" "#{@node_path}/bin")
48
+ CONTENTS
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ddenv
4
+ module Goals
5
+ class NpmPackesInstalled < Goal
6
+ def message
7
+ "Installing npm packages"
8
+ end
9
+
10
+ def achieved?
11
+ # TODO: use :pretty when passing --verbose
12
+ cmd = TTY::Command.new(printer: :null)
13
+ cmd.run("shadowenv", "exec", "--", "npx", "check-dependencies")
14
+ true
15
+ rescue StandardError
16
+ false
17
+ end
18
+
19
+ def achieve
20
+ # TODO: use :pretty when passing --verbose
21
+ cmd = TTY::Command.new(printer: :null)
22
+ cmd.run("shadowenv", "exec", "--", "npm", "install")
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ddenv
4
+ module Goals
5
+ module RubyInstallInstalled
6
+ def self.new
7
+ Ddenv::Goals::HomebrewPackageInstalled.new("ruby-install")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ddenv
4
+ module Goals
5
+ class RubyInstalled < Goal
6
+ def message
7
+ "Installing Ruby v#{ruby_version}"
8
+ end
9
+
10
+ def achieved?
11
+ pathname = Pathname.new(Dir.home) / ".rubies" / "ruby-#{ruby_version}"
12
+ pathname.directory?
13
+ end
14
+
15
+ def achieve
16
+ # TODO: use :pretty when passing --verbose
17
+ cmd = TTY::Command.new(printer: :null)
18
+ cmd.run("ruby-install", "--cleanup", ruby_version)
19
+ end
20
+
21
+ def pre_goals
22
+ [
23
+ RubyInstallInstalled.new
24
+ ]
25
+ end
26
+
27
+ def post_goals
28
+ [
29
+ RubyShadowenvCreated.new(ruby_version, ruby_pathname.to_s)
30
+ ]
31
+ end
32
+
33
+ private
34
+
35
+ def ruby_version
36
+ @_ruby_version ||= File.read(".ruby-version").chomp
37
+ end
38
+
39
+ def ruby_pathname
40
+ @_ruby_pathname ||= Pathname.new(Dir.home) / ".rubies" / "ruby-#{ruby_version}"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ddenv
4
+ module Goals
5
+ class RubyShadowenvCreated < Goal
6
+ PATH = ".shadowenv.d/10_ruby.lisp"
7
+
8
+ def initialize(ruby_version, ruby_path)
9
+ super()
10
+
11
+ @ruby_version = ruby_version
12
+ @ruby_path = ruby_path
13
+ end
14
+
15
+ def message
16
+ "Adding Ruby to shadowenv"
17
+ end
18
+
19
+ def achieved?
20
+ File.file?(PATH) && File.read(PATH) == shadowenv_contents
21
+ end
22
+
23
+ def achieve
24
+ File.write(PATH, shadowenv_contents)
25
+
26
+ # TODO: use :pretty when passing --verbose
27
+ cmd = TTY::Command.new(printer: :null)
28
+ cmd.run("shadowenv", "trust")
29
+ end
30
+
31
+ def pre_goals
32
+ [
33
+ Ddenv::Goals::ShadowenvDirCreated.new
34
+ ]
35
+ end
36
+
37
+ def props
38
+ [@ruby_version, @ruby_path]
39
+ end
40
+
41
+ private
42
+
43
+ def shadowenv_contents
44
+ <<~CONTENTS
45
+ (provide "ruby" "#{@ruby_version}")
46
+
47
+ (when-let ((ruby-root (env/get "RUBY_ROOT")))
48
+ (env/remove-from-pathlist "PATH" (path-concat ruby-root "bin"))
49
+ (when-let ((gem-root (env/get "GEM_ROOT")))
50
+ (env/remove-from-pathlist "PATH" (path-concat gem-root "bin")))
51
+ (when-let ((gem-home (env/get "GEM_HOME")))
52
+ (env/remove-from-pathlist "PATH" (path-concat gem-home "bin"))))
53
+
54
+ (env/set "GEM_PATH" ())
55
+ (env/set "GEM_HOME" ())
56
+ (env/set "RUBYOPT" ())
57
+
58
+ (env/set "RUBY_ROOT" "#{@ruby_path}")
59
+ (env/prepend-to-pathlist "PATH" "#{@ruby_path}/bin")
60
+ (env/set "RUBY_ENGINE" "ruby")
61
+ (env/set "RUBY_VERSION" "#{@ruby_version}")
62
+ (env/set "GEM_ROOT" "#{@ruby_path}/lib/ruby/gems/#{@ruby_version}")
63
+
64
+ (when-let ((gem-root (env/get "GEM_ROOT")))
65
+ (env/prepend-to-pathlist "GEM_PATH" gem-root)
66
+ (env/prepend-to-pathlist "PATH" (path-concat gem-root "bin")))
67
+
68
+ (let ((gem-home
69
+ (path-concat (env/get "HOME") ".gem" (env/get "RUBY_ENGINE") (env/get "RUBY_VERSION"))))
70
+ (do
71
+ (env/set "GEM_HOME" gem-home)
72
+ (env/prepend-to-pathlist "GEM_PATH" gem-home)
73
+ (env/prepend-to-pathlist "PATH" (path-concat gem-home "bin"))))
74
+ CONTENTS
75
+ end
76
+ end
77
+ end
78
+ end