asgard 0.5.1 → 0.6.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/CHANGELOG.md +179 -99
- data/README.md +141 -46
- data/bin/asgard +10 -0
- data/lib/asgard/base/dispatch.rb +257 -31
- data/lib/asgard/base/registry.rb +7 -3
- data/lib/asgard/base.rb +140 -9
- data/lib/asgard/declarations.rb +21 -0
- data/lib/asgard/doctor/task_sections.rb +11 -3
- data/lib/asgard/doctor.rb +12 -8
- data/lib/asgard/jobs/commands.rb +152 -0
- data/lib/asgard/jobs/declaration.rb +149 -0
- data/lib/asgard/jobs/launcher.rb +109 -0
- data/lib/asgard/jobs/state.rb +190 -0
- data/lib/asgard/jobs/stopper.rb +141 -0
- data/lib/asgard/jobs.rb +25 -0
- data/lib/asgard/kernel_methods.rb +19 -14
- data/lib/asgard/leading_flag.rb +10 -0
- data/lib/asgard/pid.rb +130 -0
- data/lib/asgard/schedule/commands.rb +39 -11
- data/lib/asgard/schedule/declaration.rb +139 -12
- data/lib/asgard/schedule/job_files.rb +24 -0
- data/lib/asgard/schedule/launchd.rb +55 -18
- data/lib/asgard/schedule/systemd.rb +69 -27
- data/lib/asgard/schedule/table.rb +34 -16
- data/lib/asgard/schedule.rb +49 -9
- data/lib/asgard/shell.rb +372 -44
- data/lib/asgard/state_dir.rb +84 -0
- data/lib/asgard/tasks.rb +8 -0
- data/lib/asgard/version.rb +1 -1
- data/lib/asgard/watch/changes.rb +26 -1
- data/lib/asgard/watch/commands.rb +52 -12
- data/lib/asgard/watch/daemon.rb +334 -43
- data/lib/asgard/watch/declaration.rb +135 -40
- data/lib/asgard/watch/dispatcher.rb +61 -10
- data/lib/asgard/watch/mailbox.rb +50 -0
- data/lib/asgard/watch/reporter.rb +50 -34
- data/lib/asgard/watch/spawn.rb +41 -16
- data/lib/asgard/watch/state.rb +93 -27
- data/lib/asgard/watch/worker.rb +44 -9
- data/lib/asgard/watch.rb +4 -11
- data/lib/asgard.rb +80 -14
- metadata +167 -186
- data/.envrc +0 -6
- data/.github/workflows/deploy-github-pages.yml +0 -52
- data/.loki +0 -39
- data/.reek.yml +0 -166
- data/.rubocop.yml +0 -163
- data/Archspec.rb +0 -24
- data/CLAUDE.md +0 -139
- data/COMMITS.md +0 -196
- data/bin/console +0 -11
- data/bin/setup +0 -8
- data/doc_tasks.loki +0 -21
- data/docs/api.md +0 -368
- data/docs/assets/css/custom.css +0 -93
- data/docs/assets/images/asgard.jpg +0 -0
- data/docs/changelog.md +0 -1
- data/docs/dependencies.md +0 -313
- data/docs/environment.md +0 -129
- data/docs/examples.md +0 -194
- data/docs/getting-started.md +0 -181
- data/docs/helpers.md +0 -262
- data/docs/index.md +0 -87
- data/docs/options.md +0 -224
- data/docs/schedule.md +0 -106
- data/docs/shell.md +0 -237
- data/docs/subcommands.md +0 -181
- data/docs/task-files.md +0 -407
- data/docs/tasks.md +0 -326
- data/docs/variables.md +0 -338
- data/docs/watch.md +0 -123
- data/examples/.env +0 -4
- data/examples/.envrc +0 -3
- data/examples/.loki +0 -24
- data/examples/bad.loki +0 -63
- data/examples/concurrent.loki +0 -58
- data/examples/confirm.loki +0 -48
- data/examples/db_subcommands.loki +0 -74
- data/examples/depends_on_block/bad/.loki +0 -51
- data/examples/depends_on_block/good/.loki +0 -83
- data/examples/do.loki +0 -45
- data/examples/env_usage.loki +0 -27
- data/examples/kitchen_sink.loki +0 -210
- data/examples/server_subcommands.loki +0 -65
- data/examples/subdir/.loki +0 -12
- data/examples/subdir/import_demo.loki +0 -14
- data/examples/subdir/import_up_demo.loki +0 -18
- data/examples/watcher_examples/.envrc +0 -3
- data/examples/watcher_examples/.loki +0 -54
- data/examples/watcher_examples/README.md +0 -112
- data/examples/watcher_examples/bin/hello +0 -3
- data/examples/watcher_examples/data/records.json +0 -1
- data/examples/watcher_examples/docs/drafts/ideas.md +0 -5
- data/examples/watcher_examples/docs/index.md +0 -4
- data/examples/watcher_examples/lib/greeter.rb +0 -7
- data/gem_tasks.loki +0 -51
- data/git.loki +0 -13
- data/mkdocs.yml +0 -166
- data/quality.loki +0 -266
- data/quality_rails.loki +0 -47
- data/xyzzy.loki +0 -12
data/docs/dependencies.md
DELETED
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
# Task Dependencies
|
|
2
|
-
|
|
3
|
-
`depends_on` declares what must run before a task. Asgard resolves the dependency graph at startup, validates it for cycles, and executes prerequisites automatically when a task is invoked.
|
|
4
|
-
|
|
5
|
-
!!! note
|
|
6
|
-
`desc` and `depends_on` are independent — either can come first. Both must appear before the `def`.
|
|
7
|
-
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
## How It Works
|
|
11
|
-
|
|
12
|
-
When you run `asgard <task>`, Asgard:
|
|
13
|
-
|
|
14
|
-
1. Validates the full dependency graph for circular references (fails fast with a clear error).
|
|
15
|
-
2. Resolves the dependency stages for the requested task in order.
|
|
16
|
-
3. Executes each stage — running parallel groups in native Ruby threads.
|
|
17
|
-
4. Runs the task itself after all prerequisites complete.
|
|
18
|
-
|
|
19
|
-
**Deduplication:** each task runs at most once per `asgard` invocation, regardless of how many other tasks declare it as a dependency. This is enforced thread-safely via a class-level `Set` and `Mutex`.
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## Sequential Dependencies
|
|
24
|
-
|
|
25
|
-
Bare symbols run one after another in the order declared:
|
|
26
|
-
|
|
27
|
-
```ruby
|
|
28
|
-
class Tasks
|
|
29
|
-
desc "Compile the project"
|
|
30
|
-
def build = sh "rake build"
|
|
31
|
-
|
|
32
|
-
depends_on :build
|
|
33
|
-
desc "Run the test suite"
|
|
34
|
-
def test = sh "rake test"
|
|
35
|
-
|
|
36
|
-
depends_on :test
|
|
37
|
-
desc "Publish the gem"
|
|
38
|
-
def release = sh "bundle exec rake release"
|
|
39
|
-
end
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
asgard release # build → test → release
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Multiple sequential dependencies in a single `depends_on` call run left to right:
|
|
47
|
-
|
|
48
|
-
```ruby
|
|
49
|
-
depends_on :clean, :build, :test
|
|
50
|
-
desc "Clean, build, and test"
|
|
51
|
-
def package = sh "rake package"
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
---
|
|
55
|
-
|
|
56
|
-
## Parallel Dependencies
|
|
57
|
-
|
|
58
|
-
Wrap symbols in an array to declare they can run concurrently. Asgard waits for all tasks in a parallel group to finish before moving to the next stage:
|
|
59
|
-
|
|
60
|
-
```ruby
|
|
61
|
-
class Tasks
|
|
62
|
-
desc "Check code style"
|
|
63
|
-
def lint = sh "bundle exec rubocop"
|
|
64
|
-
|
|
65
|
-
desc "Run type checks"
|
|
66
|
-
def typecheck = sh "bundle exec srb tc"
|
|
67
|
-
|
|
68
|
-
depends_on [:lint, :typecheck]
|
|
69
|
-
desc "Run tests (after lint and typecheck)"
|
|
70
|
-
def test = sh "bundle exec rake test"
|
|
71
|
-
end
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
asgard test # lint ∥ typecheck → test
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
Parallel groups run in native Ruby threads. For CPU-bound work, keep in mind the GVL; for I/O-bound work (shell commands, network), true concurrency is achieved.
|
|
79
|
-
|
|
80
|
-
---
|
|
81
|
-
|
|
82
|
-
## Mixed Sequential and Parallel
|
|
83
|
-
|
|
84
|
-
Mix bare symbols and arrays in a single `depends_on` call. Execution proceeds stage by stage — each stage completes before the next begins:
|
|
85
|
-
|
|
86
|
-
```ruby
|
|
87
|
-
class Tasks
|
|
88
|
-
desc "Install dependencies"; def setup = sh "bundle install"
|
|
89
|
-
desc "Check code style"; def lint = sh "bundle exec rubocop"
|
|
90
|
-
desc "Compile assets"; def build = sh "rake assets:precompile"
|
|
91
|
-
desc "Run tests"; def test = sh "bundle exec rake test"
|
|
92
|
-
desc "Post to Slack"; def notify = sh "curl $SLACK_WEBHOOK -d '{\"text\":\"done\"}'"
|
|
93
|
-
|
|
94
|
-
# setup first, then lint+build in parallel, then test, then notify
|
|
95
|
-
depends_on :setup, [:lint, :build], :test, :notify
|
|
96
|
-
desc "Full CI pipeline"
|
|
97
|
-
def ci = puts "CI complete"
|
|
98
|
-
end
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
asgard ci
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
Execution order:
|
|
106
|
-
|
|
107
|
-
```
|
|
108
|
-
setup
|
|
109
|
-
↓
|
|
110
|
-
lint ∥ build (concurrent)
|
|
111
|
-
↓
|
|
112
|
-
test
|
|
113
|
-
↓
|
|
114
|
-
notify
|
|
115
|
-
↓
|
|
116
|
-
ci
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
---
|
|
120
|
-
|
|
121
|
-
## Deduplication
|
|
122
|
-
|
|
123
|
-
Each task runs at most once per `asgard` invocation. If multiple tasks declare the same dependency, it executes only on its first encounter:
|
|
124
|
-
|
|
125
|
-
```ruby
|
|
126
|
-
class Tasks
|
|
127
|
-
desc "Install gems"
|
|
128
|
-
def setup = sh "bundle install"
|
|
129
|
-
|
|
130
|
-
depends_on :setup
|
|
131
|
-
desc "Run tests"
|
|
132
|
-
def test = sh "rake test"
|
|
133
|
-
|
|
134
|
-
depends_on :setup
|
|
135
|
-
desc "Check style"
|
|
136
|
-
def lint = sh "rubocop"
|
|
137
|
-
|
|
138
|
-
depends_on [:test, :lint]
|
|
139
|
-
desc "Test and lint (setup runs once)"
|
|
140
|
-
def ci = puts "done"
|
|
141
|
-
end
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
When `asgard ci` runs, `setup` executes once even though both `test` and `lint` declare it as a dependency. The deduplication set is managed with a `Mutex` so parallel groups are also safe.
|
|
145
|
-
|
|
146
|
-
---
|
|
147
|
-
|
|
148
|
-
## Transitive Dependencies
|
|
149
|
-
|
|
150
|
-
When a dependency has its own dependencies, Asgard resolves them recursively before running the dependent task. The deduplication set ensures each task runs at most once regardless of how many paths lead to it.
|
|
151
|
-
|
|
152
|
-
Consider this graph:
|
|
153
|
-
|
|
154
|
-
```ruby
|
|
155
|
-
class Tasks
|
|
156
|
-
desc "Fetch gems"
|
|
157
|
-
def setup = sh "bundle install"
|
|
158
|
-
|
|
159
|
-
depends_on :setup
|
|
160
|
-
desc "Compile assets"
|
|
161
|
-
def build = sh "rake assets:precompile"
|
|
162
|
-
|
|
163
|
-
desc "Check code style"
|
|
164
|
-
def lint = sh "bundle exec rubocop"
|
|
165
|
-
|
|
166
|
-
depends_on :build, :lint, :setup
|
|
167
|
-
desc "Run the full pipeline"
|
|
168
|
-
def ci = puts "Done."
|
|
169
|
-
end
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
`ci` declares three sequential dependencies: `build`, `lint`, `setup`. But `build` itself depends on `setup`. The effective execution order is:
|
|
173
|
-
|
|
174
|
-
```
|
|
175
|
-
setup ← run as build's prerequisite
|
|
176
|
-
↓
|
|
177
|
-
build
|
|
178
|
-
↓
|
|
179
|
-
lint
|
|
180
|
-
↓
|
|
181
|
-
(setup skipped — already done)
|
|
182
|
-
↓
|
|
183
|
-
ci
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
`setup` runs once — on its first encounter as `build`'s prerequisite. When `ci`'s own stage for `setup` is reached, the deduplication set skips it.
|
|
187
|
-
|
|
188
|
-
!!! tip
|
|
189
|
-
When a task is both a transitive dependency and a direct dependency, declare it only where it logically belongs — as a prerequisite of the task that needs it. Declaring it redundantly at the top level is harmless (deduplication handles it) but adds noise.
|
|
190
|
-
|
|
191
|
-
---
|
|
192
|
-
|
|
193
|
-
## Circular Dependency Detection
|
|
194
|
-
|
|
195
|
-
Asgard validates the full dependency graph using stdlib [TSort](https://docs.ruby-lang.org/en/master/TSort.html) before any task runs. A circular dependency produces a clean error and exits:
|
|
196
|
-
|
|
197
|
-
```ruby
|
|
198
|
-
class Tasks
|
|
199
|
-
depends_on :b
|
|
200
|
-
desc "Task A"; def a = puts "a"
|
|
201
|
-
|
|
202
|
-
depends_on :a
|
|
203
|
-
desc "Task B"; def b = puts "b"
|
|
204
|
-
end
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
```bash
|
|
208
|
-
asgard a
|
|
209
|
-
# asgard: circular dependency — TSort::Cyclic: ...
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
No backtrace is shown — just a single diagnostic line.
|
|
213
|
-
|
|
214
|
-
---
|
|
215
|
-
|
|
216
|
-
## depends_on Across Multiple Files
|
|
217
|
-
|
|
218
|
-
`depends_on` works across `.loki` files because all files reopen the same `class Tasks`. The dependency is recorded when the `def` is encountered, so load order matters:
|
|
219
|
-
|
|
220
|
-
```ruby
|
|
221
|
-
# build.loki
|
|
222
|
-
class Tasks
|
|
223
|
-
desc "Compile"
|
|
224
|
-
def build = sh "rake build"
|
|
225
|
-
end
|
|
226
|
-
|
|
227
|
-
# test.loki
|
|
228
|
-
class Tasks
|
|
229
|
-
depends_on :build # build.loki must be loaded first
|
|
230
|
-
desc "Test"
|
|
231
|
-
def test = sh "rake test"
|
|
232
|
-
end
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
Because `*.loki` files are loaded alphabetically when `import "*.loki"` is used, `build.loki` loads before `test.loki`. If you need to control load order precisely, use explicit `import` calls with full filenames rather than a glob.
|
|
236
|
-
|
|
237
|
-
---
|
|
238
|
-
|
|
239
|
-
## Dynamic Dependencies (Proc / Block Form)
|
|
240
|
-
|
|
241
|
-
`depends_on` normally takes a fixed list, recorded the moment the `def` right after it is encountered — which is why load order matters, as above. Pass a `Proc` or lambda instead — or, equivalently, a block — and that list is computed *later*, after every `.loki` file has finished loading, rather than at the point `depends_on` itself is evaluated:
|
|
242
|
-
|
|
243
|
-
```ruby
|
|
244
|
-
depends_on -> { [all_commands.keys.grep(/_check\z/).sort.map(&:to_sym)] }
|
|
245
|
-
desc "Run every *_check quality gate task in parallel"
|
|
246
|
-
def quality
|
|
247
|
-
# ...
|
|
248
|
-
end
|
|
249
|
-
|
|
250
|
-
depends_on { [all_commands.keys.grep(/_check\z/).sort.map(&:to_sym)] }
|
|
251
|
-
desc "Same thing, written as a block"
|
|
252
|
-
def quality2
|
|
253
|
-
# ...
|
|
254
|
-
end
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
This solves exactly the "load order matters" problem from the previous section: a plain array can only name tasks that already exist in `.loki` files loaded *before* this one. A Proc/block is resolved once every file has loaded, so it can safely reference a task defined in a file that hasn't been imported yet at the point `depends_on` is written — including one that only exists conditionally, e.g. a Rails-specific task file imported with `import "quality_rails.loki" if defined?(Rails)`. `depends_on` accepts task arguments *or* a block, never both — combining them raises `Asgard::Error`.
|
|
258
|
-
|
|
259
|
-
**Shape:** the Proc/block must return exactly what the plain-array form would receive as its splat arguments — an array of stages, each a `Symbol`/`String` (sequential) or an `Array` of `Symbol`/`String` (parallel group), nested no deeper than that. The example above returns `[[:a_check, :b_check, :c_check]]`: one stage, containing every matching task, all running in parallel — the same shape as `depends_on [:a_check, :b_check, :c_check]`. This shape is validated once the result comes back — a bad return value (wrong type, a stage that isn't a Symbol/String/Array, a leaf inside a parallel group that isn't a Symbol/String, or nesting more than one level deep) raises `Asgard::Error` naming the task and the offending value, instead of failing later with an opaque `NoMethodError`:
|
|
260
|
-
|
|
261
|
-
```bash
|
|
262
|
-
asgard quality
|
|
263
|
-
# asgard: depends_on proc/block for 'quality' returned invalid stage 123 (Integer); expected a Symbol, String, or Array of them
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
**When it runs:** once, when `validate_deps!` runs (right after the `.loki` chain finishes loading, before any task dispatches). The resolved result replaces the Proc/block in the dependency table, so cycle detection, undefined-task checks, and arity checks all run against the *resolved* list — a Proc/block that references an undefined task, or that itself introduces a cycle, is caught at startup exactly like a plain array would be:
|
|
267
|
-
|
|
268
|
-
```bash
|
|
269
|
-
asgard quality
|
|
270
|
-
# asgard: undefined task(s) in depends_on: ghost_check
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
**`self` inside the Proc/block:** since it's written directly in a `class Tasks` body, it lexically captures that class as `self` — so it can call `all_commands`, `_deps`, or any other class-level method bare, without a `self.class.` prefix, even though it's actually invoked later from inside `validate_deps!`.
|
|
274
|
-
|
|
275
|
-
**If the Proc/block raises**, the error is caught and re-raised as `Asgard::Error` naming the task it was declared for:
|
|
276
|
-
|
|
277
|
-
```
|
|
278
|
-
asgard: depends_on proc for 'quality' raised RuntimeError: boom
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
!!! tip
|
|
282
|
-
Reach for this only when the dependency list genuinely can't be known until every file has loaded — like "every task whose name ends in `_check`," discovered across several `.loki` files. For a fixed, known-upfront list, the plain array form is simpler and reads just as clearly.
|
|
283
|
-
|
|
284
|
-
---
|
|
285
|
-
|
|
286
|
-
## depends_on Inside Subcommands
|
|
287
|
-
|
|
288
|
-
`depends_on` works within subcommand classes exactly as it does at the top level. Dependency scope is per-class:
|
|
289
|
-
|
|
290
|
-
```ruby
|
|
291
|
-
class DBCommands < Tasks
|
|
292
|
-
desc "Run migrations"
|
|
293
|
-
def migrate = sh "rails db:migrate"
|
|
294
|
-
|
|
295
|
-
desc "Load seed data"
|
|
296
|
-
def seed = sh "rails db:seed"
|
|
297
|
-
|
|
298
|
-
depends_on :migrate, :seed
|
|
299
|
-
desc "Migrate then seed"
|
|
300
|
-
def reset = puts "Done."
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
class Tasks
|
|
304
|
-
desc "db SUBCOMMAND", "Manage the database"
|
|
305
|
-
subcommand "db", DBCommands
|
|
306
|
-
end
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
```bash
|
|
310
|
-
asgard db reset # migrate → seed → reset
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
See [Subcommands](subcommands.md) for the full guide.
|
data/docs/environment.md
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
# Environment Variables
|
|
2
|
-
|
|
3
|
-
Asgard provides the `dotenv` class method to load `.env` files into the process environment before tasks run. It is a thin wrapper around the [dotenv gem](https://github.com/bkeepers/dotenv).
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Basic Usage
|
|
8
|
-
|
|
9
|
-
Call `dotenv` inside the class body (not inside a task method) to load the default `.env` file:
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
class Tasks
|
|
13
|
-
dotenv # loads .env from the current working directory
|
|
14
|
-
|
|
15
|
-
desc "Print the app name from .env"
|
|
16
|
-
def check = puts env(:app_name)
|
|
17
|
-
end
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
### Load a Named File
|
|
21
|
-
|
|
22
|
-
Pass a file path string to load a specific file:
|
|
23
|
-
|
|
24
|
-
```ruby
|
|
25
|
-
class Tasks
|
|
26
|
-
dotenv ".env.local" # load a local override
|
|
27
|
-
dotenv ".env.staging" # load staging-specific vars
|
|
28
|
-
end
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
### Multiple Calls
|
|
32
|
-
|
|
33
|
-
Call `dotenv` multiple times to load several files. Each call merges the loaded variables into `ENV`. Later calls do not overwrite variables already set by earlier calls (standard dotenv behavior):
|
|
34
|
-
|
|
35
|
-
```ruby
|
|
36
|
-
class Tasks
|
|
37
|
-
dotenv # loads .env (base config)
|
|
38
|
-
dotenv ".env.local" # loads .env.local (local overrides)
|
|
39
|
-
end
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## When `dotenv` Runs
|
|
45
|
-
|
|
46
|
-
`dotenv` is a **class-level** call — it executes at Ruby class-load time, not when a task is invoked. This means:
|
|
47
|
-
|
|
48
|
-
1. Variables are available in `ENV` before any task method runs.
|
|
49
|
-
2. They are available during `depends_on` dependency resolution.
|
|
50
|
-
|
|
51
|
-
```ruby
|
|
52
|
-
class Tasks
|
|
53
|
-
dotenv
|
|
54
|
-
|
|
55
|
-
desc "Run migrations"
|
|
56
|
-
def migrate = sh "DATABASE_URL=#{env(:database_url)} rails db:migrate"
|
|
57
|
-
end
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
!!! warning
|
|
61
|
-
If `.env` does not exist, `dotenv` silently does nothing — it checks `File.exist?` before loading. There is no error for a missing file.
|
|
62
|
-
|
|
63
|
-
---
|
|
64
|
-
|
|
65
|
-
## File Not Found
|
|
66
|
-
|
|
67
|
-
Asgard calls `Dotenv.load(path)` only when `File.exist?(path)` is true. If the file is absent, the call is a no-op:
|
|
68
|
-
|
|
69
|
-
```ruby
|
|
70
|
-
class Tasks
|
|
71
|
-
dotenv ".env.local" # silently skipped if .env.local does not exist
|
|
72
|
-
end
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
This makes it safe to commit a `.env.local` line to your `.loki` without requiring every developer to create the file.
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
|
|
79
|
-
## Environment Variables vs. Class Variables
|
|
80
|
-
|
|
81
|
-
Use `dotenv` to bring external configuration into `ENV`. Use `@@` class variables for fixed values declared in the task file, and read from `ENV` directly in task bodies or helper methods when the value comes from the environment:
|
|
82
|
-
|
|
83
|
-
```ruby
|
|
84
|
-
class Tasks
|
|
85
|
-
dotenv
|
|
86
|
-
|
|
87
|
-
@@app_name ||= "myapp".freeze
|
|
88
|
-
|
|
89
|
-
desc "Start the server"
|
|
90
|
-
def start
|
|
91
|
-
port = ENV.fetch("PORT", "3000").to_i
|
|
92
|
-
sh "puma -b tcp://0.0.0.0:#{port} -w #{ENV.fetch('WORKERS', '2')}"
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
For `ENV` values used in multiple tasks, define a private helper method:
|
|
98
|
-
|
|
99
|
-
```ruby
|
|
100
|
-
class Tasks
|
|
101
|
-
dotenv
|
|
102
|
-
|
|
103
|
-
desc "Start the server"
|
|
104
|
-
def start = sh "puma -p #{port}"
|
|
105
|
-
|
|
106
|
-
desc "Show config"
|
|
107
|
-
def config = puts "#{@@app_name} on port #{port}"
|
|
108
|
-
|
|
109
|
-
private
|
|
110
|
-
|
|
111
|
-
def port = ENV.fetch("PORT", "3000").to_i
|
|
112
|
-
end
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
## Dotenv File Format
|
|
118
|
-
|
|
119
|
-
Standard dotenv file syntax applies:
|
|
120
|
-
|
|
121
|
-
```bash
|
|
122
|
-
# .env
|
|
123
|
-
APP_NAME=myapp
|
|
124
|
-
DATABASE_URL=postgres://localhost/myapp_development
|
|
125
|
-
REDIS_URL=redis://localhost:6379/0
|
|
126
|
-
PORT=3000
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
Multi-line values and quotes are supported per the dotenv gem's own documentation.
|
data/docs/examples.md
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
# Examples
|
|
2
|
-
|
|
3
|
-
The `examples/` directory in the Asgard repository contains complete, working `.loki` files that demonstrate every feature of the gem. You can use them as a standalone Asgard project or as copy-paste references.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Using the Examples Directory
|
|
8
|
-
|
|
9
|
-
The `examples/` directory contains its own `.loki` root marker (the gem's project-level `.loki`), which means you can run `asgard` from inside the `examples/` directory and all example files will be loaded:
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
git clone https://github.com/MadBomber/asgard.git
|
|
13
|
-
cd asgard/examples
|
|
14
|
-
asgard help
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Alternatively, copy individual example files into your own project's directory.
|
|
18
|
-
|
|
19
|
-
!!! note
|
|
20
|
-
Some examples (notably `concurrent.loki`) produce visible interleaved output to demonstrate real thread concurrency. They are designed to be run, not just read.
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## `kitchen_sink.loki`
|
|
25
|
-
|
|
26
|
-
**Path:** `examples/kitchen_sink.loki`
|
|
27
|
-
|
|
28
|
-
The most comprehensive example — demonstrates every Thor DSL feature available in Asgard:
|
|
29
|
-
|
|
30
|
-
- `@@` class variables for shared configuration values
|
|
31
|
-
- `dotenv` (commented out, ready to activate)
|
|
32
|
-
- `class_option` with `:boolean` and `:string` types, including `enum`
|
|
33
|
-
- `default_task` — sets the default command when `asgard` is run with no arguments
|
|
34
|
-
- `map` — short aliases for multiple tasks
|
|
35
|
-
- A basic task with no parameters
|
|
36
|
-
- A task with a positional parameter and default
|
|
37
|
-
- A task with `option` (the `method_option` alias)
|
|
38
|
-
- All five `method_option` types: `:string`, `:boolean`, `:numeric`, `:array`, `:hash`
|
|
39
|
-
- `required` option, `enum` validation, and `banner` customization
|
|
40
|
-
- `long_desc` with `\x5` line-break trick for formatted examples in help text
|
|
41
|
-
- Sequential `depends_on` (`:analyze` before `:spec`)
|
|
42
|
-
- Parallel `depends_on` (`[:analyze, :typecheck]` run concurrently)
|
|
43
|
-
- Mixed sequential + parallel `depends_on` (`:check`, `[:compile, :spec]`, `:pack`)
|
|
44
|
-
- `no_commands` block for a public helper excluded from CLI
|
|
45
|
-
- `private` methods for internal helpers
|
|
46
|
-
|
|
47
|
-
```bash
|
|
48
|
-
asgard help # see all tasks
|
|
49
|
-
asgard greet # default task
|
|
50
|
-
asgard hello Alice
|
|
51
|
-
asgard compile --jobs 4 --tags debug release --defines VERSION:2 MODE:fast
|
|
52
|
-
asgard deploy --strategy rolling
|
|
53
|
-
asgard report --format html --since 2024-01-01
|
|
54
|
-
asgard pipeline
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
---
|
|
58
|
-
|
|
59
|
-
## Server Subcommands
|
|
60
|
-
|
|
61
|
-
**Path:** `examples/server_subcommands.loki`
|
|
62
|
-
|
|
63
|
-
Demonstrates Thor subcommands with a server management group. Covers:
|
|
64
|
-
|
|
65
|
-
- Defining a subcommand class (`ServerCommands < Tasks`)
|
|
66
|
-
- Registering it with `subcommand "server", ServerCommands`
|
|
67
|
-
- Per-command options (`--daemon`, `--workers`, `--log`, `--force`, `--wait`)
|
|
68
|
-
- `depends_on` inside a subcommand group (`:stop` and `:start` before `:restart`)
|
|
69
|
-
|
|
70
|
-
```bash
|
|
71
|
-
asgard server help
|
|
72
|
-
asgard server start
|
|
73
|
-
asgard server start 4000 --workers 4 --daemon
|
|
74
|
-
asgard server stop --force
|
|
75
|
-
asgard server status
|
|
76
|
-
asgard server restart 4000
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
The `ServerCommands` class inherits from `Tasks`, giving it access to `sh`, `depends_on`, and the built-in `--debug`/`--verbose` flags.
|
|
80
|
-
|
|
81
|
-
---
|
|
82
|
-
|
|
83
|
-
## DB Subcommands
|
|
84
|
-
|
|
85
|
-
**Path:** `examples/db_subcommands.loki`
|
|
86
|
-
|
|
87
|
-
Demonstrates subcommands with more complex `depends_on` chaining within the group. Covers:
|
|
88
|
-
|
|
89
|
-
- `DBCommands < Tasks` with migrate, rollback, seed, reset, console, and status commands
|
|
90
|
-
- Multi-step `depends_on` chain: `rollback → migrate → seed → reset`
|
|
91
|
-
- `long_desc` with formatted examples inside a subcommand class
|
|
92
|
-
- `enum` validation on subcommand options
|
|
93
|
-
- Optional positional parameters (`migrate [VERSION]`, `rollback [STEPS]`, `seed [FILE]`)
|
|
94
|
-
|
|
95
|
-
```bash
|
|
96
|
-
asgard db help
|
|
97
|
-
asgard db migrate
|
|
98
|
-
asgard db migrate 20240101120000 --dry-run
|
|
99
|
-
asgard db rollback
|
|
100
|
-
asgard db rollback 3
|
|
101
|
-
asgard db seed --env staging
|
|
102
|
-
asgard db reset # rollback → migrate → seed → reset
|
|
103
|
-
asgard db console --env staging
|
|
104
|
-
asgard db status
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
## `concurrent.loki`
|
|
110
|
-
|
|
111
|
-
**Path:** `examples/concurrent.loki`
|
|
112
|
-
|
|
113
|
-
A focused demonstration of true concurrent execution via parallel `depends_on` groups:
|
|
114
|
-
|
|
115
|
-
- Three worker tasks (`worker_a`, `worker_b`, `worker_c`) each print a character repeatedly with random sleep delays
|
|
116
|
-
- All three run in parallel threads when `asgard finish` is invoked
|
|
117
|
-
- The interleaved output proves that real concurrency is occurring (not sequential batching)
|
|
118
|
-
- Uses `$stdout.sync = true` to ensure thread-safe immediate output flushing
|
|
119
|
-
|
|
120
|
-
```bash
|
|
121
|
-
asgard finish
|
|
122
|
-
# starting demo of concurrent task execution ...
|
|
123
|
-
# ABCBACBACBABCBACBACB (order varies every run)
|
|
124
|
-
# fini - the end of concurrent task demo
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
Execution order: `start` → `worker_a ∥ worker_b ∥ worker_c` → `finish`
|
|
128
|
-
|
|
129
|
-
This example is also useful as a test harness for verifying that parallel execution is working correctly on a given system.
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
## `do.loki`
|
|
134
|
-
|
|
135
|
-
**Path:** `examples/do.loki`
|
|
136
|
-
|
|
137
|
-
A plain user task that runs other tasks in sequence, the way `rake a b c` or `just a b c` does. No gem changes needed:
|
|
138
|
-
|
|
139
|
-
- Steps are comma-separated, so each step can carry its own arguments and options; with no commas, every word is a task of its own
|
|
140
|
-
- Each step is parsed by Thor as if it were typed on the command line
|
|
141
|
-
- The sequence stops at the first failure (a failing `sh`, or a step returning `:fail`)
|
|
142
|
-
|
|
143
|
-
```bash
|
|
144
|
-
asgard do greet finish
|
|
145
|
-
asgard do hello Alice, farewell Bob --formal
|
|
146
|
-
# Hello, Alice!
|
|
147
|
-
# Goodbye, Bob.
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
!!! note
|
|
151
|
-
Every task runs at most once per `asgard` invocation, so `asgard do hello Alice, hello Bob` greets only Alice. The same rule makes a dependency shared by several steps run once.
|
|
152
|
-
|
|
153
|
-
---
|
|
154
|
-
|
|
155
|
-
## `confirm.loki`
|
|
156
|
-
|
|
157
|
-
**Path:** `examples/confirm.loki`
|
|
158
|
-
|
|
159
|
-
A guard task. List `confirm` first in a dangerous task's `depends_on`, and the task runs only after the user answers yes:
|
|
160
|
-
|
|
161
|
-
```ruby
|
|
162
|
-
depends_on :confirm
|
|
163
|
-
desc "Drop and recreate the database"
|
|
164
|
-
def reset_database = sh "bin/rails db:reset"
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
```bash
|
|
168
|
-
asgard reset_database
|
|
169
|
-
# Really run `asgard reset_database`? [y/N] n
|
|
170
|
-
# asgard: aborted `asgard reset_database` (exit 1)
|
|
171
|
-
|
|
172
|
-
ASGARD_YES=1 asgard reset_database # no question, for scripts and CI
|
|
173
|
-
```
|
|
174
|
-
|
|
175
|
-
- Answering no **raises** `Asgard::Error`, which stops the dependent task. Returning `:fail` would not: a dependency's return value never stops the task that depends on it
|
|
176
|
-
- A dependency doesn't know which task asked for it, so the question shows the whole command line
|
|
177
|
-
- With no terminal (a schedule, the watcher, a pipe) it refuses unless `ASGARD_YES` is set
|
|
178
|
-
- `confirm` is declared with `hide: true`, so it stays out of `asgard help`
|
|
179
|
-
|
|
180
|
-
!!! note
|
|
181
|
-
`confirm` runs at most once per `asgard` invocation, so `asgard do reset_database, drop_cache` asks once for the whole line. Keep it in its own first stage: in `depends_on [:confirm, :backup]`, `backup` runs while the question waits.
|
|
182
|
-
|
|
183
|
-
---
|
|
184
|
-
|
|
185
|
-
## Summary
|
|
186
|
-
|
|
187
|
-
| File | Primary Focus |
|
|
188
|
-
|---|---|
|
|
189
|
-
| `kitchen_sink.loki` | Comprehensive Thor DSL reference — options, aliases, long_desc, depends_on |
|
|
190
|
-
| `server_subcommands.loki` | Subcommand groups, per-command options, depends_on in subcommands |
|
|
191
|
-
| `db_subcommands.loki` | Multi-step depends_on chains, enum validation, long_desc in subcommands |
|
|
192
|
-
| `concurrent.loki` | Parallel task execution, thread concurrency demonstration |
|
|
193
|
-
| `do.loki` | Running several tasks in sequence from one command line |
|
|
194
|
-
| `confirm.loki` | A guard dependency that asks before a dangerous task runs |
|