wslc-wip 1.1.2 → 1.1.3

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: 37c2e70c286c4dd7b163201674eff9b4111fbe3f05ef032500a5b7ac516f97b5
4
- data.tar.gz: fbad7b94e10bb0cbd9efc8c70f1a2546830ac0c20f276a925b48e0d668679902
3
+ metadata.gz: 753cbc1f34c7c0c598edd7b4e4bc821953b97e12c66cec448096e05a0debd909
4
+ data.tar.gz: c9cc51e4f53a4f8240be292487315ee4cdec019febc006d232d09560457e1a20
5
5
  SHA512:
6
- metadata.gz: 70319980be04f8abdbb6d5e167e8941ca4fa3a8ab65b0cd71ec884daff639205a70935b4c5a756ec3403678982c2859423051930379aa3d79f302506b0f88326
7
- data.tar.gz: f100b5622d566ab77da4437cb028138a7fa33f47d4d19ff70874c683e2995e9c3213bc95b0109c03c82556827a26ee22b47a034bf7fc96895effc2c8fcb3950f
6
+ metadata.gz: c45401f388f42dc378066a1a3e14b39b622f13087f8627439f59ce8fa384fa2fb7f162bee8d6ee1f11581e32d5ece6d782bcb2d1c7bfd06f64973944d30ee1c5
7
+ data.tar.gz: beb47f232295309e11aa6df585b829165fa125024283da3e29f8ee222ea05eaee530df0ee9f759bb2de8b29b11dfb789d883691c8750e43e6e5655b44ebc4122
data/README.md CHANGED
@@ -159,9 +159,12 @@ The two are aliases for the same feature, not separate ones: pick whichever name
159
159
 
160
160
  `dependencies:` holds every container uniformly — the primary one `container:` points at and any
161
161
  sidecar services (a database, Redis, ...) alongside it. Each entry accepts `image` (required),
162
- `command`, `env`, `ports`, `volumes`, and `workdir`; there's no separate, differently-shaped block
163
- for "the one you exec into." `container:` has no default; once `dependencies:` has any entries,
164
- wip needs to be told explicitly which one is primary rather than guessing a name.
162
+ `command`, `env`, `ports`, `volumes`, `workdir`, and `restart`; there's no separate, differently-shaped
163
+ block for "the one you exec into." `container:` has no default; once `dependencies:` has any entries,
164
+ wip needs to be told explicitly which one is primary rather than guessing a name. `restart` is stored
165
+ but inert on its own — see
166
+ [Restarting exited dependencies](#restarting-exited-dependencies-wip-up---watch) for what actually
167
+ acts on it (`wip up --watch`).
165
168
 
166
169
  What sets the primary entry apart is operational, not structural: `wip up` brings up every other
167
170
  entry by name first (creating `network:` beforehand if it doesn't exist and set), then boots or
@@ -172,6 +175,46 @@ in place). Only the primary container is a target for `exec`/`run`/`build`/`inte
172
175
  are only ever started and stopped, matching Compose's own service-vs-you-exec-into-one-of-them
173
176
  split.
174
177
 
178
+ #### Restarting exited dependencies (`wip up --watch`)
179
+
180
+ Real Compose auto-restarts a container tagged `restart: always`/`unless-stopped`/`on-failure` when
181
+ it exits. `wslc` has no such policy, and no push-based "container exited" notification for `wip` to
182
+ hook into, so the closest approximation is polling: `wip up --watch` brings everything up the same
183
+ way `wip up -d` does, then keeps checking (`--interval SECONDS`, default 5) whether any
184
+ dependency — the primary container included — has exited, restarting the ones whose `restart:`
185
+ allows it.
186
+
187
+ ```console
188
+ $ wip up --watch
189
+ wip: watching app, mysql for exited restart: containers every 5s (running detached; Ctrl-C to stop)
190
+ ```
191
+
192
+ - `restart:` accepts the same values Compose does — `no` (the default), `always`, `unless-stopped`,
193
+ `on-failure`, optionally with a `:MAX_RETRIES` suffix. `wip up --watch` treats the three
194
+ restarting values identically: it restarts on any exited container regardless of exit code,
195
+ unlike real `on-failure`, which skips a clean (zero) exit — reading an exit code needs a heavier
196
+ call this polling loop doesn't make.
197
+ - This is a foreground loop, not a background daemon or service — the project intentionally has
198
+ neither (see [Roadmap](#roadmap)). Keep the terminal it's running in open, the same as
199
+ `wip sync --watch`; Ctrl-C (or closing the terminal) stops the supervision.
200
+ - `--watch` implies `-d`: it can't attach a TTY to the primary container and poll in a loop on the
201
+ same thread, so the primary container always runs detached under `--watch`, whether or not you
202
+ also passed `-d`.
203
+ - Not available under `mode: compose` — wip never parses a service list in that mode, so there's
204
+ nothing for it to poll; use whatever restart support your external compose-for-`wslc` tool offers.
205
+ - It's status-based, not event-based: each tick checks whether a dependency is currently exited, not
206
+ whether it *just* exited. It can't tell "crashed on its own" apart from "you ran `wip stop`/
207
+ `wip down` in another terminal" — Ctrl-C the `--watch` loop first, or it may race and restart
208
+ what you just stopped.
209
+ - Re-running `wip up -d --watch` against an already-running stack is safe: an already-running
210
+ container's `start` is a no-op, the same as it is for plain `wip up`.
211
+ - The exit-detection reads `wslc list --all --format json`'s `State` field as a raw integer, per
212
+ `WslcContainerState` ([wsl.dev](https://wsl.dev/api-reference/c/enumerations/wslccontainerstate/)):
213
+ `0` invalid, `1` created, `2` running, `3` exited, `4` deleted. Unlike Docker, there's no separate
214
+ `dead` state — a `deleted` container is gone and needs `wip up` (not `--watch`) to recreate it.
215
+ If `--watch` never restarts anything you believe really exited, run `wip up --watch --debug` and
216
+ check the logged `list` entry against this enum.
217
+
175
218
  ### Compose mode
176
219
 
177
220
  If your project already has a real `compose.yml`, don't duplicate it in `dependencies:` — point
@@ -250,11 +293,13 @@ valid compose.yml over sections it doesn't need to look at:
250
293
  does), `command` (shell or exec form), `environment`
251
294
  (mapping or `KEY=VALUE` array — a mapping value must not be null; host environment pass-through
252
295
  isn't supported), `ports`/`volumes` (short syntax only — `"host:container"` strings, not
253
- long-syntax mappings), `working_dir`, `user`, `depends_on` (ordering only a `condition:` other
254
- than `service_started` is rejected, since there's no health-check support). `tty`, `stdin_open`,
255
- and `networks` are accepted but silently ignored: TTY/stdin allocation is already decided per
256
- invocation (see "TTY allocation" below), not fixed per service, and every service already shares
257
- the one project network `compose.project` sets up.
296
+ long-syntax mappings), `working_dir`, `user`, `restart` (stored as-is; `no` is the default —
297
+ `wip up --watch` is what actually acts on it, see
298
+ [Restarting exited dependencies](#restarting-exited-dependencies-wip-up---watch)), `depends_on`
299
+ (ordering only a `condition:` other than `service_started` is rejected, since there's no
300
+ health-check support). `tty`, `stdin_open`, and `networks` are accepted but silently ignored:
301
+ TTY/stdin allocation is already decided per invocation (see "TTY allocation" below), not fixed
302
+ per service, and every service already shares the one project network `compose.project` sets up.
258
303
  - `wip logs` takes at most one `SERVICE` (defaulting to `compose.service`) — `wslc logs`, like
259
304
  `docker logs`, follows a single container, unlike a real compose tool's multi-service view.
260
305
  - `sync:` behaves exactly like `mode: container`'s (falls back to the primary service's own image,
@@ -422,7 +467,7 @@ to that tag directly — `sync.build`'s tag wins if both are set, so don't confi
422
467
  | `wip doctor` | Diagnose WSL2, interop, WSLC, config, architecture, and Git |
423
468
  | `wip config` | Print the effective configuration (secrets masked) |
424
469
  | `wip build [--no-cache] [-- OPTIONS]` | Build the image from the `build` definition. `wslc build` reuses matching local layers by default; `--no-cache` disables that. |
425
- | `wip up [-d] [--no-sync] [--no-cache]` | Start the primary `dependencies:` entry (`container:` names which one) and its sidecars (creating any that are missing, on `network:` if set). `-d` runs the main container in the background; with `sync:` configured, the source is mirrored into the volume first unless `--no-sync` |
470
+ | `wip up [-d] [--no-sync] [--no-cache] [--watch] [--interval N]` | Start the primary `dependencies:` entry (`container:` names which one) and its sidecars (creating any that are missing, on `network:` if set). `-d` runs the main container in the background; with `sync:` configured, the source is mirrored into the volume first unless `--no-sync`. `--watch` polls every `N` seconds (default 5, implies `-d`) and restarts any exited dependency whose `restart:` allows it (not available under mode: compose) |
426
471
  | `wip stop` | Stop the primary container and its sidecar `dependencies:` without removing them |
427
472
  | `wip down` | Stop and remove the primary container and its sidecar `dependencies:` |
428
473
  | `wip exec [--no-interactive] COMMAND...` | Run a command in the existing container |
@@ -635,7 +680,11 @@ and its current limitations (`run`, and `interaction:` of type `run`/`build`).
635
680
 
636
681
  Beyond Compose parity, a resident/daemon process, a GUI, PowerShell-specific tuning, direct
637
682
  registry API/manifest parsing, self-update, and plugins are all unimplemented and not currently
638
- planned. What's still planned for `wip`, roughly in priority order:
683
+ planned. (`wip up --watch`'s restart-policy poll loop isn't an exception to this — it's a
684
+ foreground, opt-in loop you keep a terminal open for, the same shape as `wip sync --watch`, not a
685
+ background service; see
686
+ [Restarting exited dependencies](#restarting-exited-dependencies-wip-up---watch).) What's still
687
+ planned for `wip`, roughly in priority order:
639
688
 
640
689
  1. **`wip provision`** — a dip-style one-shot bootstrap hook (build → up deps → install deps →
641
690
  create/migrate/seed DB) so a new contributor can go from `git clone` to a working environment
data/lib/wip/cli.rb CHANGED
@@ -16,6 +16,42 @@ module Wip
16
16
  class_option :debug_log, type: :string, desc: 'Where --debug snapshots go: a file path, or "-" for inline'
17
17
  default_task :dispatch
18
18
 
19
+ # Thor only recognizes class_options (--config, --debug, ...) once they
20
+ # appear after the command name (`wip up --config foo`). Pull a leading
21
+ # run of them off the front of ARGV and reinsert it right after the
22
+ # command name so `wip --config foo up` works too.
23
+ GLOBAL_SWITCHES = {
24
+ '--config' => :value,
25
+ '--env-file' => :value,
26
+ '--debug' => :flag,
27
+ '--debug-log' => :value
28
+ }.freeze
29
+
30
+ def self.start(given_args = ARGV, config = {})
31
+ super(reorder_global_options(given_args), config)
32
+ end
33
+
34
+ def self.reorder_global_options(args)
35
+ remaining = args.dup
36
+ extracted = []
37
+
38
+ until remaining.empty?
39
+ name, _, inline_value = remaining.first.to_s.partition('=')
40
+ kind = GLOBAL_SWITCHES[name]
41
+ break unless kind
42
+
43
+ switch = remaining.shift
44
+ extracted << switch
45
+ extracted << remaining.shift if kind == :value && inline_value.empty? && !remaining.empty?
46
+ end
47
+
48
+ command_index = remaining.index { |arg| !arg.start_with?('-') }
49
+ return args if command_index.nil?
50
+
51
+ remaining.insert(command_index + 1, *extracted)
52
+ remaining
53
+ end
54
+
19
55
  def self.exit_on_failure? = true
20
56
 
21
57
  # Thor only falls back to the default task when no command name is given at
@@ -82,21 +118,41 @@ module Wip
82
118
  progress&.finish
83
119
  end
84
120
 
121
+ # Real Compose values that trigger a restart when a container's exited; `no` (the default)
122
+ # and anything unrecognized both mean "leave it alone." Exact match only — a typo'd value
123
+ # like "always-invalid" must stay inert, not accidentally match via a loose prefix check.
124
+ AUTO_RESTART_POLICIES = %w[always unless-stopped].freeze
125
+ ON_FAILURE_POLICY = /\Aon-failure(?::\d+)?\z/ # optional `:MAX_RETRIES` suffix, e.g. "on-failure:3"
126
+
127
+ # WSLC's `WslcContainerState` (confirmed via microsoft/WSL's own docs and
128
+ # ContainerModel.h — ContainerInformation#State has no custom JSON enum serializer, so
129
+ # nlohmann::json emits its raw ordinal): 0 invalid, 1 created, 2 running, 3 exited, 4
130
+ # deleted. Unlike Docker, there's no separate "dead" state — only `exited` is a live,
131
+ # restartable exit; `deleted` means the container itself is gone (needs `wip up` to
132
+ # recreate it, not `start`).
133
+ WSLC_CONTAINER_STATE_EXITED = 3
134
+
85
135
  desc 'up', 'Start the configured container and its dependencies, creating them if necessary'
86
136
  option :detach, type: :boolean, default: false, aliases: '-d'
87
137
  option :sync, type: :boolean, default: true, desc: 'Mirror the source into the sync volume first (--no-sync skips)'
88
138
  option :no_cache, type: :boolean, default: false, desc: 'Build compose-native images without cached layers'
139
+ option :watch, type: :boolean, default: false, aliases: '-w',
140
+ desc: 'Poll dependencies and restart any exited one whose restart: allows it (implies -d)'
141
+ option :interval, type: :numeric, default: 5, desc: 'Seconds between --watch polls (default: 5)'
89
142
  def up
90
- if load_config.compose?
91
- sync_before_boot if options[:sync]
92
- return execute(compose_bridge.up(detach: options[:detach]), interactive: tty?(!options[:detach]))
93
- end
143
+ return up_via_compose_bridge if load_config.compose?
144
+
145
+ # Validated up front, before any startup side effect (image build, network/dependency/
146
+ # container creation) — otherwise a bad --interval would only surface as a ConfigError
147
+ # after already bringing everything up.
148
+ interval = restart_interval if options[:watch]
94
149
 
95
150
  ensure_compose_images
96
151
  ensure_network
97
152
  sidecar_names.each { |name| ensure_dependency(name) }
98
153
  sync_before_boot if options[:sync]
99
154
  ensure_container
155
+ watch_restarts(interval) if options[:watch]
100
156
  end
101
157
 
102
158
  desc 'sync', 'Mirror the source tree into the sync volume'
@@ -194,7 +250,7 @@ module Wip
194
250
 
195
251
  desc 'dispatch COMMAND [ARGS...]', 'Run a command defined in wip.yml'
196
252
  def dispatch(name = nil, *arguments)
197
- raise ConfigError, 'A command is required' unless name
253
+ return help if name.nil?
198
254
 
199
255
  values = load_config.command(name) || raise(ConfigError, "Unknown command: #{name}")
200
256
  return dispatch_compose(name, values, arguments) if load_config.compose?
@@ -407,16 +463,79 @@ module Wip
407
463
  warn "wip: run `wip sync --watch` in another terminal to keep #{settings.target} up to date"
408
464
  end
409
465
 
466
+ def up_via_compose_bridge
467
+ if options[:watch]
468
+ raise ConfigError, '`wip up --watch` is not supported under mode: compose (wip never parses a ' \
469
+ 'compose.yml service list in that mode, so there is nothing to poll)'
470
+ end
471
+
472
+ sync_before_boot if options[:sync]
473
+ execute(compose_bridge.up(detach: options[:detach]), interactive: tty?(!options[:detach]))
474
+ end
475
+
410
476
  def ensure_container
411
477
  container = load_config.container
412
- interactive = tty?(!options[:detach])
478
+ interactive = tty?(!detach?)
413
479
  if resource_exists?(builder.find)
414
480
  warn "wip: starting existing container '#{container}'"
415
- execute(builder.start(detach: options[:detach]), interactive: interactive)
481
+ execute(builder.start(detach: detach?), interactive: interactive)
416
482
  else
417
483
  warn "wip: container '#{container}' not found, creating it"
418
- execute(builder.up(detach: options[:detach]), interactive: interactive)
484
+ execute(builder.up(detach: detach?), interactive: interactive)
485
+ end
486
+ end
487
+
488
+ # --watch polls in a loop after boot, which can't share this one thread with an attached
489
+ # (`-it`) primary container — force the same effective behavior `-d` gives ensure_container.
490
+ def detach? = options[:detach] || options[:watch]
491
+
492
+ # Approximates Docker Compose's `restart:` policy via a foreground poll loop — not a
493
+ # background daemon/service (see README "Roadmap"); the same opt-in, keep-a-terminal-open
494
+ # shape as `wip sync --watch`.
495
+ def watch_restarts(interval)
496
+ names = load_config.dependencies.keys
497
+ warn "wip: watching #{names.join(', ')} for exited restart: containers every #{interval}s " \
498
+ '(running detached; Ctrl-C to stop)'
499
+ loop do
500
+ names.each { |name| restart_if_exited(name) }
501
+ sleep interval
419
502
  end
503
+ rescue Interrupt
504
+ warn "\nwip: watch stopped"
505
+ end
506
+
507
+ def restart_interval
508
+ raise ConfigError, '--interval must be a positive number' unless options[:interval].positive?
509
+
510
+ options[:interval]
511
+ end
512
+
513
+ # Status-based, not transition-based: each tick checks current state, not whether it *just*
514
+ # exited. Can't distinguish "crashed on its own" from "you ran `wip stop`/`wip down` in
515
+ # another terminal" — Ctrl-C this loop first if you're about to do either (see README).
516
+ def restart_if_exited(name)
517
+ policy = load_config.dependency(name)['restart']
518
+ return unless auto_restart?(policy)
519
+ return unless container_status(name) == WSLC_CONTAINER_STATE_EXITED
520
+
521
+ warn "wip: '#{name}' has exited, restarting it (restart: #{policy})"
522
+ execute(builder.dependency_start(name), exit_on_failure: false)
523
+ end
524
+
525
+ def auto_restart?(policy) = AUTO_RESTART_POLICIES.include?(policy) || ON_FAILURE_POLICY.match?(policy.to_s)
526
+
527
+ # Isolated to this one method: if a future wslc release changes this shape, fixing it here
528
+ # is a one-line change. Logs the raw entry under --debug so that's immediately visible
529
+ # instead of silently no-op'ing forever.
530
+ def container_status(name)
531
+ code, output = probe(builder.dependency_find(name))
532
+ return nil unless code.zero?
533
+
534
+ entry = JSON.parse(output).first
535
+ warn "wip: [debug] '#{name}': #{entry.inspect}" if debug?
536
+ entry&.fetch('State', nil)
537
+ rescue JSON::ParserError
538
+ nil
420
539
  end
421
540
  end
422
541
  end
@@ -15,15 +15,17 @@ module Wip
15
15
  # "Compose mode (native)") — once wslc ships that support, or a
16
16
  # compose-for-wslc tool reliably supports `run`.
17
17
  class ComposeFile
18
- Service = Struct.new(:image, :build, :command, :env, :ports, :volumes, :workdir, :user, :depends_on, :profiles,
19
- keyword_init: true)
18
+ Service = Struct.new(:image, :build, :command, :env, :ports, :volumes, :workdir, :user, :restart, :depends_on,
19
+ :profiles, keyword_init: true)
20
20
 
21
- SERVICE_KEYS = %w[image build command environment ports volumes working_dir user depends_on profiles].freeze
21
+ SERVICE_KEYS = %w[image build command environment ports volumes working_dir user restart depends_on
22
+ profiles].freeze
22
23
  # Real Compose keys that read as meaningful here but have nothing to map onto: TTY/stdin
23
24
  # allocation is decided per invocation (CommandBuilder#tty?), not per service; every
24
25
  # service already shares one project network (config.rb); and `wslc run`/`exec` has no
25
- # restart-policy or capability flag to forward `restart:`/`cap_add:` to.
26
- IGNORED_SERVICE_KEYS = %w[tty stdin_open networks restart cap_add].freeze
26
+ # capability flag to forward `cap_add:` to. (`restart:` used to be here too, silently
27
+ # ignored it's parsed below now and approximated by `wip up --watch`, see cli.rb.)
28
+ IGNORED_SERVICE_KEYS = %w[tty stdin_open networks cap_add].freeze
27
29
  BUILD_KEYS = %w[context dockerfile args shadow_context].freeze
28
30
  SUPPORTED_CONDITIONS = %w[service_started].freeze
29
31
  LIST_HINT = 'only supports short syntax ("host:container"), not long-syntax mappings'
@@ -65,14 +67,14 @@ module Wip
65
67
  end.to_h
66
68
  end
67
69
 
68
- # Shaped like Config::DEPENDENCY_DEFAULTS expects: image/command/env/ports/volumes/workdir,
70
+ # Shaped like Config::DEPENDENCY_DEFAULTS expects: image/command/env/ports/volumes/workdir/restart,
69
71
  # in dependency order so callers iterating sidecars start them before their dependents.
70
72
  def to_dependencies_hash
71
73
  startable_order.to_h do |name|
72
74
  service = @services.fetch(name)
73
75
  [name, { 'image' => service.build ? image_tag(name, service) : service.image, 'command' => service.command,
74
76
  'env' => service.env, 'ports' => service.ports, 'volumes' => service.volumes,
75
- 'workdir' => service.workdir, 'user' => service.user }]
77
+ 'workdir' => service.workdir, 'user' => service.user, 'restart' => service.restart }]
76
78
  end
77
79
  end
78
80
 
@@ -97,6 +99,7 @@ module Wip
97
99
  Service.new(image: image, build: build, command: normalize_command(entry['command']),
98
100
  env: normalize_env(name, entry['environment']), **normalize_service_lists(name, entry),
99
101
  workdir: presence(entry['working_dir']), user: presence(entry['user']),
102
+ restart: normalize_restart(entry['restart']),
100
103
  depends_on: normalize_depends_on(name, entry['depends_on']))
101
104
  end
102
105
 
@@ -250,6 +253,20 @@ module Wip
250
253
  order << name
251
254
  end
252
255
 
256
+ # Compose's own default (`no`) applies whether restart: is absent or explicitly falsy —
257
+ # including the very common unquoted `restart: no`, which YAML resolves to the boolean
258
+ # `false`, not the string "no" (confirmed against this repo's own Psych: `YAML.safe_load
259
+ # ("restart: no")` => {"restart"=>false}). Every other value is accepted as-is, even ones
260
+ # outside always/unless-stopped/on-failure[:N]: this parser's job is to read what's in
261
+ # compose.yml, not police it — `wip up --watch` (cli.rb) decides which values it acts on.
262
+ # Rejecting a real, valid Compose value here would break projects that already work today
263
+ # (compose.yml predates wip, unlike wip.yml itself).
264
+ def normalize_restart(value)
265
+ return 'no' if value == false
266
+
267
+ presence(value) || 'no'
268
+ end
269
+
253
270
  def presence(value) = value.to_s.empty? ? nil : value.to_s
254
271
  end
255
272
  end
data/lib/wip/config.rb CHANGED
@@ -8,7 +8,7 @@ module Wip
8
8
  # Applied to every dependencies: entry, primary container included — there
9
9
  # is no separate, differently-shaped bucket for "the one you exec into."
10
10
  DEPENDENCY_DEFAULTS = { 'workdir' => nil, 'user' => nil, 'interactive' => false, 'remove' => true,
11
- 'env' => {}, 'ports' => [], 'volumes' => [] }.freeze
11
+ 'env' => {}, 'ports' => [], 'volumes' => [], 'restart' => 'no' }.freeze
12
12
  SECRET_PATTERN = /token|password|secret|credential|auth/i
13
13
  # Which orchestration path `up`/`down`/`sync`/etc. take. Explicit rather than
14
14
  # inferred from a `compose:` block's presence, so a config reader doesn't have
@@ -209,6 +209,16 @@ module Wip
209
209
  raise ConfigError, "dependencies.#{name} must set image" if entry['image'].to_s.empty?
210
210
 
211
211
  entry['env']&.transform_values!(&:to_s)
212
+ normalize_dependency_restart!(entry)
213
+ end
214
+
215
+ # Mirrors ComposeFile#normalize_restart: an unquoted `restart: no` parses as the boolean
216
+ # false rather than the string "no", and an explicit nil/"" should default the same way an
217
+ # absent key does (DEPENDENCY_DEFAULTS only fills in a genuinely *missing* key).
218
+ def normalize_dependency_restart!(entry)
219
+ return unless entry.key?('restart')
220
+
221
+ entry['restart'] = 'no' if entry['restart'] == false || entry['restart'].to_s.empty?
212
222
  end
213
223
 
214
224
  def stringify(object)
@@ -159,6 +159,10 @@ module Wip
159
159
  # optional; remove the container after each run
160
160
  remove: true
161
161
 
162
+ # optional; restart policy `wip up --watch` polls for — always/unless-stopped/on-failure
163
+ # restart an exited container; "no" (the default) never does — see README
164
+ restart: "no"
165
+
162
166
  # optional; environment variables passed to the container, e.g. {FOO: bar}
163
167
  env: {}
164
168
 
data/lib/wip/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wip
4
- VERSION = '1.1.2'
4
+ VERSION = '1.1.3'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wslc-wip
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wip contributors