mxup 0.3.1 → 1.0.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e54d64b96a5c0a433b057548e5fab1488f368ce0f8dc76eb13143247d375b76f
4
- data.tar.gz: 7b5aba9d337cdd67584bdd7650df537cd745fbe6bcc7f1fe3b04b618cd47ae57
3
+ metadata.gz: 4531ad3aa556a0ca370760f6f2021bbbf4c9a111d404989cd6b24fb6e88f1e06
4
+ data.tar.gz: 93a8f02afacc7332e064124807f5ea47c400d0de44584c90fbcc18b7e8399134
5
5
  SHA512:
6
- metadata.gz: a684f0279fc7a09f59dc9aa34765ce5b1fd451e20fb3aadb5612d574d16328e12d938221ac68a36ec746f9dd977585194fa69d9483e18b13d49624aaae0310c8
7
- data.tar.gz: a88115c2a794c40cb11800f7380f8e6d65d69b07a0f565f0606b3142062b82c352bd5f8717a76111e391e1bef8731cd80fa373d6ddbc1281ee703734e0b33f14
6
+ metadata.gz: 4a3c824064e22953dd14a56a6cef28548b62d2b0d8d7bd0603d61070a89c602476436977d16da51e13e5511d0ca6249fb223784456407bae5c4e777e76efd334
7
+ data.tar.gz: 8df2f3eade22728fca31fcd384de5a7820358b0ec2d4512898b4c5424601bc60dddfae6a12845de428ee4a8a9e24dadbb48b25f725054268433b6452eb058a2f
data/README.md CHANGED
@@ -6,20 +6,37 @@ Run `mxup up` any time — it creates what's missing, restarts what crashed, rem
6
6
 
7
7
  ## Install
8
8
 
9
- Requires `tmux` and Ruby 3.1+ (stdlib only — no runtime gem dependencies).
9
+ ### Homebrew (recommended on macOS)
10
10
 
11
- ### RubyGems
11
+ Current Homebrew versions require third-party taps to be added and trusted
12
+ explicitly:
12
13
 
13
14
  ```bash
14
- gem install mxup
15
+ brew tap Recognized/mxup
16
+ brew trust Recognized/mxup
17
+ brew install mxup
18
+ mxup --version
15
19
  ```
16
20
 
17
- ### Homebrew
21
+ Homebrew installs the required `tmux` and Ruby versions automatically. If
22
+ Homebrew reports that Xcode or the Command Line Tools are outdated, update
23
+ them through System Settings or Apple Developer Downloads, then retry.
24
+
25
+ ### RubyGems
26
+
27
+ RubyGems installation requires Ruby 3.1+ and `tmux`:
18
28
 
19
29
  ```bash
20
- brew install Recognized/mxup/mxup
30
+ ruby --version
31
+ gem install mxup
32
+ mxup --version
21
33
  ```
22
34
 
35
+ The Ruby bundled with older macOS versions is too old. Install a current Ruby
36
+ first (for example with `brew install ruby`). If installation succeeds but
37
+ `mxup` is not found, add the executable directory shown by `gem environment`
38
+ to your `PATH`.
39
+
23
40
  ### From source
24
41
 
25
42
  ```bash
@@ -112,6 +129,7 @@ windows:
112
129
  | Field | Required | Description |
113
130
  |-------|----------|-------------|
114
131
  | `session` | yes | tmux session name |
132
+ | `mxup_version` | no | Config format generation this config is written for. Omit it and the config is read as the pre-1.0 format, which keeps working (see [Config format versions](#config-format-versions)) |
115
133
  | `setup` | no | Shell snippet prepended to every window's command |
116
134
  | `root` | no | Base directory for relative window `root` values. Relative paths resolve against the config file's directory — or, if the config lives in a project-local `.mxup/` directory, against `.mxup/`'s parent (the project root). `~` and absolute paths are kept as-is. Supports `$VAR` / `${VAR}` env-var expansion (an unset variable is an error). |
117
135
  | `required_env` | no | Env vars that must be filled in before `mxup up` will start (see [Required env vars](#required-env-vars)) |
@@ -127,6 +145,96 @@ Per window:
127
145
  | `wait_for` | no | Readiness check to pass before running command (see below) |
128
146
  | `commands` | no | Map of named one-off commands runnable via `mxup <config> <window>:<name>` (see [Named commands](#named-commands-mxup-configwindowname)) |
129
147
 
148
+ ### Config format versions
149
+
150
+ `mxup_version` names the **config format generation** a config is written
151
+ for, and mxup reads every generation up to its own:
152
+
153
+ ```yaml
154
+ mxup_version: '1'
155
+ session: my-project
156
+ ```
157
+
158
+ Only the major is read, so `'1'`, `'1.0'` and `'1.4.2'` all name the same
159
+ generation — write whichever reads best. Majors compare numerically, so
160
+ `10.x` is newer than `9.x`.
161
+
162
+ **Omitting the key is fine, and always will be.** A config with no
163
+ `mxup_version` is read as the pre-1.0 format, major 0 — including the way
164
+ its [profiles](#profiles) work — so every config written for `0.3.1` or
165
+ earlier keeps running under `1.x` untouched. Nothing to migrate, no flag to
166
+ pass.
167
+
168
+ Two things follow from that:
169
+
170
+ - **Upgrading mxup never forces a config rewrite.** A new generation ships
171
+ with a new mxup major, and older generations keep being read. `mxup_version`
172
+ selects how your config is interpreted; it is not a gate that locks you out.
173
+ - **The generation is a choice, not a version check.** Set `mxup_version: '1'`
174
+ when you want the 1.x format's behaviour — [additive
175
+ profiles](#profiles), and selecting several at once. Until then your config
176
+ behaves the way it always did.
177
+
178
+ The one thing mxup can't read is a config from the *future* — a generation
179
+ newer than it knows about:
180
+
181
+ ```
182
+ $ mxup up my-project
183
+ This config targets the mxup 2.x config format, but mxup 1.0.0
184
+ reads up to 1.x. Update mxup, then try again:
185
+ brew upgrade mxup # if installed via Homebrew
186
+ gem update mxup # if installed via RubyGems
187
+ ```
188
+
189
+ That's resolved before any other validation, so such a config says so
190
+ instead of failing on a key it doesn't recognise.
191
+
192
+ Because format 0 and format 1 read the same `profiles:` block differently,
193
+ a config that uses profiles and hasn't picked a generation is told which
194
+ meaning it's getting, once per command, on stderr:
195
+
196
+ ```
197
+ mxup: reading `profiles:` with the 0.x meaning (every window in `windows:` runs;
198
+ the profile overrides some and drops others with `~`). Run `mxup migrations`,
199
+ then set `mxup_version: '1'` to switch to additive profiles.
200
+ ```
201
+
202
+ Configs with no `profiles:` block stay silent — the two generations are
203
+ identical for them. So does one that pins `mxup_version: '0'`, which says
204
+ the 0.x reading is deliberate.
205
+
206
+ ### What each major changed (`mxup migrations`)
207
+
208
+ `mxup migrations` prints the config format's history — for every major, what
209
+ changed and how to update a config written for the previous one. It needs no
210
+ config file, which is the point: it's what you run when your config won't
211
+ load.
212
+
213
+ ```
214
+ $ mxup migrations
215
+ mxup 1.0.0 — config format changes by major version.
216
+
217
+ 0.x -> 1.x
218
+
219
+ 1. Profiles select windows additively instead of subtracting them
220
+ Before: Every window in `windows:` ran. A profile overrode some of them
221
+ and dropped others with `~`, so `staging: {}` meant "run
222
+ everything, unchanged".
223
+ Now: `windows:` is a catalog and nothing in it runs on its own. Each
224
+ profile lists the windows it runs, and several profiles can be
225
+ selected at once (`-p a,b`) — the live set is their union. A
226
+ window no selected profile lists stays down.
227
+ Fix: In every profile, list the windows it should run: `name: {}` to
228
+ include one as declared in the catalog, or a non-empty block to
229
+ include it with overrides. …
230
+ ```
231
+
232
+ One caveat this can't solve retroactively, and it points the other way:
233
+ mxup versions released before `mxup_version` existed (0.3.1 and earlier)
234
+ don't know the key, so a config carrying `mxup_version: '1'` is refused by
235
+ an *old* mxup as an unknown top-level key. New mxup reading old configs is
236
+ the direction that's seamless.
237
+
130
238
  ### Wait-for checks
131
239
 
132
240
  `wait_for` blocks a window's command until a readiness condition is met.
@@ -336,19 +444,24 @@ mxup layout my-project compact
336
444
 
337
445
  ### Profiles
338
446
 
339
- A single project often needs to run under different stacks — "local
340
- everything", "staging backend with local frontend", etc. Profiles express
341
- those variants as a set of overrides on top of a shared base. Only one
342
- profile of a given config may be live at a time; `mxup up` of a different
343
- profile automatically tears the current one down first.
447
+ A single project often needs to run under different stacks — "just the
448
+ backend", "frontend against staging", "everything plus a scratch shell".
449
+ Profiles are **additive slices** of the `windows:` block: each profile
450
+ lists the windows it runs, and you can select as many as you like at
451
+ once. The live selection is the union of what those profiles include.
452
+
453
+ This describes the `1.x` config format, so the config declares it with
454
+ [`mxup_version`](#config-format-versions). A config that omits the key
455
+ keeps the older, subtractive meaning — see [Profiles in the 0.x
456
+ format](#profiles-in-the-0x-format) below.
344
457
 
345
458
  ```yaml
459
+ mxup_version: '1'
346
460
  session: my-project
347
461
 
348
- windows:
462
+ windows: # a catalog; nothing here runs on its own
349
463
  backend:
350
464
  root: ~/projects/my-app/backend
351
- command: ./start-server.sh
352
465
  env:
353
466
  DATABASE_URL: postgres://localhost/myapp_dev
354
467
 
@@ -356,56 +469,150 @@ windows:
356
469
  root: ~/projects/my-app/frontend
357
470
  command: npm run dev
358
471
 
472
+ scratch:
473
+ root: ~/projects/my-app
474
+
359
475
  profiles:
360
- local: {} # uses the base as-is
476
+ backend: # backend, served locally
477
+ windows:
478
+ backend:
479
+ command: ./start-server.sh
361
480
 
362
- staging:
481
+ staging-backend: # same window, pointed at staging
363
482
  windows:
364
483
  backend:
365
484
  command: ./connect-staging.sh
366
485
  env:
367
486
  DATABASE_URL: postgres://staging-db/myapp
487
+
488
+ frontend:
489
+ windows:
490
+ frontend: {} # include as declared in the catalog
491
+
492
+ scratch:
493
+ windows:
494
+ scratch: {}
368
495
  ```
369
496
 
370
- Pick a profile with `--profile` (short: `-p`):
497
+ Settings shared by every variant of a window live in the catalog; the bits
498
+ that differ live in the profiles that select it.
499
+
500
+ Select profiles with `--profile` (short: `-p`), which is repeatable and
501
+ accepts a comma-separated list:
371
502
 
372
503
  ```bash
373
- mxup up my-project --profile=local
374
- mxup up my-project -p staging # tears down 'local' first
375
- mxup status my-project # shows "profile: staging" in the header
504
+ mxup up my-project -p backend,frontend # both slices
505
+ mxup up my-project -p backend -p frontend # same thing
506
+ mxup up my-project -p staging-backend -p frontend
507
+ mxup status my-project # shows the live selection
376
508
  ```
377
509
 
378
510
  | Field | Required | Description |
379
511
  |-------|----------|-------------|
380
- | `profiles` | no | Map of profile name → override block |
381
- | `default_profile` | no | Profile to use when `--profile` is omitted (defaults to the first declared) |
382
-
383
- **Override semantics**: the active profile's `setup`, `windows`, and
384
- `layouts` override the base. Window overrides are merged per-key (so you
385
- can tweak just `command` or `env` without redeclaring `root`). `env` maps
386
- are themselves merged keys not in the profile are inherited from the
387
- base. A profile may not override `session`; profiles of the same group
388
- must share one tmux session.
389
-
390
- **Dropping windows**: to exclude a base window from a profile, map it to
391
- `~` (YAML null):
512
+ | `profiles` | no | Map of profile name → the windows it runs (plus optional overrides) |
513
+ | `default_profile` | no | Profile name, or list of names, selected when `--profile` is omitted (defaults to the first declared) |
514
+
515
+ **Inclusion**: a window runs when at least one selected profile lists it.
516
+ A window no selected profile lists stays down including when it is
517
+ declared in the base `windows:` block. A profile that lists no windows at
518
+ all is an error, since it would bring up an empty session.
519
+
520
+ Layout groups are pruned to the windows that actually run: names are
521
+ stripped from `panes:` lists, and a group that ends up empty is removed
522
+ from its layout.
523
+
524
+ **Overrides**: `{}` includes a window as declared in the catalog. A
525
+ non-empty block overrides it per key, so you can tweak just `command` or
526
+ `env` without redeclaring `root`; `env` and `commands` maps are merged
527
+ entry by entry. A profile may also declare a window that isn't in the base
528
+ catalog at all. `setup`, `root` and `layouts` replace their base value; a
529
+ profile may not override `session`, since all profiles of a config share
530
+ one tmux session.
531
+
532
+ **Vetoing**: mapping a window to `~` (YAML null) is an explicit "this must
533
+ not run". On its own it changes nothing — an unlisted window is already
534
+ down — but it collides loudly if another selected profile includes the
535
+ same window:
392
536
 
393
537
  ```yaml
394
538
  profiles:
395
- minimal:
539
+ no-db:
396
540
  windows:
397
- dev-kit: ~ # don't start dev-kit under the `minimal` profile
398
- scratch: ~
541
+ backend: {}
542
+ db: ~ # refuse to run alongside anything that starts db
543
+ ```
544
+
545
+ #### Collisions
546
+
547
+ Overlap between selected profiles is fine as long as they agree. Two
548
+ profiles including the same window, or setting the same key to the same
549
+ value, merge silently. A genuine disagreement aborts before anything
550
+ starts, naming the setting and both profiles:
551
+
399
552
  ```
553
+ $ mxup up my-project -p backend,staging-backend
554
+ Profile collision: the selected profiles (backend, staging-backend) disagree.
400
555
 
401
- Any layout groups that reference a dropped window are automatically
402
- pruned — entries are stripped from `panes:` lists, and a group that ends
403
- up empty is removed from its layout.
556
+ window 'backend' command
557
+ backend sets "./start-server.sh"
558
+ staging-backend sets "./connect-staging.sh"
559
+
560
+ Select fewer profiles, or make the conflicting values agree.
561
+ ```
562
+
563
+ | Situation | Result |
564
+ |-----------|--------|
565
+ | Several profiles include the same window | fine — included once |
566
+ | One includes a window, another includes it with overrides | fine |
567
+ | Two profiles set different keys of one window | fine — merged |
568
+ | Two profiles set the same key to the same value | fine |
569
+ | Two profiles set the same key to different values | **collision** |
570
+ | One profile vetoes a window another includes | **collision** |
571
+ | Several profiles veto the same window | fine — it stays down |
572
+
573
+ The same rule covers `setup`, `root` and `layouts` (compared whole) and
574
+ `live_env` (compared per variable). Every conflict in a selection is
575
+ reported at once, not one per run.
404
576
 
405
577
  **Switching**: if the tmux session is already running under a different
406
- profile, `mxup up` for a new profile runs `down` first (including the
407
- graceful-stop dance), then brings the new profile up from a clean slate.
408
- `mxup status` always shows the currently live profile in its header.
578
+ selection, `mxup up` runs `down` first (including the graceful-stop
579
+ dance), then brings the new selection up from a clean slate. Order doesn't
580
+ matter — `-p a,b` and `-p b,a` are the same live stack, so re-running
581
+ either leaves the panes alone. `mxup status` always shows the currently
582
+ live selection in its header.
583
+
584
+ #### Profiles in the 0.x format
585
+
586
+ A config with no [`mxup_version`](#config-format-versions) — anything
587
+ written for mxup `0.3.1` or earlier — keeps the profile semantics it was
588
+ written against. They are **subtractive**, and the differences are worth
589
+ knowing if you're reading the section above with an old config in hand:
590
+
591
+ | | 0.x format (no `mxup_version`) | 1.x format (`mxup_version: '1'`) |
592
+ |---|---|---|
593
+ | `windows:` | the live set — everything in it runs | a catalog — nothing runs until a profile opts in |
594
+ | A profile listing nothing (`staging: {}`) | runs every window, unchanged | runs nothing |
595
+ | A window no profile mentions | runs | stays down |
596
+ | `name: ~` in a profile | drops that window | vetoes it; collides with a profile that includes it |
597
+ | Profiles selected at once | exactly one | any number, unioned |
598
+ | `default_profile` | one name | a name or a list |
599
+
600
+ Everything else — `setup`, `root` and `layouts` replacing their base
601
+ value, `live_env` merging per variable, `env` and `commands` merging entry
602
+ by entry, the ban on overriding `session` — behaves the same in both.
603
+
604
+ So under a 0.x config, `-p` takes a single profile:
605
+
606
+ ```bash
607
+ mxup up my-project -p staging # fine
608
+ mxup up my-project -p a,b # rejected: multi-select is a 1.x feature
609
+ ```
610
+
611
+ To move a config to the 1.x format, run `mxup migrations`, list the
612
+ windows each profile should run, then add `mxup_version: '1'`. There's no
613
+ deadline: mxup goes on reading the 0.x format. The one thing that changed
614
+ is that a profile which drops *every* window is now an error ("Config
615
+ declares no windows") rather than an empty session.
409
616
 
410
617
  ## Commands
411
618
 
@@ -422,6 +629,7 @@ graceful-stop dance), then brings the new profile up from a clean slate.
422
629
  | `mxup target [name]` | Print targets for every declared window (tab-separated) |
423
630
  | `mxup exec -t [name:]<window> "<cmd>"` | Run `<cmd>` in a pane, wait for completion, print output, exit with its status |
424
631
  | `mxup [name] <window>:<command>` | Run a window's named `command` in a copy of its environment, streaming output to your shell |
632
+ | `mxup migrations` | Print what each major version changed in the config format, and how to update a config (needs no config file) |
425
633
 
426
634
  ### Flags
427
635
 
@@ -431,7 +639,7 @@ graceful-stop dance), then brings the new profile up from a clean slate.
431
639
  | `--dry-run` | Preview changes without applying (for `up`, `restart`, `exec`) |
432
640
  | `--lines N` | Output lines to show (for `status` default 15, for `exec` default 50) |
433
641
  | `--layout NAME` | Layout to use (for `up`) |
434
- | `-p`, `--profile NAME` | Profile to use; auto-teardowns a live session running under a different profile (for `up`, `status`, `restart`) |
642
+ | `-p`, `--profile NAME` | Profile(s) to select; repeatable and comma-separated. Auto-teardowns a live session running under a different selection (for `up`, `status`, `restart`) |
435
643
  | `-t TARGET` | Pane target (for `exec`); accepts `name:window`, `window`, or `window.pane` |
436
644
  | `--timeout N` | Max seconds to wait for the command (for `exec`; exit 124 on timeout) |
437
645
  | `--force` | Send the command even if the pane is busy with another process (for `exec`) |
@@ -519,6 +727,12 @@ session is up, and won't disturb the long-running process. (The window's
519
727
 
520
728
  ## Releasing
521
729
 
730
+ **Versioning promise.** The major version is bumped **only** when the config
731
+ format gains a new generation, and every older generation goes on being read
732
+ — so a config that any release accepts is accepted by every later release,
733
+ whatever its major. That's what [`mxup_version`](#config-format-versions)
734
+ selects. Additive config features (new optional keys) go in a minor release.
735
+
522
736
  Releases are automated by `.github/workflows/release.yml`. To cut a new version:
523
737
 
524
738
  1. Bump `Mxup::VERSION` in `lib/mxup/version.rb`.
data/bin/mxup CHANGED
@@ -7,4 +7,9 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
7
7
 
8
8
  require 'mxup'
9
9
 
10
- Mxup::CLI.new.run(ARGV) if __FILE__ == $PROGRAM_NAME
10
+ # No `__FILE__ == $PROGRAM_NAME` guard: this file is only ever an entry point,
11
+ # and RubyGems' generated wrapper `load`s it with $PROGRAM_NAME pointing at the
12
+ # wrapper — so the guard was false for every `gem install` and the CLI silently
13
+ # did nothing. Homebrew's wrapper execs this file directly, which is why the
14
+ # formula kept working and hid it.
15
+ Mxup::CLI.new.run(ARGV)
@@ -2,8 +2,8 @@
2
2
  #
3
3
  # This is a showcase config — it exercises most mxup features (multiple
4
4
  # windows, wait_for probes, per-window env, alternative layouts, and
5
- # profiles that drop or override windows). Adapt the paths, commands,
6
- # and env vars to your own project.
5
+ # combinable profiles). Adapt the paths, commands, and env vars to your
6
+ # own project.
7
7
  #
8
8
  # Place your copy at ~/.config/mxup/myapp-dev.yml, then:
9
9
  # mxup up myapp-dev
@@ -15,13 +15,23 @@
15
15
  # mxup layout myapp-dev compact
16
16
  # mxup up myapp-dev --layout=flat
17
17
  #
18
- # Switch profiles (tears down the current profile first):
19
- # mxup up myapp-dev --profile=local # default, everything on localhost
20
- # mxup up myapp-dev --profile=staging # frontend only, talks to hosted API
18
+ # Select profiles (tears down a different live selection first):
19
+ # mxup up myapp-dev # the default_profile selection
20
+ # mxup up myapp-dev -p backend,frontend # everything on localhost
21
+ # mxup up myapp-dev -p frontend-staging # frontend only, hosted API
22
+ # mxup up myapp-dev -p backend -p scratch # the flag is repeatable too
21
23
  #
22
24
  # Parameterize via env vars:
23
25
  # DOCKER_PROFILES=core mxup up myapp-dev
24
26
 
27
+ # The config format generation this file is written for. Optional: mxup reads
28
+ # every generation up to its own, so a file without this key is read as the
29
+ # pre-1.0 format (major 0) and keeps working. Declaring '1' is what opts this
30
+ # file in to the 1.x format — most visibly, additive profiles (see the bottom
31
+ # of this file). Only the major is read; minor and patch don't matter. Run
32
+ # `mxup migrations` to see what each generation changed.
33
+ mxup_version: '1'
34
+
25
35
  session: myapp-dev
26
36
 
27
37
  # Base directory for the windows below. Relative values are resolved against
@@ -97,8 +107,8 @@ windows:
97
107
  wait_for:
98
108
  http: http://localhost:8080/health
99
109
  timeout: 60
100
- env:
101
- API_URL: http://localhost:8080
110
+ # API_URL is deliberately absent: each profile that selects this window
111
+ # supplies the backend it should talk to.
102
112
  command: pnpm run dev
103
113
 
104
114
  storybook:
@@ -126,16 +136,41 @@ layouts:
126
136
 
127
137
  flat: {}
128
138
 
139
+ # Selection used when `--profile` is omitted. Without this, `mxup up` would
140
+ # select only the first profile declared below.
141
+ default_profile: [backend, frontend]
142
+
143
+ # Profiles are additive slices of the `windows:` block above: each one lists
144
+ # the windows it runs, and any number can be selected at once. A window no
145
+ # selected profile lists simply doesn't start. Selecting two profiles that
146
+ # override the same setting differently aborts with an explanation.
129
147
  profiles:
130
- # Everything runs locally via docker + ./gradlew run (the base config).
131
- local: {}
148
+ # Everything backend, running locally via docker + ./gradlew run.
149
+ backend:
150
+ windows:
151
+ docker: {}
152
+ api: {}
153
+ worker: {}
154
+ scheduler: {}
155
+
156
+ frontend:
157
+ windows:
158
+ frontend:
159
+ env:
160
+ API_URL: http://localhost:8080
161
+ storybook: {}
132
162
 
133
- # Staging: skip local backends, point the frontend at the hosted API.
134
- staging:
163
+ # The same frontend windows, pointed at the hosted API. Selecting this
164
+ # together with `frontend` collides — both set `frontend`'s API_URL, to
165
+ # different values — so pick one or the other.
166
+ frontend-staging:
135
167
  windows:
136
- api: ~
137
- worker: ~
138
- scheduler: ~
139
168
  frontend:
140
169
  env:
141
170
  API_URL: https://api.staging.example.com
171
+ storybook: {}
172
+
173
+ # Add-on slice: composes with any of the above.
174
+ scratch:
175
+ windows:
176
+ scratch: {}
data/lib/mxup/cli.rb CHANGED
@@ -6,7 +6,7 @@ module Mxup
6
6
  # Argv parser + dispatch. Keeps parsing rules in one place; the actual
7
7
  # behaviour lives in Runner and the focused modules it drives.
8
8
  class CLI
9
- COMMANDS = %w[up status down restart layout target exec watch].freeze
9
+ COMMANDS = %w[up status down restart layout target exec watch migrations].freeze
10
10
 
11
11
  def run(argv)
12
12
  args, options = parse(argv.dup)
@@ -28,7 +28,7 @@ module Mxup
28
28
  options = {
29
29
  dry_run: false, config: nil, lines: nil, layout: nil,
30
30
  target: nil, timeout: nil, force: false, quiet: false,
31
- profile: nil
31
+ profiles: []
32
32
  }
33
33
 
34
34
  parser = OptionParser.new do |opts|
@@ -38,7 +38,8 @@ module Mxup
38
38
  opts.on('--lines N', Integer, 'Output lines (status/exec)') { |n| options[:lines] = n }
39
39
  opts.on('--layout NAME', 'Layout to use (for up/layout)') { |l| options[:layout] = l }
40
40
  opts.on('-p', '--profile NAME',
41
- 'Profile to use (for up/status/restart/layout)') { |p| options[:profile] = p }
41
+ 'Profile(s) to use; repeatable or comma-separated ' \
42
+ '(for up/status/restart/layout)') { |p| options[:profiles] << p }
42
43
  opts.on('-t', '--target TARGET',
43
44
  'Target window/pane for exec (e.g. name:window)') { |t| options[:target] = t }
44
45
  opts.on('--timeout N', Integer, 'Timeout in seconds (exec)') { |n| options[:timeout] = n }
@@ -69,6 +70,8 @@ module Mxup
69
70
  when 'layout' then run_layout(args, options)
70
71
  when 'exec' then run_exec(args, options)
71
72
  when 'watch' then run_watch(args, options)
73
+ # Needs no config — it's what you run when your config won't load.
74
+ when 'migrations' then Migrations.render
72
75
  else
73
76
  abort "Unknown command: #{command}. Use: #{COMMANDS.join(', ')}"
74
77
  end
@@ -126,7 +129,7 @@ module Mxup
126
129
  # declares any `live_env`. Useful in the foreground for debugging.
127
130
  def run_watch(args, options)
128
131
  name = args.shift
129
- config = load_config(options[:config], name, options[:profile])
132
+ config = load_config(options[:config], name, options[:profiles])
130
133
  if config.live_env.empty?
131
134
  abort "Config has no live_env declared; nothing to watch."
132
135
  end
@@ -176,17 +179,17 @@ module Mxup
176
179
  end
177
180
 
178
181
  def build_runner(options, name)
179
- config = load_config(options[:config], name, options[:profile])
182
+ config = load_config(options[:config], name, options[:profiles])
180
183
  Runner.new(config, dry_run: options[:dry_run], layout: options[:layout])
181
184
  end
182
185
 
183
- def load_config(explicit_path, name, profile = nil)
186
+ def load_config(explicit_path, name, profiles = [])
184
187
  path = resolve_config(explicit_path, name)
185
188
  unless path
186
189
  abort "Config not found. Provide -f path, place config in #{CONFIG_DIR}/, " \
187
190
  "or in a local ./#{LOCAL_CONFIG_DIR}/ directory."
188
191
  end
189
- Config.new(path, profile: profile)
192
+ Config.new(path, profiles: profiles)
190
193
  rescue ArgumentError => e
191
194
  abort e.message
192
195
  end