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/subcommands.md
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
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.
|
data/docs/task-files.md
DELETED
|
@@ -1,407 +0,0 @@
|
|
|
1
|
-
# Task Files
|
|
2
|
-
|
|
3
|
-
Asgard uses a convention-based file discovery system. A hidden `.loki` file marks the project root. Everything else — loading sibling files, shared task libraries, monorepo-wide tasks — is controlled explicitly from inside your `.loki` file using the `import` and `import_up` Kernel methods.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## The `.loki` Root Marker
|
|
8
|
-
|
|
9
|
-
When you run `asgard`, it searches for a `.loki` file starting in the current working directory and walking upward through parent directories until it finds one or reaches the filesystem root. The first `.loki` file found marks the project root and is the only file Asgard loads automatically.
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
myproject/
|
|
13
|
-
.loki ← found regardless of which subdirectory you're in
|
|
14
|
-
src/
|
|
15
|
-
app/
|
|
16
|
-
# asgard still works from here
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
The `.loki` file can be completely empty — its presence alone marks the project root. It can also contain task definitions, `import` calls, or any valid Ruby.
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## Loading Files with `import`
|
|
24
|
-
|
|
25
|
-
`import` is a Kernel method available everywhere in Ruby — at the top level of `.loki` files, inside class bodies, and inside task method bodies. It loads `.loki` files with `require`-like idempotency: a file is loaded at most once per process, no matter how many times `import` is called with the same path.
|
|
26
|
-
|
|
27
|
-
### Single file by absolute path
|
|
28
|
-
|
|
29
|
-
```ruby
|
|
30
|
-
import "/home/shared/gem_tasks.loki"
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### Single file by relative path
|
|
34
|
-
|
|
35
|
-
Relative paths are resolved relative to the **caller's file location**, like `require_relative`:
|
|
36
|
-
|
|
37
|
-
```ruby
|
|
38
|
-
# .loki — relative to this file's directory
|
|
39
|
-
import "build.loki"
|
|
40
|
-
import "../shared/gem_tasks.loki"
|
|
41
|
-
import "tasks/ci.loki"
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
### All files in the same directory (glob)
|
|
45
|
-
|
|
46
|
-
```ruby
|
|
47
|
-
import "*.loki" # all *.loki files in the same directory as the calling file
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
`*.loki` never matches `.loki` (the dotfile entry point) — Ruby's `Dir.glob` excludes dotfiles from `*` patterns by default.
|
|
51
|
-
|
|
52
|
-
### All files recursively (recursive glob)
|
|
53
|
-
|
|
54
|
-
```ruby
|
|
55
|
-
import "**/*.loki" # every .loki file in this directory and all subdirectories
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### Specific named files
|
|
59
|
-
|
|
60
|
-
```ruby
|
|
61
|
-
import "gem_tasks.loki"
|
|
62
|
-
import "ci_tasks.loki"
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### Combining patterns
|
|
66
|
-
|
|
67
|
-
```ruby
|
|
68
|
-
# .loki
|
|
69
|
-
import "*.loki" # load all siblings
|
|
70
|
-
import "../shared/*.loki" # load a parent-level shared library
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### Typical `.loki` entry point
|
|
74
|
-
|
|
75
|
-
```ruby
|
|
76
|
-
# .loki
|
|
77
|
-
import "*.loki" # load all sibling task files
|
|
78
|
-
|
|
79
|
-
class Tasks
|
|
80
|
-
# any top-level task definitions or overrides
|
|
81
|
-
end
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### Return value
|
|
85
|
-
|
|
86
|
-
`import` returns `true` if at least one file was newly loaded, `false` if all files were already loaded or no glob pattern matched any file. If a specific (non-glob) file does not exist, `import` raises `LoadError`.
|
|
87
|
-
|
|
88
|
-
```ruby
|
|
89
|
-
import("gem_tasks.loki") ? "loaded now" : "already loaded"
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## Finding Files with `loki_up`
|
|
95
|
-
|
|
96
|
-
`loki_up(name = ".loki")` searches `Dir.pwd` and each ancestor directory for a file with the given name, returning a `Pathname` or `nil`. It does **not** load the file — it only finds it.
|
|
97
|
-
|
|
98
|
-
Despite the name, `loki_up` is not limited to `.loki` files — it will locate any file by name. This makes it useful for finding shared config files, `.env` files, or any other resource that lives somewhere up the directory tree:
|
|
99
|
-
|
|
100
|
-
```ruby
|
|
101
|
-
loki_up # finds .loki (the project root marker)
|
|
102
|
-
loki_up("gem_tasks.loki") # finds gem_tasks.loki in CWD or any ancestor
|
|
103
|
-
loki_up(".env") # finds the nearest .env file up the tree
|
|
104
|
-
loki_up("VERSION") # finds a VERSION file in CWD or any ancestor
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
Use `loki_up` when you need the path for other purposes, or to check whether a file exists before deciding to load it:
|
|
108
|
-
|
|
109
|
-
```ruby
|
|
110
|
-
if (path = loki_up("gem_tasks.loki"))
|
|
111
|
-
import path
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Pass the located .env to dotenv — works from any subdirectory
|
|
115
|
-
dotenv loki_up(".env") || ".env"
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
`loki_up` accepts exact filenames only. Glob patterns are not expanded by `loki_up` — use `import_up` for glob-aware ancestor search.
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
## Loading Files Found up the Tree with `import_up`
|
|
123
|
-
|
|
124
|
-
`import_up(name = ".loki")` combines `loki_up` and `import` into a single call. It finds the file (or files) up the ancestor chain and loads them.
|
|
125
|
-
|
|
126
|
-
### Exact filename
|
|
127
|
-
|
|
128
|
-
```ruby
|
|
129
|
-
import_up "gem_tasks.loki"
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
Walks up from `Dir.pwd` until it finds `gem_tasks.loki`, then loads it. Returns `false` if not found anywhere.
|
|
133
|
-
|
|
134
|
-
### Glob pattern
|
|
135
|
-
|
|
136
|
-
```ruby
|
|
137
|
-
import_up "*.loki"
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
Walks up from `Dir.pwd` and stops at the **first ancestor directory** that contains any `*.loki` files, loading all of them. It does not aggregate matches from multiple levels — it loads only the nearest match, then stops.
|
|
141
|
-
|
|
142
|
-
```
|
|
143
|
-
~/sandbox/
|
|
144
|
-
gem_tasks.loki ← loaded by import_up("*.loki") from ~/sandbox/myproject/sub/
|
|
145
|
-
ci_tasks.loki ← also loaded — same directory as the first match
|
|
146
|
-
myproject/
|
|
147
|
-
.loki
|
|
148
|
-
sub/
|
|
149
|
-
# Dir.pwd here; import_up("*.loki") finds ~/sandbox/*.loki files
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
### Return value
|
|
153
|
-
|
|
154
|
-
Returns `true` if any file was newly loaded, `false` if the file was not found or was already loaded.
|
|
155
|
-
|
|
156
|
-
### Conditional load
|
|
157
|
-
|
|
158
|
-
Since `import_up` returns `false` when a file is not found (rather than raising), it composes naturally with `||`:
|
|
159
|
-
|
|
160
|
-
```ruby
|
|
161
|
-
import_up("project_tasks.loki") || import_up("gem_tasks.loki")
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
---
|
|
165
|
-
|
|
166
|
-
## Idempotency
|
|
167
|
-
|
|
168
|
-
Both `import` and `import_up` track loaded files in Ruby's `$LOADED_FEATURES`. A second call with the same path is a no-op and returns `false`. This means:
|
|
169
|
-
|
|
170
|
-
- You can call `import "*.loki"` from both `.loki` and a shared task file without double-loading.
|
|
171
|
-
- `import_up("gem_tasks.loki")` from two different projects in the same process each load their nearest match once.
|
|
172
|
-
- Swapping `require` for `import` in a `.loki` file gives the same once-per-process guarantee.
|
|
173
|
-
|
|
174
|
-
---
|
|
175
|
-
|
|
176
|
-
## Verbose and Debug Feedback
|
|
177
|
-
|
|
178
|
-
`import` and `import_up` emit diagnostic messages to stderr when the `verbose?` or `debug?` flags are active (set via `--verbose` or `--debug` on the CLI, or by setting `$VERBOSE`/`$DEBUG` directly):
|
|
179
|
-
|
|
180
|
-
| Flag | `import` output | `import_up` output |
|
|
181
|
-
|---|---|---|
|
|
182
|
-
| `--verbose` | Prints each file path as it is loaded | Prints `name → /full/path` when found |
|
|
183
|
-
| `--debug` | Same as verbose, plus prints a skip message for already-loaded files | Same as verbose, plus prints `name not found` when the search comes up empty |
|
|
184
|
-
|
|
185
|
-
```
|
|
186
|
-
$ asgard --verbose build
|
|
187
|
-
import: /home/user/myproject/build.loki
|
|
188
|
-
import: /home/user/myproject/test.loki
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
---
|
|
192
|
-
|
|
193
|
-
## Loading Patterns
|
|
194
|
-
|
|
195
|
-
### Single-file project
|
|
196
|
-
|
|
197
|
-
All tasks in `.loki`, nothing else:
|
|
198
|
-
|
|
199
|
-
```ruby
|
|
200
|
-
# .loki
|
|
201
|
-
class Tasks
|
|
202
|
-
@@app ||= "myapp".freeze
|
|
203
|
-
|
|
204
|
-
desc "Compile the project"
|
|
205
|
-
def build = sh "rake build"
|
|
206
|
-
|
|
207
|
-
desc "Run the test suite"
|
|
208
|
-
def test = sh "rake test"
|
|
209
|
-
|
|
210
|
-
desc "Build and push the gem"
|
|
211
|
-
def release = sh "gem push pkg/#{@@app}-*.gem"
|
|
212
|
-
end
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
### Multi-file project
|
|
216
|
-
|
|
217
|
-
Split tasks across files by concern. Load them all from `.loki` with a glob:
|
|
218
|
-
|
|
219
|
-
```
|
|
220
|
-
myproject/
|
|
221
|
-
.loki ← entry point; imports siblings
|
|
222
|
-
build.loki ← build tasks
|
|
223
|
-
deploy.loki ← deploy tasks
|
|
224
|
-
test.loki ← test tasks
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
```ruby
|
|
228
|
-
# .loki
|
|
229
|
-
import "*.loki"
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
```ruby
|
|
233
|
-
# build.loki
|
|
234
|
-
class Tasks
|
|
235
|
-
desc "Compile the project"
|
|
236
|
-
def build = sh "rake build"
|
|
237
|
-
end
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
```ruby
|
|
241
|
-
# test.loki
|
|
242
|
-
class Tasks
|
|
243
|
-
depends_on :build
|
|
244
|
-
desc "Run the test suite"
|
|
245
|
-
def test = sh "bundle exec rake test"
|
|
246
|
-
end
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
```ruby
|
|
250
|
-
# deploy.loki
|
|
251
|
-
class Tasks
|
|
252
|
-
depends_on :test
|
|
253
|
-
desc "Deploy to production"
|
|
254
|
-
def deploy = sh "cap production deploy"
|
|
255
|
-
end
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
Files loaded via glob are sorted alphabetically by `Dir.glob`, so `build.loki` loads before `test.loki`. Tasks defined in earlier files are available to later files via `depends_on`.
|
|
259
|
-
|
|
260
|
-
### Controlled load order
|
|
261
|
-
|
|
262
|
-
When alphabetical order does not match your dependency order, import explicitly:
|
|
263
|
-
|
|
264
|
-
```ruby
|
|
265
|
-
# .loki
|
|
266
|
-
import "infra.loki" # must be first
|
|
267
|
-
import "build.loki" # depends on infra
|
|
268
|
-
import "deploy.loki" # depends on build
|
|
269
|
-
```
|
|
270
|
-
|
|
271
|
-
### Shared task library in a monorepo
|
|
272
|
-
|
|
273
|
-
Place shared tasks in a parent directory and load them from any sub-project:
|
|
274
|
-
|
|
275
|
-
```
|
|
276
|
-
~/sandbox/
|
|
277
|
-
gem_tasks.loki ← shared: build, install, release tasks for any gem
|
|
278
|
-
myproject/
|
|
279
|
-
.loki ← loads gem_tasks.loki via import_up
|
|
280
|
-
other_project/
|
|
281
|
-
.loki ← also loads gem_tasks.loki via import_up
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
```ruby
|
|
285
|
-
# myproject/.loki
|
|
286
|
-
import_up "gem_tasks.loki" # finds ~/sandbox/gem_tasks.loki
|
|
287
|
-
|
|
288
|
-
class Tasks
|
|
289
|
-
# project-specific overrides here
|
|
290
|
-
end
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
### Conditional shared library
|
|
294
|
-
|
|
295
|
-
```ruby
|
|
296
|
-
# .loki
|
|
297
|
-
import_up("ci_tasks.loki") || import_up("gem_tasks.loki")
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
Loads `ci_tasks.loki` if found up the tree, otherwise falls back to `gem_tasks.loki`.
|
|
301
|
-
|
|
302
|
-
### Subcommand classes across files
|
|
303
|
-
|
|
304
|
-
Define subcommand classes in separate files and register them in `.loki`:
|
|
305
|
-
|
|
306
|
-
```
|
|
307
|
-
myproject/
|
|
308
|
-
.loki
|
|
309
|
-
db.loki
|
|
310
|
-
server.loki
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
```ruby
|
|
314
|
-
# db.loki
|
|
315
|
-
class DBCommands < Tasks
|
|
316
|
-
desc "Run migrations"
|
|
317
|
-
def migrate = sh "rails db:migrate"
|
|
318
|
-
end
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
```ruby
|
|
322
|
-
# server.loki
|
|
323
|
-
class ServerCommands < Tasks
|
|
324
|
-
desc "Start the server"
|
|
325
|
-
def start = sh "rails server"
|
|
326
|
-
end
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
```ruby
|
|
330
|
-
# .loki
|
|
331
|
-
import "*.loki" # db.loki and server.loki load first
|
|
332
|
-
|
|
333
|
-
class Tasks
|
|
334
|
-
desc "db SUBCOMMAND", "Manage the database"; subcommand "db", DBCommands
|
|
335
|
-
desc "server SUBCOMMAND", "Manage the server"; subcommand "server", ServerCommands
|
|
336
|
-
end
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
Subcommand classes are available in `.loki` because siblings loaded via `import "*.loki"` execute before `.loki`'s own class body.
|
|
340
|
-
|
|
341
|
-
---
|
|
342
|
-
|
|
343
|
-
## Task Name Overloading
|
|
344
|
-
|
|
345
|
-
Because all `*.loki` files reopen the same `class Tasks`, two files can define a method with the same name. Ruby's class reopening semantics apply: the last definition loaded wins, silently replacing the earlier one.
|
|
346
|
-
|
|
347
|
-
Three things are overwritten when a task name is reused:
|
|
348
|
-
|
|
349
|
-
| What | Effect |
|
|
350
|
-
|---|---|
|
|
351
|
-
| `def method_name` | The Ruby method body — the earlier implementation is gone |
|
|
352
|
-
| `desc` metadata | Thor registers the new usage/description string, discarding the old one |
|
|
353
|
-
| `depends_on` stages | `method_added` captures the pending deps for the new definition; the earlier dep chain is replaced |
|
|
354
|
-
|
|
355
|
-
**Accidental overloading** is a silent bug. Keep task names unique across files.
|
|
356
|
-
|
|
357
|
-
!!! warning
|
|
358
|
-
There is no runtime error when a task is overloaded. If a task is not behaving as expected, check whether another `.loki` file defines the same method name and loads after it.
|
|
359
|
-
|
|
360
|
-
**Intentional overloading** lets you extend a task defined in an earlier file using `alias_method`:
|
|
361
|
-
|
|
362
|
-
```ruby
|
|
363
|
-
# build.loki (loaded first)
|
|
364
|
-
class Tasks
|
|
365
|
-
desc "Compile the project"
|
|
366
|
-
def build = sh "rake build"
|
|
367
|
-
end
|
|
368
|
-
|
|
369
|
-
# postbuild.loki (loaded after build.loki, alphabetically)
|
|
370
|
-
class Tasks
|
|
371
|
-
no_commands { alias_method :_build_original, :build }
|
|
372
|
-
|
|
373
|
-
desc "Compile the project and copy assets"
|
|
374
|
-
def build
|
|
375
|
-
_build_original
|
|
376
|
-
sh "cp -r dist/ public/"
|
|
377
|
-
end
|
|
378
|
-
end
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
!!! warning "Prefer `depends_on` over intentional overloading"
|
|
382
|
-
Using `alias_method` to bolt post-task behaviour onto an existing task is fragile and load-order dependent. The idiomatic alternative is `depends_on`:
|
|
383
|
-
|
|
384
|
-
```ruby
|
|
385
|
-
class Tasks
|
|
386
|
-
desc "Compile the project"
|
|
387
|
-
def build = sh "rake build"
|
|
388
|
-
|
|
389
|
-
desc "Copy build output to public/"
|
|
390
|
-
def copy_assets = sh "cp -r dist/ public/"
|
|
391
|
-
|
|
392
|
-
depends_on :build, :copy_assets
|
|
393
|
-
desc "Compile and copy assets"
|
|
394
|
-
def build_all; end
|
|
395
|
-
end
|
|
396
|
-
```
|
|
397
|
-
|
|
398
|
-
---
|
|
399
|
-
|
|
400
|
-
## Summary of Loading Rules
|
|
401
|
-
|
|
402
|
-
| Method | Finds? | Loads? | Glob? | Ancestor search? |
|
|
403
|
-
|---|---|---|---|---|
|
|
404
|
-
| `loki_up(name)` | Yes (`Pathname`) | No | No | Yes |
|
|
405
|
-
| `import(path)` | No | Yes | Yes | No |
|
|
406
|
-
| `import_up(name)` | Yes | Yes | Yes | Yes |
|
|
407
|
-
| Asgard's `run!` | Yes | `.loki` only | No | Yes |
|