wod 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +47 -0
- data/Rakefile +2 -0
- data/bin/wod +11 -0
- data/lib/wod.rb +6 -0
- data/lib/wod/client.rb +81 -0
- data/lib/wod/command.rb +59 -0
- data/lib/wod/commands/app.rb +15 -0
- data/lib/wod/commands/auth.rb +113 -0
- data/lib/wod/commands/base.rb +18 -0
- data/lib/wod/commands/devices.rb +50 -0
- data/lib/wod/commands/help.rb +76 -0
- data/lib/wod/commands/version.rb +7 -0
- data/lib/wod/helpers.rb +29 -0
- data/lib/wod/version.rb +3 -0
- data/wod.gemspec +23 -0
- metadata +83 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
The Wizard Of Dev center interacts with the Apple Dev Center web pages so you don't have to!
|
2
|
+
|
3
|
+
Install
|
4
|
+
---
|
5
|
+
|
6
|
+
$ gem install wod
|
7
|
+
|
8
|
+
Usage
|
9
|
+
---
|
10
|
+
$ wod help
|
11
|
+
|
12
|
+
=== General Commands
|
13
|
+
|
14
|
+
help # show this usage
|
15
|
+
version # show the gem version
|
16
|
+
|
17
|
+
login # log in with your apple credentials
|
18
|
+
logout # clear local authentication credentials
|
19
|
+
|
20
|
+
devices # list your registered devices
|
21
|
+
devices:add <name> <udid> # add a new device
|
22
|
+
devices:remove <name> # remove device (Still counts against device limit)
|
23
|
+
|
24
|
+
Examples
|
25
|
+
---
|
26
|
+
|
27
|
+
List Devices:
|
28
|
+
|
29
|
+
$ wod devices
|
30
|
+
Steve Jobs iPad 3 | 554f3fg54bc953547ry7a6bd62c678c11e912345
|
31
|
+
Jon Ive's iPhone 5 | 2d84d56ceg52c49379537413d3b9865ae2b12345
|
32
|
+
|
33
|
+
Add Device:
|
34
|
+
|
35
|
+
$ wod devices:add "Dave's iPod Touch" 2d84d56ceg52c49379537413d3b9865ae2b12345
|
36
|
+
|
37
|
+
Remove Device:
|
38
|
+
|
39
|
+
$ wod devices:remove "Jon Ive's iPhone 5"
|
40
|
+
|
41
|
+
|
42
|
+
Roadmap
|
43
|
+
---
|
44
|
+
|
45
|
+
More features to come as I need them or as other people fork and add em ;)
|
46
|
+
|
47
|
+
Proudly brought to you by the wizard of identity crises (whatupdave/snappycode/wizard of id)
|
data/Rakefile
ADDED
data/bin/wod
ADDED
data/lib/wod.rb
ADDED
data/lib/wod/client.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'mechanize'
|
2
|
+
require 'wod/helpers'
|
3
|
+
require 'wod/version'
|
4
|
+
|
5
|
+
module Wod
|
6
|
+
class DevCenterPage
|
7
|
+
attr_reader :page
|
8
|
+
|
9
|
+
def initialize page
|
10
|
+
@page = page
|
11
|
+
end
|
12
|
+
|
13
|
+
def logged_in?
|
14
|
+
(page.title =~ /sign in/i) == nil && (page.search("span").find {|s| s.text == "Log in"}) == nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def search arg
|
18
|
+
@page.search arg
|
19
|
+
end
|
20
|
+
|
21
|
+
def form arg
|
22
|
+
@page.form arg
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Client
|
27
|
+
include Wod::Helpers
|
28
|
+
|
29
|
+
attr_reader :name
|
30
|
+
|
31
|
+
def initialize username, password
|
32
|
+
@username = username
|
33
|
+
@password = password
|
34
|
+
end
|
35
|
+
|
36
|
+
def cookies_file
|
37
|
+
"#{home_directory}/.wod/cookie_jar"
|
38
|
+
end
|
39
|
+
|
40
|
+
def create_agent
|
41
|
+
agent = Mechanize.new
|
42
|
+
agent.cookie_jar.load cookies_file if File.exists?(cookies_file)
|
43
|
+
agent
|
44
|
+
end
|
45
|
+
|
46
|
+
def agent
|
47
|
+
@agent ||= create_agent
|
48
|
+
end
|
49
|
+
|
50
|
+
def login_and_reopen url
|
51
|
+
page = DevCenterPage.new agent.get("https://developer.apple.com/devcenter/ios/index.action")
|
52
|
+
|
53
|
+
unless page.logged_in?
|
54
|
+
puts "Creating session"
|
55
|
+
login_page = page.page.links.find { |l| l.text == 'Log in'}.click
|
56
|
+
|
57
|
+
f = login_page.form("appleConnectForm")
|
58
|
+
f.theAccountName = @username
|
59
|
+
f.theAccountPW = @password
|
60
|
+
f.submit
|
61
|
+
end
|
62
|
+
|
63
|
+
page = DevCenterPage.new agent.get url
|
64
|
+
raise InvalidCredentials unless page.logged_in?
|
65
|
+
agent.cookie_jar.save_as cookies_file
|
66
|
+
page
|
67
|
+
end
|
68
|
+
|
69
|
+
def get url
|
70
|
+
page = DevCenterPage.new agent.get(url)
|
71
|
+
page = login_and_reopen(url) unless page.logged_in?
|
72
|
+
page
|
73
|
+
end
|
74
|
+
|
75
|
+
def logged_in?
|
76
|
+
page = get "https://developer.apple.com/devcenter/ios/index.action"
|
77
|
+
page.logged_in?
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
data/lib/wod/command.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'wod/helpers'
|
2
|
+
require 'wod/commands/base'
|
3
|
+
|
4
|
+
Dir["#{File.dirname(__FILE__)}/commands/*.rb"].each { |c| require c }
|
5
|
+
|
6
|
+
module Wod
|
7
|
+
module Command
|
8
|
+
class InvalidCommand < RuntimeError; end
|
9
|
+
class CommandFailed < RuntimeError; end
|
10
|
+
|
11
|
+
extend Wod::Helpers
|
12
|
+
|
13
|
+
def self.run(command, args, retries=0)
|
14
|
+
begin
|
15
|
+
run_internal 'auth:reauthorize', args.dup if retries > 0
|
16
|
+
run_internal command, args.dup
|
17
|
+
rescue InvalidCommand
|
18
|
+
error "Unknown command. Run 'wod help' for usage information."
|
19
|
+
rescue Wod::InvalidCredentials
|
20
|
+
if retries < 3
|
21
|
+
STDERR.puts "Authentication failure"
|
22
|
+
run command, args, retries + 1
|
23
|
+
else
|
24
|
+
error "Authentication failure"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.run_internal(command, args, wod=nil)
|
31
|
+
klass, method = parse command
|
32
|
+
runner = klass.new args, wod
|
33
|
+
raise InvalidCommand unless runner.respond_to?(method)
|
34
|
+
runner.send method
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.parse(command)
|
38
|
+
parts = command.split(':')
|
39
|
+
case parts.size
|
40
|
+
when 1
|
41
|
+
begin
|
42
|
+
return eval("Wod::Command::#{command.capitalize}"), :index
|
43
|
+
rescue NameError, NoMethodError
|
44
|
+
return Wod::Command::App, command.to_sym
|
45
|
+
end
|
46
|
+
else
|
47
|
+
begin
|
48
|
+
const = Wod::Command
|
49
|
+
command = parts.pop
|
50
|
+
parts.each { |part| const = const.const_get(part.capitalize) }
|
51
|
+
return const, command.to_sym
|
52
|
+
rescue NameError
|
53
|
+
raise InvalidCommand
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'readline'
|
2
|
+
|
3
|
+
module Wod::Command
|
4
|
+
class App < Base
|
5
|
+
def login
|
6
|
+
Wod::Command.run_internal "auth:reauthorize", args.dup
|
7
|
+
end
|
8
|
+
|
9
|
+
def logout
|
10
|
+
Wod::Command.run_internal "auth:delete_credentials", args.dup
|
11
|
+
puts "Local credentials cleared."
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'readline'
|
2
|
+
|
3
|
+
module Wod::Command
|
4
|
+
class Auth < Base
|
5
|
+
attr_accessor :credentials
|
6
|
+
|
7
|
+
def client
|
8
|
+
@client = Wod::Client.new(user, password)
|
9
|
+
end
|
10
|
+
|
11
|
+
# just a stub; will raise if not authenticated
|
12
|
+
def check
|
13
|
+
client.logged_in?
|
14
|
+
end
|
15
|
+
|
16
|
+
def reauthorize
|
17
|
+
@credentials = ask_for_and_save_credentials
|
18
|
+
end
|
19
|
+
|
20
|
+
def user
|
21
|
+
get_credentials
|
22
|
+
@credentials[0]
|
23
|
+
end
|
24
|
+
|
25
|
+
def password
|
26
|
+
get_credentials
|
27
|
+
@credentials[1]
|
28
|
+
end
|
29
|
+
|
30
|
+
def credentials_file
|
31
|
+
"#{home_directory}/.wod/credentials"
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_credentials
|
35
|
+
return if @credentials
|
36
|
+
unless @credentials = read_credentials
|
37
|
+
ask_for_and_save_credentials
|
38
|
+
end
|
39
|
+
@credentials
|
40
|
+
end
|
41
|
+
|
42
|
+
def read_credentials
|
43
|
+
File.exists?(credentials_file) and File.read(credentials_file).split("\n")
|
44
|
+
end
|
45
|
+
|
46
|
+
def echo_off
|
47
|
+
system "stty -echo"
|
48
|
+
end
|
49
|
+
|
50
|
+
def echo_on
|
51
|
+
system "stty echo"
|
52
|
+
end
|
53
|
+
|
54
|
+
def ask_for_credentials
|
55
|
+
puts "Enter your apple credentials"
|
56
|
+
|
57
|
+
print "Apple ID: "
|
58
|
+
user = ask
|
59
|
+
|
60
|
+
print "Password: "
|
61
|
+
password = ask_for_password
|
62
|
+
|
63
|
+
[user, password]
|
64
|
+
end
|
65
|
+
|
66
|
+
def ask_for_password
|
67
|
+
echo_off
|
68
|
+
password = ask
|
69
|
+
puts
|
70
|
+
echo_on
|
71
|
+
return password
|
72
|
+
end
|
73
|
+
|
74
|
+
def ask_for_and_save_credentials
|
75
|
+
begin
|
76
|
+
@credentials = ask_for_credentials
|
77
|
+
write_credentials
|
78
|
+
check
|
79
|
+
rescue ::Wod::InvalidCredentials
|
80
|
+
delete_credentials
|
81
|
+
@client = nil
|
82
|
+
@credentials = nil
|
83
|
+
puts "Authentication failed."
|
84
|
+
return if retry_login?
|
85
|
+
exit 1
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def retry_login?
|
90
|
+
@login_attempts ||= 0
|
91
|
+
@login_attempts += 1
|
92
|
+
@login_attempts < 3
|
93
|
+
end
|
94
|
+
|
95
|
+
def write_credentials
|
96
|
+
FileUtils.mkdir_p(File.dirname(credentials_file))
|
97
|
+
f = File.open(credentials_file, 'w')
|
98
|
+
f.chmod(0600)
|
99
|
+
f.puts self.credentials
|
100
|
+
f.close
|
101
|
+
set_credentials_permissions
|
102
|
+
end
|
103
|
+
|
104
|
+
def set_credentials_permissions
|
105
|
+
FileUtils.chmod 0700, File.dirname(credentials_file)
|
106
|
+
FileUtils.chmod 0600, credentials_file
|
107
|
+
end
|
108
|
+
|
109
|
+
def delete_credentials
|
110
|
+
FileUtils.rm_rf "#{home_directory}/.wod/"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
3
|
+
module Wod::Command
|
4
|
+
class Base
|
5
|
+
include Wod::Helpers
|
6
|
+
|
7
|
+
attr_accessor :args
|
8
|
+
|
9
|
+
def initialize(args, wod=nil)
|
10
|
+
@args = args
|
11
|
+
@wod = wod
|
12
|
+
end
|
13
|
+
|
14
|
+
def wod
|
15
|
+
@wod ||= Wod::Command.run_internal('auth:client', args)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
|
2
|
+
module Wod::Command
|
3
|
+
class Devices < Base
|
4
|
+
def index
|
5
|
+
page = wod.get "https://developer.apple.com/ios/manage/devices/index.action"
|
6
|
+
|
7
|
+
names = page.search("td.name span").map(&:text)
|
8
|
+
udids = page.search("td.id").map(&:text)
|
9
|
+
|
10
|
+
devices_left = page.search(".devicesannounce strong").first.text
|
11
|
+
devices = names.map.with_index{|name, i| {:name => name, :udid => udids[i] } }
|
12
|
+
|
13
|
+
display_formatted devices, [:name, :udid]
|
14
|
+
puts
|
15
|
+
puts "#{devices.size} devices registered. #{devices_left}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def add
|
19
|
+
name = args.shift
|
20
|
+
udid = args.shift
|
21
|
+
|
22
|
+
page = wod.get "https://developer.apple.com/ios/manage/devices/add.action"
|
23
|
+
|
24
|
+
form = page.form "add"
|
25
|
+
form["deviceNameList[0]"] = name
|
26
|
+
form["deviceNumberList[0]"] = udid
|
27
|
+
|
28
|
+
form.submit
|
29
|
+
end
|
30
|
+
|
31
|
+
def remove
|
32
|
+
name = args.shift
|
33
|
+
|
34
|
+
page = wod.get "http://developer.apple.com/ios/manage/devices/index.action"
|
35
|
+
|
36
|
+
device_span = page.search("span:contains('#{name}')")
|
37
|
+
if device_span.empty?
|
38
|
+
error "Device not found"
|
39
|
+
end
|
40
|
+
|
41
|
+
tr = device_span.first.parent.parent
|
42
|
+
row_identifier = tr.search("input[name='__checkbox_selectedValues']").first[:value]
|
43
|
+
|
44
|
+
form = page.form "removeDevice"
|
45
|
+
checkbox = form.checkboxes.find {|c| c[:value] == row_identifier}
|
46
|
+
checkbox.check
|
47
|
+
form.submit
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Wod::Command
|
2
|
+
class Help < Base
|
3
|
+
class HelpGroup < Array
|
4
|
+
attr_reader :title
|
5
|
+
|
6
|
+
def initialize(title)
|
7
|
+
@title = title
|
8
|
+
end
|
9
|
+
|
10
|
+
def command(name, description)
|
11
|
+
self << [name, description]
|
12
|
+
end
|
13
|
+
|
14
|
+
def space
|
15
|
+
self << ['', '']
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.groups
|
20
|
+
@groups ||= []
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.group(title, &block)
|
24
|
+
groups << begin
|
25
|
+
group = HelpGroup.new(title)
|
26
|
+
yield group
|
27
|
+
group
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.create_default_groups!
|
32
|
+
return if @defaults_created
|
33
|
+
@defaults_created = true
|
34
|
+
group 'General Commands' do |group|
|
35
|
+
group.command 'help', 'show this usage'
|
36
|
+
group.command 'version', 'show the gem version'
|
37
|
+
group.space
|
38
|
+
group.command 'login', 'log in with your apple credentials'
|
39
|
+
group.command 'logout', 'clear local authentication credentials'
|
40
|
+
group.space
|
41
|
+
group.command 'devices', 'list your registered devices'
|
42
|
+
group.command 'devices:add <name> <udid>', 'add a new device'
|
43
|
+
group.command 'devices:remove <name>', 'remove device (Still counts against device limit)'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def index
|
48
|
+
puts usage
|
49
|
+
end
|
50
|
+
|
51
|
+
def usage
|
52
|
+
longest_command_length = self.class.groups.map do |group|
|
53
|
+
group.map { |g| g.first.length }
|
54
|
+
end.flatten.max
|
55
|
+
|
56
|
+
self.class.groups.inject(StringIO.new) do |output, group|
|
57
|
+
output.puts "=== %s" % group.title
|
58
|
+
output.puts
|
59
|
+
|
60
|
+
group.each do |command, description|
|
61
|
+
if command.empty?
|
62
|
+
output.puts
|
63
|
+
else
|
64
|
+
output.puts "%-*s # %s" % [longest_command_length, command, description]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
output.puts
|
69
|
+
output
|
70
|
+
end.string
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
Wod::Command::Help.create_default_groups!
|
data/lib/wod/helpers.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
module Wod
|
2
|
+
module Helpers
|
3
|
+
def home_directory
|
4
|
+
ENV['HOME']
|
5
|
+
end
|
6
|
+
|
7
|
+
def error(msg)
|
8
|
+
STDERR.puts(msg)
|
9
|
+
exit 1
|
10
|
+
end
|
11
|
+
|
12
|
+
def ask
|
13
|
+
gets.strip
|
14
|
+
end
|
15
|
+
|
16
|
+
def display_formatted hashes, columns
|
17
|
+
column_lengths = columns.map do |c|
|
18
|
+
hashes.map { |h| h[c].length }.max
|
19
|
+
end
|
20
|
+
sorted = hashes.sort_by {|h| h[columns.first] }
|
21
|
+
|
22
|
+
sorted.each.with_index do |h, i|
|
23
|
+
puts " " + columns.map.with_index{ |c, i| h[c].ljust(column_lengths[i]) }.join(" | ")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
data/lib/wod/version.rb
ADDED
data/wod.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "wod/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "wod"
|
7
|
+
s.version = Wod::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Dave Newman"]
|
10
|
+
s.email = ["dave@whatupdave.com"]
|
11
|
+
s.homepage = "http://github.com/snappycode/wod"
|
12
|
+
s.summary = %q{The Wizard Of Dev center}
|
13
|
+
s.description = %q{Command line tool for interacting with the Apple Dev Center}
|
14
|
+
|
15
|
+
s.rubyforge_project = "wod"
|
16
|
+
|
17
|
+
s.add_dependency "mechanize"
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
+
s.require_paths = ["lib"]
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wod
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Dave Newman
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-04-01 00:00:00 +11:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: mechanize
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
description: Command line tool for interacting with the Apple Dev Center
|
28
|
+
email:
|
29
|
+
- dave@whatupdave.com
|
30
|
+
executables:
|
31
|
+
- wod
|
32
|
+
extensions: []
|
33
|
+
|
34
|
+
extra_rdoc_files: []
|
35
|
+
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- Gemfile
|
39
|
+
- README.md
|
40
|
+
- Rakefile
|
41
|
+
- bin/wod
|
42
|
+
- lib/wod.rb
|
43
|
+
- lib/wod/client.rb
|
44
|
+
- lib/wod/command.rb
|
45
|
+
- lib/wod/commands/app.rb
|
46
|
+
- lib/wod/commands/auth.rb
|
47
|
+
- lib/wod/commands/base.rb
|
48
|
+
- lib/wod/commands/devices.rb
|
49
|
+
- lib/wod/commands/help.rb
|
50
|
+
- lib/wod/commands/version.rb
|
51
|
+
- lib/wod/helpers.rb
|
52
|
+
- lib/wod/version.rb
|
53
|
+
- wod.gemspec
|
54
|
+
has_rdoc: true
|
55
|
+
homepage: http://github.com/snappycode/wod
|
56
|
+
licenses: []
|
57
|
+
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
|
61
|
+
require_paths:
|
62
|
+
- lib
|
63
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
none: false
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: "0"
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project: wod
|
78
|
+
rubygems_version: 1.5.3
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: The Wizard Of Dev center
|
82
|
+
test_files: []
|
83
|
+
|