feature_flipper 1.0.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.
- data/LICENSE +20 -0
- data/README.md +118 -0
- data/Rakefile +21 -0
- data/examples/features.rb +19 -0
- data/examples/simple.rb +39 -0
- data/lib/feature_flipper/config.rb +97 -0
- data/lib/feature_flipper/show.rb +13 -0
- data/lib/feature_flipper/version.rb +3 -0
- data/lib/feature_flipper.rb +5 -0
- data/test/feature_flipper_test.rb +94 -0
- data/test/features.rb +16 -0
- data/test/features_dsl.rb +23 -0
- data/test/test_helper.rb +38 -0
- metadata +71 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Florian Munz
|
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,118 @@
|
|
1
|
+
FeatureFlipper
|
2
|
+
==============
|
3
|
+
|
4
|
+
FeatureFlipper is a simple library that allows you to restrict certain blocks
|
5
|
+
of code to certain environments. This is mainly useful in projects where
|
6
|
+
you deploy your application from HEAD and don't use branches.
|
7
|
+
|
8
|
+
Install
|
9
|
+
-------
|
10
|
+
|
11
|
+
FeatureFlipper is packaged as a gem:
|
12
|
+
|
13
|
+
$ gem install feature_flipper
|
14
|
+
|
15
|
+
In your project you have to configure the path to the app specific
|
16
|
+
configuration file after requiring FeatureFlipper:
|
17
|
+
|
18
|
+
require 'feature_flipper'
|
19
|
+
FeatureFlipper::Config.path_to_file = "#{RAILS_ROOT}/config/features.rb"
|
20
|
+
|
21
|
+
Configuration
|
22
|
+
-------------
|
23
|
+
|
24
|
+
You need to create a configuration file which defines the two entities
|
25
|
+
FeatureFlipper cares about:
|
26
|
+
|
27
|
+
* states
|
28
|
+
* features
|
29
|
+
|
30
|
+
You first define multiple 'states' which normally depend on an environment
|
31
|
+
(for example: the state 'dev' is only active on development boxes). After that
|
32
|
+
you add 'features' which correspond to logical chunks of work in your project.
|
33
|
+
These features then move through the different states as they get developed.
|
34
|
+
|
35
|
+
### Defining features
|
36
|
+
|
37
|
+
A feature needs to have a name and you can add additional information like a
|
38
|
+
more detailed description, a ticket number, a date when it was started, etc.
|
39
|
+
Features are always defined in a state, you cannot define a feature which
|
40
|
+
doesn't belong to a state.
|
41
|
+
|
42
|
+
in_status :dev do
|
43
|
+
feature :rating_game, :description => 'play a game to get recommendations'
|
44
|
+
end
|
45
|
+
|
46
|
+
### Defining states
|
47
|
+
|
48
|
+
A state is just a name and a boolean check. The check needs to evaluate to
|
49
|
+
´true´ when it is active. For a Rails app you can just use environments:
|
50
|
+
|
51
|
+
:dev => ['development', 'test'].include?(Rails.env),
|
52
|
+
|
53
|
+
Usage
|
54
|
+
-----
|
55
|
+
|
56
|
+
In your code you then use the `show_feature?` method to branch depending on
|
57
|
+
wether the feature is active or not:
|
58
|
+
|
59
|
+
if show_feature?(:rating_game)
|
60
|
+
# new code
|
61
|
+
else
|
62
|
+
# old code
|
63
|
+
end
|
64
|
+
|
65
|
+
The `show_feature?` method is defined on Object, so you can use it everywhere.
|
66
|
+
|
67
|
+
Example config file
|
68
|
+
-------------------
|
69
|
+
|
70
|
+
FeatureFlipper.features do
|
71
|
+
in_status :dev do
|
72
|
+
feature :rating_game, :description => 'play a game to get recommendations'
|
73
|
+
end
|
74
|
+
|
75
|
+
in_status :live do
|
76
|
+
feature :city_feed, :description => 'stream of content for each city'
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
FeatureFlipper::Config.states = {
|
81
|
+
:dev => ['development', 'test'].include?(Rails.env),
|
82
|
+
:live => true
|
83
|
+
}
|
84
|
+
|
85
|
+
This is your complete features.rb config file. In the example there are two
|
86
|
+
states: `:dev` is active on development boxes and `:live` is always active
|
87
|
+
(this is the last state a feature is in).
|
88
|
+
|
89
|
+
The feature `:rating_game` is still in development and not shown on the
|
90
|
+
production site. The feature `:city_feed` is done and already enabled
|
91
|
+
everywhere. You transition features between states by just moving the line to
|
92
|
+
the new state block.
|
93
|
+
|
94
|
+
You can take a look at this example in detail in the 'examples' folder.
|
95
|
+
|
96
|
+
Cleaning up
|
97
|
+
-----------
|
98
|
+
|
99
|
+
The drawback of this approach is that your code can get quite ugly with all
|
100
|
+
these if/else branches. So you have to be strict about removing (we call it
|
101
|
+
de-featurizing) features after they have gone live.
|
102
|
+
|
103
|
+
Meta
|
104
|
+
----
|
105
|
+
|
106
|
+
* Code: `git clone git://github.com/qype/feature_flipper.git`
|
107
|
+
* Home: <http://github.com/qype/feature_flipper>
|
108
|
+
* Bugs: <http://github.com/qype/feature_flipper/issues>
|
109
|
+
|
110
|
+
This project uses [Semantic Versioning][sv].
|
111
|
+
|
112
|
+
Author
|
113
|
+
------
|
114
|
+
|
115
|
+
Florian Munz, Qype GmbH - florian@qype.com
|
116
|
+
|
117
|
+
|
118
|
+
[sv]: http://semver.org/
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
desc 'Default: run unit tests'
|
5
|
+
task :default => :test
|
6
|
+
|
7
|
+
desc 'run the feature_flipper tests'
|
8
|
+
Rake::TestTask.new(:test) do |t|
|
9
|
+
t.libs << 'lib'
|
10
|
+
t.libs << 'test'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
require 'mg'
|
17
|
+
MG.new('feature_flipper.gemspec')
|
18
|
+
rescue LoadError
|
19
|
+
warn 'mg not available.'
|
20
|
+
warn 'Install it with: gem install mg'
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#
|
2
|
+
# This is an example configuration file. It defines two states and
|
3
|
+
# two features.
|
4
|
+
#
|
5
|
+
|
6
|
+
FeatureFlipper.features do
|
7
|
+
in_status :dev do
|
8
|
+
feature :rating_game, :description => 'play a game to get recommendations'
|
9
|
+
end
|
10
|
+
|
11
|
+
in_status :live do
|
12
|
+
feature :city_feed, :description => 'stream of content for each city'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
FeatureFlipper::Config.states = {
|
17
|
+
:dev => ['development', 'test'].include?(Rails.env),
|
18
|
+
:live => true
|
19
|
+
}
|
data/examples/simple.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Setup
|
2
|
+
#
|
3
|
+
|
4
|
+
# just need this to make it work from within the library
|
5
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
6
|
+
|
7
|
+
# fake production Rails environment
|
8
|
+
module Rails
|
9
|
+
def self.env
|
10
|
+
'production'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
# Configuration
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'feature_flipper'
|
19
|
+
|
20
|
+
# set the path to your app specific config file
|
21
|
+
FeatureFlipper::Config.path_to_file = "features.rb"
|
22
|
+
|
23
|
+
|
24
|
+
# Usage
|
25
|
+
#
|
26
|
+
|
27
|
+
# rating_game is still in development, so shouldn't be shown on production
|
28
|
+
if show_feature?(:rating_game)
|
29
|
+
puts "Rating Game"
|
30
|
+
else
|
31
|
+
puts "old stuff"
|
32
|
+
end
|
33
|
+
|
34
|
+
# city_feed is enabled everywhere
|
35
|
+
if show_feature?(:city_feed)
|
36
|
+
puts "City Feed"
|
37
|
+
else
|
38
|
+
puts "old stuff"
|
39
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module FeatureFlipper
|
2
|
+
module Config
|
3
|
+
@features = {}
|
4
|
+
@states = {}
|
5
|
+
|
6
|
+
def self.path_to_file
|
7
|
+
@path_to_file || File.join(Rails.root, 'config', 'features.rb')
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.path_to_file=(path_to_file)
|
11
|
+
@path_to_file = path_to_file
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.ensure_config_is_loaded
|
15
|
+
return if @config_loaded
|
16
|
+
|
17
|
+
load path_to_file
|
18
|
+
@config_loaded = true
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.reload_config
|
22
|
+
@config_loaded = false
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.features
|
26
|
+
@features
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.features=(features)
|
30
|
+
@features = features
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.states
|
34
|
+
@states
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.states=(states)
|
38
|
+
@states = states
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.get_status(feature_name)
|
42
|
+
feature = features[feature_name]
|
43
|
+
feature ? feature[:status] : nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.active_status?(status)
|
47
|
+
active = states[status]
|
48
|
+
if active.is_a?(Hash)
|
49
|
+
group, group_status = active.to_a.flatten
|
50
|
+
FeatureFlipper.current_feature_group == group || states[group_status] == true
|
51
|
+
else
|
52
|
+
active == true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.is_active?(feature_name)
|
57
|
+
ensure_config_is_loaded
|
58
|
+
|
59
|
+
status = get_status(feature_name)
|
60
|
+
if status.is_a?(Symbol)
|
61
|
+
active_status?(status)
|
62
|
+
elsif status.is_a?(Proc)
|
63
|
+
status.call == true
|
64
|
+
else
|
65
|
+
status == true
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class Mapper
|
71
|
+
def initialize(status)
|
72
|
+
@status = status
|
73
|
+
end
|
74
|
+
|
75
|
+
def feature(name, options = {})
|
76
|
+
FeatureFlipper::Config.features[name] = options.merge(:status => @status)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class StatusMapper
|
81
|
+
def in_status(status, &block)
|
82
|
+
Mapper.new(status).instance_eval(&block)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def self.features(&block)
|
87
|
+
StatusMapper.new.instance_eval(&block)
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.current_feature_group
|
91
|
+
Thread.current[:feature_system_current_feature_group]
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.current_feature_group=(feature_group)
|
95
|
+
Thread.current[:feature_system_current_feature_group] = feature_group
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
context 'hash based FeatureFlipper' do
|
4
|
+
setup do
|
5
|
+
FeatureFlipper::Config.path_to_file = 'features.rb'
|
6
|
+
FeatureFlipper::Config.reload_config
|
7
|
+
end
|
8
|
+
|
9
|
+
test 'should show enabled features' do
|
10
|
+
assert show_feature?(:live_feature)
|
11
|
+
end
|
12
|
+
|
13
|
+
test 'should not show disabled features' do
|
14
|
+
assert !show_feature?(:disabled_feature)
|
15
|
+
end
|
16
|
+
|
17
|
+
test 'should not show a feature when on a higher environment' do
|
18
|
+
Rails.stubs(:env).returns('production')
|
19
|
+
assert !show_feature?(:dev_feature)
|
20
|
+
end
|
21
|
+
|
22
|
+
test 'show feature should work with booleans' do
|
23
|
+
assert show_feature?(:boolean_feature)
|
24
|
+
end
|
25
|
+
|
26
|
+
test 'show feature should work with procs' do
|
27
|
+
assert show_feature?(:proc_feature)
|
28
|
+
end
|
29
|
+
|
30
|
+
test 'should show a beta feature to the feature group' do
|
31
|
+
Rails.stubs(:env).returns('production')
|
32
|
+
FeatureFlipper.stubs(:current_feature_group).returns(:beta_users)
|
33
|
+
|
34
|
+
assert show_feature?(:beta_feature)
|
35
|
+
end
|
36
|
+
|
37
|
+
test 'should not show a beta feature if not in the group' do
|
38
|
+
Rails.stubs(:env).returns('production')
|
39
|
+
FeatureFlipper.stubs(:current_feature_group).returns(nil)
|
40
|
+
|
41
|
+
assert !show_feature?(:beta_feature)
|
42
|
+
end
|
43
|
+
|
44
|
+
test 'should always show a beta feature on dev' do
|
45
|
+
Rails.stubs(:env).returns('development')
|
46
|
+
FeatureFlipper.stubs(:current_feature_group).returns(nil)
|
47
|
+
|
48
|
+
assert show_feature?(:beta_feature)
|
49
|
+
end
|
50
|
+
|
51
|
+
test 'should be able to get features' do
|
52
|
+
FeatureFlipper::Config.ensure_config_is_loaded
|
53
|
+
all_features = FeatureFlipper::Config.features
|
54
|
+
|
55
|
+
assert_not_nil all_features
|
56
|
+
assert all_features.is_a?(Hash)
|
57
|
+
assert_equal :dev, all_features[:dev_feature][:status]
|
58
|
+
assert_equal 'dev feature', all_features[:dev_feature][:description]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'DSL based FeatureFlipper' do
|
63
|
+
setup do
|
64
|
+
FeatureFlipper::Config.path_to_file = 'features_dsl.rb'
|
65
|
+
FeatureFlipper::Config.reload_config
|
66
|
+
end
|
67
|
+
|
68
|
+
test 'should show enabled features' do
|
69
|
+
assert show_feature?(:live_feature)
|
70
|
+
end
|
71
|
+
|
72
|
+
test 'should not show a feature when on a higher environment' do
|
73
|
+
Rails.stubs(:env).returns('production')
|
74
|
+
assert !show_feature?(:dev_feature)
|
75
|
+
end
|
76
|
+
|
77
|
+
test 'show feature should work with booleans' do
|
78
|
+
assert show_feature?(:boolean_feature)
|
79
|
+
end
|
80
|
+
|
81
|
+
test 'show feature should work with procs' do
|
82
|
+
assert show_feature?(:proc_feature)
|
83
|
+
end
|
84
|
+
|
85
|
+
test 'should be able to get features' do
|
86
|
+
FeatureFlipper::Config.ensure_config_is_loaded
|
87
|
+
all_features = FeatureFlipper::Config.features
|
88
|
+
|
89
|
+
assert_not_nil all_features
|
90
|
+
assert all_features.is_a?(Hash)
|
91
|
+
assert_equal :dev, all_features[:dev_feature][:status]
|
92
|
+
assert_equal 'dev feature', all_features[:dev_feature][:description]
|
93
|
+
end
|
94
|
+
end
|
data/test/features.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
FeatureFlipper::Config.features = {
|
2
|
+
:live_feature => { :status => :live },
|
3
|
+
:disabled_feature => { :status => :disabled },
|
4
|
+
:dev_feature => { :status => :dev, :description => 'dev feature' },
|
5
|
+
:boolean_feature => { :status => true },
|
6
|
+
:proc_feature => { :status => Proc.new { Date.today > Date.today - 84000 } },
|
7
|
+
:beta_feature => { :status => :beta }
|
8
|
+
}
|
9
|
+
|
10
|
+
|
11
|
+
FeatureFlipper::Config.states = {
|
12
|
+
:disabled => false,
|
13
|
+
:dev => ['development', 'test'].include?(Rails.env),
|
14
|
+
:beta => { :beta_users => :dev },
|
15
|
+
:live => true
|
16
|
+
}
|
@@ -0,0 +1,23 @@
|
|
1
|
+
FeatureFlipper.features do
|
2
|
+
in_status :dev do
|
3
|
+
feature :dev_feature, :description => 'dev feature'
|
4
|
+
end
|
5
|
+
|
6
|
+
in_status :live do
|
7
|
+
feature :live_feature
|
8
|
+
end
|
9
|
+
|
10
|
+
in_status true do
|
11
|
+
feature :boolean_feature
|
12
|
+
end
|
13
|
+
|
14
|
+
in_status Proc.new { Date.today > Date.today - 84000 } do
|
15
|
+
feature :proc_feature
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
FeatureFlipper::Config.states = {
|
21
|
+
:dev => ['development', 'test'].include?(Rails.env),
|
22
|
+
:live => true
|
23
|
+
}
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'rubygems'
|
3
|
+
|
4
|
+
require 'mocha'
|
5
|
+
|
6
|
+
##
|
7
|
+
# test/spec/mini 5
|
8
|
+
# http://gist.github.com/307649
|
9
|
+
# chris@ozmm.org
|
10
|
+
#
|
11
|
+
def context(*args, &block)
|
12
|
+
return super unless (name = args.first) && block
|
13
|
+
require 'test/unit'
|
14
|
+
klass = Class.new(defined?(ActiveSupport::TestCase) ? ActiveSupport::TestCase : Test::Unit::TestCase) do
|
15
|
+
def self.test(name, &block)
|
16
|
+
define_method("test_#{name.to_s.gsub(/\W/,'_')}", &block) if block
|
17
|
+
end
|
18
|
+
def self.xtest(*args) end
|
19
|
+
def self.context(*args, &block) instance_eval(&block) end
|
20
|
+
def self.setup(&block)
|
21
|
+
define_method(:setup) { self.class.setups.each { |s| instance_eval(&s) } }
|
22
|
+
setups << block
|
23
|
+
end
|
24
|
+
def self.setups; @setups ||= [] end
|
25
|
+
def self.teardown(&block) define_method(:teardown, &block) end
|
26
|
+
end
|
27
|
+
(class << klass; self end).send(:define_method, :name) { name.gsub(/\W/,'_') }
|
28
|
+
klass.class_eval &block
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
module Rails
|
33
|
+
def self.env
|
34
|
+
'test'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
require 'feature_flipper'
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: feature_flipper
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Florian Munz
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2010-05-07 00:00:00 +02:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: |
|
17
|
+
FeatureFlipper is a simple library that allows you to restrict certain blocks
|
18
|
+
of code to certain environments. This is mainly useful in projects where
|
19
|
+
you deploy your application from HEAD and don't use branches.
|
20
|
+
|
21
|
+
email: florian@qype.com
|
22
|
+
executables: []
|
23
|
+
|
24
|
+
extensions: []
|
25
|
+
|
26
|
+
extra_rdoc_files: []
|
27
|
+
|
28
|
+
files:
|
29
|
+
- README.md
|
30
|
+
- Rakefile
|
31
|
+
- LICENSE
|
32
|
+
- lib/feature_flipper/config.rb
|
33
|
+
- lib/feature_flipper/show.rb
|
34
|
+
- lib/feature_flipper/version.rb
|
35
|
+
- lib/feature_flipper.rb
|
36
|
+
- test/feature_flipper_test.rb
|
37
|
+
- test/features.rb
|
38
|
+
- test/features_dsl.rb
|
39
|
+
- test/test_helper.rb
|
40
|
+
- examples/features.rb
|
41
|
+
- examples/simple.rb
|
42
|
+
has_rdoc: true
|
43
|
+
homepage: http://github.com/qype/feature_flipper
|
44
|
+
licenses: []
|
45
|
+
|
46
|
+
post_install_message:
|
47
|
+
rdoc_options: []
|
48
|
+
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: "0"
|
56
|
+
version:
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: "0"
|
62
|
+
version:
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.3.5
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: FeatureFlipper helps you flipping features
|
70
|
+
test_files: []
|
71
|
+
|