workroom 0.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +73 -0
- data/bin/workroom +7 -0
- data/lib/workroom/commands.rb +294 -0
- data/lib/workroom/engine.rb +6 -0
- data/lib/workroom/version.rb +5 -0
- data/lib/workroom.rb +20 -0
- metadata +78 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 709e14f9e8836400b840ad9772320ad6ddc9f2fbb163c800a0ba932b5d26ebe1
|
|
4
|
+
data.tar.gz: 6334a7e2daa13faaf34e3d7931bfe1082ec752b456f88531b255d429515c99da
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 07ec6e20dfc896dec2a75cd11fe402b31c9dad2619ca323ac5b191ef23aa8cef681c742d41254356e62edef460aa5377c1e37d20c461d1e9f14caf15dec14658
|
|
7
|
+
data.tar.gz: 8915fa7afb229916379e8c0343550734b25300f87d6a64d3b495b4509afe1112e976e3138720920c1881dbac02366aee68dee7bed4e8f26d6c27ed95d6d49e40
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright Joel Moss
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Workroom
|
|
2
|
+
|
|
3
|
+
Create and manage local development workrooms using [JJ](https://martinvonz.github.io/jj/) workspaces or git worktrees.
|
|
4
|
+
|
|
5
|
+
A workroom is an isolated copy of your project created as a sibling directory, allowing you to work on multiple branches or features simultaneously without stashing or switching contexts.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add to your Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'workroom'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then run `bundle install`.
|
|
16
|
+
|
|
17
|
+
Or install directly:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
gem install workroom
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Requirements
|
|
24
|
+
|
|
25
|
+
- Ruby >= 3.1
|
|
26
|
+
- [JJ (Jujutsu)](https://martinvonz.github.io/jj/) or Git
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### Create a workroom
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
workroom create my-feature
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
This creates a new workroom at `../my-feature` relative to your project root. Workroom automatically detects whether you're using JJ or Git and uses the appropriate mechanism (JJ workspace or git worktree).
|
|
37
|
+
|
|
38
|
+
### Delete a workroom
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
workroom delete my-feature
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Removes the workspace/worktree and cleans up the directory. You'll be prompted for confirmation before deletion.
|
|
45
|
+
|
|
46
|
+
### Options
|
|
47
|
+
|
|
48
|
+
- `-v`, `--verbose` - Print detailed output
|
|
49
|
+
- `-p`, `--pretend` - Run through the command without making changes (dry run)
|
|
50
|
+
|
|
51
|
+
### Naming rules
|
|
52
|
+
|
|
53
|
+
Workroom names must be alphanumeric (dashes and underscores allowed) and must not start or end with a dash or underscore.
|
|
54
|
+
|
|
55
|
+
## Setup and teardown scripts
|
|
56
|
+
|
|
57
|
+
Workroom supports user-defined scripts that run automatically during create and delete operations.
|
|
58
|
+
|
|
59
|
+
### Setup script
|
|
60
|
+
|
|
61
|
+
Place an executable script at `scripts/workroom_setup` in your project. It will run inside the new workroom directory after creation.
|
|
62
|
+
|
|
63
|
+
### Teardown script
|
|
64
|
+
|
|
65
|
+
Place an executable script at `scripts/workroom_teardown` in your project. It will run after a workroom is deleted.
|
|
66
|
+
|
|
67
|
+
## Rails integration
|
|
68
|
+
|
|
69
|
+
Workroom includes a Rails Engine for auto-discovery by host Rails apps. Simply add the gem to your Rails app's Gemfile and it will be loaded automatically.
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
[MIT](MIT-LICENSE)
|
data/bin/workroom
ADDED
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'open3'
|
|
4
|
+
require 'thor'
|
|
5
|
+
|
|
6
|
+
module Workroom
|
|
7
|
+
class Commands < Thor
|
|
8
|
+
include Thor::Actions
|
|
9
|
+
|
|
10
|
+
IGNORED_JJ_WORKSPACE_NAMES = ['', 'default'].freeze
|
|
11
|
+
|
|
12
|
+
attr_reader :name
|
|
13
|
+
|
|
14
|
+
class_option :verbose, type: :boolean, aliases: '-v', group: :runtime,
|
|
15
|
+
desc: 'Print detailed and verbose output'
|
|
16
|
+
add_runtime_options!
|
|
17
|
+
|
|
18
|
+
def self.exit_on_failure?
|
|
19
|
+
true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
desc 'create NAME', 'Create a new workroom'
|
|
23
|
+
long_desc <<-DESC, wrap: false
|
|
24
|
+
Create a new workroom with the given NAME at the same level as your main project directory,
|
|
25
|
+
using JJ workspaces if available, otherwise falling back to git worktrees.
|
|
26
|
+
DESC
|
|
27
|
+
def create(name)
|
|
28
|
+
@name = name
|
|
29
|
+
check_not_in_workroom!
|
|
30
|
+
validate_name!
|
|
31
|
+
|
|
32
|
+
if !options[:pretend]
|
|
33
|
+
if workroom_exists?
|
|
34
|
+
exception = jj? ? JJWorkspaceExistsError : GitWorktreeExistsError
|
|
35
|
+
raise_error exception, "#{vcs_label} '#{name}' already exists!"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if workroom_path.exist?
|
|
39
|
+
raise_error DirExistsError, "Workroom directory '#{workroom_path}' already exists!"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
create_workroom
|
|
44
|
+
run_setup_script
|
|
45
|
+
|
|
46
|
+
say
|
|
47
|
+
say "Workroom '#{name}' created successfully at #{workroom_path}.", :green
|
|
48
|
+
|
|
49
|
+
return if !@setup_result
|
|
50
|
+
|
|
51
|
+
say 'Setup script output:', :blue
|
|
52
|
+
say @setup_result
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
desc 'delete NAME', 'Delete an existing workroom'
|
|
56
|
+
def delete(name)
|
|
57
|
+
@name = name
|
|
58
|
+
check_not_in_workroom!
|
|
59
|
+
validate_name!
|
|
60
|
+
|
|
61
|
+
if !options[:pretend]
|
|
62
|
+
if !workroom_exists?
|
|
63
|
+
exception = jj? ? JJWorkspaceExistsError : GitWorktreeExistsError
|
|
64
|
+
raise_error exception, "#{vcs_label} '#{name}' does not exist!"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if !yes?("Are you sure you want to delete workroom '#{name}'?")
|
|
68
|
+
say_error "Aborting. Workroom '#{name}' was not deleted.", :yellow
|
|
69
|
+
return
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
delete_workroom
|
|
74
|
+
cleanup_directory if jj?
|
|
75
|
+
run_teardown_script
|
|
76
|
+
|
|
77
|
+
say
|
|
78
|
+
say "Workroom '#{name}' deleted successfully.", :green
|
|
79
|
+
|
|
80
|
+
if !jj?
|
|
81
|
+
say
|
|
82
|
+
say "Note: Git branch '#{name}' was not deleted."
|
|
83
|
+
say " Delete manually with `git branch -D #{name}` if needed."
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
return if !@teardown_result
|
|
87
|
+
|
|
88
|
+
say
|
|
89
|
+
say 'Teardown script output:', :blue
|
|
90
|
+
say @teardown_result
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def run_setup_script
|
|
96
|
+
return if !setup_script.exist?
|
|
97
|
+
|
|
98
|
+
inside workroom_path do
|
|
99
|
+
run_user_script :setup, setup_script_to_run.to_s
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def setup_script
|
|
104
|
+
@setup_script ||= workroom_path.join('scripts', 'workroom_setup')
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def setup_script_to_run
|
|
108
|
+
setup_script
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def run_teardown_script
|
|
112
|
+
return if !teardown_script.exist?
|
|
113
|
+
|
|
114
|
+
run_user_script :teardown, teardown_script_to_run.to_s
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def teardown_script
|
|
118
|
+
@teardown_script ||= Pathname.pwd.join('scripts', 'workroom_teardown')
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def teardown_script_to_run
|
|
122
|
+
teardown_script
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def run_user_script(type, command)
|
|
126
|
+
return if behavior != :invoke
|
|
127
|
+
|
|
128
|
+
destination = relative_to_original_destination_root(destination_root, false)
|
|
129
|
+
|
|
130
|
+
say_status type, "Running #{command} from #{destination.inspect}"
|
|
131
|
+
|
|
132
|
+
return if options[:pretend]
|
|
133
|
+
|
|
134
|
+
result, status = Open3.capture2e(command)
|
|
135
|
+
|
|
136
|
+
instance_variable_set :"@#{type}_result", result
|
|
137
|
+
|
|
138
|
+
return if status.success?
|
|
139
|
+
|
|
140
|
+
exception_class = Object.const_get("::Workroom::#{type.to_s.capitalize}Error")
|
|
141
|
+
|
|
142
|
+
raise_error exception_class, "#{command} returned a non-zero exit code.\n#{result}"
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def raise_error(exception_class, message)
|
|
146
|
+
message = shell.set_color message, :red if !testing?
|
|
147
|
+
raise exception_class, message
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def workroom_path
|
|
151
|
+
@workroom_path ||= Pathname.pwd.join("../#{name}")
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def jj?
|
|
155
|
+
vcs == :jj
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def vcs
|
|
159
|
+
@vcs ||= if Dir.exist?('.jj')
|
|
160
|
+
say_status :repo, 'Detected Jujutsu'
|
|
161
|
+
:jj
|
|
162
|
+
elsif Dir.exist?('.git')
|
|
163
|
+
say_status :repo, 'Detected Git'
|
|
164
|
+
:git
|
|
165
|
+
else
|
|
166
|
+
say_status :repo, 'No supported VCS detected', :red
|
|
167
|
+
raise_error UnsupportedVCSError, <<~_
|
|
168
|
+
No supported VCS detected. Workroom requires either Jujutsu or Git to manage workspaces.
|
|
169
|
+
_
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def vcs_label
|
|
174
|
+
jj? ? 'JJ workspace' : 'Git worktree'
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def workroom_exists?
|
|
178
|
+
jj? ? jj_workspace_exists? : git_worktree_exists?
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def jj_workspace_exists?
|
|
182
|
+
jj_workspaces.include?(name)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def git_worktree_exists?
|
|
186
|
+
git_worktrees.any? { |path| File.basename(path) == name }
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def jj_workspaces
|
|
190
|
+
@jj_workspaces ||= begin
|
|
191
|
+
out = raw_jj_workspace_list.lines
|
|
192
|
+
out.map { |line| line.split(':').first.strip }.reject do |name|
|
|
193
|
+
IGNORED_JJ_WORKSPACE_NAMES.include?(name)
|
|
194
|
+
end.compact
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def raw_jj_workspace_list
|
|
199
|
+
run 'jj workspace list --color never', capture: true
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def git_worktrees
|
|
203
|
+
@git_worktrees ||= begin
|
|
204
|
+
arr = []
|
|
205
|
+
directory = ''
|
|
206
|
+
raw_git_worktree_list.split("\n").each do |w|
|
|
207
|
+
s = w.split
|
|
208
|
+
directory = s[1] if s[0] == 'worktree'
|
|
209
|
+
arr << directory if s[0] == 'HEAD' && Dir.pwd != directory
|
|
210
|
+
end
|
|
211
|
+
arr
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def raw_git_worktree_list
|
|
216
|
+
run 'git worktree list --porcelain', capture: true
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def validate_name!
|
|
220
|
+
return if /\A[a-zA-Z0-9]([a-zA-Z0-9_-]*[a-zA-Z0-9])?\z/.match?(name)
|
|
221
|
+
|
|
222
|
+
say_status :create, name, :red
|
|
223
|
+
raise_error InvalidNameError, <<~_
|
|
224
|
+
Workroom name must be alphanumeric (dashes and underscores allowed), and must not start or end with a dash or underscore.
|
|
225
|
+
_
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Ensure the command is not being run from within an existing workroom by checking for the
|
|
229
|
+
# presence of the a `.Workroom`.
|
|
230
|
+
def check_not_in_workroom!
|
|
231
|
+
return if !Pathname.pwd.join('.Workroom').exist?
|
|
232
|
+
|
|
233
|
+
say_status :create, name, :red
|
|
234
|
+
raise_error InWorkroomError, <<~_
|
|
235
|
+
Looks like you are already in a workroom. Run this command from the root of your main development directory, not from within an existing workroom.
|
|
236
|
+
_
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def create_workroom
|
|
240
|
+
if testing?
|
|
241
|
+
FileUtils.copy('./', workroom_path)
|
|
242
|
+
return
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
if jj?
|
|
246
|
+
run "jj workspace add #{workroom_path}"
|
|
247
|
+
else
|
|
248
|
+
run "git worktree add -b #{name} #{workroom_path}"
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def delete_workroom
|
|
253
|
+
if testing?
|
|
254
|
+
FileUtils.rm_rf(workroom_path)
|
|
255
|
+
return
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
if jj?
|
|
259
|
+
run "jj workspace forget #{name}"
|
|
260
|
+
else
|
|
261
|
+
run "git worktree remove #{workroom_path} --force"
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def cleanup_directory
|
|
266
|
+
return if !workroom_path.exist?
|
|
267
|
+
|
|
268
|
+
remove_dir(workroom_path, verbose:)
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def run(command, config = {})
|
|
272
|
+
if !config[:force] && testing?
|
|
273
|
+
raise TestError, "Command execution blocked during testing: `#{command}`"
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
config[:verbose] = verbose
|
|
277
|
+
config[:capture] = !verbose if !config.key?(:capture)
|
|
278
|
+
|
|
279
|
+
super
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
def say_status(...)
|
|
283
|
+
super if verbose
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def verbose
|
|
287
|
+
options[:verbose]
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def testing?
|
|
291
|
+
ENV['WORKROOM_TEST'] == '1'
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
data/lib/workroom.rb
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'zeitwerk'
|
|
4
|
+
loader = Zeitwerk::Loader.for_gem
|
|
5
|
+
loader.setup
|
|
6
|
+
|
|
7
|
+
require 'thor'
|
|
8
|
+
|
|
9
|
+
module Workroom
|
|
10
|
+
class Error < Thor::Error; end
|
|
11
|
+
class TestError < Thor::Error; end
|
|
12
|
+
class InvalidNameError < Error; end
|
|
13
|
+
class InWorkroomError < Error; end
|
|
14
|
+
class DirExistsError < Error; end
|
|
15
|
+
class UnsupportedVCSError < Error; end
|
|
16
|
+
class JJWorkspaceExistsError < Error; end
|
|
17
|
+
class GitWorktreeExistsError < Error; end
|
|
18
|
+
class SetupError < Error; end
|
|
19
|
+
class TeardownError < Error; end
|
|
20
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: workroom
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Joel Moss
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: thor
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.5'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.5'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: zeitwerk
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.7'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.7'
|
|
40
|
+
description: Create and manage local development workrooms using JJ workspaces or
|
|
41
|
+
git worktrees
|
|
42
|
+
email:
|
|
43
|
+
- joel@developwithstyle.com
|
|
44
|
+
executables:
|
|
45
|
+
- workroom
|
|
46
|
+
extensions: []
|
|
47
|
+
extra_rdoc_files: []
|
|
48
|
+
files:
|
|
49
|
+
- MIT-LICENSE
|
|
50
|
+
- README.md
|
|
51
|
+
- bin/workroom
|
|
52
|
+
- lib/workroom.rb
|
|
53
|
+
- lib/workroom/commands.rb
|
|
54
|
+
- lib/workroom/engine.rb
|
|
55
|
+
- lib/workroom/version.rb
|
|
56
|
+
homepage: https://github.com/joelmoss/workroom
|
|
57
|
+
licenses:
|
|
58
|
+
- MIT
|
|
59
|
+
metadata:
|
|
60
|
+
rubygems_mfa_required: 'true'
|
|
61
|
+
rdoc_options: []
|
|
62
|
+
require_paths:
|
|
63
|
+
- lib
|
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: 3.1.0
|
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - ">="
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: '0'
|
|
74
|
+
requirements: []
|
|
75
|
+
rubygems_version: 4.0.6
|
|
76
|
+
specification_version: 4
|
|
77
|
+
summary: Manage development workrooms
|
|
78
|
+
test_files: []
|