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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +179 -99
  3. data/README.md +141 -46
  4. data/bin/asgard +10 -0
  5. data/lib/asgard/base/dispatch.rb +257 -31
  6. data/lib/asgard/base/registry.rb +7 -3
  7. data/lib/asgard/base.rb +140 -9
  8. data/lib/asgard/declarations.rb +21 -0
  9. data/lib/asgard/doctor/task_sections.rb +11 -3
  10. data/lib/asgard/doctor.rb +12 -8
  11. data/lib/asgard/jobs/commands.rb +152 -0
  12. data/lib/asgard/jobs/declaration.rb +149 -0
  13. data/lib/asgard/jobs/launcher.rb +109 -0
  14. data/lib/asgard/jobs/state.rb +190 -0
  15. data/lib/asgard/jobs/stopper.rb +141 -0
  16. data/lib/asgard/jobs.rb +25 -0
  17. data/lib/asgard/kernel_methods.rb +19 -14
  18. data/lib/asgard/leading_flag.rb +10 -0
  19. data/lib/asgard/pid.rb +130 -0
  20. data/lib/asgard/schedule/commands.rb +39 -11
  21. data/lib/asgard/schedule/declaration.rb +139 -12
  22. data/lib/asgard/schedule/job_files.rb +24 -0
  23. data/lib/asgard/schedule/launchd.rb +55 -18
  24. data/lib/asgard/schedule/systemd.rb +69 -27
  25. data/lib/asgard/schedule/table.rb +34 -16
  26. data/lib/asgard/schedule.rb +49 -9
  27. data/lib/asgard/shell.rb +372 -44
  28. data/lib/asgard/state_dir.rb +84 -0
  29. data/lib/asgard/tasks.rb +8 -0
  30. data/lib/asgard/version.rb +1 -1
  31. data/lib/asgard/watch/changes.rb +26 -1
  32. data/lib/asgard/watch/commands.rb +52 -12
  33. data/lib/asgard/watch/daemon.rb +334 -43
  34. data/lib/asgard/watch/declaration.rb +135 -40
  35. data/lib/asgard/watch/dispatcher.rb +61 -10
  36. data/lib/asgard/watch/mailbox.rb +50 -0
  37. data/lib/asgard/watch/reporter.rb +50 -34
  38. data/lib/asgard/watch/spawn.rb +41 -16
  39. data/lib/asgard/watch/state.rb +93 -27
  40. data/lib/asgard/watch/worker.rb +44 -9
  41. data/lib/asgard/watch.rb +4 -11
  42. data/lib/asgard.rb +80 -14
  43. metadata +167 -186
  44. data/.envrc +0 -6
  45. data/.github/workflows/deploy-github-pages.yml +0 -52
  46. data/.loki +0 -39
  47. data/.reek.yml +0 -166
  48. data/.rubocop.yml +0 -163
  49. data/Archspec.rb +0 -24
  50. data/CLAUDE.md +0 -139
  51. data/COMMITS.md +0 -196
  52. data/bin/console +0 -11
  53. data/bin/setup +0 -8
  54. data/doc_tasks.loki +0 -21
  55. data/docs/api.md +0 -368
  56. data/docs/assets/css/custom.css +0 -93
  57. data/docs/assets/images/asgard.jpg +0 -0
  58. data/docs/changelog.md +0 -1
  59. data/docs/dependencies.md +0 -313
  60. data/docs/environment.md +0 -129
  61. data/docs/examples.md +0 -194
  62. data/docs/getting-started.md +0 -181
  63. data/docs/helpers.md +0 -262
  64. data/docs/index.md +0 -87
  65. data/docs/options.md +0 -224
  66. data/docs/schedule.md +0 -106
  67. data/docs/shell.md +0 -237
  68. data/docs/subcommands.md +0 -181
  69. data/docs/task-files.md +0 -407
  70. data/docs/tasks.md +0 -326
  71. data/docs/variables.md +0 -338
  72. data/docs/watch.md +0 -123
  73. data/examples/.env +0 -4
  74. data/examples/.envrc +0 -3
  75. data/examples/.loki +0 -24
  76. data/examples/bad.loki +0 -63
  77. data/examples/concurrent.loki +0 -58
  78. data/examples/confirm.loki +0 -48
  79. data/examples/db_subcommands.loki +0 -74
  80. data/examples/depends_on_block/bad/.loki +0 -51
  81. data/examples/depends_on_block/good/.loki +0 -83
  82. data/examples/do.loki +0 -45
  83. data/examples/env_usage.loki +0 -27
  84. data/examples/kitchen_sink.loki +0 -210
  85. data/examples/server_subcommands.loki +0 -65
  86. data/examples/subdir/.loki +0 -12
  87. data/examples/subdir/import_demo.loki +0 -14
  88. data/examples/subdir/import_up_demo.loki +0 -18
  89. data/examples/watcher_examples/.envrc +0 -3
  90. data/examples/watcher_examples/.loki +0 -54
  91. data/examples/watcher_examples/README.md +0 -112
  92. data/examples/watcher_examples/bin/hello +0 -3
  93. data/examples/watcher_examples/data/records.json +0 -1
  94. data/examples/watcher_examples/docs/drafts/ideas.md +0 -5
  95. data/examples/watcher_examples/docs/index.md +0 -4
  96. data/examples/watcher_examples/lib/greeter.rb +0 -7
  97. data/gem_tasks.loki +0 -51
  98. data/git.loki +0 -13
  99. data/mkdocs.yml +0 -166
  100. data/quality.loki +0 -266
  101. data/quality_rails.loki +0 -47
  102. 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 |