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/README.md
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
> [!INFO]
|
|
4
4
|
> See the [CHANGELOG](CHANGELOG.md) for the latest changes. The [examples directory](examples/) contains working `.loki` files demonstrating the full feature set.
|
|
5
5
|
|
|
6
|
+
> [!WARNING]
|
|
7
|
+
> Version 0.6.0 has breaking changes to scheduled tasks, watchers and `sh`. Read [Breaking Changes](https://madbomber.github.io/asgard/breaking-changes/) before you upgrade.
|
|
8
|
+
|
|
6
9
|
<br>
|
|
7
10
|
<table>
|
|
8
11
|
<tr>
|
|
@@ -13,27 +16,30 @@
|
|
|
13
16
|
<td width="60%" valign="top">
|
|
14
17
|
<strong>Key Features</strong><br>
|
|
15
18
|
|
|
16
|
-
- <strong>Thor-Powered CLI</strong
|
|
17
|
-
- <strong>Task Dependencies</strong
|
|
18
|
-
- <strong>Concurrent Execution</strong
|
|
19
|
-
- <strong>Subcommands</strong
|
|
20
|
-
- <strong>Variables</strong
|
|
21
|
-
- <strong>`helper` DSL</strong
|
|
22
|
-
- <strong>Shell Helpers</strong>
|
|
23
|
-
- <strong>Dotenv Support</strong
|
|
24
|
-
- <strong>Auto-Discovery</strong
|
|
25
|
-
- <strong>Multi-File Tasks</strong
|
|
26
|
-
- <strong>Scheduled Tasks</strong
|
|
27
|
-
- <strong>Watched Files</strong
|
|
28
|
-
- <strong>
|
|
19
|
+
- <strong>Thor-Powered CLI</strong>: every Thor DSL feature available inside <code>.loki</code> task files<br>
|
|
20
|
+
- <strong>Task Dependencies</strong>: sequential, parallel, and mixed dependency graphs via <code>depends_on</code><br>
|
|
21
|
+
- <strong>Concurrent Execution</strong>: parallel task groups run in native Ruby threads<br>
|
|
22
|
+
- <strong>Subcommands</strong>: group related tasks under a named namespace<br>
|
|
23
|
+
- <strong>Variables</strong>: shared configuration via Ruby class variables (<code>@@name</code>), visible across all tasks and subcommands<br>
|
|
24
|
+
- <strong>`helper` DSL</strong>: define a method once, available in both class-level DSL calls (<code>header</code>) and inside task instance methods<br>
|
|
25
|
+
- <strong>Shell Helpers</strong>: <code>sh</code> (alias <code>shebang</code>) runs a command or heredoc under the default shell, or under any registered interpreter when you name one<br>
|
|
26
|
+
- <strong>Dotenv Support</strong>: load <code>.env</code> files into the environment with <code>dotenv</code><br>
|
|
27
|
+
- <strong>Auto-Discovery</strong>: <code>.loki</code> root marker searched from CWD upward through parent directories<br>
|
|
28
|
+
- <strong>Multi-File Tasks</strong>: split tasks across <code>*.loki</code> files, loaded via <code>import</code> from your <code>.loki</code><br>
|
|
29
|
+
- <strong>Scheduled Tasks</strong>: run any task periodically under launchd (macOS) or systemd timers (Linux) with <code>schedule</code> and <code>asgard schedule install</code><br>
|
|
30
|
+
- <strong>Watched Files</strong>: run any task when files change with <code>watch</code> and <code>asgard watcher start</code>; the task reads what changed through <code>changes</code><br>
|
|
31
|
+
- <strong>Background Jobs</strong>: <code>sh "mkdocs serve", background: true</code> starts a job that outlives asgard; <code>asgard jobs</code> lists, stops, restarts and cleans them<br>
|
|
32
|
+
- <strong>Built-in Flags</strong>: <code>--debug</code>, <code>--verbose</code>, <code>--version</code>, and <code>--doctor</code> built-in class options; header/footer DSL for static help text<br>
|
|
29
33
|
</td>
|
|
30
34
|
</tr>
|
|
31
35
|
</table>
|
|
32
36
|
|
|
33
|
-
<p>Asgard is a <a href="https://github.com/rails/thor">Thor</a>-based task runner for Ruby projects. Define tasks in <code>.loki</code> files, declare dependencies between them, and let Asgard handle ordering and concurrent execution. Anything Thor can do
|
|
37
|
+
<p>Asgard is a <a href="https://github.com/rails/thor">Thor</a>-based task runner for Ruby projects. Define tasks in <code>.loki</code> files, declare dependencies between them, and let Asgard handle ordering and concurrent execution. Anything Thor can do (subcommands, typed options, argument validation) is available inside a <code>.loki</code> file.</p>
|
|
34
38
|
|
|
35
39
|
## Installation
|
|
36
40
|
|
|
41
|
+
Asgard requires Ruby 4.0 or later. Scheduled tasks and the file watcher need macOS or Linux, background jobs need a POSIX system (`fork`, `setsid` and `/bin/sh`), and installing compiles one native extension, so a C++17 compiler must be available (Xcode's command-line tools or a distribution's `build-essential` package).
|
|
42
|
+
|
|
37
43
|
```bash
|
|
38
44
|
gem install asgard
|
|
39
45
|
```
|
|
@@ -48,7 +54,7 @@ bundle add asgard
|
|
|
48
54
|
|
|
49
55
|
## Tasks
|
|
50
56
|
|
|
51
|
-
Every `.loki` file defines tasks as methods inside `class Tasks`. The `Tasks` class is pre-defined by the gem
|
|
57
|
+
Every `.loki` file defines tasks as methods inside `class Tasks`. The `Tasks` class is pre-defined by the gem; just reopen it and add methods.
|
|
52
58
|
|
|
53
59
|
### A task with no parameters
|
|
54
60
|
|
|
@@ -81,7 +87,7 @@ asgard hello Alice
|
|
|
81
87
|
|
|
82
88
|
### A task with a formal argument declaration
|
|
83
89
|
|
|
84
|
-
Use `argument` for richer metadata
|
|
90
|
+
Use `argument` for richer metadata: type checking, enums, and help text. **Warning: `argument` is a class-level declaration that applies to every task in the class**, not just the one below it. It is best suited for single-command CLIs or when every task genuinely shares the same positional input. In multi-task files, prefer method signature parameters instead.
|
|
85
91
|
|
|
86
92
|
```ruby
|
|
87
93
|
class Tasks
|
|
@@ -142,7 +148,7 @@ end
|
|
|
142
148
|
|
|
143
149
|
`depends_on` declares what must run before a task. Each dependency runs at most once per `asgard` invocation regardless of how many tasks declare it. Circular dependencies are caught at startup.
|
|
144
150
|
|
|
145
|
-
`desc` and `depends_on` are independent
|
|
151
|
+
`desc` and `depends_on` are independent: either can come first, but both must appear before `def`.
|
|
146
152
|
|
|
147
153
|
### Sequential dependencies
|
|
148
154
|
|
|
@@ -192,7 +198,7 @@ asgard test # lint ∥ typecheck → test
|
|
|
192
198
|
|
|
193
199
|
### Mixed sequential and parallel
|
|
194
200
|
|
|
195
|
-
Mix bare symbols (sequential) and arrays (parallel) in a single `depends_on` call. Execution proceeds stage by stage
|
|
201
|
+
Mix bare symbols (sequential) and arrays (parallel) in a single `depends_on` call. Execution proceeds stage by stage; each stage must complete before the next begins:
|
|
196
202
|
|
|
197
203
|
```ruby
|
|
198
204
|
class Tasks
|
|
@@ -223,7 +229,7 @@ asgard ci executes:
|
|
|
223
229
|
ci
|
|
224
230
|
```
|
|
225
231
|
|
|
226
|
-
`depends_on` also accepts a `Proc`/lambda (or, equivalently, a block) instead of a fixed list, resolved once every `.loki` file has finished loading rather than immediately
|
|
232
|
+
`depends_on` also accepts a `Proc`/lambda (or, equivalently, a block) instead of a fixed list, resolved once every `.loki` file has finished loading rather than immediately. This is useful when the list can't be known upfront, e.g. "every task whose name ends in `_check`," discovered across several files:
|
|
227
233
|
|
|
228
234
|
```ruby
|
|
229
235
|
depends_on -> { [all_commands.keys.grep(/_check\z/).map(&:to_sym)] }
|
|
@@ -232,7 +238,7 @@ def quality = puts "running every *_check task..."
|
|
|
232
238
|
depends_on { [all_commands.keys.grep(/_check\z/).map(&:to_sym)] }
|
|
233
239
|
def quality2 = puts "same, as a block..."
|
|
234
240
|
|
|
235
|
-
# do...end for a block spanning multiple statements
|
|
241
|
+
# do...end for a block spanning multiple statements; braces are for
|
|
236
242
|
# single-line blocks like the two above. The last expression is still what
|
|
237
243
|
# gets returned and validated.
|
|
238
244
|
depends_on do
|
|
@@ -244,7 +250,7 @@ end
|
|
|
244
250
|
def quality3 = puts "same, minus the slow checks, as a multi-line block..."
|
|
245
251
|
```
|
|
246
252
|
|
|
247
|
-
See [Dependencies](https://madbomber.github.io/asgard/dependencies/#dynamic-dependencies
|
|
253
|
+
See [Dependencies](https://madbomber.github.io/asgard/dependencies/#dynamic-dependencies) for the full explanation.
|
|
248
254
|
|
|
249
255
|
---
|
|
250
256
|
|
|
@@ -266,13 +272,13 @@ class Tasks
|
|
|
266
272
|
end
|
|
267
273
|
```
|
|
268
274
|
|
|
269
|
-
Class variables are visible in all task instance methods and in subcommand subclasses
|
|
275
|
+
Class variables are visible in all task instance methods and in subcommand subclasses, unlike class instance variables (`@name`), which are not accessible inside instance methods.
|
|
270
276
|
|
|
271
277
|
---
|
|
272
278
|
|
|
273
279
|
## Helper methods
|
|
274
280
|
|
|
275
|
-
Private methods are callable from any task in the same class but are never registered as commands
|
|
281
|
+
Private methods are callable from any task in the same class but are never registered as commands: they won't appear in `--help` output and can't be invoked from the CLI.
|
|
276
282
|
|
|
277
283
|
```ruby
|
|
278
284
|
class Tasks
|
|
@@ -297,6 +303,8 @@ class Tasks
|
|
|
297
303
|
def package(ver)
|
|
298
304
|
sh "tar czf pkg/myapp-#{ver}.tar.gz bin/"
|
|
299
305
|
end
|
|
306
|
+
|
|
307
|
+
def version = File.read("VERSION").strip
|
|
300
308
|
end
|
|
301
309
|
```
|
|
302
310
|
|
|
@@ -328,7 +336,7 @@ def self.version = File.read(...).match(...)[1].freeze
|
|
|
328
336
|
no_commands { private def version = self.class.version }
|
|
329
337
|
```
|
|
330
338
|
|
|
331
|
-
`helper` accepts positional arguments, keyword arguments, and blocks
|
|
339
|
+
`helper` accepts positional arguments, keyword arguments, and blocks: any signature valid in a Ruby method definition:
|
|
332
340
|
|
|
333
341
|
```ruby
|
|
334
342
|
helper(:tag) { |name, ver, prefix: "v"| "#{prefix}#{name}-#{ver}" }
|
|
@@ -365,7 +373,7 @@ Add static text above and below the command list in `asgard help` output:
|
|
|
365
373
|
|
|
366
374
|
```ruby
|
|
367
375
|
class Tasks
|
|
368
|
-
header "my-project
|
|
376
|
+
header "my-project: build & release tasks"
|
|
369
377
|
footer "See https://example.com/docs for details"
|
|
370
378
|
end
|
|
371
379
|
```
|
|
@@ -389,7 +397,10 @@ end
|
|
|
389
397
|
```
|
|
390
398
|
|
|
391
399
|
```
|
|
400
|
+
asgard v0.6.0 The Mighty Thor and Loki working for you
|
|
401
|
+
|
|
392
402
|
my-project
|
|
403
|
+
|
|
393
404
|
deploy targets: staging, production
|
|
394
405
|
|
|
395
406
|
Commands:
|
|
@@ -399,7 +410,12 @@ Options:
|
|
|
399
410
|
...
|
|
400
411
|
|
|
401
412
|
See runbook at wiki/deploy
|
|
413
|
+
|
|
402
414
|
Maintainer: you@example.com
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
Documentation ... https://madbomber.github.io/asgard
|
|
418
|
+
Github Repo ..... https://github.com/MadBomber/asgard
|
|
403
419
|
```
|
|
404
420
|
|
|
405
421
|
Header and footer text is only shown for the general `asgard help` page, not for `asgard help <command>`.
|
|
@@ -429,7 +445,7 @@ end
|
|
|
429
445
|
|
|
430
446
|
## Shell helpers
|
|
431
447
|
|
|
432
|
-
`sh` runs
|
|
448
|
+
`sh` writes a script body to a tempfile and runs it with a registered interpreter; given a script alone it uses the `default_shell` (bash), for one line or a heredoc. `shebang` is the same method under a longer name, which many authors reserve for scripts in another language. Both return `true` and exit with the command's status code on failure.
|
|
433
449
|
|
|
434
450
|
```ruby
|
|
435
451
|
class Tasks
|
|
@@ -462,7 +478,18 @@ class Tasks
|
|
|
462
478
|
end
|
|
463
479
|
```
|
|
464
480
|
|
|
465
|
-
|
|
481
|
+
Registered shells: `:bash`, `:sh`, `:python3`, `:python`, `:node`, `:ruby`, `:perl`. Any other symbol runs as itself from `PATH` with a `.tmp` extension, except zsh, fish, csh and tcsh, which get `.sh`, `.fish` and `.csh`. Register your own, with a pinned path or options in front of the script, with `shell`:
|
|
482
|
+
|
|
483
|
+
```ruby
|
|
484
|
+
class Tasks
|
|
485
|
+
shell :bash, "/opt/homebrew/bin/bash" # what sh and shebang :bash run
|
|
486
|
+
shell :strict, "bash -eu -o pipefail"
|
|
487
|
+
shell :uv, "uv run --script", ext: ".py"
|
|
488
|
+
default_shell :strict # what sh and a nameless shebang run
|
|
489
|
+
end
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
The name may be left out: `shell "/usr/local/bin/bash -c -w"` registers `:bash`, named after the executable, with the options as its `args`. `default_shell` registers and selects in one line the same way: `default_shell "/usr/local/bin/bash -c -w"`, or `default_shell :strict, "bash", args: "-eu -o pipefail"`. A bare `default_shell :bash` only selects and leaves a pinned entry alone.
|
|
466
493
|
|
|
467
494
|
Pass `silent: true` to both `sh` and `shebang` to suppress the script echo:
|
|
468
495
|
|
|
@@ -478,7 +505,7 @@ PY
|
|
|
478
505
|
|
|
479
506
|
## Environment variables
|
|
480
507
|
|
|
481
|
-
`dotenv` loads a `.env` file into the environment before tasks run. Use the `env` Kernel method to read environment variables inside task bodies
|
|
508
|
+
`dotenv` loads a `.env` file into the environment before tasks run. Use the `env` Kernel method to read environment variables inside task bodies. It accepts a symbol or string and upcases the key automatically:
|
|
482
509
|
|
|
483
510
|
```ruby
|
|
484
511
|
class Tasks
|
|
@@ -501,13 +528,13 @@ end
|
|
|
501
528
|
|------|-----------|
|
|
502
529
|
| `env(:port, "3000")` | Returns `"3000"` when `PORT` is unset |
|
|
503
530
|
| `env(:api_key)` | Raises `KeyError` when `API_KEY` is missing |
|
|
504
|
-
| `env("DATABASE_URL")` | String name works too
|
|
531
|
+
| `env("DATABASE_URL")` | String name works too; always upcased |
|
|
505
532
|
|
|
506
533
|
---
|
|
507
534
|
|
|
508
535
|
## Abbreviated command matching
|
|
509
536
|
|
|
510
|
-
Every task is a Thor command, so you don't have to type the full name
|
|
537
|
+
Every task is a Thor command, so you don't have to type the full name: Thor resolves any unambiguous prefix automatically, with no extra code:
|
|
511
538
|
|
|
512
539
|
```ruby
|
|
513
540
|
class Tasks
|
|
@@ -523,12 +550,12 @@ end
|
|
|
523
550
|
```
|
|
524
551
|
|
|
525
552
|
```bash
|
|
526
|
-
asgard b # same as: asgard build
|
|
553
|
+
asgard b # same as: asgard build (only task starting with "b")
|
|
527
554
|
asgard depl # Ambiguous command depl matches [deploy, deploy_staging]
|
|
528
|
-
asgard deploy # runs deploy
|
|
555
|
+
asgard deploy # runs deploy; an exact match always wins, even over a shorter ambiguous prefix
|
|
529
556
|
```
|
|
530
557
|
|
|
531
|
-
This is Thor's own dispatch behavior, not an Asgard feature
|
|
558
|
+
This is Thor's own dispatch behavior, not an Asgard feature; it applies to every task in every `.loki` file automatically. When a prefix matches more than one task, Thor lists the candidates instead of guessing; type enough of the name to disambiguate, or use `map` (below) to pin a short name that stays stable even if a later-added task would otherwise make it ambiguous.
|
|
532
559
|
|
|
533
560
|
---
|
|
534
561
|
|
|
@@ -632,10 +659,11 @@ asgard schedule stop NAME # stop one entry (persists across reboots and ins
|
|
|
632
659
|
asgard schedule start NAME # start a stopped entry, or install just this one
|
|
633
660
|
asgard schedule trigger NAME # run an installed entry now
|
|
634
661
|
asgard schedule log NAME [-f] # print (or follow) the entry's log
|
|
635
|
-
asgard schedule remove
|
|
662
|
+
asgard schedule remove [NAME] # remove one entry, or all of this project's
|
|
663
|
+
asgard schedule prune [-n] # remove every project's entries whose directory is gone (-n: list only)
|
|
636
664
|
```
|
|
637
665
|
|
|
638
|
-
`stop`, `start`, `trigger` and `
|
|
666
|
+
`stop`, `start`, `trigger`, `log` and `remove` also take an entry of another project
|
|
639
667
|
(`list --all` shows them); write `project/name` when two projects share a name.
|
|
640
668
|
|
|
641
669
|
Jobs run `asgard <task> [options]` from the directory holding `.loki`, with
|
|
@@ -667,6 +695,7 @@ Then manage the watcher with the built-in `watcher` command:
|
|
|
667
695
|
```bash
|
|
668
696
|
asgard watcher start # watch this project in the background
|
|
669
697
|
asgard watcher stop # stop it; a task already running finishes first
|
|
698
|
+
asgard watcher stop --force # stop it and end any running task
|
|
670
699
|
asgard watcher list # watches, daemon state, each entry's last run (--all: every project's)
|
|
671
700
|
asgard watcher preview # the command and environment each watch would run
|
|
672
701
|
asgard watcher log [-f] # print (or follow) the watcher's log
|
|
@@ -679,6 +708,44 @@ overlaps itself. Saving a `.loki` file restarts the watcher with the new
|
|
|
679
708
|
declarations. See [Watched Files](https://madbomber.github.io/asgard/watch/)
|
|
680
709
|
for every keyword, the environment, and file locations.
|
|
681
710
|
|
|
711
|
+
## Background jobs
|
|
712
|
+
|
|
713
|
+
A script runs in one of three modes. `sh "rake build"` is the foreground:
|
|
714
|
+
the task waits for it and a failure exits asgard. `sh "mkdocs serve",
|
|
715
|
+
background: true` starts a managed job: the task continues at once, the
|
|
716
|
+
job runs on in its own session with its output in a log, it is still
|
|
717
|
+
running when asgard exits, and `asgard jobs` manages it from then on. A
|
|
718
|
+
trailing `&` in the script is the third mode, detached: the shell exits,
|
|
719
|
+
asgard reports success and keeps no record, and the process is yours to
|
|
720
|
+
find and kill by hand.
|
|
721
|
+
|
|
722
|
+
```ruby
|
|
723
|
+
class Tasks
|
|
724
|
+
desc "doc_server", "Serve the documentation"
|
|
725
|
+
def doc_server = sh "mkdocs serve", background: true # job doc_server-1
|
|
726
|
+
|
|
727
|
+
desc "servers", "Bring up the development stack"
|
|
728
|
+
def servers
|
|
729
|
+
sh "docker compose up postgres", background: true # servers-1
|
|
730
|
+
sh "bin/rails server", background: :api # api
|
|
731
|
+
end
|
|
732
|
+
end
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
```bash
|
|
736
|
+
asgard jobs list # running, exited with a status, or gone (--all: every project's)
|
|
737
|
+
asgard jobs stop servers # by job name, or by task and the tasks it depends on
|
|
738
|
+
asgard jobs log api -f # print (or follow) one job's log
|
|
739
|
+
asgard jobs restart api # stop it, then run its owning task again
|
|
740
|
+
asgard jobs clean # forget every job that is no longer running
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
`stop` sends TERM to the job's whole process group and KILL after a
|
|
744
|
+
timeout. A job's pid is verified against its command line before anything
|
|
745
|
+
trusts it, so a pid reused after a reboot shows as `gone`, never as
|
|
746
|
+
running. See [Background Jobs](https://madbomber.github.io/asgard/jobs/)
|
|
747
|
+
for naming, the trampoline, and file locations.
|
|
748
|
+
|
|
682
749
|
## `method_option` types reference
|
|
683
750
|
|
|
684
751
|
| Type | CLI example | Ruby value |
|
|
@@ -687,7 +754,7 @@ for every keyword, the environment, and file locations.
|
|
|
687
754
|
| `:boolean` | `--force` / `--no-force` | `true` / `false` |
|
|
688
755
|
| `:numeric` | `--count 3` | `3` |
|
|
689
756
|
| `:array` | `--tags foo bar baz` | `["foo", "bar", "baz"]` |
|
|
690
|
-
| `:hash` | `--vars KEY:val FOO:bar` | `{"KEY"=>"val", "FOO"=>"bar"}` |
|
|
757
|
+
| `:hash` | `--vars KEY:val FOO:bar` | `{"KEY" => "val", "FOO" => "bar"}` |
|
|
691
758
|
|
|
692
759
|
Common `method_option` keys: `aliases`, `type`, `default`, `required`, `desc`, `enum`, `banner`.
|
|
693
760
|
|
|
@@ -706,7 +773,7 @@ myproject/
|
|
|
706
773
|
|
|
707
774
|
### Multiple files
|
|
708
775
|
|
|
709
|
-
Split tasks across files
|
|
776
|
+
Split tasks across files; each reopens `class Tasks`:
|
|
710
777
|
|
|
711
778
|
```
|
|
712
779
|
myproject/
|
|
@@ -749,7 +816,7 @@ class Tasks
|
|
|
749
816
|
end
|
|
750
817
|
```
|
|
751
818
|
|
|
752
|
-
|
|
819
|
+
`.loki` is the only file asgard loads on its own, so in a single-file project it holds the tasks. In a multi-file project it needs at least the `import` calls that load the others, and may hold nothing else.
|
|
753
820
|
|
|
754
821
|
### Explicit loading
|
|
755
822
|
|
|
@@ -767,14 +834,41 @@ end
|
|
|
767
834
|
|
|
768
835
|
---
|
|
769
836
|
|
|
770
|
-
##
|
|
837
|
+
## Tips
|
|
838
|
+
|
|
839
|
+
- **Start from working files.** The task files at this repository's root (`.loki`, `quality.loki`, `gem_tasks.loki`, `git.loki`, `doc_tasks.loki`) are a real multi-file project: quality gates that return `:pass` or `:fail`, a background job, a subcommand group, a `helper` feeding the `header`, and a deliberate override for `--doctor`. Copy what fits.
|
|
840
|
+
|
|
841
|
+
- **More examples.** [MadBomber/dev](https://github.com/MadBomber/dev) holds shared `.loki` libraries used across many repos: gem, git, Rails quality, schedule, status report and iTerm tasks. The [examples directory](examples/) here has one small file per feature.
|
|
842
|
+
|
|
843
|
+
- **One setup for every project.** Put the tasks, shells and helpers you want everywhere in `~/.loki`, then start each project's `.loki` with `import "~/.loki"`. Outside any project the same file is found by the walk up the tree, so those tasks work in any directory.
|
|
844
|
+
|
|
845
|
+
- **Give busy tasks a short name with `map`.** `map "t" => "test"` stays stable when a later task would otherwise make the prefix `t` ambiguous. Thor's prefix matching is handy; an alias is dependable.
|
|
846
|
+
|
|
847
|
+
- **Make bare `asgard` do the common thing.** `default_task :test` (or `:quality`, as this repo does) saves typing on the task you run most.
|
|
848
|
+
|
|
849
|
+
- **Flags go after the task name.** `asgard build --verbose` runs the task; `asgard --verbose build` prints the help for `build`. Only `--version` and `--doctor` go first.
|
|
850
|
+
|
|
851
|
+
- **See the tasks by file with `asgard --doctor`.** It lists every `.loki` file that loaded and, under each, the tasks that file defines with their line numbers, plus which `.loki` won the walk up the tree, what every `import` loaded, tasks hidden because they lack a `desc`, and definitions silently replaced by a later one. It works even when the `.loki` will not load.
|
|
852
|
+
|
|
853
|
+
- **See the commands as a tree with `asgard tree`.** Every command with its description, each subcommand group expanded under its own heading, header and footer included. `help` shows what you can type; `tree` shows how it is organised.
|
|
854
|
+
|
|
855
|
+
- **Keep helpers out of the command list.** A public method without a `desc` is a hidden task: it runs from the command line and as a dependency. Make helpers `private`, wrap them in `no_commands`, or define them with `helper`.
|
|
856
|
+
|
|
857
|
+
- **Pass results, not instance variables.** Parallel dependencies each run on their own copy of the task object, so an `@ivar` set in one never reaches the task that depends on it. Return a value and read it with `dep_result(:name)` or `dep_results`; [`examples/bad.loki`](examples/bad.loki) shows the trap.
|
|
858
|
+
|
|
859
|
+
- **Let gates report, let guards stop.** A quality gate returns `:fail` so every gate runs and the summary shows all failures at once. A guard such as [`examples/confirm.loki`](examples/confirm.loki) raises `Asgard::Error` so nothing after it runs. A dependency's return value never stops its dependent; only an exit, `abort` or `raise` does.
|
|
860
|
+
|
|
861
|
+
- **Name shell configurations, not shells.** The name you give `shell` is yours: `shell :strict, "bash -eu -o pipefail"` names a bash configuration that stops at the first failing line, and `shell :uv, "uv run --script", ext: ".py"` names a way of running Python. `sh` and `shebang` take a name as an optional first argument (`sh :strict, "..."`); without one they run the `default_shell`, so `default_shell :strict` makes every plain `sh` fail fast.
|
|
862
|
+
|
|
863
|
+
- **An AI agent is just another shell.** Register a coding agent's CLI with `shell` and a heredoc becomes the prompt: [`examples/robots/.loki`](examples/robots/.loki) wires up `claude -p` in one line, and its `ask` task sends a prompt with a plain `sh :claude, <<~PROMPT`. Any agent that reads a prompt from stdin fits the same way, so a task can review a diff, draft release notes or explain a failing test alongside the tasks that run them.
|
|
864
|
+
|
|
865
|
+
- **Prefer `background: true` to a trailing `&`.** A managed job gets a name, a log and `asgard jobs stop`; a process detached with `&` leaves no record and must be found with `ps`.
|
|
866
|
+
|
|
867
|
+
- **Import conditionally.** `.loki` is plain Ruby: `import "quality_rails.loki" if ENV["RAILS_ROOT"]` keeps Rails-only gates out of non-Rails projects, and a block-form `depends_on` picks up whatever `*_check` tasks are loaded.
|
|
771
868
|
|
|
772
|
-
|
|
773
|
-
|---|---|
|
|
774
|
-
| `Asgard.run!(argv)` | Entry point — finds `.loki`, loads task files, starts CLI |
|
|
775
|
-
| `Asgard.find_task_file` | Returns a `Pathname` to `.loki` searching from CWD upward, or `nil` |
|
|
869
|
+
- **Trace loading when something is missing.** `RUBYOPT=-w asgard help` prints each file as `import` loads it; `ASGARD_TRACE=1` turns a one-line load error into the full backtrace with Ruby's "Did you mean?" hint.
|
|
776
870
|
|
|
777
|
-
|
|
871
|
+
More tips live in the manual at [Tips](https://madbomber.github.io/asgard/tips/).
|
|
778
872
|
|
|
779
873
|
---
|
|
780
874
|
|
|
@@ -784,8 +878,9 @@ end
|
|
|
784
878
|
git clone git@github.com:MadBomber/asgard.git
|
|
785
879
|
cd asgard
|
|
786
880
|
bundle install
|
|
787
|
-
bundle exec
|
|
788
|
-
bundle exec bin/asgard
|
|
881
|
+
bundle exec bin/asgard test_check # run tests (95% coverage minimum enforced)
|
|
882
|
+
bundle exec bin/asgard quality # every quality gate in parallel
|
|
883
|
+
bundle exec bin/asgard help # exercise the CLI against this gem's own .loki
|
|
789
884
|
```
|
|
790
885
|
|
|
791
886
|
## Contributing
|
data/bin/asgard
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
# --version needs nothing but the constant, so answer it before loading
|
|
5
|
+
# Thor and the rest of the gem. Only before the task name: after it,
|
|
6
|
+
# --version is the task's own argument.
|
|
7
|
+
require_relative "../lib/asgard/leading_flag"
|
|
8
|
+
if Asgard.leading_flag?(ARGV, "--version")
|
|
9
|
+
require_relative "../lib/asgard/version"
|
|
10
|
+
puts Asgard::VERSION
|
|
11
|
+
exit
|
|
12
|
+
end
|
|
13
|
+
|
|
4
14
|
require_relative "../lib/asgard"
|
|
5
15
|
Asgard.run!(ARGV)
|