wslc-wip 0.2.0 → 0.3.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: bc4331f98464658b13ec0ac51737183931ce6ed5320d15cf64b2313b50aa7999
4
- data.tar.gz: 3d9cac0a97bd2abb2a0c30f5c5bc5a8c626320d900f4ac7cd46aca682368b56c
3
+ metadata.gz: d9b186a9b511381675ce6b5c7073292bc3ba8401287807c2f088d722f7cc1d7c
4
+ data.tar.gz: 84a5b9b0ed0c9a3ada3ffe06eb793c12192d36d76b22197c9310776ddbe28c18
5
5
  SHA512:
6
- metadata.gz: 8568791708d1e6b87b85c209568d47580daf0008253dc41b824c9a4377d35f08c2b1005a6082b7ab3eefeaf2cc7ead6ce8357a31047bc17dda0b9f6afcc70266
7
- data.tar.gz: 827d83e3244bc9269b12139f5e4554f7b0e2137b1a2a54ac3258799a2c531d355e9a3405ec4bbffff1b134b7b39cbcde0a13a1ba06df35f93cc995b45e4637b7
6
+ metadata.gz: b46b8daa014806f40f361a5a8fb696cfbfa66f7e0652be23de5d328aaa13401248d640126767c39f1d87ca0329e2428e4a3abe7e19fb5d94e139209e43421646
7
+ data.tar.gz: ed2eb9f43b08a29f8ac48d16e2820f7aa088bd8d8eb2e8db5f27c22b145f4e26e082f6b0efda81db9e04d395dc7a90a96a9b211b5d723ebd859fd0605899f28d
data/README.md CHANGED
@@ -1,37 +1,49 @@
1
1
  # wip
2
2
 
3
- `wip` は、Docker 開発環境 CLI の dip に近い操作感を Microsoft WSLC に提供する Ruby 製 OSS CLI ラッパーです。プロジェクト固有のコンテナ、イメージ、環境変数やコマンドを `wip.yml` にまとめ、安全な引数配列として `wslc.exe` / `wslc` に渡します。
3
+ [![Tests](https://github.com/slidict/wip/actions/workflows/test.yml/badge.svg)](https://github.com/slidict/wip/actions/workflows/test.yml)
4
+ [![Gem Version](https://img.shields.io/gem/v/wslc-wip.svg)](https://rubygems.org/gems/wslc-wip)
5
+ [![License: MIT](https://img.shields.io/github/license/slidict/wip.svg)](LICENSE)
6
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.2-red.svg)](wslc-wip.gemspec)
4
7
 
5
- > **Status:** 初期リリース 0.1.0。WSLC 自体の仕様変更に追随が必要になる可能性があります。
8
+ `wip` is a Ruby-built OSS CLI wrapper that brings a [`dip`](https://github.com/bibendi/dip)-like
9
+ workflow to Microsoft WSLC. It collects a project's container, image, environment variables, and
10
+ commands into a single `wip.yml`, and forwards them to `wslc.exe` / `wslc` as safe argument arrays
11
+ (no shell interpolation).
6
12
 
7
- ## 対応環境とインストール
13
+ ![wip demo](https://raw.githubusercontent.com/slidict/wip/main/docs/demo.gif)
8
14
 
9
- Ruby 3.2 以上、WSL2、および Microsoft WSLC が必要です。RubyGems 公開後は次で導入できます。
15
+ > **Status:** early release. Expect to track WSLC's own interface as it evolves.
16
+
17
+ ## Requirements & installation
18
+
19
+ Ruby 3.2+, WSL2, and Microsoft WSLC.
10
20
 
11
21
  ```bash
12
- gem install wip
22
+ gem install wslc-wip
13
23
  ```
14
24
 
15
- ソースからは `bundle install && bundle exec exe/wip version` を使用します。
25
+ From source: `bundle install && bundle exec exe/wip version`.
16
26
 
17
- ## クイックスタート
27
+ ## Quick start
18
28
 
19
29
  ```bash
20
- gem install wip
30
+ gem install wslc-wip
21
31
  cd my-project
22
32
  wip doctor
23
33
  wip build
34
+ wip up -d
24
35
  wip rails console
25
36
  ```
26
37
 
27
- ## 完全な設定例
38
+ ## Full configuration example
28
39
 
29
- プロジェクトルートに `wip.yml` を置きます。子ディレクトリから実行しても親方向へ探索され、`--config PATH` で明示指定もできます。
40
+ Put a `wip.yml` in your project root. Running from a subdirectory walks up to find it, or pass
41
+ `--config PATH` to point at one explicitly.
30
42
 
31
43
  ```yaml
32
44
  version: 1
33
45
  wslc:
34
- command: auto # wslc.exewslcSystem32 の順。絶対パスも指定可能
46
+ command: auto # tries wslc.exe, wslc, then System32; an absolute path also works
35
47
  defaults:
36
48
  container: app
37
49
  image: slidict/slidict:development
@@ -45,6 +57,9 @@ defaults:
45
57
  - "3000:3000"
46
58
  volumes:
47
59
  - ".:/app"
60
+ up:
61
+ command: server # command `wip up` passes to the image when it creates the container
62
+ # (omit to use the image's default CMD)
48
63
  commands:
49
64
  rails:
50
65
  type: exec
@@ -72,46 +87,53 @@ commands:
72
87
  tag: slidict/slidict:development
73
88
  ```
74
89
 
75
- `env` の値は文字列化されます。`config` の表示では tokenpasswordsecret、credential、auth を含むキーを伏せます。秘密値は可能なら設定ファイルではなく実行環境側で管理してください。
90
+ `env` values are stringified. `wip config` masks any key matching token, password, secret,
91
+ credential, or auth. Keep real secrets out of the config file and in your runtime environment
92
+ instead.
76
93
 
77
- ## コマンド
94
+ ## Commands
78
95
 
79
- | コマンド | 説明 |
96
+ | Command | Description |
80
97
  |---|---|
81
- | `wip version` | wip と、検出できた WSLC のバージョン |
82
- | `wip doctor` | WSL2、相互運用、WSLC、設定、アーキテクチャ、Git を診断 |
83
- | `wip config` | デフォルト適用済み設定(秘密値をマスク) |
84
- | `wip build -- --no-cache` | build 定義でイメージをビルド |
85
- | `wip up [-d]` | `defaults.container` を起動(無ければ作成)。`-d` でバックグラウンド実行 |
86
- | `wip down` | `defaults.container` を停止・削除 |
87
- | `wip exec [--no-interactive] COMMAND...` | 既存コンテナ内で実行 |
88
- | `wip run [--no-interactive] COMMAND...` | `--rm` の新規コンテナで実行 |
89
- | `wip shell` | 定義済み shell、または `bash`、`sh` の順に起動 |
90
- | `wip NAME ARGS...` | `commands.NAME` を実行し、引数を末尾に追加 |
91
-
92
- TTY はコマンド設定、CLI オプション、標準入出力が双方 TTY かを合わせて判定します。`WIP_DEBUG=1` では `Shellwords.join` した実行コマンドを表示します。
98
+ | `wip version` | wip's version, plus WSLC's if it can be detected |
99
+ | `wip doctor` | Diagnose WSL2, interop, WSLC, config, architecture, and Git |
100
+ | `wip config` | Print the effective configuration (secrets masked) |
101
+ | `wip build -- --no-cache` | Build the image from the `build` definition |
102
+ | `wip up [-d]` | Start `defaults.container` (creating it with `up.command` if missing). `-d` runs it in the background |
103
+ | `wip down` | Stop and remove `defaults.container` |
104
+ | `wip exec [--no-interactive] COMMAND...` | Run a command in the existing container |
105
+ | `wip run [--no-interactive] COMMAND...` | Run a command in a new `--rm` container |
106
+ | `wip shell` | Open the configured shell, falling back to `bash` then `sh` |
107
+ | `wip NAME ARGS...` | Run `commands.NAME`, appending any extra arguments |
108
+
109
+ TTY allocation is decided by combining the command's config, the CLI option, and whether both
110
+ stdin and stdout are real TTYs. Set `WIP_DEBUG=1` to print the `Shellwords`-joined command before
111
+ running it.
93
112
 
94
113
  ## doctor
95
114
 
96
- `[OK]`、`[WARN]`、`[FAIL]` で各診断を表示します。警告だけなら終了コード 0、WSL2/相互運用/WSLC/設定など実行を妨げる問題があれば 1 です。実際のビルド環境から Git が見えない場合は警告になります。
115
+ Each check prints as `[OK]`, `[WARN]`, or `[FAIL]`. Warnings alone exit 0; a WSL2, interop, WSLC,
116
+ or config problem that blocks execution exits 1. Git being unreachable from the real build
117
+ environment is only a warning.
97
118
 
98
- ## よくあるエラー
119
+ ## Common errors
99
120
 
100
- ### WSLC がない
121
+ ### WSLC not found
101
122
 
102
- WSLC/WSL コンテナツールをインストールまたは更新し、`wip doctor` を実行してください。`auto` `wslc.exe`、`wslc`、`/mnt/c/Windows/System32/wslc.exe` の順で探します。
123
+ Install or update the WSL container tooling, then run `wip doctor`. `auto` looks for `wslc.exe`,
124
+ `wslc`, then `/mnt/c/Windows/System32/wslc.exe`, in that order.
103
125
 
104
- ### Docker Hub の認証
126
+ ### Docker Hub authentication
105
127
 
106
- `pull access denied` 等を検出するとログイン方法を補足します。
128
+ When `pull access denied` (or similar) is detected, wip suggests how to log in:
107
129
 
108
130
  ```bash
109
131
  wslc registry login -u <username> docker.io
110
132
  ```
111
133
 
112
- ### CPU アーキテクチャ不一致
134
+ ### CPU architecture mismatch
113
135
 
114
- イメージを確認し、amd64/arm64 のマルチアーキテクチャイメージを公開します。
136
+ Check the image and publish a multi-arch (amd64/arm64) image:
115
137
 
116
138
  ```bash
117
139
  docker buildx imagetools inspect <image>
@@ -121,10 +143,10 @@ docker buildx build \
121
143
  --push .
122
144
  ```
123
145
 
124
- ## 開発
146
+ ## Development
125
147
 
126
148
  ```bash
127
- git clone <repository-url>
149
+ git clone https://github.com/slidict/wip.git
128
150
  cd wip
129
151
  bundle install
130
152
  bundle exec rspec
@@ -132,11 +154,21 @@ bundle exec rubocop
132
154
  bundle exec rake
133
155
  ```
134
156
 
135
- テストは WSLC を必要とせず、解決・構築・実行層を差し替え可能にしています。GitHub Actions Ruby 3.2、3.3、3.4 RSpec RuboCop を実行します。
157
+ The test suite doesn't need WSLC the resolution, build, and execution layers are all
158
+ swappable. GitHub Actions runs RSpec and RuboCop on Ruby 3.2, 3.3, 3.4, and 4.0.
159
+
160
+ ## Contributing
161
+
162
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/slidict/wip). See
163
+ [CONTRIBUTING.md](CONTRIBUTING.md) for commit conventions, versioning policy, and the PR
164
+ checklist.
136
165
 
137
- ## 初期リリースに含まれないもの
166
+ ## Not in the initial release
138
167
 
139
- Compose 互換、常駐プロセス、GUIPowerShell 最適化、レジストリ API/manifest の直接解析、自動更新、プラグインは未実装です。将来は設定スキーマ、ライフサイクルフック、複数コンテナ、プラットフォーム選択、より詳細な診断の追加が有用です。
168
+ Compose compatibility, a resident/daemon process, a GUI, PowerShell-specific tuning, direct
169
+ registry API/manifest parsing, self-update, and plugins are all unimplemented. Likely future
170
+ additions: a richer config schema, lifecycle hooks, multi-container support, platform selection,
171
+ and more detailed diagnostics.
140
172
 
141
173
  ## License
142
174
 
data/lib/wip/cli.rb CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  require 'thor'
4
4
  require 'yaml'
5
+ require 'json'
6
+ require 'stringio'
5
7
 
6
8
  module Wip
7
9
  # Thor-based command-line interface for wip.
@@ -54,8 +56,15 @@ module Wip
54
56
  desc 'up', 'Start the configured container, creating it if necessary'
55
57
  option :detach, type: :boolean, default: false, aliases: '-d'
56
58
  def up
57
- code = execute(builder.start(detach: options[:detach]), exit_on_failure: false)
58
- execute(builder.up(detach: options[:detach])) if code != 0
59
+ container = load_config.defaults['container']
60
+ interactive = builder.tty?(!options[:detach])
61
+ if container_exists?
62
+ warn "wip: starting existing container '#{container}'"
63
+ execute(builder.start(detach: options[:detach]), interactive: interactive)
64
+ else
65
+ warn "wip: container '#{container}' not found, creating it"
66
+ execute(builder.up(detach: options[:detach]), interactive: interactive)
67
+ end
59
68
  end
60
69
 
61
70
  desc 'down', 'Stop and remove the configured container'
@@ -67,24 +76,32 @@ module Wip
67
76
  desc 'exec COMMAND...', 'Execute a command in the running container'
68
77
  option :interactive, type: :boolean, default: true
69
78
  def exec(*command)
70
- execute(builder.exec(command, interactive: options[:interactive]))
79
+ tty = builder.tty?(options[:interactive])
80
+ execute(builder.exec(command, interactive: options[:interactive]), interactive: tty)
71
81
  end
72
82
 
73
83
  desc 'run COMMAND...', 'Run a command in a new container'
74
84
  option :interactive, type: :boolean, default: true
75
85
  def run_command(*command)
76
- execute(builder.run(command, interactive: options[:interactive]))
86
+ tty = builder.tty?(options[:interactive])
87
+ execute(builder.run(command, interactive: options[:interactive]), interactive: tty)
77
88
  end
78
89
  map 'run' => :run_command
79
90
 
80
91
  desc 'shell', 'Open a shell in the configured container'
81
92
  def shell_command
82
93
  configured = load_config.command('shell')
83
- return execute(builder.custom('shell', [])) if configured
94
+ if configured
95
+ tty = builder.tty?(configured.fetch('interactive', false))
96
+ return execute(builder.custom('shell', []), interactive: tty)
97
+ end
84
98
 
99
+ tty = builder.tty?(true)
85
100
  code = execute(builder.exec(['bash'], settings: { 'interactive' => true }, interactive: true),
86
- exit_on_failure: false)
87
- execute(builder.exec(['sh'], settings: { 'interactive' => true }, interactive: true)) if code != 0
101
+ interactive: tty, exit_on_failure: false)
102
+ return if code.zero?
103
+
104
+ execute(builder.exec(['sh'], settings: { 'interactive' => true }, interactive: true), interactive: tty)
88
105
  end
89
106
  map 'shell' => :shell_command
90
107
 
@@ -92,7 +109,8 @@ module Wip
92
109
  def dispatch(name = nil, *arguments)
93
110
  raise ConfigError, 'A command is required' unless name
94
111
 
95
- execute(builder.custom(name, arguments))
112
+ values = load_config.command(name) || raise(ConfigError, "Unknown command: #{name}")
113
+ execute(builder.custom(name, arguments), interactive: builder.tty?(values.fetch('interactive', false)))
96
114
  end
97
115
 
98
116
  private
@@ -102,10 +120,18 @@ module Wip
102
120
  def resolver = CommandResolver.new
103
121
  def builder = CommandBuilder.new(wslc: resolver.resolve(load_config.wslc_command), config: load_config)
104
122
 
105
- def execute(command, exit_on_failure: true)
106
- code = CommandRunner.new.run(command)
123
+ def execute(command, interactive: false, exit_on_failure: true)
124
+ code = CommandRunner.new.run(command, interactive: interactive)
107
125
  exit(code) if exit_on_failure && !code.zero?
108
126
  code
109
127
  end
128
+
129
+ def container_exists?
130
+ out = StringIO.new
131
+ code = CommandRunner.new(stdout: out, stderr: StringIO.new).run(builder.find)
132
+ code.zero? && !JSON.parse(out.string).empty?
133
+ rescue JSON::ParserError
134
+ false
135
+ end
110
136
  end
111
137
  end
@@ -32,6 +32,8 @@ module Wip
32
32
  command << '-d' if detach
33
33
  command << '-it' if !detach && tty?(true)
34
34
  command.concat(options(values)).push(required(values, 'image'))
35
+ command.concat(Shellwords.split(@config.up_command.to_s)) if @config.up_command
36
+ command
35
37
  end
36
38
 
37
39
  def start(detach: false)
@@ -40,6 +42,11 @@ module Wip
40
42
  command
41
43
  end
42
44
 
45
+ def find
46
+ container = required(@config.defaults, 'container')
47
+ [@wslc, 'list', '--all', '--filter', "name=#{container}", '--format', 'json']
48
+ end
49
+
43
50
  def down
44
51
  [@wslc, 'stop', required(@config.defaults, 'container')]
45
52
  end
@@ -66,6 +73,8 @@ module Wip
66
73
  public_send(type, base + arguments, settings: values, interactive: values.fetch('interactive', false))
67
74
  end
68
75
 
76
+ def tty?(requested) = requested && @environment.interactive?
77
+
69
78
  private
70
79
 
71
80
  def options(values, include_container: false, include_publish: true)
@@ -86,7 +95,5 @@ module Wip
86
95
 
87
96
  value
88
97
  end
89
-
90
- def tty?(requested) = requested && @environment.interactive?
91
98
  end
92
99
  end
@@ -13,8 +13,10 @@ module Wip
13
13
  @interpreter = interpreter
14
14
  end
15
15
 
16
- def run(command, env: {})
16
+ def run(command, env: {}, interactive: false)
17
17
  @stderr.puts "+ #{Shellwords.join(command)}" if ENV['WIP_DEBUG']
18
+ return run_attached(command, env) if interactive
19
+
18
20
  captured = +''
19
21
  status = nil
20
22
  Open3.popen3(env, *command) do |input, output, error, wait|
@@ -33,6 +35,19 @@ module Wip
33
35
 
34
36
  private
35
37
 
38
+ # Piping stdin/stdout/stderr (as `run` does above) closes the child's
39
+ # stdin immediately, which breaks anything that reads from the terminal
40
+ # (a shell, `rails console`, ...). Inherit the real file descriptors
41
+ # instead so the child gets a genuine TTY.
42
+ def run_attached(command, env)
43
+ pid = Process.spawn(env, *command)
44
+ _, status = Process.wait2(pid)
45
+ status.exitstatus
46
+ rescue Errno::ENOENT => e
47
+ @stderr.puts e.message
48
+ 127
49
+ end
50
+
36
51
  def pump(source, destination, captured)
37
52
  Thread.new do
38
53
  source.each(4096) do |chunk|
data/lib/wip/config.rb CHANGED
@@ -17,6 +17,7 @@ module Wip
17
17
  def wslc_command = @raw.dig('wslc', 'command') || 'auto'
18
18
  def commands = @raw['commands'] || {}
19
19
  def defaults = DEFAULTS.merge(@raw['defaults'] || {})
20
+ def up_command = @raw.dig('up', 'command')
20
21
 
21
22
  def command(name)
22
23
  entry = commands[name.to_s]
@@ -27,6 +28,7 @@ module Wip
27
28
 
28
29
  def to_h(redact: true)
29
30
  value = { 'version' => 1, 'wslc' => { 'command' => wslc_command }, 'defaults' => defaults,
31
+ 'up' => { 'command' => up_command },
30
32
  'commands' => commands.transform_values { |entry| defaults.merge('type' => 'exec').merge(entry) } }
31
33
  redact ? redact_secrets(value) : value
32
34
  end
@@ -36,6 +38,7 @@ module Wip
36
38
  def validate!
37
39
  raise ConfigError, "Unsupported configuration version: #{@raw['version']}" unless (@raw['version'] || 1) == 1
38
40
  raise ConfigError, 'commands must be a mapping' unless commands.is_a?(Hash)
41
+ raise ConfigError, 'up must be a mapping' if @raw.key?('up') && !@raw['up'].is_a?(Hash)
39
42
 
40
43
  commands.each { |name, entry| validate_command!(name, entry) }
41
44
  end
data/lib/wip/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Wip
4
- VERSION = '0.2.0'
4
+ VERSION = '0.3.1'
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: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Wip contributors
@@ -44,11 +44,14 @@ files:
44
44
  - lib/wip/error_interpreter.rb
45
45
  - lib/wip/errors.rb
46
46
  - lib/wip/version.rb
47
- homepage: https://github.com/example/wip
47
+ homepage: https://github.com/slidict/wip
48
48
  licenses:
49
49
  - MIT
50
50
  metadata:
51
51
  rubygems_mfa_required: 'true'
52
+ source_code_uri: https://github.com/slidict/wip
53
+ changelog_uri: https://github.com/slidict/wip/releases
54
+ bug_tracker_uri: https://github.com/slidict/wip/issues
52
55
  rdoc_options: []
53
56
  require_paths:
54
57
  - lib