asgard 0.1.2 → 0.3.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 +4 -4
- data/.github/workflows/deploy-github-pages.yml +52 -0
- data/.loki +7 -9
- data/.rubocop.yml +157 -0
- data/CHANGELOG.md +93 -2
- data/CLAUDE.md +19 -9
- data/README.md +110 -58
- data/Rakefile +83 -4
- data/docs/api.md +204 -0
- data/docs/assets/css/custom.css +93 -0
- data/docs/assets/images/asgard.jpg +0 -0
- data/docs/changelog.md +104 -0
- data/docs/dependencies.md +221 -0
- data/docs/environment.md +129 -0
- data/docs/examples.md +140 -0
- data/docs/getting-started.md +179 -0
- data/docs/helpers.md +178 -0
- data/docs/index.md +85 -0
- data/docs/options.md +180 -0
- data/docs/shell.md +208 -0
- data/docs/subcommands.md +181 -0
- data/docs/task-files.md +407 -0
- data/docs/tasks.md +286 -0
- data/docs/variables.md +338 -0
- data/examples/.env +4 -0
- data/examples/.loki +24 -2
- data/examples/concurrent.loki +58 -0
- data/examples/db_subcommands.loki +3 -3
- data/examples/env_usage.loki +27 -0
- data/examples/kitchen_sink.loki +48 -15
- data/examples/server_subcommands.loki +3 -3
- data/examples/subdir/.loki +12 -0
- data/examples/subdir/import_demo.loki +14 -0
- data/examples/subdir/import_up_demo.loki +18 -0
- data/lib/asgard/base.rb +159 -54
- data/lib/asgard/kernel_methods.rb +77 -0
- data/lib/asgard/shell.rb +9 -6
- data/lib/asgard/tasks.rb +0 -5
- data/lib/asgard/version.rb +1 -1
- data/lib/asgard.rb +7 -18
- data/mkdocs.yml +164 -0
- metadata +32 -4
data/docs/options.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Options & Flags
|
|
2
|
+
|
|
3
|
+
Asgard tasks use the full Thor option system. Options declared with `method_option` (alias: `option`) apply to a single task. Options declared with `class_option` apply to every task in the class. Asgard ships with three built-in `class_option` declarations on `Tasks`: `--debug`, `--verbose`, and `--version`.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Per-Task Options
|
|
8
|
+
|
|
9
|
+
`method_option` (or its alias `option`) declares an option for the immediately following task:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
class Tasks
|
|
13
|
+
desc "deploy ENV", "Deploy to ENV"
|
|
14
|
+
method_option :branch,
|
|
15
|
+
aliases: "-b",
|
|
16
|
+
type: :string,
|
|
17
|
+
default: "main",
|
|
18
|
+
desc: "Git branch to deploy"
|
|
19
|
+
method_option :dry_run,
|
|
20
|
+
aliases: "-n",
|
|
21
|
+
type: :boolean,
|
|
22
|
+
default: false,
|
|
23
|
+
desc: "Print commands without running"
|
|
24
|
+
def deploy(env = "staging")
|
|
25
|
+
if options[:dry_run]
|
|
26
|
+
puts "Would deploy #{options[:branch]} to #{env}"
|
|
27
|
+
else
|
|
28
|
+
sh "cap #{env} deploy BRANCH=#{options[:branch]}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Access option values inside the task body via `options[:name]` (a hash keyed by symbol).
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Class Options (Shared Across All Tasks)
|
|
39
|
+
|
|
40
|
+
`class_option` defines an option available on every task in the class. Add your own to complement the built-in ones:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
class Tasks
|
|
44
|
+
class_option :dry_run,
|
|
45
|
+
aliases: "-n",
|
|
46
|
+
type: :boolean,
|
|
47
|
+
default: false,
|
|
48
|
+
desc: "Print commands without running"
|
|
49
|
+
|
|
50
|
+
class_option :env,
|
|
51
|
+
type: :string,
|
|
52
|
+
default: "development",
|
|
53
|
+
enum: %w[development staging production],
|
|
54
|
+
desc: "Target environment"
|
|
55
|
+
|
|
56
|
+
desc "Deploy the application"
|
|
57
|
+
def deploy
|
|
58
|
+
if options[:dry_run]
|
|
59
|
+
puts "Would deploy to #{options[:env]}"
|
|
60
|
+
else
|
|
61
|
+
sh "cap #{options[:env]} deploy"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
desc "Run database migrations"
|
|
66
|
+
def migrate
|
|
67
|
+
sh "rails db:migrate RAILS_ENV=#{options[:env]}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Both `deploy` and `migrate` automatically accept `--dry-run` and `--env`.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Built-in Flags
|
|
77
|
+
|
|
78
|
+
`Tasks` ships with three built-in class options and a version flag:
|
|
79
|
+
|
|
80
|
+
### `--version`
|
|
81
|
+
|
|
82
|
+
Prints `Asgard::VERSION` and exits. Implemented as the `_version` method with the `_` prefix convention (gem-owned, blocked from direct CLI invocation):
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
asgard --version
|
|
86
|
+
# 0.1.2
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `--debug`
|
|
90
|
+
|
|
91
|
+
A `class_option :debug` of type `:boolean`. When passed, sets `$DEBUG = true` before the task body runs (via the `invoke_command` hook in `Asgard::Base`):
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
asgard build --debug
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Inside the task, use the `debug?` predicate:
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
def build
|
|
101
|
+
sh "rake build"
|
|
102
|
+
sh "rake build --trace" if debug?
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### `--verbose`
|
|
107
|
+
|
|
108
|
+
A `class_option :verbose` of type `:boolean`. When passed, sets `$VERBOSE = true` before the task body runs:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
asgard test --verbose
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Inside the task, use the `verbose?` predicate:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
def test
|
|
118
|
+
flags = verbose? ? "--verbose" : ""
|
|
119
|
+
sh "bundle exec rake test #{flags}"
|
|
120
|
+
end
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## `debug?` and `verbose?` Predicates
|
|
126
|
+
|
|
127
|
+
Both are private methods on `Tasks`, thin wrappers around the global variables:
|
|
128
|
+
|
|
129
|
+
```ruby
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
def debug? = $DEBUG
|
|
133
|
+
def verbose? = $VERBOSE
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
They are available in every task body and in subcommand classes that inherit from `Tasks`. Because `--debug` and `--verbose` are `class_option` declarations (not standalone commands), they work as modifiers alongside any task:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
asgard build --debug --verbose
|
|
140
|
+
asgard deploy production --verbose
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Option Types Reference
|
|
146
|
+
|
|
147
|
+
| Type | CLI Example | Ruby Value |
|
|
148
|
+
|---|---|---|
|
|
149
|
+
| `:string` | `--branch main` | `"main"` |
|
|
150
|
+
| `:boolean` | `--force` / `--no-force` | `true` / `false` |
|
|
151
|
+
| `:numeric` | `--count 3` | `3` |
|
|
152
|
+
| `:array` | `--tags foo bar baz` | `["foo", "bar", "baz"]` |
|
|
153
|
+
| `:hash` | `--vars KEY:val FOO:bar` | `{"KEY"=>"val", "FOO"=>"bar"}` |
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Option Keys Reference
|
|
158
|
+
|
|
159
|
+
| Key | Applies to | Description |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| `aliases` | `method_option`, `class_option` | Short-form flag string, e.g. `"-b"` |
|
|
162
|
+
| `type` | `method_option`, `class_option` | One of the five types above |
|
|
163
|
+
| `default` | `method_option`, `class_option` | Value used when the flag is omitted |
|
|
164
|
+
| `required` | `method_option` | Raises an error if the flag is missing |
|
|
165
|
+
| `desc` | `method_option`, `class_option` | One-line description shown in help |
|
|
166
|
+
| `enum` | `method_option`, `class_option` | Allowed values; validated by Thor |
|
|
167
|
+
| `banner` | `method_option` | Placeholder shown in help for the value slot |
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## `_` Prefix Convention
|
|
172
|
+
|
|
173
|
+
Methods whose names start with `_` are considered gem-owned in Asgard's naming convention. `run!` guards against invoking them directly from the CLI:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
asgard _version
|
|
177
|
+
# asgard: unknown command '_version'
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
If you define your own methods on `Tasks`, avoid the `_` prefix to prevent them from being silently blocked.
|
data/docs/shell.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Shell Helpers
|
|
2
|
+
|
|
3
|
+
Asgard provides two methods for running shell commands and scripts from within task bodies: `sh` for shell commands and heredocs, and `shebang` for polyglot scripts. Both are provided by `Asgard::Shell` and mixed into every `Tasks` instance.
|
|
4
|
+
|
|
5
|
+
Both methods exit with the command's status code on failure — they do not raise Ruby exceptions.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## `sh` — Run Shell Commands
|
|
10
|
+
|
|
11
|
+
### Single-Line Command
|
|
12
|
+
|
|
13
|
+
Pass a single-line string to run it via `system`:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
class Tasks
|
|
17
|
+
desc "Compile the project"
|
|
18
|
+
def build = sh "rake build"
|
|
19
|
+
|
|
20
|
+
desc "Remove build artifacts"
|
|
21
|
+
def clean = sh "rm -rf dist/ tmp/"
|
|
22
|
+
end
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
By default, `sh` prints the command before running it:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
rake build
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Multi-Line Heredoc
|
|
32
|
+
|
|
33
|
+
Pass a multiline string (e.g., a heredoc) to run it as a single `bash -c` script. All lines execute in the same shell session, so variable assignments carry across lines:
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
class Tasks
|
|
37
|
+
desc "Bootstrap the development environment"
|
|
38
|
+
def setup
|
|
39
|
+
sh <<~SHELL
|
|
40
|
+
brew install redis postgresql
|
|
41
|
+
brew services start redis
|
|
42
|
+
bundle install
|
|
43
|
+
rails db:setup
|
|
44
|
+
SHELL
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Asgard detects the newline and automatically routes multiline scripts through `bash -c`.
|
|
50
|
+
|
|
51
|
+
### Silent Mode
|
|
52
|
+
|
|
53
|
+
Pass `silent: true` to suppress the command echo. The command still runs and still exits on failure; it just doesn't print the command text first:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
class Tasks
|
|
57
|
+
desc "Compile (quiet)"
|
|
58
|
+
def build = sh "rake build", silent: true
|
|
59
|
+
|
|
60
|
+
desc "Print environment info without noise"
|
|
61
|
+
def info
|
|
62
|
+
sh "printenv | grep APP_", silent: true
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Exit on Failure
|
|
68
|
+
|
|
69
|
+
`sh` always calls `exit($?.exitstatus)` if the command fails. There is no rescue path — a failing command terminates the `asgard` process. This is intentional: failed steps should stop the pipeline rather than silently continue.
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
class Tasks
|
|
73
|
+
depends_on :test
|
|
74
|
+
desc "Test then release"
|
|
75
|
+
def release
|
|
76
|
+
sh "bundle exec rake release"
|
|
77
|
+
# Never reached if rake release fails
|
|
78
|
+
puts "Released!"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## `shebang` — Polyglot Scripts
|
|
86
|
+
|
|
87
|
+
`shebang` writes the script body to a tempfile with the appropriate extension and executes it with the specified interpreter. Use it to embed Python, Node.js, Ruby, Perl, or any other interpreter directly in a task:
|
|
88
|
+
|
|
89
|
+
### Python
|
|
90
|
+
|
|
91
|
+
```ruby
|
|
92
|
+
class Tasks
|
|
93
|
+
desc "Run Python data analysis"
|
|
94
|
+
def analyze
|
|
95
|
+
shebang :python3, <<~PYTHON
|
|
96
|
+
import json
|
|
97
|
+
data = json.load(open("results.json"))
|
|
98
|
+
print(f"Total: {sum(data.values())}")
|
|
99
|
+
PYTHON
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Node.js
|
|
105
|
+
|
|
106
|
+
```ruby
|
|
107
|
+
class Tasks
|
|
108
|
+
desc "Build frontend assets with esbuild"
|
|
109
|
+
def bundle_assets
|
|
110
|
+
shebang :node, <<~JS
|
|
111
|
+
const esbuild = require("esbuild")
|
|
112
|
+
esbuild.buildSync({
|
|
113
|
+
entryPoints: ["src/app.js"],
|
|
114
|
+
bundle: true,
|
|
115
|
+
outfile: "dist/app.js"
|
|
116
|
+
})
|
|
117
|
+
JS
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Ruby
|
|
123
|
+
|
|
124
|
+
```ruby
|
|
125
|
+
class Tasks
|
|
126
|
+
desc "Transform data with Ruby"
|
|
127
|
+
def transform
|
|
128
|
+
shebang :ruby, <<~RUBY
|
|
129
|
+
require "json"
|
|
130
|
+
data = JSON.parse(File.read("input.json"))
|
|
131
|
+
File.write("output.json", JSON.pretty_generate(data.transform_values(&:upcase)))
|
|
132
|
+
RUBY
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Bash
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
class Tasks
|
|
141
|
+
desc "Run a bash provisioning script"
|
|
142
|
+
def provision
|
|
143
|
+
shebang :bash, <<~BASH
|
|
144
|
+
set -euo pipefail
|
|
145
|
+
apt-get update
|
|
146
|
+
apt-get install -y curl wget git
|
|
147
|
+
echo "Provisioned at $(date)"
|
|
148
|
+
BASH
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Supported Interpreters
|
|
156
|
+
|
|
157
|
+
| Symbol | File Extension | Interpreter |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| `:python3` | `.py` | `python3` |
|
|
160
|
+
| `:python` | `.py` | `python` |
|
|
161
|
+
| `:node` | `.js` | `node` |
|
|
162
|
+
| `:ruby` | `.rb` | `ruby` |
|
|
163
|
+
| `:perl` | `.pl` | `perl` |
|
|
164
|
+
| `:bash` | `.sh` | `bash` |
|
|
165
|
+
| `:sh` | `.sh` | `sh` |
|
|
166
|
+
| Any other symbol | `.tmp` | Passed directly to `system` |
|
|
167
|
+
|
|
168
|
+
!!! note
|
|
169
|
+
Unknown interpreter symbols get a `.tmp` extension and are passed to `system` directly. This makes it easy to use interpreters not in the table above:
|
|
170
|
+
|
|
171
|
+
```ruby
|
|
172
|
+
shebang :lua, <<~LUA
|
|
173
|
+
print("Hello from Lua!")
|
|
174
|
+
LUA
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Silent Mode
|
|
178
|
+
|
|
179
|
+
`shebang` also accepts `silent: true`, though in practice the interpreter itself controls what is printed:
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
def analyze
|
|
183
|
+
shebang :python3, script_body, silent: true
|
|
184
|
+
end
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Combining `sh` and `shebang`
|
|
190
|
+
|
|
191
|
+
You can mix both in the same task:
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
class Tasks
|
|
195
|
+
desc "Run a mixed shell + Python pipeline"
|
|
196
|
+
def pipeline
|
|
197
|
+
sh "bundle exec rake build"
|
|
198
|
+
|
|
199
|
+
shebang :python3, <<~PYTHON
|
|
200
|
+
import subprocess
|
|
201
|
+
result = subprocess.run(["./bin/validate"], capture_output=True, text=True)
|
|
202
|
+
print(result.stdout)
|
|
203
|
+
PYTHON
|
|
204
|
+
|
|
205
|
+
sh "rake deploy"
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
```
|
data/docs/subcommands.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Subcommands
|
|
2
|
+
|
|
3
|
+
Subcommands group related tasks under a common namespace, giving you commands like `asgard server start` or `asgard db migrate`. Asgard uses Thor's `subcommand` method for this, with one important convention: subcommand classes inherit from `Tasks` rather than from `Asgard::Base` or `Thor` directly.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Basic Pattern
|
|
8
|
+
|
|
9
|
+
Define a subcommand class that inherits from `Tasks`, then register it on the top-level `Tasks` class with `subcommand`:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
class DeployCommands < Tasks
|
|
13
|
+
desc "Deploy to staging"
|
|
14
|
+
def staging = sh "cap staging deploy"
|
|
15
|
+
|
|
16
|
+
desc "Deploy to production"
|
|
17
|
+
def production = sh "cap production deploy"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class Tasks
|
|
21
|
+
desc "deploy SUBCOMMAND", "Deploy the application"
|
|
22
|
+
subcommand "deploy", DeployCommands
|
|
23
|
+
end
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
asgard deploy # shows deploy subcommand help
|
|
28
|
+
asgard deploy staging
|
|
29
|
+
asgard deploy production
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Why Inherit from `Tasks`?
|
|
35
|
+
|
|
36
|
+
Inheriting from `Tasks` (rather than `Asgard::Base` or `Thor`) gives the subcommand class access to:
|
|
37
|
+
|
|
38
|
+
- `sh` and `shebang` shell helpers (from `Asgard::Shell`)
|
|
39
|
+
- `depends_on` for dependency declarations
|
|
40
|
+
- `dotenv` for environment loading
|
|
41
|
+
- `@@` class variables declared on `Tasks` (visible in all subclasses)
|
|
42
|
+
- The built-in `--debug` and `--verbose` class options
|
|
43
|
+
- The `debug?` and `verbose?` private predicates
|
|
44
|
+
- Any private helpers or `no_commands` methods defined on `Tasks`
|
|
45
|
+
|
|
46
|
+
!!! warning
|
|
47
|
+
Do **not** redeclare `class_option :debug` or `class_option :verbose` in your subcommand class — they are already inherited from `Tasks`. Redeclaring them causes duplicate option errors.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## depends_on Within a Subcommand
|
|
52
|
+
|
|
53
|
+
`depends_on` works exactly as at the top level, scoped to the subcommand's own dependency graph:
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
class DBCommands < Tasks
|
|
57
|
+
desc "Run pending migrations"
|
|
58
|
+
def migrate = sh "rails db:migrate"
|
|
59
|
+
|
|
60
|
+
desc "Load seed data"
|
|
61
|
+
def seed = sh "rails db:seed"
|
|
62
|
+
|
|
63
|
+
depends_on :migrate, :seed
|
|
64
|
+
desc "Migrate then seed"
|
|
65
|
+
def reset = puts "Done."
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class Tasks
|
|
69
|
+
desc "db SUBCOMMAND", "Manage the database"
|
|
70
|
+
subcommand "db", DBCommands
|
|
71
|
+
end
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
asgard db reset # migrate → seed → reset
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Server Subcommand Example
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
class ServerCommands < Tasks
|
|
84
|
+
desc "start [PORT]", "Start the server on PORT (default: 3000)"
|
|
85
|
+
option :daemon, aliases: "-d", type: :boolean, default: false, desc: "Run as a background daemon"
|
|
86
|
+
option :workers, aliases: "-w", type: :numeric, default: 2, desc: "Number of worker processes"
|
|
87
|
+
option :log, type: :string, default: "log/server.log",
|
|
88
|
+
banner: "FILE", desc: "Write logs to FILE"
|
|
89
|
+
def start(port = "3000")
|
|
90
|
+
flags = []
|
|
91
|
+
flags << "--daemon" if options[:daemon]
|
|
92
|
+
flags << "--workers #{options[:workers]}"
|
|
93
|
+
flags << "--log #{options[:log]}"
|
|
94
|
+
sh "puma -p #{port} #{flags.join(' ')}"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
desc "Stop the running server"
|
|
98
|
+
option :force, aliases: "-f", type: :boolean, default: false, desc: "Force-kill without draining"
|
|
99
|
+
def stop
|
|
100
|
+
options[:force] ? sh "pkill -9 puma" : sh "pumactl stop"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
desc "Show server status"
|
|
104
|
+
def status = sh "pumactl stats"
|
|
105
|
+
|
|
106
|
+
depends_on :stop, :start
|
|
107
|
+
desc "restart [PORT]", "Stop then start"
|
|
108
|
+
def restart(port = "3000") = puts "Server restarted on :#{port}."
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
class Tasks
|
|
112
|
+
desc "server SUBCOMMAND", "Manage the application server"
|
|
113
|
+
subcommand "server", ServerCommands
|
|
114
|
+
end
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
asgard server start
|
|
119
|
+
asgard server start 4000 --workers 4 --daemon
|
|
120
|
+
asgard server stop --force
|
|
121
|
+
asgard server restart
|
|
122
|
+
asgard server status
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Scoped DSL
|
|
128
|
+
|
|
129
|
+
Each subcommand class has its own independent scope for:
|
|
130
|
+
|
|
131
|
+
- `desc` / `long_desc` — documentation strings
|
|
132
|
+
- `method_option` / `option` — per-command options
|
|
133
|
+
- `class_option` — options shared across the subcommand's tasks (in addition to inherited ones)
|
|
134
|
+
- `map` — aliases within the subcommand group
|
|
135
|
+
- `default_task` — which command runs when the subcommand is invoked with no further arguments
|
|
136
|
+
- `depends_on` — dependency declarations scoped to this class
|
|
137
|
+
|
|
138
|
+
These do not bleed into the parent `Tasks` class or other subcommand classes.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Multiple Subcommands
|
|
143
|
+
|
|
144
|
+
You can register as many subcommand groups as needed:
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
class ServerCommands < Tasks
|
|
148
|
+
# ... server tasks ...
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
class DBCommands < Tasks
|
|
152
|
+
# ... database tasks ...
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
class DeployCommands < Tasks
|
|
156
|
+
# ... deploy tasks ...
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
class Tasks
|
|
160
|
+
desc "server SUBCOMMAND", "Manage the server"; subcommand "server", ServerCommands
|
|
161
|
+
desc "db SUBCOMMAND", "Manage the database"; subcommand "db", DBCommands
|
|
162
|
+
desc "deploy SUBCOMMAND", "Deploy"; subcommand "deploy", DeployCommands
|
|
163
|
+
end
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Subcommands Across Files
|
|
169
|
+
|
|
170
|
+
Define each subcommand class in its own `.loki` file. Because all files reopen the same Ruby classes, the classes are available when `.loki` registers them:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
myproject/
|
|
174
|
+
.loki ← registers all subcommands
|
|
175
|
+
server_subcommands.loki ← defines ServerCommands
|
|
176
|
+
db_subcommands.loki ← defines DBCommands
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Because siblings loaded via `import "*.loki"` execute before `.loki`'s own class body, both `DBCommands` and `ServerCommands` are defined by the time `.loki` runs its `subcommand` calls.
|
|
180
|
+
|
|
181
|
+
See [`examples/server_subcommands.loki`](examples.md#server-subcommands) and [`examples/db_subcommands.loki`](examples.md#db-subcommands) for complete working examples.
|