wslc-wip 0.18.2 → 1.1.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/README.md +237 -126
- data/lib/wip/build_context.rb +151 -9
- data/lib/wip/cli.rb +2 -1
- data/lib/wip/config.rb +27 -1
- data/lib/wip/initializer.rb +6 -5
- data/lib/wip/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e2bffbab317006bdd799443ef1dc7e16d9527219261f91c9758504ae85aef638
|
|
4
|
+
data.tar.gz: b1e0f8f72d44878564573fcc8076af18defe14375d1bee4cd4946c72139bcc1e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d16eda21cb080e2dd7decc2522c4dac7c99536f3c720c1de7d64f0065edd7487c633450db9eeb687b12138b2763d3093628e0d169b82d7896c781e7c8298bb3c
|
|
7
|
+
data.tar.gz: 5f668930355e17877237892da08dc440630f2015253b072c715e324c38045598765c58cb84d9657d7769527d3f27774f8d4f03f6bec2cc1bb785bca9a308cce2
|
data/README.md
CHANGED
|
@@ -16,6 +16,40 @@ commands into a single `wip.yml`, and forwards them to `wslc.exe` / `wslc` as sa
|
|
|
16
16
|
|
|
17
17
|
> **Status:** early release. Expect to track WSLC's own interface as it evolves.
|
|
18
18
|
|
|
19
|
+
## Contents
|
|
20
|
+
|
|
21
|
+
- [Which mode should you use?](#which-mode-should-you-use)
|
|
22
|
+
- [Requirements & installation](#requirements--installation)
|
|
23
|
+
- [Quick start](#quick-start)
|
|
24
|
+
- [Configuration](#configuration)
|
|
25
|
+
- [Container mode](#container-mode)
|
|
26
|
+
- [Compose mode](#compose-mode)
|
|
27
|
+
- [Compose mode (native)](#compose-mode-native)
|
|
28
|
+
- [.env](#env)
|
|
29
|
+
- [.dockerignore](#dockerignore)
|
|
30
|
+
- [Source sync](#source-sync)
|
|
31
|
+
- [Commands](#commands)
|
|
32
|
+
- [doctor](#doctor)
|
|
33
|
+
- [Common errors](#common-errors)
|
|
34
|
+
- [FAQ](#faq)
|
|
35
|
+
- [Development](#development)
|
|
36
|
+
- [Contributing](#contributing)
|
|
37
|
+
- [Roadmap](#roadmap)
|
|
38
|
+
- [License](#license)
|
|
39
|
+
|
|
40
|
+
## Which mode should you use?
|
|
41
|
+
|
|
42
|
+
`wip.yml` runs in one of three modes, set with `mode:`. Pick the one that matches your project:
|
|
43
|
+
|
|
44
|
+
| Situation | Use |
|
|
45
|
+
|---|---|
|
|
46
|
+
| No `compose.yml` — wip manages containers directly | [`mode: container`](#container-mode) (default) |
|
|
47
|
+
| Have `compose.yml`, don't want to install a third-party tool | [`mode: compose-native`](#compose-mode-native) |
|
|
48
|
+
| Have `compose.yml` and already use/prefer a third-party compose-for-`wslc` tool | [`mode: compose`](#compose-mode) |
|
|
49
|
+
|
|
50
|
+
`wip init` picks `compose-native` automatically when it finds a `compose.yml`/`docker-compose.yml`
|
|
51
|
+
next to it, and `container` otherwise — see [Commands](#commands).
|
|
52
|
+
|
|
19
53
|
## Requirements & installation
|
|
20
54
|
|
|
21
55
|
Ruby 3.2+, WSL2, and Microsoft WSLC.
|
|
@@ -28,6 +62,9 @@ From source: `bundle install && bundle exec exe/wip version`.
|
|
|
28
62
|
|
|
29
63
|
## Quick start
|
|
30
64
|
|
|
65
|
+
This walks through `mode: container` (the default). Already have a `compose.yml`? See
|
|
66
|
+
[Which mode should you use?](#which-mode-should-you-use) first.
|
|
67
|
+
|
|
31
68
|
```bash
|
|
32
69
|
gem install wslc-wip
|
|
33
70
|
cd my-project
|
|
@@ -38,19 +75,19 @@ wip up -d
|
|
|
38
75
|
wip rails console
|
|
39
76
|
```
|
|
40
77
|
|
|
41
|
-
##
|
|
78
|
+
## Configuration
|
|
42
79
|
|
|
43
80
|
Put a `wip.yml` in your project root. Running from a subdirectory walks up to find it, or pass
|
|
44
81
|
`--config PATH` to point at one explicitly.
|
|
45
82
|
|
|
83
|
+
### Container mode
|
|
84
|
+
|
|
46
85
|
```yaml
|
|
47
86
|
version: 1
|
|
48
|
-
mode: container # default
|
|
49
|
-
# for mode: compose, which replaces container:/network:/dependencies: with a
|
|
50
|
-
# compose: block instead; the two don't mix within one wip.yml.
|
|
87
|
+
mode: container # default
|
|
51
88
|
wslc:
|
|
52
89
|
command: auto # tries wslc.exe, wslc, then System32; an absolute path also works
|
|
53
|
-
container: app # required once dependencies: has entries; which one `up`/`exec`/`run`/`build`/`
|
|
90
|
+
container: app # required once dependencies: has entries; which one `up`/`exec`/`run`/`build`/`interaction:`
|
|
54
91
|
# target. No default — a project must say which entry is the primary one explicitly.
|
|
55
92
|
network: app-tier # optional; shared by every dependencies: entry so containers can resolve each other by name
|
|
56
93
|
dependencies:
|
|
@@ -76,7 +113,7 @@ dependencies:
|
|
|
76
113
|
env:
|
|
77
114
|
MYSQL_ROOT_PASSWORD: password
|
|
78
115
|
MYSQL_DATABASE: development
|
|
79
|
-
|
|
116
|
+
interaction:
|
|
80
117
|
rails:
|
|
81
118
|
type: exec
|
|
82
119
|
command: bin/rails
|
|
@@ -101,6 +138,7 @@ commands:
|
|
|
101
138
|
type: build
|
|
102
139
|
context: .
|
|
103
140
|
tag: slidict/slidict:development
|
|
141
|
+
shadow_context: /mnt/c/Users/me/AppData/Local/wip/build-contexts
|
|
104
142
|
sync: # optional; mirror the source into a named volume instead of bind-mounting it live
|
|
105
143
|
exclude:
|
|
106
144
|
- .git
|
|
@@ -112,23 +150,12 @@ sync: # optional; mirror the source into a named volume instead of bind-mounting
|
|
|
112
150
|
credential, or auth. Keep real secrets out of the config file and in your runtime environment
|
|
113
151
|
instead.
|
|
114
152
|
|
|
115
|
-
|
|
153
|
+
`interaction:` can also be spelled `commands:` — the same block under a different name, e.g. for
|
|
154
|
+
projects that already use `commands:`. `wip config` prints the effective config using `commands:`.
|
|
155
|
+
The two are aliases for the same feature, not separate ones: pick whichever name you like, but declaring both
|
|
156
|
+
`commands:` and `interaction:` in the same `wip.yml` is a `ConfigError`.
|
|
116
157
|
|
|
117
|
-
|
|
118
|
-
per line; `#` comments, blank lines, `export` prefixes, and quoted values are all supported) and
|
|
119
|
-
passes its keys through as container environment variables on `build`, `up`, `run`, `exec`, and
|
|
120
|
-
custom commands. `.env` only fills in keys that aren't already set by the primary container's
|
|
121
|
-
`env` or a command/dependency's own `env` — those always win on conflict. Pass `--env-file PATH`
|
|
122
|
-
to load a different file instead.
|
|
123
|
-
|
|
124
|
-
### .dockerignore
|
|
125
|
-
|
|
126
|
-
`wip build` reads `.dockerignore` from the build context and stages a filtered copy of the
|
|
127
|
-
context (skipping anything it matches) before handing it to `wslc build`, since `wslc` sends the
|
|
128
|
-
context as-is otherwise. If there's no `.dockerignore`, the original context directory is used
|
|
129
|
-
directly with no copying.
|
|
130
|
-
|
|
131
|
-
### Dependency containers
|
|
158
|
+
#### Dependency containers
|
|
132
159
|
|
|
133
160
|
`dependencies:` holds every container uniformly — the primary one `container:` points at and any
|
|
134
161
|
sidecar services (a database, Redis, ...) alongside it. Each entry accepts `image` (required),
|
|
@@ -141,10 +168,118 @@ entry by name first (creating `network:` beforehand if it doesn't exist and set)
|
|
|
141
168
|
starts the primary one — so `bin/rails c` (or anything else run inside it) can reach
|
|
142
169
|
`development.mysql`/`redis`/etc. by their dependency name, the same way Compose's service names
|
|
143
170
|
resolve. `wip down` tears the primary container and all sidecars down (the network itself is left
|
|
144
|
-
in place). Only the primary container is a target for `exec`/`run`/`build`/`
|
|
171
|
+
in place). Only the primary container is a target for `exec`/`run`/`build`/`interaction:` — sidecars
|
|
145
172
|
are only ever started and stopped, matching Compose's own service-vs-you-exec-into-one-of-them
|
|
146
173
|
split.
|
|
147
174
|
|
|
175
|
+
### Compose mode
|
|
176
|
+
|
|
177
|
+
If your project already has a real `compose.yml`, don't duplicate it in `dependencies:` — point
|
|
178
|
+
`wip` at it instead:
|
|
179
|
+
|
|
180
|
+
```yaml
|
|
181
|
+
version: 1
|
|
182
|
+
mode: compose # required to enable compose mode; a compose: block with no mode: compose is an error
|
|
183
|
+
compose:
|
|
184
|
+
service: app # required: which compose service wip run/exec/NAME target
|
|
185
|
+
command: wslc-compose # required: the compose-for-wslc binary/path you have installed
|
|
186
|
+
file: compose.yml # optional; auto-detected next to wip.yml otherwise
|
|
187
|
+
project: myapp # optional; omitted lets the compose tool pick its own default
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`compose:` is mutually exclusive with `dependencies:`/`network` — pick one orchestration path per
|
|
191
|
+
project. In compose mode, `wip` becomes a thin bridge to an external compose-for-`wslc` CLI rather
|
|
192
|
+
than reimplementing Compose itself.
|
|
193
|
+
|
|
194
|
+
`wslc` itself has no native Compose support yet (tracked upstream in
|
|
195
|
+
[microsoft/WSL#40948](https://github.com/microsoft/WSL/issues/40948)), and until it does,
|
|
196
|
+
independent third-party tools fill the gap — for example
|
|
197
|
+
[bacarndiaye/wslc-compose](https://github.com/bacarndiaye/wslc-compose) (Python) and
|
|
198
|
+
[inuyume/wslc-compose](https://github.com/inuyume/wslc-compose) (Go), among others. `wslc` is new
|
|
199
|
+
and still evolving, so expect more of these to show up (and existing ones to change) over time.
|
|
200
|
+
`wip` deliberately doesn't pick a winner or default to any of them (unlike `wslc.command`, which
|
|
201
|
+
defaults to `auto` and searches for `wslc.exe`/`wslc`): `compose.command` is required and treats
|
|
202
|
+
every implementation equally — set it to whichever binary name or absolute path you've installed.
|
|
203
|
+
Whichever one you use needs to understand `-f FILE [-p PROJECT] up|down|exec|logs`, the subset of
|
|
204
|
+
the Compose CLI vocabulary `wip` drives. `wip doctor` reports whether the configured command is
|
|
205
|
+
found, its version, and which compose file `wip` resolved.
|
|
206
|
+
|
|
207
|
+
- `wip up`/`wip down` delegate straight to `<compose command> up -d`/`down`.
|
|
208
|
+
- `wip exec`/`wip NAME` (custom `interaction:`) run inside `compose.service`.
|
|
209
|
+
- `wip shell` also goes through the bridge: unless `interaction.shell` is defined in `wip.yml`, it
|
|
210
|
+
`exec`s `bash` against `compose.service`, falling back to `sh`.
|
|
211
|
+
- `wip logs [-f] [SERVICE...]` is only available in compose mode.
|
|
212
|
+
- `wip run` has no ephemeral-container equivalent in this exec-only vocabulary, so it falls back
|
|
213
|
+
to `exec` against the already-running `compose.service` (wip warns when this happens).
|
|
214
|
+
- `interaction:` entries with `type: run`/`type: build` aren't supported in compose mode — use your
|
|
215
|
+
compose tool's own `build`/`up --build` directly; compose owns builds for its own services.
|
|
216
|
+
|
|
217
|
+
### Compose mode (native)
|
|
218
|
+
|
|
219
|
+
Don't want to install a third-party compose-for-`wslc` tool at all? `mode: compose-native` parses
|
|
220
|
+
`compose.yml` itself and drives `wslc` directly, the same way `mode: container` +`dependencies:`
|
|
221
|
+
already does — no external binary, and `wip run` gets a real ephemeral `wslc run --rm` instead of
|
|
222
|
+
the `exec` fallback above:
|
|
223
|
+
|
|
224
|
+
```yaml
|
|
225
|
+
version: 1
|
|
226
|
+
mode: compose-native
|
|
227
|
+
|
|
228
|
+
compose:
|
|
229
|
+
service: app # required: which compose service wip run/exec/NAME target
|
|
230
|
+
file: compose.yml # optional; auto-detected next to wip.yml otherwise
|
|
231
|
+
project: myapp # optional; also names wip's own project network (defaults to the wip.yml
|
|
232
|
+
# directory's name) so services can reach each other by name
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
There's no `compose.command` here (no external binary to name), and no top-level `container:` —
|
|
236
|
+
`compose.service` already names it.
|
|
237
|
+
|
|
238
|
+
This is explicitly a stopgap for as long as `wslc` itself has no native Compose support (tracked
|
|
239
|
+
upstream in [microsoft/WSL#40948](https://github.com/microsoft/WSL/issues/40948)) and third-party
|
|
240
|
+
compose-for-`wslc` tools stay incomplete. It only understands a minimal subset of the Compose spec.
|
|
241
|
+
Within `services.<name>:`, anything outside that subset is a load-time `ConfigError` naming the
|
|
242
|
+
offending key, rather than silently ignored — but everything *outside* `services:` at the document's
|
|
243
|
+
top level (`networks:`, `volumes:`, `configs:`, `secrets:`, ...) is the one exception: it's read by
|
|
244
|
+
real Compose tools, not by `wip`, so `wip` silently ignores it rather than rejecting an otherwise
|
|
245
|
+
valid compose.yml over sections it doesn't need to look at:
|
|
246
|
+
|
|
247
|
+
- Per service: `image`, `build` (string or `{context:, dockerfile:}`; resolved relative to
|
|
248
|
+
`compose.yml`, not wherever `wip` is invoked from), `command` (shell or exec form), `environment`
|
|
249
|
+
(mapping or `KEY=VALUE` array — a mapping value must not be null; host environment pass-through
|
|
250
|
+
isn't supported), `ports`/`volumes` (short syntax only — `"host:container"` strings, not
|
|
251
|
+
long-syntax mappings), `working_dir`, `user`, `depends_on` (ordering only — a `condition:` other
|
|
252
|
+
than `service_started` is rejected, since there's no health-check support). `tty`, `stdin_open`,
|
|
253
|
+
and `networks` are accepted but silently ignored: TTY/stdin allocation is already decided per
|
|
254
|
+
invocation (see "TTY allocation" below), not fixed per service, and every service already shares
|
|
255
|
+
the one project network `compose.project` sets up.
|
|
256
|
+
- `wip logs` takes at most one `SERVICE` (defaulting to `compose.service`) — `wslc logs`, like
|
|
257
|
+
`docker logs`, follows a single container, unlike a real compose tool's multi-service view.
|
|
258
|
+
- `sync:` behaves exactly like `mode: container`'s (falls back to the primary service's own image,
|
|
259
|
+
defaults to `sync.mode: exec`) — none of the external bridge's `sync.image`/`sync.build`
|
|
260
|
+
requirement applies, since wip itself boots every container here.
|
|
261
|
+
|
|
262
|
+
### .env
|
|
263
|
+
|
|
264
|
+
Like `docker compose`, `wip` automatically loads a `.env` file next to `wip.yml` (one `KEY=VALUE`
|
|
265
|
+
per line; `#` comments, blank lines, `export` prefixes, and quoted values are all supported) and
|
|
266
|
+
passes its keys through as container environment variables on `build`, `up`, `run`, `exec`, and
|
|
267
|
+
custom commands. `.env` only fills in keys that aren't already set by the primary container's
|
|
268
|
+
`env` or a command/dependency's own `env` — those always win on conflict. Pass `--env-file PATH`
|
|
269
|
+
to load a different file instead.
|
|
270
|
+
|
|
271
|
+
### .dockerignore
|
|
272
|
+
|
|
273
|
+
`wip build` reads `.dockerignore` from the build context and excludes anything it matches before
|
|
274
|
+
handing the context to `wslc build`, since `wslc` sends the context as-is otherwise. Set
|
|
275
|
+
`commands.build.shadow_context` in `wip.yml` to a directory on the Windows filesystem to enable a
|
|
276
|
+
persistent shadow context for projects outside `/mnt/<drive>`. The first build copies every
|
|
277
|
+
included file; later builds only copy added or changed files and remove deleted or newly ignored
|
|
278
|
+
files. Without this setting the optimization is disabled, and it only applies under WSL2 — on WSL1
|
|
279
|
+
(or anywhere else) the context is handed to `wslc build` directly. Projects already on `/mnt/c` (or
|
|
280
|
+
another mounted Windows drive) also continue to build directly even when the setting is present.
|
|
281
|
+
The path must live outside the build context itself, or `wip build` refuses it.
|
|
282
|
+
|
|
148
283
|
### Source sync
|
|
149
284
|
|
|
150
285
|
Bind-mounting the app directory (`.:/app`) is what usually makes a container boot crawl under
|
|
@@ -221,7 +356,7 @@ Everything below `sync:` is optional — `sync: {}` alone already works. With it
|
|
|
221
356
|
- With `sync.build` configured, `wip build`s that image once per `wip up`/`wip sync` invocation
|
|
222
357
|
(including once before a `--watch` loop starts, not on every tick) before mirroring with it.
|
|
223
358
|
|
|
224
|
-
Like every built-in command, `wip sync` takes precedence over
|
|
359
|
+
Like every built-in command, `wip sync` takes precedence over an `interaction:` entry of the same
|
|
225
360
|
name; wip says so and points at `wip dispatch sync`, which still runs yours.
|
|
226
361
|
|
|
227
362
|
Two things to keep in mind. The mirror runs `rsync` *inside* the container, so the image needs it
|
|
@@ -275,93 +410,6 @@ and uses the result, tagged `wip-sync-<container>:latest` by default (`build.tag
|
|
|
275
410
|
Prefer managing the image yourself instead? Build and tag it however you like, then set `sync.image`
|
|
276
411
|
to that tag directly — `sync.build`'s tag wins if both are set, so don't configure both at once.
|
|
277
412
|
|
|
278
|
-
### Compose mode
|
|
279
|
-
|
|
280
|
-
If your project already has a real `compose.yml`, don't duplicate it in `dependencies:` — point
|
|
281
|
-
`wip` at it instead:
|
|
282
|
-
|
|
283
|
-
```yaml
|
|
284
|
-
version: 1
|
|
285
|
-
mode: compose # required to enable compose mode; a compose: block with no mode: compose is an error
|
|
286
|
-
compose:
|
|
287
|
-
service: app # required: which compose service wip run/exec/NAME target
|
|
288
|
-
command: wslc-compose # required: the compose-for-wslc binary/path you have installed
|
|
289
|
-
file: compose.yml # optional; auto-detected next to wip.yml otherwise
|
|
290
|
-
project: myapp # optional; omitted lets the compose tool pick its own default
|
|
291
|
-
```
|
|
292
|
-
|
|
293
|
-
`compose:` is mutually exclusive with `dependencies:`/`network` — pick one orchestration path per
|
|
294
|
-
project. In compose mode, `wip` becomes a thin bridge to an external compose-for-`wslc` CLI rather
|
|
295
|
-
than reimplementing Compose itself.
|
|
296
|
-
|
|
297
|
-
`wslc` itself has no native Compose support yet (tracked upstream in
|
|
298
|
-
[microsoft/WSL#40948](https://github.com/microsoft/WSL/issues/40948)), and until it does,
|
|
299
|
-
independent third-party tools fill the gap — for example
|
|
300
|
-
[bacarndiaye/wslc-compose](https://github.com/bacarndiaye/wslc-compose) (Python) and
|
|
301
|
-
[inuyume/wslc-compose](https://github.com/inuyume/wslc-compose) (Go), among others. `wslc` is new
|
|
302
|
-
and still evolving, so expect more of these to show up (and existing ones to change) over time.
|
|
303
|
-
`wip` deliberately doesn't pick a winner or default to any of them (unlike `wslc.command`, which
|
|
304
|
-
defaults to `auto` and searches for `wslc.exe`/`wslc`): `compose.command` is required and treats
|
|
305
|
-
every implementation equally — set it to whichever binary name or absolute path you've installed.
|
|
306
|
-
Whichever one you use needs to understand `-f FILE [-p PROJECT] up|down|exec|logs`, the subset of
|
|
307
|
-
the Compose CLI vocabulary `wip` drives. `wip doctor` reports whether the configured command is
|
|
308
|
-
found, its version, and which compose file `wip` resolved.
|
|
309
|
-
|
|
310
|
-
- `wip up`/`wip down` delegate straight to `<compose command> up -d`/`down`.
|
|
311
|
-
- `wip exec`/`wip NAME` (custom `commands:`) run inside `compose.service`.
|
|
312
|
-
- `wip shell` also goes through the bridge: unless `commands.shell` is defined in `wip.yml`, it
|
|
313
|
-
`exec`s `bash` against `compose.service`, falling back to `sh`.
|
|
314
|
-
- `wip logs [-f] [SERVICE...]` is only available in compose mode.
|
|
315
|
-
- `wip run` has no ephemeral-container equivalent in this exec-only vocabulary, so it falls back
|
|
316
|
-
to `exec` against the already-running `compose.service` (wip warns when this happens).
|
|
317
|
-
- `commands:` entries with `type: run`/`type: build` aren't supported in compose mode — use your
|
|
318
|
-
compose tool's own `build`/`up --build` directly; compose owns builds for its own services.
|
|
319
|
-
|
|
320
|
-
### Compose mode (native)
|
|
321
|
-
|
|
322
|
-
Don't want to install a third-party compose-for-`wslc` tool at all? `mode: compose-native` parses
|
|
323
|
-
`compose.yml` itself and drives `wslc` directly, the same way `mode: container` +`dependencies:`
|
|
324
|
-
already does — no external binary, and `wip run` gets a real ephemeral `wslc run --rm` instead of
|
|
325
|
-
the `exec` fallback above:
|
|
326
|
-
|
|
327
|
-
```yaml
|
|
328
|
-
version: 1
|
|
329
|
-
mode: compose-native
|
|
330
|
-
|
|
331
|
-
compose:
|
|
332
|
-
service: app # required: which compose service wip run/exec/NAME target
|
|
333
|
-
file: compose.yml # optional; auto-detected next to wip.yml otherwise
|
|
334
|
-
project: myapp # optional; also names wip's own project network (defaults to the wip.yml
|
|
335
|
-
# directory's name) so services can reach each other by name
|
|
336
|
-
```
|
|
337
|
-
|
|
338
|
-
There's no `compose.command` here (no external binary to name), and no top-level `container:` —
|
|
339
|
-
`compose.service` already names it.
|
|
340
|
-
|
|
341
|
-
This is explicitly a stopgap for as long as `wslc` itself has no native Compose support (tracked
|
|
342
|
-
upstream in [microsoft/WSL#40948](https://github.com/microsoft/WSL/issues/40948)) and third-party
|
|
343
|
-
compose-for-`wslc` tools stay incomplete. It only understands a minimal subset of the Compose spec.
|
|
344
|
-
Within `services.<name>:`, anything outside that subset is a load-time `ConfigError` naming the
|
|
345
|
-
offending key, rather than silently ignored — but everything *outside* `services:` at the document's
|
|
346
|
-
top level (`networks:`, `volumes:`, `configs:`, `secrets:`, ...) is the one exception: it's read by
|
|
347
|
-
real Compose tools, not by `wip`, so `wip` silently ignores it rather than rejecting an otherwise
|
|
348
|
-
valid compose.yml over sections it doesn't need to look at:
|
|
349
|
-
|
|
350
|
-
- Per service: `image`, `build` (string or `{context:, dockerfile:}`; resolved relative to
|
|
351
|
-
`compose.yml`, not wherever `wip` is invoked from), `command` (shell or exec form), `environment`
|
|
352
|
-
(mapping or `KEY=VALUE` array — a mapping value must not be null; host environment pass-through
|
|
353
|
-
isn't supported), `ports`/`volumes` (short syntax only — `"host:container"` strings, not
|
|
354
|
-
long-syntax mappings), `working_dir`, `user`, `depends_on` (ordering only — a `condition:` other
|
|
355
|
-
than `service_started` is rejected, since there's no health-check support). `tty`, `stdin_open`,
|
|
356
|
-
and `networks` are accepted but silently ignored: TTY/stdin allocation is already decided per
|
|
357
|
-
invocation (see "TTY allocation" below), not fixed per service, and every service already shares
|
|
358
|
-
the one project network `compose.project` sets up.
|
|
359
|
-
- `wip logs` takes at most one `SERVICE` (defaulting to `compose.service`) — `wslc logs`, like
|
|
360
|
-
`docker logs`, follows a single container, unlike a real compose tool's multi-service view.
|
|
361
|
-
- `sync:` behaves exactly like `mode: container`'s (falls back to the primary service's own image,
|
|
362
|
-
defaults to `sync.mode: exec`) — none of the external bridge's `sync.image`/`sync.build`
|
|
363
|
-
requirement applies, since wip itself boots every container here.
|
|
364
|
-
|
|
365
413
|
## Commands
|
|
366
414
|
|
|
367
415
|
| Command | Description |
|
|
@@ -379,7 +427,7 @@ valid compose.yml over sections it doesn't need to look at:
|
|
|
379
427
|
| `wip shell` | Open the configured shell, falling back to `bash` then `sh` |
|
|
380
428
|
| `wip logs [-f] [SERVICE...]` | Follow compose service logs (compose modes only; mode: compose-native takes at most one `SERVICE`) |
|
|
381
429
|
| `wip sync [-w] [--interval N]` | Mirror the source into the sync volume once, or keep re-syncing with `--watch` (needs `sync:`) |
|
|
382
|
-
| `wip NAME ARGS...` | Run `
|
|
430
|
+
| `wip NAME ARGS...` | Run `interaction.NAME`, appending any extra arguments |
|
|
383
431
|
|
|
384
432
|
TTY allocation is decided by combining the command's config, the CLI option, and whether both
|
|
385
433
|
stdin and stdout are real TTYs.
|
|
@@ -474,6 +522,74 @@ docker buildx build \
|
|
|
474
522
|
--push .
|
|
475
523
|
```
|
|
476
524
|
|
|
525
|
+
## FAQ
|
|
526
|
+
|
|
527
|
+
**Which mode should I start with?**
|
|
528
|
+
Pick whichever `mode:` fits your project — see [Which mode should you use?](#which-mode-should-you-use)
|
|
529
|
+
for the breakdown.
|
|
530
|
+
|
|
531
|
+
**Can I use `dependencies:` and `compose:` together?**
|
|
532
|
+
No — `compose:` is mutually exclusive with `dependencies:`/`network`. Pick one orchestration path
|
|
533
|
+
per project; see [Configuration](#configuration).
|
|
534
|
+
|
|
535
|
+
**What's the difference between `mode: compose` and `mode: compose-native`?**
|
|
536
|
+
`compose` delegates to a third-party compose-for-`wslc` binary you install yourself
|
|
537
|
+
(`compose.command`); `compose-native` parses `compose.yml` itself and drives `wslc` directly, no
|
|
538
|
+
external tool required. `compose-native`'s Compose coverage isn't frozen at whatever it handles
|
|
539
|
+
today — it keeps growing until `wslc` ships native Compose support of its own. See
|
|
540
|
+
[Compose mode](#compose-mode) and [Compose mode (native)](#compose-mode-native).
|
|
541
|
+
|
|
542
|
+
**`dependencies:` already gives me sidecar containers — why would I need `compose-native` too?**
|
|
543
|
+
`dependencies:` and `compose-native` aren't really alternatives to each other — they're for two
|
|
544
|
+
different starting points. No `compose.yml`? Declare containers directly in `wip.yml`'s own shape
|
|
545
|
+
with `dependencies:`. Already have a `compose.yml`? Reusing it is where `compose-native` and
|
|
546
|
+
`mode: compose` both come in, so the real comparison is between those two, not against
|
|
547
|
+
`dependencies:`: `mode: compose` reuses it too, but only by delegating to a third-party
|
|
548
|
+
compose-for-`wslc` binary you install yourself; `compose-native` reuses the same `compose.yml`
|
|
549
|
+
without installing anything external, parsing it and driving `wslc` directly — and gets a real
|
|
550
|
+
`wslc run --rm` for `wip run` instead of the `exec` fallback `mode: compose` falls back to.
|
|
551
|
+
`mode: compose`'s coverage is whatever the external tool you point it at supports; `compose-native`'s
|
|
552
|
+
is maintained in this repo and actively extended, not treated as a permanent ceiling. See
|
|
553
|
+
[Which mode should you use?](#which-mode-should-you-use) for the full picture.
|
|
554
|
+
|
|
555
|
+
**What happens to `compose-native` once `wslc` gets official Compose support?**
|
|
556
|
+
`compose-native` exists to close the gap for as long as `wslc` has no native Compose support of its
|
|
557
|
+
own (tracked upstream in [microsoft/WSL#40948](https://github.com/microsoft/WSL/issues/40948)), and
|
|
558
|
+
we intend to keep extending its Compose coverage until that lands — see
|
|
559
|
+
[Compose mode (native)](#compose-mode-native) and [Roadmap](#roadmap). `wip.yml`'s shape (`mode:`,
|
|
560
|
+
`compose:`) isn't planned to change for existing `container`/`compose`/`compose-native` setups, so
|
|
561
|
+
whatever we do once `wslc` catches up won't require rewriting your config.
|
|
562
|
+
|
|
563
|
+
**Is `sync:` required?**
|
|
564
|
+
No, it's entirely optional. Add it if boot times feel slow with a bind-mounted app directory — see
|
|
565
|
+
[Slow boot when the app directory is bind-mounted](#slow-boot-when-the-app-directory-is-bind-mounted).
|
|
566
|
+
|
|
567
|
+
**How does `wip` actually fix the slow bind-mount boot problem?**
|
|
568
|
+
The slowness comes from `.:/app`-style bind mounts going through virtiofs, where frameworks that
|
|
569
|
+
stat/open many small files at startup (e.g. Ruby's Zeitwerk) pay a round trip per file. A `sync:`
|
|
570
|
+
block moves the app off that path entirely: the host source is mounted read-only, the app itself
|
|
571
|
+
runs off a named volume (fast native storage inside the VM), and `wip` mirrors the read-only
|
|
572
|
+
source into that volume with `rsync` — once before boot, and on demand afterward via `wip sync`
|
|
573
|
+
(or continuously with `wip sync --watch`). Since the app never touches the bind mount directly, its
|
|
574
|
+
own file access is no longer paying the virtiofs cost; the trade-off is a one-way, slightly-delayed
|
|
575
|
+
mirror instead of an always-live view of host edits. See [Source sync](#source-sync) for the full
|
|
576
|
+
config and [Slow boot when the app directory is bind-mounted](#slow-boot-when-the-app-directory-is-bind-mounted)
|
|
577
|
+
for the root cause.
|
|
578
|
+
|
|
579
|
+
**Is it safe to put passwords/secrets in `wip.yml`?**
|
|
580
|
+
`wip config` masks any key matching token/password/secret/credential/auth when printing, but the
|
|
581
|
+
raw file itself is not encrypted. Keep real secrets in your runtime environment or `.env` instead
|
|
582
|
+
of committing them in `wip.yml` — but `.env` is only safe if it's actually untracked: add `.env` to
|
|
583
|
+
`.gitignore` (and confirm with `git check-ignore .env`) before putting anything sensitive in it.
|
|
584
|
+
|
|
585
|
+
**`wslc.exe`/`wslc` isn't found — what do I do?**
|
|
586
|
+
See [WSLC not found](#wslc-not-found) under Common errors.
|
|
587
|
+
|
|
588
|
+
**I'm migrating from `dip` — do I have to rename `interaction:`?**
|
|
589
|
+
No, `wip.yml` accepts `interaction:` as-is — it's the primary spelling, same as in `dip`. `commands:`
|
|
590
|
+
also works as an alias if you prefer it, but not both in the same file (that's a `ConfigError`). See
|
|
591
|
+
[Container mode](#container-mode).
|
|
592
|
+
|
|
477
593
|
## Development
|
|
478
594
|
|
|
479
595
|
```bash
|
|
@@ -486,7 +602,8 @@ bundle exec rake
|
|
|
486
602
|
```
|
|
487
603
|
|
|
488
604
|
The test suite doesn't need WSLC — the resolution, build, and execution layers are all
|
|
489
|
-
swappable.
|
|
605
|
+
swappable. This project uses RuboCop for Ruby style and static analysis; `bundle exec rake` runs
|
|
606
|
+
both RSpec and RuboCop. GitHub Actions checks them on Ruby 3.2, 3.3, 3.4, and 4.0.
|
|
490
607
|
|
|
491
608
|
## Contributing
|
|
492
609
|
|
|
@@ -494,19 +611,10 @@ Bug reports and pull requests are welcome on [GitHub](https://github.com/slidict
|
|
|
494
611
|
[CONTRIBUTING.md](CONTRIBUTING.md) for commit conventions, versioning policy, and the PR
|
|
495
612
|
checklist.
|
|
496
613
|
|
|
497
|
-
## Not in the initial release
|
|
498
|
-
|
|
499
|
-
Full Compose compatibility isn't reimplemented in `wip` itself — `mode: compose-native` parses
|
|
500
|
-
`compose.yml` and drives `wslc` directly, but only covers a minimal subset (see
|
|
501
|
-
[Compose mode (native)](#compose-mode-native)); full parity is otherwise available by delegating
|
|
502
|
-
to a third-party compose-for-`wslc` tool (see [Compose mode](#compose-mode)). A resident/daemon
|
|
503
|
-
process, a GUI, PowerShell-specific tuning, direct registry API/manifest parsing, self-update, and
|
|
504
|
-
plugins are all unimplemented.
|
|
505
|
-
|
|
506
614
|
## Roadmap
|
|
507
615
|
|
|
508
616
|
`wip` already covers most of what [`dip`](https://github.com/bibendi/dip) adds on top of Compose —
|
|
509
|
-
named commands (`
|
|
617
|
+
named commands (`interaction:`), `run`/`exec` hidden behind a single verb, `.env` passthrough, and
|
|
510
618
|
sidecar services via `dependencies:` + `network:`. Rather than waiting on `wslc`'s own Compose
|
|
511
619
|
support ([microsoft/WSL#40948](https://github.com/microsoft/WSL/issues/40948)) or an external
|
|
512
620
|
compose-for-`wslc` tool staying complete, `mode: compose-native` (see
|
|
@@ -520,8 +628,11 @@ compose-for-`wslc` tool in [compose mode](#compose-mode) — which of those actu
|
|
|
520
628
|
entirely up to the external tool you point `compose.command` at; `wip` only forwards
|
|
521
629
|
`-f FILE [-p PROJECT] up|down|exec|logs`, so treat that list as what Compose offers, not as
|
|
522
630
|
something `wip` guarantees. See that section for what compose mode covers
|
|
523
|
-
and its current limitations (`run`, and `
|
|
524
|
-
|
|
631
|
+
and its current limitations (`run`, and `interaction:` of type `run`/`build`).
|
|
632
|
+
|
|
633
|
+
Beyond Compose parity, a resident/daemon process, a GUI, PowerShell-specific tuning, direct
|
|
634
|
+
registry API/manifest parsing, self-update, and plugins are all unimplemented and not currently
|
|
635
|
+
planned. What's still planned for `wip`, roughly in priority order:
|
|
525
636
|
|
|
526
637
|
1. **`wip provision`** — a dip-style one-shot bootstrap hook (build → up deps → install deps →
|
|
527
638
|
create/migrate/seed DB) so a new contributor can go from `git clone` to a working environment
|
|
@@ -541,7 +652,7 @@ for `wip`, roughly in priority order:
|
|
|
541
652
|
|
|
542
653
|
Each of these should stay additive to the existing `wip.yml` shape — no breaking changes to
|
|
543
654
|
`container`, `network`, `commands`, `dependencies`, or `compose` are planned. A resident daemon
|
|
544
|
-
and a GUI remain out of scope
|
|
655
|
+
and a GUI remain out of scope.
|
|
545
656
|
|
|
546
657
|
## License
|
|
547
658
|
|
data/lib/wip/build_context.rb
CHANGED
|
@@ -1,33 +1,175 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require '
|
|
3
|
+
require 'digest'
|
|
4
4
|
require 'fileutils'
|
|
5
|
+
require 'find'
|
|
6
|
+
require 'json'
|
|
5
7
|
require 'pathname'
|
|
6
8
|
require 'tmpdir'
|
|
7
9
|
|
|
8
10
|
module Wip
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# context as-is instead of filtering it itself.
|
|
11
|
+
# Filters build contexts for wslc and keeps WSL-hosted sources in a fast,
|
|
12
|
+
# persistent Windows-side shadow directory.
|
|
12
13
|
class BuildContext
|
|
13
|
-
def initialize(context, ignore: nil)
|
|
14
|
+
def initialize(context, ignore: nil, environment: Environment.new, shadow_root: nil)
|
|
14
15
|
@root = Pathname(context).expand_path
|
|
15
16
|
@ignore = ignore || DockerIgnore.load(@root.join('.dockerignore'))
|
|
17
|
+
@environment = environment
|
|
18
|
+
@shadow_root = validated_shadow_root(shadow_root) if shadow_root
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
# on_progress fires before copying and after each file, as `|count, total|`,
|
|
19
22
|
# so a caller can report elapsed progress even while copying one large file.
|
|
20
|
-
def stage(on_progress: nil)
|
|
21
|
-
return
|
|
23
|
+
def stage(on_progress: nil, &block)
|
|
24
|
+
return stage_shadow(on_progress, &block) if shadow_required?
|
|
25
|
+
return block.call(@root.to_s) if @ignore.empty?
|
|
22
26
|
|
|
23
27
|
Dir.mktmpdir('wip-build-context-') do |dir|
|
|
24
28
|
copy_included_files(Pathname(dir), on_progress)
|
|
25
|
-
|
|
29
|
+
block.call(dir)
|
|
26
30
|
end
|
|
27
31
|
end
|
|
28
32
|
|
|
29
33
|
private
|
|
30
34
|
|
|
35
|
+
# A shadow root under the context would itself be walked by included_files
|
|
36
|
+
# on the next build and copied into itself at ever-deeper paths, so the
|
|
37
|
+
# cache would grow without bound and the build would eventually fail.
|
|
38
|
+
def validated_shadow_root(shadow_root)
|
|
39
|
+
root = Pathname(shadow_root).expand_path
|
|
40
|
+
if root == @root || root.to_s.start_with?("#{@root}/")
|
|
41
|
+
raise ConfigError, "shadow_context (#{root}) must not be inside the build context (#{@root})"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
root
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def shadow_required?
|
|
48
|
+
return false unless @shadow_root
|
|
49
|
+
|
|
50
|
+
@environment.wsl2? && !@root.to_s.match?(%r{\A/mnt/[a-z](?:/|\z)}i)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Keep one stable Windows-side context per source path. Its manifest lives
|
|
54
|
+
# beside (rather than inside) the context so it is never sent to wslc.
|
|
55
|
+
def stage_shadow(on_progress)
|
|
56
|
+
key = Digest::SHA256.hexdigest(@root.to_s)
|
|
57
|
+
cache = @shadow_root.join(key)
|
|
58
|
+
context = cache.join('context')
|
|
59
|
+
FileUtils.mkdir_p(cache)
|
|
60
|
+
|
|
61
|
+
File.open(cache.join('lock'), File::RDWR | File::CREAT, 0o600) do |lock|
|
|
62
|
+
lock.flock(File::LOCK_EX)
|
|
63
|
+
synchronize_shadow(context, cache.join('manifest.json'), on_progress)
|
|
64
|
+
# Keep the shadow immutable until wslc has finished reading it.
|
|
65
|
+
yield context.to_s
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def synchronize_shadow(context, manifest_path, on_progress)
|
|
70
|
+
current = included_files.to_h { |entry| [entry, fingerprint(@root.join(entry))] }
|
|
71
|
+
previous = previous_manifest(context, manifest_path)
|
|
72
|
+
changed = current.keys.reject { |entry| current[entry] == previous[entry] }
|
|
73
|
+
removed = previous.keys - current.keys
|
|
74
|
+
|
|
75
|
+
apply_shadow_changes(context, changed, removed, on_progress)
|
|
76
|
+
FileUtils.mkdir_p(context)
|
|
77
|
+
write_manifest(manifest_path, current)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# A context we can't describe is a context we can't update incrementally:
|
|
81
|
+
# with no manifest there is no way to tell which of its entries are stale,
|
|
82
|
+
# so it gets discarded and rebuilt rather than left holding deleted or
|
|
83
|
+
# newly ignored files.
|
|
84
|
+
def previous_manifest(context, manifest_path)
|
|
85
|
+
return {} unless context.directory?
|
|
86
|
+
|
|
87
|
+
manifest = load_manifest(manifest_path)
|
|
88
|
+
return manifest if manifest
|
|
89
|
+
|
|
90
|
+
FileUtils.rm_rf(context)
|
|
91
|
+
{}
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def apply_shadow_changes(context, changed, removed, on_progress)
|
|
95
|
+
total = changed.size + removed.size
|
|
96
|
+
on_progress&.call(0, total)
|
|
97
|
+
|
|
98
|
+
removed.each_with_index do |entry, index|
|
|
99
|
+
FileUtils.rm_rf(context.join(entry))
|
|
100
|
+
prune_empty_parents(context.join(entry).dirname, context)
|
|
101
|
+
on_progress&.call(index + 1, total)
|
|
102
|
+
end
|
|
103
|
+
changed.each_with_index do |entry, index|
|
|
104
|
+
copy_entry_atomically(@root.join(entry), context.join(entry))
|
|
105
|
+
on_progress&.call(removed.size + index + 1, total)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Returns nil — not an empty manifest — when the manifest is missing,
|
|
110
|
+
# unreadable, or not a manifest at all, so callers can tell "nothing was
|
|
111
|
+
# synced yet" apart from "we no longer know what was synced".
|
|
112
|
+
def load_manifest(path)
|
|
113
|
+
manifest = JSON.parse(path.read)
|
|
114
|
+
manifest if manifest.is_a?(Hash)
|
|
115
|
+
rescue JSON::ParserError, SystemCallError
|
|
116
|
+
nil
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def write_manifest(path, contents)
|
|
120
|
+
temporary = Pathname("#{path}.tmp-#{Process.pid}")
|
|
121
|
+
temporary.write(JSON.generate(contents))
|
|
122
|
+
File.rename(temporary, path)
|
|
123
|
+
ensure
|
|
124
|
+
FileUtils.rm_f(temporary) if temporary
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def fingerprint(path)
|
|
128
|
+
stat = path.lstat
|
|
129
|
+
if stat.symlink?
|
|
130
|
+
{ 'type' => 'link', 'target' => path.readlink.to_s }
|
|
131
|
+
else
|
|
132
|
+
{ 'type' => 'file', 'size' => stat.size, 'mtime_ns' => stat.mtime.nsec + (stat.mtime.to_i * 1_000_000_000),
|
|
133
|
+
'mode' => stat.mode }
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# preserve: true keeps the source mode, so an executable stays executable
|
|
138
|
+
# even when the shadow lives on a DrvFs mount whose fmask would otherwise
|
|
139
|
+
# strip the bit and break a `RUN ./script` in the image build.
|
|
140
|
+
def copy_entry_atomically(source, target)
|
|
141
|
+
FileUtils.mkdir_p(target.dirname)
|
|
142
|
+
temporary = target.dirname.join(".#{target.basename}.wip-#{Process.pid}")
|
|
143
|
+
FileUtils.rm_rf(temporary)
|
|
144
|
+
FileUtils.copy_entry(source, temporary, true, false, false)
|
|
145
|
+
replace_atomically(temporary, target)
|
|
146
|
+
ensure
|
|
147
|
+
FileUtils.rm_rf(temporary) if temporary
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# rename replaces an existing entry in a single step, so an interrupted
|
|
151
|
+
# update leaves the previous copy in place instead of no copy at all. Only
|
|
152
|
+
# a target rename can't overwrite — a directory where a file now lives, or
|
|
153
|
+
# a filesystem without overwrite semantics — needs the unsafe fallback.
|
|
154
|
+
def replace_atomically(temporary, target)
|
|
155
|
+
File.rename(temporary, target)
|
|
156
|
+
rescue Errno::EISDIR, Errno::ENOTDIR, Errno::ENOTEMPTY, Errno::EEXIST, Errno::EPERM, Errno::EACCES
|
|
157
|
+
FileUtils.rm_rf(target)
|
|
158
|
+
File.rename(temporary, target)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def prune_empty_parents(directory, root)
|
|
162
|
+
while empty_descendant?(directory, root)
|
|
163
|
+
directory.rmdir
|
|
164
|
+
directory = directory.dirname
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def empty_descendant?(directory, root)
|
|
169
|
+
directory != root && directory.to_s.start_with?("#{root}/") &&
|
|
170
|
+
directory.directory? && directory.children.empty?
|
|
171
|
+
end
|
|
172
|
+
|
|
31
173
|
def copy_included_files(destination, on_progress)
|
|
32
174
|
files = included_files
|
|
33
175
|
on_progress&.call(0, files.size)
|
|
@@ -37,7 +179,7 @@ module Wip
|
|
|
37
179
|
# Keep links as links. Dereferencing a link here could copy arbitrary
|
|
38
180
|
# host files outside the build context (for example, ~/.ssh/id_rsa)
|
|
39
181
|
# into the staged directory and expose them to the image build.
|
|
40
|
-
FileUtils.copy_entry(@root.join(relative_path), target,
|
|
182
|
+
FileUtils.copy_entry(@root.join(relative_path), target, true, false, false)
|
|
41
183
|
on_progress&.call(index + 1, files.size)
|
|
42
184
|
end
|
|
43
185
|
end
|
data/lib/wip/cli.rb
CHANGED
|
@@ -73,7 +73,8 @@ module Wip
|
|
|
73
73
|
context = Pathname(load_config.path).dirname.join(settings['context'] || '.').to_s
|
|
74
74
|
warn "wip: staging build context (#{context})"
|
|
75
75
|
progress = StagingProgress.new
|
|
76
|
-
BuildContext.new(context
|
|
76
|
+
build_context = BuildContext.new(context, shadow_root: settings['shadow_context'])
|
|
77
|
+
build_context.stage(on_progress: progress.method(:tick)) do |staged_context|
|
|
77
78
|
progress.finish
|
|
78
79
|
built = builder.build(settings: settings.merge('context' => staged_context), extra: extra)
|
|
79
80
|
execute(built, interactive: tty?(true))
|
data/lib/wip/config.rb
CHANGED
|
@@ -27,7 +27,18 @@ module Wip
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def wslc_command = @raw.dig('wslc', 'command') || 'auto'
|
|
30
|
-
|
|
30
|
+
|
|
31
|
+
# interaction: is dip's name for the same concept — accepted as an alias so a
|
|
32
|
+
# dip.yml can be renamed to wip.yml with fewer edits. The two are mutually
|
|
33
|
+
# exclusive (validate_commands!) rather than merged, so a project doesn't end up
|
|
34
|
+
# with the same command split across both keys.
|
|
35
|
+
def commands
|
|
36
|
+
return @raw['commands'] if @raw.key?('commands')
|
|
37
|
+
return @raw['interaction'] if @raw.key?('interaction')
|
|
38
|
+
|
|
39
|
+
{}
|
|
40
|
+
end
|
|
41
|
+
|
|
31
42
|
# Raw dependencies: block as written in wip.yml. Under compose-native mode this stays
|
|
32
43
|
# empty by construction (validate_compose! forbids combining the two) — #dependencies
|
|
33
44
|
# below is what callers actually want, since it's synthesized from compose.yml there.
|
|
@@ -126,6 +137,8 @@ module Wip
|
|
|
126
137
|
end
|
|
127
138
|
|
|
128
139
|
def validate_commands!
|
|
140
|
+
raise ConfigError, 'commands is mutually exclusive with interaction — pick one' \
|
|
141
|
+
if @raw.key?('commands') && @raw.key?('interaction')
|
|
129
142
|
raise ConfigError, 'commands must be a mapping' unless commands.is_a?(Hash)
|
|
130
143
|
|
|
131
144
|
commands.each { |name, entry| validate_command!(name, entry) }
|
|
@@ -175,9 +188,22 @@ module Wip
|
|
|
175
188
|
type = entry['type'] || (name == 'build' ? 'build' : 'exec')
|
|
176
189
|
raise ConfigError, "Invalid command type for #{name}: #{type}" unless %w[exec run build].include?(type)
|
|
177
190
|
|
|
191
|
+
validate_shadow_context!(name, entry, type)
|
|
192
|
+
|
|
178
193
|
entry['env']&.transform_values!(&:to_s)
|
|
179
194
|
end
|
|
180
195
|
|
|
196
|
+
# shadow_context names a Windows-side mirror of the build context, so it only
|
|
197
|
+
# means anything on a build command — and only as a real path.
|
|
198
|
+
def validate_shadow_context!(name, entry, type)
|
|
199
|
+
return unless entry.key?('shadow_context')
|
|
200
|
+
|
|
201
|
+
shadow_context = entry['shadow_context']
|
|
202
|
+
return if type == 'build' && shadow_context.is_a?(String) && !shadow_context.empty?
|
|
203
|
+
|
|
204
|
+
raise ConfigError, "commands.#{name}.shadow_context must be a non-empty path for a build command"
|
|
205
|
+
end
|
|
206
|
+
|
|
181
207
|
def validate_dependency!(name, entry)
|
|
182
208
|
raise ConfigError, "dependencies.#{name} must be a mapping" unless entry.is_a?(Hash)
|
|
183
209
|
raise ConfigError, "dependencies.#{name} must set image" if entry['image'].to_s.empty?
|
data/lib/wip/initializer.rb
CHANGED
|
@@ -31,17 +31,18 @@ module Wip
|
|
|
31
31
|
# example uses.
|
|
32
32
|
FALLBACK_EXCLUDES = %w[.git tmp/ node_modules/].freeze
|
|
33
33
|
|
|
34
|
-
# Appended to both templates after dependencies/compose.
|
|
35
|
-
# commented since an empty
|
|
34
|
+
# Appended to both templates after dependencies/compose. interaction: itself is left
|
|
35
|
+
# commented since an empty interaction: {} is indistinguishable from omitting the key.
|
|
36
36
|
COMMANDS_EXAMPLE = <<~YAML.chomp
|
|
37
37
|
# optional; custom subcommands, e.g. `wip test` — see README
|
|
38
|
-
# commands:
|
|
38
|
+
# (interaction: is the primary key; commands: is accepted as an alias — the two are
|
|
39
|
+
# mutually exclusive, so pick one)
|
|
40
|
+
# interaction:
|
|
39
41
|
# test:
|
|
40
42
|
# optional; exec (default, runs in the running container) | run (fresh container) | build
|
|
41
43
|
# type: exec
|
|
42
44
|
|
|
43
|
-
#
|
|
44
|
-
# command: bundle exec rspec
|
|
45
|
+
# command: ls
|
|
45
46
|
YAML
|
|
46
47
|
|
|
47
48
|
def initialize(dir: Dir.pwd, template: nil)
|
data/lib/wip/version.rb
CHANGED