sensible-cli 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.sensible/tasks/another.yml +7 -0
- data/.sensible/tasks/test.yml +4 -0
- data/README.md +210 -0
- data/Rakefile +8 -0
- data/exe/sensible +90 -0
- data/lib/sensible/log.rb +68 -0
- data/lib/sensible/package.rb +52 -0
- data/lib/sensible/parse.rb +36 -0
- data/lib/sensible/requirement.rb +7 -0
- data/lib/sensible/task.rb +67 -0
- data/lib/sensible/version.rb +5 -0
- data/lib/sensible.rb +286 -0
- data/sensible.yml +23 -0
- data/sig/sensible.rbs +4 -0
- metadata +141 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4841010ea567e04f3f358f1f98b9e61aedb419c6f6857f5f2644bc0d9c5d58e3
|
4
|
+
data.tar.gz: 16b5ce3ead9edfd3ff17465f8cb4d5c9463ef5bf14e429e02ab8b70f249921c7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dd36abd65d68819f83f817aa4979bda0ad78aa4b173e42c1792500812ac99501d5d0aaccac02d3d84da2c636338769adc41fcba7453266dbbf1b36efc0763b42
|
7
|
+
data.tar.gz: 5b1e16c926d168f4498e440161c294587428562b1a8672e2bf7606f111219c32e12fd0c5719e96141530698ae9e8c4cd8d3554aca15c65aebf16a6dffa296f90
|
data/.rspec
ADDED
data/README.md
ADDED
@@ -0,0 +1,210 @@
|
|
1
|
+
# Sensible
|
2
|
+
A small tool to manage projects.
|
3
|
+
|
4
|
+
## NOTICE
|
5
|
+
The tool is still in very early development, and it's not ready for production use.
|
6
|
+
|
7
|
+
Everything is subject to change, and the tool is not stable.
|
8
|
+
|
9
|
+
## Why?
|
10
|
+
The reason I made this tool, is I work on a lot of project at work, and we are a very small team. We work on separate things for the same projects, like I work on the frontend, and the other guy on the database.
|
11
|
+
He has his own tooling, and need a bunch of stuff installed and various settings set in files, to run the database. I have also my own tooling, and we often have to spend a lot of time helping with setting up our part of the project for each other so we can run them locally.
|
12
|
+
|
13
|
+
So to make things easier, I created this tool to help us document and setup each part of the projects, and make sure we have everything required to run each part.
|
14
|
+
|
15
|
+
It can also be used to document the setup process for a project, and make it easier for new developers to get started with the project.
|
16
|
+
|
17
|
+
It's inspired by the `flutter doctor` feature in Flutter.
|
18
|
+
|
19
|
+
It's currently focused for usage in linux environments, but it should work on MacOS as well. It's not tested on Windows, but it should work with WSL. It uses zx by google to run the scripts, and it's only tested on linux.
|
20
|
+
|
21
|
+
## Installing
|
22
|
+
You can use the binary from the releases page, or you can install it with the RPM package from the same page
|
23
|
+
|
24
|
+
```
|
25
|
+
gem install sensible-ruby
|
26
|
+
```
|
27
|
+
|
28
|
+
# Docs
|
29
|
+
|
30
|
+
## Sensible file
|
31
|
+
The sensible file is a yaml file that defines the packages, requirements and tasks for the project. The sensible file should be in the root of the project and should be named `sensible.yml`. You can run the `sensible init` command to create a new sensible file.
|
32
|
+
|
33
|
+
The `sensible.yml` should look like this:
|
34
|
+
|
35
|
+
```yaml
|
36
|
+
---
|
37
|
+
preTasks:
|
38
|
+
packages:
|
39
|
+
requirements:
|
40
|
+
postTasks:
|
41
|
+
```
|
42
|
+
This is the basic structure of the sensible file. You can add the packages, requirements, tasks, and postTasks to the file.
|
43
|
+
|
44
|
+
**All properties are optional, and you can omit them if you don't need them.**
|
45
|
+
|
46
|
+
### Packages
|
47
|
+
The packages are the packages that should be installed. The packages should be defined in the `packages` section of the sensible file.
|
48
|
+
|
49
|
+
The packages should be defined like this:
|
50
|
+
|
51
|
+
```yaml
|
52
|
+
packages:
|
53
|
+
- name: <package-name>
|
54
|
+
install: <install-command>
|
55
|
+
env:
|
56
|
+
- <environment 1>
|
57
|
+
- <environment 2>
|
58
|
+
```
|
59
|
+
The package should be the name of the command the package provides. As an example, if you check if Nodejs is installed `node`, the package name should be `node`.
|
60
|
+
It uses the [semver](https://www.npmjs.com/package/semver) npm package to check if the version is in the correct range.
|
61
|
+
|
62
|
+
The env property is optional, and if it's not defined, the package will be installed in every environment
|
63
|
+
|
64
|
+
The property `install` is the shell command that install the package.
|
65
|
+
|
66
|
+
### Requirements
|
67
|
+
The requirements section defines the requirements for the project, like if you need specific settings in a file, or a line in /etc/hosts. They work pretty much like tasks, but it's a way to document the requirements for the project, and structure the setup process.
|
68
|
+
|
69
|
+
The requirement should be defined like this:
|
70
|
+
```yaml
|
71
|
+
# sensible.yml
|
72
|
+
requirements:
|
73
|
+
- name: <requirement-name>
|
74
|
+
check: <shell script to check if the requirement is met>
|
75
|
+
install: <shell script to make the requirement met>
|
76
|
+
env:
|
77
|
+
- <environment 1>
|
78
|
+
- <environment 2>
|
79
|
+
```
|
80
|
+
The check and install script should be kept as short and simple as possible. If it gets too complex, it should be a task instead.
|
81
|
+
|
82
|
+
#### Tasks
|
83
|
+
Tasks are the files that define the tasks that should be run. The task files should be placed in the `.sensible/tasks` folder in the root of the project.
|
84
|
+
The tasks should be defined like this:
|
85
|
+
|
86
|
+
```yaml
|
87
|
+
# .sensible/tasks/example.yml
|
88
|
+
---
|
89
|
+
name: Example task
|
90
|
+
description: This is an example task, showing how to define a task
|
91
|
+
showOutput: true | false # Default is false
|
92
|
+
script: echo "This is an example task"
|
93
|
+
```
|
94
|
+
You can use | to make multiline scripts
|
95
|
+
```yaml
|
96
|
+
# .sensible/tasks/example.yml
|
97
|
+
---
|
98
|
+
name: Example task 2
|
99
|
+
description: This is an example task, showing how to define a task
|
100
|
+
script: |
|
101
|
+
echo "This is an example task"
|
102
|
+
echo "This is a multiline script"
|
103
|
+
```
|
104
|
+
The script part is pure bash script. You can use any bash command in the script.
|
105
|
+
|
106
|
+
Tasks has the following properties:
|
107
|
+
- name: The name of the task
|
108
|
+
- description: A description of the task
|
109
|
+
- showOutput: If the output of the task should be shown in the terminal. Default is false
|
110
|
+
- script: The bash script that should be run
|
111
|
+
- env: The list of environments the task should be run in. The default is `dev`, and if you omit this property, it will be run in every environment.
|
112
|
+
|
113
|
+
## Commands
|
114
|
+
|
115
|
+
```
|
116
|
+
Usage: sensible [options] [command]
|
117
|
+
|
118
|
+
A simple sensible tool for your projects
|
119
|
+
|
120
|
+
Options:
|
121
|
+
--env <string> Set the environment (default: "dev")
|
122
|
+
-f, --sensibleFile <string> Path to the sensible file
|
123
|
+
-d, --sensibleFolder <string> Path to the sensible folder (default: ".sensible")
|
124
|
+
-p, --prod Set to production mode (default: false)
|
125
|
+
-V, --version output the version number
|
126
|
+
-h, --help display help for command
|
127
|
+
|
128
|
+
Commands:
|
129
|
+
check [options] Check the project for missing dependencies
|
130
|
+
install [options] Install missing dependencies and requirements
|
131
|
+
task <task> Manage tasks
|
132
|
+
init [options] Create a new sensible file
|
133
|
+
help [command] display help for command`
|
134
|
+
```
|
135
|
+
|
136
|
+
### `sensible init`
|
137
|
+
This command creates a new sensible file in the root of your project. It will create a file called `sensible.yml`.
|
138
|
+
|
139
|
+
|
140
|
+
### `sensible check`
|
141
|
+
This checks the project for missing dependencies. It will check if the packages are installed and if they are in the correct version range. It uses the bash command `command -v` to check if the packages are installed.
|
142
|
+
To determine the version of the package, it uses the `<command> --version` and `<command> --V` command.
|
143
|
+
|
144
|
+
### `sensible install`
|
145
|
+
This command installs missing dependencies and requirements. It will install the packages that are missing and are in the correct version range.
|
146
|
+
|
147
|
+
It will also run the tasks that are defined in the sensible file.
|
148
|
+
The order of the tasks is important. The tasks will be run in the order they are defined in the sensible file.
|
149
|
+
|
150
|
+
The total order of things are like this:
|
151
|
+
|
152
|
+
- preTasks
|
153
|
+
- packages
|
154
|
+
- requirements
|
155
|
+
- postTasks
|
156
|
+
|
157
|
+
### `sensible task`
|
158
|
+
This command manages the tasks that are defined in your project.
|
159
|
+
|
160
|
+
```
|
161
|
+
Usage: sensible task [command] <task>
|
162
|
+
|
163
|
+
Manage tasks
|
164
|
+
|
165
|
+
Arguments:
|
166
|
+
task Task to run
|
167
|
+
|
168
|
+
Options:
|
169
|
+
-h, --help display help for command
|
170
|
+
|
171
|
+
Commands:
|
172
|
+
run [options] <task> Run a task
|
173
|
+
list [options] List available tasks
|
174
|
+
create [options] <task> Create a new task
|
175
|
+
```
|
176
|
+
|
177
|
+
Tasks will be placed in the `.sensible/tasks` when created, and it looks for them in that folder.
|
178
|
+
|
179
|
+
#### `sensible task run`
|
180
|
+
This will run a single task. Very useful if your project has need to do a lot of things to run it.
|
181
|
+
|
182
|
+
The last argument for the command is the name of the task you want to run.
|
183
|
+
|
184
|
+
`sensible run task example`
|
185
|
+
|
186
|
+
This will run the task `example` in the `.sensible/tasks` folder.
|
187
|
+
|
188
|
+
Possible use cases for tasks:
|
189
|
+
- Setting up a database
|
190
|
+
- Running migrations
|
191
|
+
- Running tests
|
192
|
+
- Running a development server
|
193
|
+
- Reinstall node dependencies
|
194
|
+
- Clearing cache files
|
195
|
+
|
196
|
+
#### `sensible task list`
|
197
|
+
This simply lists the tasks that are available in the `.sensible/tasks` folder.
|
198
|
+
|
199
|
+
|
200
|
+
## Environments
|
201
|
+
The sensible tool supports multiple environments. The default environment is `dev`. You can set the environment with the `--env` flag.
|
202
|
+
|
203
|
+
This means you can have different requirements, packages, and tasks for different environments, and you can name them however you want.
|
204
|
+
|
205
|
+
An example of a command that sets the environment to `prod`:
|
206
|
+
|
207
|
+
```bash
|
208
|
+
sensible check --env prod # Check the project for missing dependencies in the prod environment
|
209
|
+
sensible install --env stage # Install missing dependencies and requirements in the staging environment
|
210
|
+
```
|
data/Rakefile
ADDED
data/exe/sensible
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
SPEC = %(
|
4
|
+
@ A small tool to manage and deploy projects
|
5
|
+
|
6
|
+
Install and update systems much like ansible but using shell scripts. It uses
|
7
|
+
a sensible.yml configuration file to control the process
|
8
|
+
|
9
|
+
OPTIONS
|
10
|
+
-e,env,environment=ENV
|
11
|
+
Sets the environment (dev/prod/etc),
|
12
|
+
|
13
|
+
-f,file=FILE
|
14
|
+
Path to sensible configuration file
|
15
|
+
|
16
|
+
-d,dir,directory=DIR
|
17
|
+
Path to sensible directory. Default '.sensible'
|
18
|
+
|
19
|
+
-v,verbose
|
20
|
+
Verbose output
|
21
|
+
|
22
|
+
COMMANDS
|
23
|
+
check!
|
24
|
+
Check the project for missing dependencies
|
25
|
+
|
26
|
+
install!
|
27
|
+
Install missing dependencies and requirements
|
28
|
+
|
29
|
+
task.list!
|
30
|
+
List tasks
|
31
|
+
|
32
|
+
task.run! -- TASK
|
33
|
+
Run a single task
|
34
|
+
|
35
|
+
task.create! -- TASK
|
36
|
+
Create a task
|
37
|
+
|
38
|
+
init!
|
39
|
+
Create a new sensible configuration file
|
40
|
+
)
|
41
|
+
|
42
|
+
require_relative '../lib/sensible.rb'
|
43
|
+
require 'shellopts'
|
44
|
+
|
45
|
+
# Monkey patch where version_number is not fetched correctly in ruby 3.4.2
|
46
|
+
module ShellOpts
|
47
|
+
class ShellOpts
|
48
|
+
def version_number
|
49
|
+
Sensible::VERSION
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
opts, args = ShellOpts::process(SPEC, ARGV)
|
55
|
+
cmd = opts.subcommand!
|
56
|
+
#cmd == :task! or args.expect(0) # Ensure no arguments except for task
|
57
|
+
|
58
|
+
if opts.verbose
|
59
|
+
puts "Options"
|
60
|
+
puts " env: #{opts.env}"
|
61
|
+
puts " file: #{opts.file}"
|
62
|
+
puts " dir: #{opts.dir}"
|
63
|
+
puts " args: #{args}"
|
64
|
+
# puts "Command: #{cmd.to_s.sub(/!$/, "")}"
|
65
|
+
end
|
66
|
+
|
67
|
+
case opts.subcommand
|
68
|
+
when :init!
|
69
|
+
Sensible::Sensible.init(opts)
|
70
|
+
else
|
71
|
+
sensible = Sensible::Sensible.new("sensible.yml", opts, args)
|
72
|
+
|
73
|
+
case opts.subcommand
|
74
|
+
when :check!
|
75
|
+
sensible.check
|
76
|
+
when :install!
|
77
|
+
sensible.install
|
78
|
+
when :task!
|
79
|
+
case cmd.subcommand
|
80
|
+
when :list!; sensible.task_list
|
81
|
+
when :run!
|
82
|
+
arg = args.expect(1) # Expect a single argument to sensible task
|
83
|
+
sensible.task_run(arg)
|
84
|
+
when :create!
|
85
|
+
arg = args.expect(1) # Expect a single argument to sensible task
|
86
|
+
sensible.task_create(arg)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
data/lib/sensible/log.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'tty-prompt'
|
2
|
+
require 'tty-spinner'
|
3
|
+
require 'pastel'
|
4
|
+
|
5
|
+
module Sensible
|
6
|
+
$pastel = Pastel.new
|
7
|
+
|
8
|
+
class Logger
|
9
|
+
def self.log(message, indent = 0, use_print: false)
|
10
|
+
puts message
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.success(message, indent = 0, use_print: false)
|
14
|
+
spaceIndent = ""
|
15
|
+
indent.times { |i| spaceIndent << " " }
|
16
|
+
|
17
|
+
if use_print
|
18
|
+
print "#{spaceIndent}#{$pastel.green("✔")} #{message}"
|
19
|
+
else
|
20
|
+
puts "#{spaceIndent}#{$pastel.green("✔")} #{message}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.info(message, indent = 0, use_print: false)
|
25
|
+
spaceIndent = ""
|
26
|
+
indent.times { |i| spaceIndent << " " }
|
27
|
+
|
28
|
+
if use_print
|
29
|
+
print "#{spaceIndent}#{$pastel.blue("i")} #{message}"
|
30
|
+
else
|
31
|
+
puts "#{spaceIndent}#{$pastel.blue("i")} #{message}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.danger(message, indent = 0, use_print: false)
|
36
|
+
spaceIndent = ""
|
37
|
+
indent.times { |i| spaceIndent << " " }
|
38
|
+
|
39
|
+
if use_print
|
40
|
+
print "#{spaceIndent}#{$pastel.red("✘")} #{message}"
|
41
|
+
else
|
42
|
+
puts "#{spaceIndent}#{$pastel.red("✘")} #{message}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.error(message, indent = 0, use_print: false)
|
47
|
+
spaceIndent = ""
|
48
|
+
indent.times { |i| spaceIndent << " " }
|
49
|
+
|
50
|
+
full_message = "#{spaceIndent}#{$pastel.on_red.black(" ERROR ")} #{message}"
|
51
|
+
|
52
|
+
if use_print
|
53
|
+
print full_message
|
54
|
+
else
|
55
|
+
puts full_message
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.yes?(message, indent = 0)
|
60
|
+
prompt = TTY::Prompt.new
|
61
|
+
|
62
|
+
spaceIndent = ""
|
63
|
+
indent.times { |i| spaceIndent << " " }
|
64
|
+
|
65
|
+
prompt.yes?("#{spaceIndent}#{message}")
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require_relative 'log'
|
2
|
+
require 'tty-prompt'
|
3
|
+
require 'pastel'
|
4
|
+
|
5
|
+
module Sensible
|
6
|
+
class Package
|
7
|
+
attr_reader :sensible
|
8
|
+
attr_reader :name
|
9
|
+
attr_reader :check
|
10
|
+
attr_reader :install
|
11
|
+
attr_reader :env
|
12
|
+
|
13
|
+
def initialize(packageHash, sensible)
|
14
|
+
@name = packageHash['name']
|
15
|
+
@check = packageHash['check']
|
16
|
+
@install = packageHash['install']
|
17
|
+
@env = packageHash['env'] || []
|
18
|
+
|
19
|
+
@sensible = sensible
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
# Check if the package is installed
|
24
|
+
def do_check
|
25
|
+
if @check
|
26
|
+
result = `#{@check}`
|
27
|
+
return $?.success?
|
28
|
+
else
|
29
|
+
# If check is not set, then infer that it's a system package
|
30
|
+
result = `rpm -q #{@name}`
|
31
|
+
|
32
|
+
if result.include? 'is not installed'
|
33
|
+
return false
|
34
|
+
else
|
35
|
+
return true
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
# Install the package
|
42
|
+
def do_install
|
43
|
+
if @install
|
44
|
+
system(@install, out: File::NULL)
|
45
|
+
return $?.success?
|
46
|
+
else
|
47
|
+
system("sudo", "dnf", "install", "-y", @name, out: File::NULL, err: File::NULL)
|
48
|
+
return $?.success?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require_relative 'task'
|
2
|
+
require_relative 'requirement'
|
3
|
+
require_relative 'package'
|
4
|
+
|
5
|
+
module Sensible
|
6
|
+
class Parse
|
7
|
+
|
8
|
+
# Parse the package list from sensible.yml
|
9
|
+
def self.parse_sensible_packages(sensible_hash_list, sensible)
|
10
|
+
list = []
|
11
|
+
for pkg in sensible_hash_list
|
12
|
+
list.append(Package.new(pkg, sensible))
|
13
|
+
end
|
14
|
+
return list
|
15
|
+
end
|
16
|
+
|
17
|
+
# Parse the task list from sensible.yml
|
18
|
+
def self.parse_sensible_tasks(sensible_hash_list, sensible)
|
19
|
+
list = []
|
20
|
+
for task in sensible_hash_list
|
21
|
+
list.append(Task.new(tash, sensible))
|
22
|
+
end
|
23
|
+
return list
|
24
|
+
end
|
25
|
+
|
26
|
+
# Parse the requirement list from sensible.yml
|
27
|
+
def self.parse_sensible_requirements(sensible_hash_list, sensible)
|
28
|
+
list = []
|
29
|
+
for pkg in sensible_hash_list
|
30
|
+
list.append(Requirement.new(pkg, sensible))
|
31
|
+
end
|
32
|
+
return list
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require_relative 'package'
|
2
|
+
|
3
|
+
module Sensible
|
4
|
+
class Task < Package
|
5
|
+
attr_reader :file_name
|
6
|
+
attr_reader :description
|
7
|
+
attr_accessor :show_output
|
8
|
+
|
9
|
+
def initialize(taskHash, file_name, sensible)
|
10
|
+
super(taskHash, sensible)
|
11
|
+
@file_name = file_name
|
12
|
+
@description = taskHash['description']
|
13
|
+
@show_output = taskHash['showOutput']
|
14
|
+
end
|
15
|
+
|
16
|
+
def do_check
|
17
|
+
do_verify()
|
18
|
+
|
19
|
+
# If check is not set, always run the task
|
20
|
+
if @check == nil
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
|
24
|
+
# If there is no check, default to false, to force task to install every time
|
25
|
+
system(@check, out: File::NULL)
|
26
|
+
return $?.success?
|
27
|
+
end
|
28
|
+
|
29
|
+
def do_install
|
30
|
+
# TODO: Handle the show output property!
|
31
|
+
|
32
|
+
if @install.include?("\n")
|
33
|
+
temp_path = "/tmp/sensible"
|
34
|
+
temp_file_name = "install.sh"
|
35
|
+
temp_file_path = "#{temp_path}/#{temp_file_name}"
|
36
|
+
|
37
|
+
# Make sure we have the tmp folder created
|
38
|
+
FileUtils.mkdir_p(temp_path)
|
39
|
+
|
40
|
+
File.open(temp_file_path, "w") do |f|
|
41
|
+
f.puts "#!/usr/bin/env bash\n\n"
|
42
|
+
f.write(@install)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Make it executable
|
46
|
+
File.chmod(0700, temp_file_path)
|
47
|
+
|
48
|
+
system("#{temp_file_path}", out: File::NULL)
|
49
|
+
return $?.success?
|
50
|
+
else
|
51
|
+
system(@install, out: File::NULL)
|
52
|
+
return $?.success?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def do_verify
|
57
|
+
if !@install
|
58
|
+
pastel = Pastel.new
|
59
|
+
Logger.error("This is not valid task, #{pastel.bold("install")} property is missing!")
|
60
|
+
exit(1)
|
61
|
+
return false
|
62
|
+
end
|
63
|
+
|
64
|
+
return true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/sensible.rb
ADDED
@@ -0,0 +1,286 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'yaml'
|
3
|
+
require_relative "sensible/version"
|
4
|
+
require_relative "sensible/package"
|
5
|
+
require_relative "sensible/log"
|
6
|
+
require_relative "sensible/parse"
|
7
|
+
|
8
|
+
module Sensible
|
9
|
+
class Error < StandardError; end
|
10
|
+
|
11
|
+
class Sensible
|
12
|
+
attr_reader :opts
|
13
|
+
attr_reader :args
|
14
|
+
|
15
|
+
attr_reader :preTasks
|
16
|
+
attr_reader :packages
|
17
|
+
attr_reader :requirements
|
18
|
+
attr_reader :postTasks
|
19
|
+
|
20
|
+
attr_reader :sensible_folder
|
21
|
+
attr_reader :tasks_folder
|
22
|
+
|
23
|
+
|
24
|
+
def initialize(sensibleFileName, opts, args)
|
25
|
+
@opts = opts
|
26
|
+
@args = args
|
27
|
+
|
28
|
+
@sensible_folder = '.sensible'
|
29
|
+
@tasks_folder = 'tasks'
|
30
|
+
|
31
|
+
file_name = opts.file || sensibleFileName
|
32
|
+
unless File.exist?(file_name)
|
33
|
+
Logger.error("Required file not found: #{file_name}")
|
34
|
+
exit(1)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Load the Sensile file
|
38
|
+
sensible_file_data = YAML.load_file(file_name)
|
39
|
+
|
40
|
+
# Parse packages
|
41
|
+
if sensible_file_data['packages']
|
42
|
+
@packages = Parse.parse_sensible_packages(sensible_file_data['packages'], self)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Parse packages
|
46
|
+
if sensible_file_data['requirements']
|
47
|
+
@requirements = Parse.parse_sensible_requirements(sensible_file_data['requirements'], self)
|
48
|
+
end
|
49
|
+
|
50
|
+
if (sensible_file_data['preTasks'])
|
51
|
+
@preTasks = sensible_file_data['preTasks']
|
52
|
+
end
|
53
|
+
|
54
|
+
if (sensible_file_data['postTasks'])
|
55
|
+
@postTasks = sensible_file_data['postTasks']
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
# Run all the checks for packages and requirements
|
61
|
+
def check
|
62
|
+
Logger.log("Checking for installed packages...")
|
63
|
+
|
64
|
+
for pkg in @packages
|
65
|
+
# Do an environment test
|
66
|
+
if @opts.env
|
67
|
+
# If package env is not define, we expect it should always be installed regardless of environment
|
68
|
+
# If user has defined an environment, skip if the set environment isn't in the package enviroment list
|
69
|
+
next if pkg.env.any? && !pkg.env.include?(@opts.env)
|
70
|
+
else
|
71
|
+
# If env contains anything, when env is not defined in opts, skip it, as this is not the correct env
|
72
|
+
next if pkg.env.any?
|
73
|
+
end
|
74
|
+
|
75
|
+
if pkg.do_check
|
76
|
+
Logger.success("#{pkg.name} is installed")
|
77
|
+
else
|
78
|
+
Logger.danger("#{pkg.name} was NOT installed")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
if @requirements != nil
|
83
|
+
Logger.log("\nChecking if requirements are met...")
|
84
|
+
|
85
|
+
for requirement in @requirements
|
86
|
+
# Do an environment test
|
87
|
+
if @opts.env
|
88
|
+
# If package env is not define, we expect it should always be installed regardless of environment
|
89
|
+
# If user has defined an environment, skip if the set environment isn't in the package enviroment list
|
90
|
+
next if requirement.env.any? && !requirement.env.include?(@opts.env)
|
91
|
+
else
|
92
|
+
# If env contains anything, when env is not defined in opts, skip it, as this is not the correct env
|
93
|
+
next if pkg.env.any?
|
94
|
+
end
|
95
|
+
|
96
|
+
if requirement.do_check
|
97
|
+
Logger.success("#{requirement.name}")
|
98
|
+
else
|
99
|
+
Logger.danger("#{requirement.name}")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def install
|
106
|
+
# Prewarm sudo, to prevent asking too much
|
107
|
+
system('sudo -v')
|
108
|
+
|
109
|
+
# Run pre tasks
|
110
|
+
if @preTasks != nil
|
111
|
+
Logger.log("Running pre tasks...")
|
112
|
+
|
113
|
+
for task in @preTasks
|
114
|
+
task_run(task)
|
115
|
+
end
|
116
|
+
Logger.log("")
|
117
|
+
end
|
118
|
+
|
119
|
+
# Install packages
|
120
|
+
if @packages != nil
|
121
|
+
Logger.log("Installing packages...")
|
122
|
+
for pkg in @packages
|
123
|
+
# Do an environment test
|
124
|
+
if @opts.env
|
125
|
+
# If package env is not defined, we expect it should always be installed regardless of environment
|
126
|
+
# If user has defined an environment, skip if the set environment isn't in the package enviroment list
|
127
|
+
next if pkg.env.any? && !pkg.env.include?(@opts.env)
|
128
|
+
else
|
129
|
+
# If env contains anything, when env is not defined in opts, skip it, as this is not the correct env
|
130
|
+
next if pkg.env.any?
|
131
|
+
end
|
132
|
+
|
133
|
+
if pkg.do_check
|
134
|
+
Logger.success("#{pkg.name} is installed")
|
135
|
+
else
|
136
|
+
Logger.info("Installing: #{pkg.name}\r", use_print: true)
|
137
|
+
if pkg.do_install
|
138
|
+
Logger.success("#{pkg.name} was installed")
|
139
|
+
$stdout.flush
|
140
|
+
else
|
141
|
+
Logger.danger("#{pkg.name} was not installed")
|
142
|
+
$stdout.flush
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
Logger.log("")
|
147
|
+
end
|
148
|
+
|
149
|
+
# Handle requirements
|
150
|
+
if @requirements != nil
|
151
|
+
Logger.log("Handling requirements...")
|
152
|
+
for requirement in @requirements
|
153
|
+
# Do an environment test
|
154
|
+
if @opts.env
|
155
|
+
# If package env is not defined, we expect it should always be installed regardless of environment
|
156
|
+
# If user has defined an environment, skip if the set environment isn't in the package enviroment list
|
157
|
+
next if requirement.env.any? && !requirement.env.include?(@opts.env)
|
158
|
+
else
|
159
|
+
# If env contains anything, when env is not defined in opts, skip it, as this is not the correct env
|
160
|
+
next if requirement.env.any?
|
161
|
+
end
|
162
|
+
|
163
|
+
if requirement.do_check
|
164
|
+
Logger.success("#{requirement.name}")
|
165
|
+
else
|
166
|
+
Logger.info("Handling: #{pkg.name}\r", use_print: true)
|
167
|
+
if requirement.do_install
|
168
|
+
Logger.success("#{requirement.name}")
|
169
|
+
$stdout.flush
|
170
|
+
else
|
171
|
+
Logger.danger("#{requirement.name}")
|
172
|
+
$stdout.flush
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
Logger.log("")
|
177
|
+
end
|
178
|
+
|
179
|
+
# Run post tasks
|
180
|
+
if @postTasks != nil
|
181
|
+
Logger.log("Running post tasks...")
|
182
|
+
|
183
|
+
for task in @postTasks
|
184
|
+
task_run(task)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.init(opts)
|
191
|
+
sensible_file_name = "sensible.yml"
|
192
|
+
|
193
|
+
if opts.file
|
194
|
+
if opts.file.end_with?(".yml")
|
195
|
+
sensible_file_name = opts.file
|
196
|
+
else
|
197
|
+
sensible_file_name = opts.file + ".yml"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
if not File.exist?(sensible_file_name)
|
202
|
+
File.open(sensible_file_name, "w") do |f|
|
203
|
+
f.write(<<~EOF)
|
204
|
+
---
|
205
|
+
packages:
|
206
|
+
requirements:
|
207
|
+
EOF
|
208
|
+
end
|
209
|
+
Logger.success("Created #{sensible_file_name}!")
|
210
|
+
else
|
211
|
+
Logger.error("Cannot create #{sensible_file_name}, it already exists!")
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def task_run(task_name)
|
216
|
+
# Load and parse the task file
|
217
|
+
task_file_path = "#{@sensible_folder}/#{@tasks_folder}/#{task_name}.yml"
|
218
|
+
|
219
|
+
# If task is not found, exit!
|
220
|
+
if not File.exist?(task_file_path)
|
221
|
+
pastel = Pastel.new
|
222
|
+
Logger.error("Task: #{pastel.bold(task_name)} does not exist!")
|
223
|
+
exit(1)
|
224
|
+
end
|
225
|
+
|
226
|
+
task = Task.new(YAML.load_file(task_file_path), "#{task_name}.yml", self)
|
227
|
+
|
228
|
+
pastel = Pastel.new
|
229
|
+
Logger.info("Running task: #{pastel.bold(task_name)}")
|
230
|
+
|
231
|
+
# Check if we need to rerun the task
|
232
|
+
if !task.do_check
|
233
|
+
if !task.do_install
|
234
|
+
Logger.error("The tasked failed!")
|
235
|
+
else
|
236
|
+
Logger.success("The task ran succesfully!")
|
237
|
+
end
|
238
|
+
else
|
239
|
+
puts "Task check is already met"
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
def task_list
|
244
|
+
# Parse tasks
|
245
|
+
tasks_path = "#{@sensible_folder}/#{@tasks_folder}"
|
246
|
+
task_files = Dir.children(tasks_path)
|
247
|
+
|
248
|
+
tasks = []
|
249
|
+
for task_file in task_files
|
250
|
+
tasks << Task.new(YAML.load_file("#{tasks_path}/#{task_file}"), task_file, self)
|
251
|
+
end
|
252
|
+
|
253
|
+
puts "Here is available tasks inside #{@sensible_folder}/#{tasks_folder}" if @opts.verbose
|
254
|
+
|
255
|
+
pastel = Pastel.new
|
256
|
+
for task in tasks
|
257
|
+
Logger.log("#{pastel.blue.bold(task.file_name)}: #{task.name}")
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def task_create(task_name)
|
262
|
+
tasks_path = "#{@sensible_folder}/#{@tasks_folder}"
|
263
|
+
task_file_path = "#{tasks_path}/#{task_name}.yml"
|
264
|
+
|
265
|
+
# Make sure the task don't already exist
|
266
|
+
if File.exist?(task_file_path)
|
267
|
+
pastel = Pastel.new
|
268
|
+
Logger.error("Task: #{pastel.bold(task_name)} already exist!")
|
269
|
+
exit(1)
|
270
|
+
end
|
271
|
+
|
272
|
+
# Create the task yaml file
|
273
|
+
File.open(task_file_path, "w") do |f|
|
274
|
+
f.write(<<~EOF)
|
275
|
+
---
|
276
|
+
name:
|
277
|
+
description:
|
278
|
+
install:
|
279
|
+
EOF
|
280
|
+
end
|
281
|
+
|
282
|
+
pastel = Pastel.new
|
283
|
+
Logger.success("Created the task: #{pastel.bold("#{task_name}.yml")}")
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
data/sensible.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
preTasks:
|
2
|
+
- test
|
3
|
+
|
4
|
+
packages:
|
5
|
+
- name: httpd
|
6
|
+
- name: htop
|
7
|
+
- name: btop
|
8
|
+
env:
|
9
|
+
- prod
|
10
|
+
- name: rvm
|
11
|
+
check: which rvm
|
12
|
+
|
13
|
+
requirements:
|
14
|
+
- name: Test requirement
|
15
|
+
check: which node
|
16
|
+
install: echo "install"
|
17
|
+
|
18
|
+
- name: Test requirement that fails
|
19
|
+
check: exit 1
|
20
|
+
install: exit 1
|
21
|
+
|
22
|
+
postTasks:
|
23
|
+
- another
|
data/sig/sensible.rbs
ADDED
metadata
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sensible-cli
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Mikkel Jensen
|
8
|
+
bindir: exe
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-06-13 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: shellopts
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: tty-which
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: tty-command
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: pastel
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
type: :runtime
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: tty-spinner
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
type: :runtime
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: tty-prompt
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
type: :runtime
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
description: A small tool to manage projects, making shell scripts easier to manage,
|
97
|
+
and faster to setup projects!
|
98
|
+
email:
|
99
|
+
- dasmikko@gmail.com
|
100
|
+
executables:
|
101
|
+
- sensible
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- ".rspec"
|
106
|
+
- ".sensible/tasks/another.yml"
|
107
|
+
- ".sensible/tasks/test.yml"
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- exe/sensible
|
111
|
+
- lib/sensible.rb
|
112
|
+
- lib/sensible/log.rb
|
113
|
+
- lib/sensible/package.rb
|
114
|
+
- lib/sensible/parse.rb
|
115
|
+
- lib/sensible/requirement.rb
|
116
|
+
- lib/sensible/task.rb
|
117
|
+
- lib/sensible/version.rb
|
118
|
+
- sensible.yml
|
119
|
+
- sig/sensible.rbs
|
120
|
+
homepage: https://github.com/dasmikko/sensible-ruby
|
121
|
+
licenses: []
|
122
|
+
metadata:
|
123
|
+
homepage_uri: https://github.com/dasmikko/sensible-ruby
|
124
|
+
rdoc_options: []
|
125
|
+
require_paths:
|
126
|
+
- lib
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: 3.1.0
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
requirements: []
|
138
|
+
rubygems_version: 3.6.5
|
139
|
+
specification_version: 4
|
140
|
+
summary: A small tool to manage projects.
|
141
|
+
test_files: []
|