one-for-all-framework 5.0.0 → 5.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 189b80a3eb7da358bd91a56cb6ffb17997f94433ee00fdd0366762320bea5f1e
4
- data.tar.gz: 5693b52708caad36aa969c58ac3f25ae3b17a1d2e964de44ab7761577050aa5e
3
+ metadata.gz: 1ca65e26e9715c8875888a62860db4e427e8b067175b380a83b966c3207dbf5a
4
+ data.tar.gz: f3898f1f830732781a4cdf43ca54b812f64c98067b58a2c25884f13afe0430f7
5
5
  SHA512:
6
- metadata.gz: 5f1bff39a001463536a39c0ee436438cb8579287cbbc4d1846b439a2172df848cead0a4dbc87ef0046cf0bc359833c47dca33e3347e2cfb94ac3c1108231f26c
7
- data.tar.gz: 8375e210da774b3fd0f6db508c5a44df418c1d395cd71d742aa10bec261f3429b65b904a9cc07c36d906d35edfa9e1fdd00d6e0916e17143410520ee2ebd44f8
6
+ metadata.gz: b0c527397ec49c0052ce8eb078f12633280d4ece5b5f9c8507bd48f7bece5920d10700bf3266a6c7b085b4c1dd8b4e6870e37da2c34395a49e88c6f6cebd4a0e
7
+ data.tar.gz: e99693905ab06c74f4a1f0010e9716a7475c4037b527f4d443e8c91cfbb72c8b6d2bb98445108e743c601181478d6017320e98c7dbcbbd1d02bc9bfb71f03720
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
  <img src="public/images/logo.png" width="500" height="500" alt="OFA Framework Logo">
3
3
  </p>
4
4
 
5
- # ⚡ One-For-All (OFA) Framework v5.0.0
5
+ # ⚡ One-For-All (OFA) Framework v5.2.0
6
6
 
7
7
  [![Ruby Version](https://img.shields.io/badge/ruby-%3E%3D%203.0.0-red.svg)](https://www.ruby-lang.org/)
8
8
  [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
data/app/views/docs.erb CHANGED
@@ -1,6 +1,6 @@
1
1
  <div class="space-y-12 pb-20">
2
2
  <div class="text-left">
3
- <div class="badge-premium">Documentation v5.0.0</div>
3
+ <div class="badge-premium">Documentation v5.2.0</div>
4
4
  <h1 class="text-5xl font-black tracking-tighter mb-4 text-slate-700 dark:text-white">Framework <span class="text-primary">Guide</span></h1>
5
5
  <p class="text-xl text-slate-500 max-w-2xl leading-relaxed">Everything you need to know about building premium web applications with the One-For-All framework.</p>
6
6
  </div>
data/app/views/index.erb CHANGED
@@ -1,6 +1,6 @@
1
1
  <div class="text-center space-y-6 max-w-2xl mx-auto py-12">
2
2
  <div class="inline-flex items-center px-4 py-1.5 rounded-full bg-primary/10 border border-primary/20 text-primary text-xs font-bold uppercase tracking-wider animate-pulse">
3
- Framework Version 5.0.0
3
+ Framework Version 5.2.0
4
4
  </div>
5
5
 
6
6
  <h1 class="text-5xl md:text-7xl font-black tracking-tight leading-tight">
data/bin/ofa CHANGED
@@ -33,7 +33,7 @@ def help
33
33
  puts " / __ \\/ ____/ / | "
34
34
  puts " / / / / /_ / /| | Framework "
35
35
  puts "/ /_/ / __/ / ___ | Premium MVC "
36
- puts "\\____/_/ /_/ |_| v5.0.0 "
36
+ puts "\\____/_/ /_/ |_| v5.2.0 "
37
37
  puts " "
38
38
  puts "✨ One-For-All Framework CLI ✨"
39
39
  puts "-----------------------------"
@@ -93,6 +93,7 @@ end
93
93
  def perform_db_migration(target_type, target_name)
94
94
  # 1. Initialize source environment
95
95
  Object.const_set(:APP_ROOT, PROJECT_ROOT) unless defined?(APP_ROOT)
96
+ ENV['SKIP_MODELS'] = '1'
96
97
  require File.join(FRAMEWORK_ROOT, 'config', 'boot')
97
98
  source_db = DB
98
99
 
@@ -196,7 +197,7 @@ def perform_db_migration(target_type, target_name)
196
197
 
197
198
  # 5. Update configuration to point to new DB
198
199
  absolute_ofa_path = File.expand_path(__FILE__)
199
- system("ruby #{absolute_ofa_path} db switch #{target_type} #{target_name}")
200
+ system("ruby #{absolute_ofa_path} db switch #{target_type} \"#{target_name}\"")
200
201
 
201
202
  puts "✅ Migration successful!"
202
203
  end
@@ -248,7 +249,7 @@ when 'init'
248
249
  puts " / __ \\/ ____/ / | "
249
250
  puts " / / / / /_ / /| | Framework "
250
251
  puts "/ /_/ / __/ / ___ | Premium MVC "
251
- puts "\\____/_/ /_/ |_| v5.0.0 "
252
+ puts "\\____/_/ /_/ |_| v5.2.0 "
252
253
  puts " "
253
254
  puts "Initializing One-For-All project as '#{app_type}' in #{PROJECT_ROOT}..."
254
255
 
@@ -861,7 +862,7 @@ when 'console'
861
862
  puts " / __ \\/ ____/ / | "
862
863
  puts " / / / / /_ / /| | Framework "
863
864
  puts "/ /_/ / __/ / ___ | Console (REPL) "
864
- puts "\\____/_/ /_/ |_| v5.0.0 "
865
+ puts "\\____/_/ /_/ |_| v5.2.0 "
865
866
  puts " "
866
867
  puts "✨ Loading environment... (Type 'exit' to quit)"
867
868
 
@@ -1,6 +1,6 @@
1
1
  Sequel.migration do
2
2
  change do
3
- create_table(:activity_logs) do
3
+ create_table?(:activity_logs) do
4
4
  primary_key :id
5
5
  String :user_id
6
6
  String :action, null: false
@@ -0,0 +1,35 @@
1
+ module OFA
2
+ @hooks = {
3
+ on_boot: [],
4
+ after_request: []
5
+ }
6
+
7
+ def self.on_boot(&block)
8
+ @hooks[:on_boot] << block
9
+ end
10
+
11
+ def self.run_boot_hooks
12
+ @hooks[:on_boot].each(&:call)
13
+ end
14
+
15
+ # Helper for plugins to add routes easily
16
+ def self.add_route(method, path, &block)
17
+ # This assumes ROUTES is available globally (defined in config.ru)
18
+ # If not, we might need a different registration mechanism
19
+ if defined?(ROUTES)
20
+ ROUTES.send(method, path, &block)
21
+ else
22
+ # Delay registration if ROUTES not yet defined
23
+ @pending_routes ||= []
24
+ @pending_routes << { method: method, path: path, block: block }
25
+ end
26
+ end
27
+
28
+ def self.apply_pending_routes(router)
29
+ return unless @pending_routes
30
+ @pending_routes.each do |r|
31
+ router.send(r[:method], r[:path], &r[:block])
32
+ end
33
+ @pending_routes = []
34
+ end
35
+ end
@@ -0,0 +1,30 @@
1
+ # Simple Task DSL for One-For-All
2
+ @tasks = {}
3
+ @last_desc = nil
4
+
5
+ def desc(text)
6
+ @last_desc = text
7
+ end
8
+
9
+ def task(name, &block)
10
+ @tasks[name.to_sym] = {
11
+ desc: @last_desc,
12
+ block: block
13
+ }
14
+ @last_desc = nil
15
+ end
16
+
17
+ def run_task(name)
18
+ task_data = @tasks[name.to_sym]
19
+ if task_data
20
+ puts "=> Running task: #{name}"
21
+ task_data[:block].call
22
+ puts "=> Completed task: #{name}"
23
+ else
24
+ puts "❌ Error: Task '#{name}' not found."
25
+ puts "Available tasks:"
26
+ @tasks.each do |n, data|
27
+ puts " #{n.to_s.ljust(15)} # #{data[:desc]}"
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,85 @@
1
+ require_relative 'test_helper'
2
+
3
+ class FeaturesTest < Minitest::Test
4
+ def setup
5
+ # Clear logs and maintenance mode before each test
6
+ ActivityLog.dataset.delete rescue nil
7
+ config_path = File.join(APP_ROOT, 'config', 'features.json')
8
+ config = JSON.parse(File.read(config_path))
9
+ config['maintenance'] = false
10
+ File.write(config_path, JSON.pretty_generate(config))
11
+ end
12
+
13
+ def test_maintenance_mode_toggle
14
+ # Turn ON
15
+ system("ruby bin/ofa maintenance on > /dev/null")
16
+ config = JSON.parse(File.read(File.join(APP_ROOT, 'config', 'features.json')))
17
+ assert_equal true, config['maintenance']
18
+
19
+ # Turn OFF
20
+ system("ruby bin/ofa maintenance off > /dev/null")
21
+ config = JSON.parse(File.read(File.join(APP_ROOT, 'config', 'features.json')))
22
+ assert_equal false, config['maintenance']
23
+ end
24
+
25
+ def test_activity_logging
26
+ # Test logging manually
27
+ ActivityLog.log(1, "Test Action", nil, "Details here")
28
+ log = ActivityLog.order(:created_at).last
29
+
30
+ assert_equal "Test Action", log.action
31
+ assert_equal "1", log.user_id
32
+ assert_equal "Details here", log.details
33
+ end
34
+
35
+ def test_plugin_generator
36
+ plugin_name = "test_plugin_#{Time.now.to_i}"
37
+ plugin_path = File.join(APP_ROOT, 'plugins', plugin_name)
38
+
39
+ begin
40
+ system("ruby bin/ofa g plugin #{plugin_name} > /dev/null")
41
+ assert Dir.exist?(plugin_path)
42
+ assert File.exist?(File.join(plugin_path, "init.rb"))
43
+ ensure
44
+ FileUtils.rm_rf(plugin_path) if Dir.exist?(plugin_path)
45
+ end
46
+ end
47
+
48
+ def test_maintenance_middleware_blocks_access
49
+ # Mock environment
50
+ env = {
51
+ 'PATH_INFO' => '/',
52
+ 'eks_cent.session' => {}
53
+ }
54
+
55
+ # 1. Test when maintenance is OFF
56
+ FEATURES_CONFIG['maintenance'] = false
57
+ app = lambda { |e| [200, {}, ['OK']] }
58
+ middleware = MaintenanceMiddleware.new(app)
59
+ status, _, _ = middleware.call(env)
60
+ assert_equal 200, status
61
+
62
+ # 2. Test when maintenance is ON (should block)
63
+ config_path = File.join(APP_ROOT, 'config', 'features.json')
64
+ config = JSON.parse(File.read(config_path))
65
+ config['maintenance'] = true
66
+ File.write(config_path, JSON.pretty_generate(config))
67
+
68
+ status, _, _ = middleware.call(env)
69
+ assert_equal 503, status
70
+
71
+ # 3. Test when maintenance is ON but allowed path
72
+ env['PATH_INFO'] = '/login'
73
+ status, _, _ = middleware.call(env)
74
+ assert_equal 200, status
75
+
76
+ # 4. Test when maintenance is ON but admin is logged in
77
+ env['PATH_INFO'] = '/'
78
+ env['eks_cent.session'] = { 'user_id' => 1 }
79
+ status, _, _ = middleware.call(env)
80
+ assert_equal 200, status
81
+
82
+ # Cleanup
83
+ FEATURES_CONFIG['maintenance'] = false
84
+ end
85
+ end
@@ -0,0 +1,51 @@
1
+ require 'eksa-mination'
2
+ require 'rack'
3
+ require 'rack/test'
4
+ require_relative '../config/boot'
5
+
6
+ describe "One-For-All Framework" do
7
+ before do
8
+ extend Rack::Test::Methods
9
+ end
10
+
11
+ let(:app) do
12
+ Rack::Builder.new do
13
+ use EksCent::Middleware::Session, secret: EksCent.secret_key_base
14
+ use AuthMiddleware
15
+ use CSRFMiddleware
16
+ use EksCent::Middleware::Logger
17
+ run ROUTES
18
+ end
19
+ end
20
+
21
+ it "menampilkan halaman utama dengan benar" do
22
+ get '/'
23
+ expect(last_response.status).to eq(200)
24
+ expect(last_response.body).to include("One-For-All")
25
+ end
26
+
27
+ it "menampilkan halaman login" do
28
+ get '/login'
29
+ expect(last_response.status).to eq(200)
30
+ expect(last_response.body).to include("Welcome Back")
31
+ end
32
+
33
+ it "menolak request POST tanpa CSRF token" do
34
+ post '/login', { username: 'admin', password: 'pwd' }
35
+ expect(last_response.status).to eq(403)
36
+ expect(last_response.body).to include("CSRF Token Invalid")
37
+ end
38
+
39
+ it "mengarahkan rute terproteksi ke halaman login jika belum autentikasi" do
40
+ get '/dashboard'
41
+ expect(last_response.status).to eq(302)
42
+ expect(last_response.location).to match(/\/login$/)
43
+ end
44
+
45
+ it "merespon endpoint API status dengan JSON" do
46
+ get '/api/status'
47
+ expect(last_response.status).to eq(200)
48
+ expect(last_response.body).to include("ok")
49
+ expect(last_response.headers['Content-Type']).to eq('application/json')
50
+ end
51
+ end
data/test/ofa_spec.rb ADDED
@@ -0,0 +1,205 @@
1
+ require 'eksa-mination'
2
+ require 'fileutils'
3
+ require 'json'
4
+ require 'sequel'
5
+ require 'bcrypt'
6
+
7
+ # Backup & Restore Logic outside DSL to ensure execution
8
+ CONFIG_FILES = ["config/database.json", "config/features.json"]
9
+ CONFIG_FILES.each do |file|
10
+ FileUtils.cp(file, "#{file}.bak") if File.exist?(file)
11
+ end
12
+
13
+ at_exit do
14
+ CONFIG_FILES.each do |file|
15
+ if File.exist?("#{file}.bak")
16
+ FileUtils.mv("#{file}.bak", file)
17
+ end
18
+ end
19
+ FileUtils.rm("db/test.sqlite3") if File.exist?("db/test.sqlite3")
20
+ end
21
+
22
+ describe "Ofa CLI" do
23
+ let(:ofa) { File.join(Dir.pwd, "bin", "ofa") }
24
+
25
+ before do
26
+ # Setting lingkungan test yang konsisten
27
+ `./bin/ofa db switch sqlite db/test.sqlite3`
28
+ `./bin/ofa feature enable auth`
29
+ `./bin/ofa type landing_page`
30
+ `./bin/ofa theme dark_glass`
31
+ end
32
+
33
+ it "menampilkan bantuan dengan benar" do
34
+ output = `./bin/ofa help`
35
+ expect(output).to include("One-For-All Framework CLI")
36
+ end
37
+
38
+ it "dapat meng-generate controller" do
39
+ `./bin/ofa g controller Blog`
40
+ expect(File.exist?("app/controllers/blog_controller.rb")).to be_truthy
41
+ expect(File.read("app/controllers/blog_controller.rb")).to include("class BlogController")
42
+ FileUtils.rm("app/controllers/blog_controller.rb")
43
+ end
44
+
45
+ it "dapat mengubah fitur (enable/disable)" do
46
+ config_path = "config/features.json"
47
+ `./bin/ofa feature disable auth`
48
+ new_config = JSON.parse(File.read(config_path))
49
+ expect(new_config["auth"]).to be_falsey
50
+ end
51
+
52
+ it "dapat mengganti tipe aplikasi" do
53
+ config_path = "config/features.json"
54
+ `./bin/ofa type portfolio`
55
+ config = JSON.parse(File.read(config_path))
56
+ expect(config["type"]).to eq("portfolio")
57
+ end
58
+
59
+ it "dapat mengganti tema aplikasi" do
60
+ config_path = "config/features.json"
61
+ `./bin/ofa theme light_glass`
62
+ config = JSON.parse(File.read(config_path))
63
+ expect(config["theme"]).to eq("light_glass")
64
+ end
65
+
66
+ it "dapat mengganti konfigurasi database" do
67
+ config_path = "config/database.json"
68
+ `./bin/ofa db switch postgres production_db`
69
+ config = JSON.parse(File.read(config_path))
70
+ expect(config["adapter"]).to eq("postgres")
71
+ expect(config["database"]).to eq("production_db")
72
+ end
73
+
74
+ it "dapat mereset password admin (dan membuat user jika belum ada)" do
75
+ username = "test_admin_#{Time.now.to_i}"
76
+ strong_password = "Secret123" # Memenuhi: 8+ karakter, huruf kapital, angka
77
+ `./bin/ofa reset-password #{username} #{strong_password}`
78
+
79
+ # Verifikasi langsung ke file DB
80
+ test_db = Sequel.connect("sqlite://db/test.sqlite3")
81
+ user = test_db[:users].first(username: username)
82
+ expect(user).not_to be_nil
83
+ expect(BCrypt::Password.new(user[:password_hash]) == strong_password).to be_truthy
84
+ test_db.disconnect
85
+ end
86
+
87
+ it "dapat meng-generate model baru" do
88
+ `./bin/ofa g model TestItem`
89
+ expect(File.exist?("app/models/testitem.rb")).to be_truthy
90
+ expect(File.read("app/models/testitem.rb")).to include("class Testitem < Sequel::Model")
91
+ FileUtils.rm("app/models/testitem.rb")
92
+ end
93
+
94
+ it "dapat meng-generate migration baru" do
95
+ `./bin/ofa g migration create_tests`
96
+ migrations = Dir.glob("db/migrations/*_create_tests.rb")
97
+ expect(migrations.any?).to be_truthy
98
+ migrations.each { |f| FileUtils.rm(f) }
99
+ end
100
+
101
+ it "dapat meng-generate post blog" do
102
+ `./bin/ofa g post "Testing Post" --author Antigravity --category Tech`
103
+ expect(File.exist?("app/views/posts/testing_post.erb")).to be_truthy
104
+ expect(File.read("app/views/posts/testing_post.erb")).to include("Testing Post")
105
+ FileUtils.rm("app/views/posts/testing_post.erb")
106
+ end
107
+
108
+ it "dapat mengganti konfigurasi storage" do
109
+ config_path = "config/features.json"
110
+ `./bin/ofa storage cloudinary`
111
+ config = JSON.parse(File.read(config_path))
112
+ expect(config["storage"]).to eq("cloudinary")
113
+ end
114
+
115
+ it "dapat melakukan migrasi data antar database (SQLite)" do
116
+ source_db_path = "db/source_test.sqlite3"
117
+ target_db_path = "db/target_test.sqlite3"
118
+ FileUtils.rm(source_db_path) if File.exist?(source_db_path)
119
+ FileUtils.rm(target_db_path) if File.exist?(target_db_path)
120
+
121
+ `./bin/ofa db switch sqlite #{source_db_path}`
122
+ username = "migrator_user"
123
+ `./bin/ofa reset-password #{username} Secret123`
124
+
125
+ `./bin/ofa db migrate-data sqlite #{target_db_path}`
126
+
127
+ expect(File.exist?(target_db_path)).to be_truthy
128
+ target_db = Sequel.connect("sqlite://#{target_db_path}")
129
+ user = target_db[:users].first(username: username)
130
+ expect(user).not_to be_nil
131
+ target_db.disconnect
132
+
133
+ FileUtils.rm(source_db_path) if File.exist?(source_db_path)
134
+ FileUtils.rm(target_db_path) if File.exist?(target_db_path)
135
+ end
136
+
137
+ it "dapat meng-generate API controller baru" do
138
+ `./bin/ofa g api Shop`
139
+ expect(File.exist?("app/controllers/shop_controller.rb")).to be_truthy
140
+ content = File.read("app/controllers/shop_controller.rb")
141
+ expect(content).to include("class ShopController < ApiController")
142
+ expect(content).to include("render_json")
143
+ FileUtils.rm("app/controllers/shop_controller.rb")
144
+ end
145
+
146
+ it "dapat meng-generate dokumentasi Swagger/OpenAPI" do
147
+ `./bin/ofa swagger`
148
+ expect(File.exist?("openapi.json")).to be_truthy
149
+ config = JSON.parse(File.read("openapi.json"))
150
+ expect(config["openapi"]).to eq("3.0.0")
151
+ expect(config["paths"].any?).to be_truthy
152
+ FileUtils.rm("openapi.json")
153
+ end
154
+
155
+ it "dapat menampilkan daftar rute" do
156
+ output = `./bin/ofa routes`
157
+ expect(output).to include("Registered Routes")
158
+ expect(output).to include("/api/status")
159
+ end
160
+
161
+ it "dapat menjalankan pemeriksaan kesehatan sistem (doctor)" do
162
+ output = `./bin/ofa doctor`
163
+ expect(output).to include("One-For-All Doctor")
164
+ expect(output).to include("Checking .env file")
165
+ expect(output).to include("Checking Database connection")
166
+ end
167
+
168
+ it "dapat meng-generate mailer baru" do
169
+ `./bin/ofa g mailer Welcome signup`
170
+ expect(File.exist?("app/mailers/welcome_mailer.rb")).to be_truthy
171
+ expect(File.exist?("app/views/mailers/welcome_mailer/signup.erb")).to be_truthy
172
+ FileUtils.rm("app/mailers/welcome_mailer.rb")
173
+ FileUtils.rm_rf("app/views/mailers/welcome_mailer")
174
+ end
175
+
176
+ it "dapat meng-generate task baru" do
177
+ `./bin/ofa g task Cleanup`
178
+ expect(File.exist?("lib/tasks/cleanup.rb")).to be_truthy
179
+ FileUtils.rm("lib/tasks/cleanup.rb")
180
+ end
181
+
182
+ it "dapat menjalankan task yang didefinisikan" do
183
+ File.write("lib/tasks/hello.rb", "task :hello do; puts 'Hello Task'; end")
184
+ output = `./bin/ofa task hello`
185
+ expect(output).to include("Running task: hello")
186
+ expect(output).to include("Hello Task")
187
+ FileUtils.rm("lib/tasks/hello.rb")
188
+ end
189
+
190
+ it "dapat meng-generate test baru" do
191
+ `./bin/ofa g test unit`
192
+ expect(File.exist?("test/unit_test.rb")).to be_truthy
193
+ FileUtils.rm("test/unit_test.rb")
194
+ end
195
+
196
+ it "dapat menjalankan perintah test suite" do
197
+ # Buat dummy test agar tidak error 'No tests found'
198
+ dummy_file = "test/z_dummy_test.rb"
199
+ File.write(dummy_file, "require_relative 'test_helper'\nclass DummyTest < Minitest::Test; def test_pass; assert true; end; end")
200
+ output = `./bin/ofa test #{dummy_file}`
201
+ expect(output).to include("Running tests")
202
+ expect(output).to include("1 runs, 1 assertions")
203
+ FileUtils.rm(dummy_file) if File.exist?(dummy_file)
204
+ end
205
+ end
@@ -0,0 +1,13 @@
1
+ ENV['EKS_ENV'] = 'test'
2
+ require 'minitest/autorun'
3
+ require 'minitest/pride' # Nice colors
4
+
5
+ # Load the application environment
6
+ require_relative '../config/boot'
7
+
8
+ class Minitest::Test
9
+ # Add helper methods for tests here
10
+ def setup
11
+ # Run migrations or setup DB for tests
12
+ end
13
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: one-for-all-framework
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.0.0
4
+ version: 5.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ishikawa Uta
@@ -262,10 +262,16 @@ files:
262
262
  - db/migrations/20260502000000_create_products.rb
263
263
  - db/migrations/20260507000000_create_activity_logs.rb
264
264
  - db/migrations/20260507000001_fix_activity_logs_user_id.rb
265
+ - lib/plugin_helper.rb
266
+ - lib/task_helper.rb
265
267
  - ofa
266
268
  - public/css/cms.css
267
269
  - public/images/logo.jpg
268
270
  - public/images/logo.png
271
+ - test/features_test.rb
272
+ - test/framework_spec.rb
273
+ - test/ofa_spec.rb
274
+ - test/test_helper.rb
269
275
  homepage: https://github.com/ishikawauta/one-for-all-framework
270
276
  licenses:
271
277
  - MIT