one-for-all-framework 4.3.0 → 4.4.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: fd84838fc3bc3f5241aa3d3a701accc2f9bfe8b6bf7303d708319e14ea68fe17
4
- data.tar.gz: 5cce2c8ac4cfbc1586f12c5551eec509ef0536a189d7fa901d53dbb7a2d2f25d
3
+ metadata.gz: 345583a2af3a1c7ee4eb6d923c1d87ea769a5f6e70c98537a36575d1ee89dc03
4
+ data.tar.gz: 596e7f2abe8bcf277d180e5044e9b5fb77fbfa894c894c92639d7cda3fc4136a
5
5
  SHA512:
6
- metadata.gz: 4ded6af832ba7cb08647f002f871cebf223057733142d8ac21c241d43fc0a96058e1439f3946195c0b231174e07293de69b08011bf2603cdfa9ddadab4bc93b4
7
- data.tar.gz: 43a940c1c4288fbf4b273937bc5725c4418736cc88c525db71b494b4cd45b74b642220cf1fb1ce13766dbed1428653bf6907a1c27be1b2c6cd2fdd376889d18c
6
+ metadata.gz: 9b5c07c96b28fc73548e35ecbb81113361d6b32575c4a681bf7bf0b916e2c39e4b18e34032f252c83176116a89d4377d8f1f58ee16acab90cdafb4d0ce467b03
7
+ data.tar.gz: bcb67f5526ebca56809ec9fa8009199b0911c2668445b8eb15ecd7f5995edde4fd8b33271b4168a416fc04aa87f768947d8109a1e0816a97b2620571cd1f911d
data/.env.example CHANGED
@@ -2,3 +2,8 @@ DATABASE_URL="sqlite://db/development.sqlite3"
2
2
  CLOUDINARY_URL="cloudinary://API_KEY:API_SECRET@CLOUD_NAME"
3
3
  EKS_CENT_SECRET_KEY_BASE="your_secret_key"
4
4
  EKS_ENV="development"
5
+ MAILER_FROM="admin@example.com"
6
+ SMTP_ADDRESS="smtp.gmail.com"
7
+ SMTP_PORT=587
8
+ SMTP_USER_NAME="your_email@gmail.com"
9
+ SMTP_PASSWORD="your_app_password"
data/Gemfile CHANGED
@@ -13,6 +13,8 @@ gem 'mongo'
13
13
  gem 'dotenv'
14
14
  gem 'kramdown'
15
15
  gem 'kramdown-parser-gfm'
16
+ gem 'mail'
17
+ gem 'minitest'
16
18
 
17
19
  group :test do
18
20
  gem 'eksa-mination'
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 v4.3.0
5
+ # ⚡ One-For-All (OFA) Framework v4.4.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)
@@ -141,6 +141,8 @@ Securely manages admin credentials.
141
141
  | `ofa doctor` | **System Health Check.** Validates `.env` config, database connectivity (SQL/MongoDB), Ruby version, and dependencies. |
142
142
  | `ofa routes` | **Route Inspection.** Lists all registered routes in your application in a clean tabular format. |
143
143
  | `ofa swagger` | **OpenAPI Generation.** Auto-generates `openapi.json` for your entire application. |
144
+ | `ofa task NAME` | **Run Background Task.** Executes a task defined in `lib/tasks/`. |
145
+ | `ofa test` | **Run Test Suite.** Executes all unit tests in the `test/` directory using Minitest. |
144
146
  | `ofa deploy` | **Production Deployment.** Automatically detects deployment targets (Railway/Docker/Git). |
145
147
 
146
148
  ---
@@ -154,6 +156,9 @@ Automate the creation of boilerplate code with the generator command.
154
156
  | `ofa g api NAME` | Creates a JSON-based controller in `app/controllers/{name}_controller.rb` inheriting from `ApiController`. |
155
157
  | `ofa g model NAME` | Generates a database model in `app/models/{name}.rb` integrated with the Sequel ORM. |
156
158
  | `ofa g migration NAME` | Creates a timestamped migration file in `db/migrations/`. Use this to define your schema changes. |
159
+ | `ofa g mailer NAME ACTION` | Generates a new mailer in `app/mailers/` and an ERB template in `app/views/mailers/`. |
160
+ | `ofa g task NAME` | Creates a new background task file in `lib/tasks/`. |
161
+ | `ofa g test NAME` | Generates a new Minitest unit test in `test/`. |
157
162
  | `ofa g post TITLE` | Creates a new Markdown/ERB post in `app/views/posts/`. <br> *Args:* `--category`, `--author`, `--image`. <br> *Example:* `./ofa g post "My First Journey" --category Tech --author "John Doe"` |
158
163
 
159
164
  ---
@@ -0,0 +1,42 @@
1
+ require 'mail'
2
+ require 'erb'
3
+
4
+ class ApplicationMailer
5
+ # Configure default settings if needed
6
+ def self.mail(to:, subject:, template:, locals: {})
7
+ # Template name expected: "welcome_mailer/welcome"
8
+ template_path = File.join(APP_ROOT, 'app', 'views', 'mailers', "#{template}.erb")
9
+
10
+ unless File.exist?(template_path)
11
+ puts "❌ Mailer Error: Template not found at #{template_path}"
12
+ return
13
+ end
14
+
15
+ # Create rendering context
16
+ context = Object.new
17
+ locals.each { |k, v| context.instance_variable_set("@#{k}", v) }
18
+
19
+ body_content = ERB.new(File.read(template_path)).result(context.instance_eval { binding })
20
+
21
+ mail = Mail.new do
22
+ from ENV['MAILER_FROM'] || 'admin@example.com'
23
+ to to
24
+ subject subject
25
+ body body_content
26
+
27
+ # Optional: set content type to HTML if needed
28
+ # content_type 'text/html; charset=UTF-8'
29
+ end
30
+
31
+ # In development/test, we might want to just log it
32
+ if ENV['EKS_ENV'] == 'production'
33
+ mail.deliver!
34
+ else
35
+ puts "--- 📧 EMAIL SENT ---"
36
+ puts "To: #{to}"
37
+ puts "Subject: #{subject}"
38
+ puts "Body:\n#{body_content}"
39
+ puts "----------------------"
40
+ end
41
+ end
42
+ end
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 v4.3.0</div>
3
+ <div class="badge-premium">Documentation v4.4.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>
@@ -17,7 +17,8 @@
17
17
  <a href="#cms" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">CMS & Features</a>
18
18
  <a href="#ecommerce" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">E-Commerce</a>
19
19
  <a href="#api" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">API & Modern Web</a>
20
- <a href="#deploy" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">Deployment</a>
20
+ <a href="#advanced" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">Mailers & Background Jobs</a>
21
+ <a href="#testing" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">Automated Testing</a>
21
22
  <a href="#deploy" class="block px-4 py-2 rounded-xl hover:bg-primary/5 text-slate-600 hover:text-primary transition-all font-semibold border-l-2 border-transparent hover:border-primary">Deployment</a>
22
23
  </nav>
23
24
  </aside>
@@ -98,10 +99,22 @@
98
99
  <td class="px-6 py-4 font-mono text-primary">./ofa g model NAME</td>
99
100
  <td class="px-6 py-4 text-slate-600 dark:text-slate-400">Creates model & migration linked to Sequel/NoSQL.</td>
100
101
  </tr>
101
- <tr>
102
- <td class="px-6 py-4 font-mono text-primary">./ofa g migration NAME</td>
103
- <td class="px-6 py-4 text-slate-600 dark:text-slate-400">Creates timestamped migration in <code>db/migrations/</code>.</td>
104
- </tr>
102
+ <tr>
103
+ <td class="px-6 py-4 font-mono text-primary">./ofa g migration NAME</td>
104
+ <td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs">Creates timestamped migration in <code>db/migrations/</code>.</td>
105
+ </tr>
106
+ <tr>
107
+ <td class="px-6 py-4 font-mono text-primary">./ofa g mailer NAME</td>
108
+ <td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs">Generates mailer class and ERB templates.</td>
109
+ </tr>
110
+ <tr>
111
+ <td class="px-6 py-4 font-mono text-primary">./ofa g task NAME</td>
112
+ <td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs">Creates a background task in <code>lib/tasks/</code>.</td>
113
+ </tr>
114
+ <tr>
115
+ <td class="px-6 py-4 font-mono text-primary">./ofa g test NAME</td>
116
+ <td class="px-6 py-4 text-slate-600 dark:text-slate-400 text-xs">Generates a Minitest unit test file.</td>
117
+ </tr>
105
118
 
106
119
  <tr class="bg-slate-500/5"><td colspan="2" class="px-6 py-2 text-[10px] font-black uppercase tracking-tighter text-slate-400">Customization</td></tr>
107
120
  <tr>
@@ -350,6 +363,48 @@ resources :posts
350
363
  </div>
351
364
  </section>
352
365
 
366
+ <!-- Advanced Features -->
367
+ <section id="advanced" class="glass-panel p-8 md:p-12 text-left bg-gradient-to-br from-primary/10 to-transparent">
368
+ <h2 class="text-3xl font-black tracking-tight mb-6 flex items-center gap-3">
369
+ <i class="fas fa-envelope text-primary"></i> Mailers & Tasks
370
+ </h2>
371
+ <div class="markdown-content space-y-8">
372
+ <div>
373
+ <h4 class="font-bold text-lg mb-2">📧 Email Mailers</h4>
374
+ <p class="text-sm text-slate-600 dark:text-slate-400">Send transactional emails with ease using ERB templates. Mailers are stored in <code>app/mailers</code> and templates in <code>app/views/mailers</code>.</p>
375
+ <div class="p-4 bg-white/5 border border-white/10 rounded-2xl mt-4 font-mono text-sm">
376
+ ./ofa g mailer welcome signup
377
+ </div>
378
+ </div>
379
+
380
+ <div>
381
+ <h4 class="font-bold text-lg mb-2">⚙️ Background Tasks</h4>
382
+ <p class="text-sm text-slate-600 dark:text-slate-400">Run long-running processes or scheduled jobs using the Task runner. Defined in <code>lib/tasks/</code>.</p>
383
+ <div class="p-4 bg-white/5 border border-white/10 rounded-2xl mt-4 font-mono text-sm">
384
+ ./ofa g task sync_data<br>
385
+ ./ofa task sync_data
386
+ </div>
387
+ </div>
388
+ </div>
389
+ </section>
390
+
391
+ <!-- Testing -->
392
+ <section id="testing" class="glass-panel p-8 md:p-12 text-left">
393
+ <h2 class="text-3xl font-black tracking-tight mb-6 flex items-center gap-3">
394
+ <i class="fas fa-vial text-primary"></i> Automated Testing
395
+ </h2>
396
+ <div class="markdown-content">
397
+ <p class="text-slate-600 dark:text-slate-400 mb-6">OFA integrates <strong>Minitest</strong> out-of-the-box for reliable application testing. Run all your tests with a single command.</p>
398
+ <div class="p-4 bg-slate-900 rounded-2xl font-mono text-sm text-green-400 mb-6">
399
+ ./ofa test
400
+ </div>
401
+ <p class="text-sm text-slate-500">To create a new test file:</p>
402
+ <div class="p-4 bg-white/5 border border-white/10 rounded-2xl mt-2 font-mono text-sm">
403
+ ./ofa g test user_profile
404
+ </div>
405
+ </div>
406
+ </section>
407
+
353
408
  <!-- Deployment -->
354
409
  <section id="deploy" class="glass-panel p-8 md:p-12 text-left border-primary/20">
355
410
  <h2 class="text-3xl font-black tracking-tight mb-6 flex items-center gap-3">
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 4.3.0
3
+ Framework Version 4.4.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
@@ -1,5 +1,16 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ # --- Bundler Setup ---
4
+ # This ensures that if we are in a project with a Gemfile, we use the correct
5
+ # gem versions specified in Gemfile.lock, preventing conflicts with system gems.
6
+ if File.exist?(File.join(Dir.pwd, 'Gemfile'))
7
+ begin
8
+ require 'bundler/setup'
9
+ rescue LoadError
10
+ # Bundler not installed, proceed without it
11
+ end
12
+ end
13
+
3
14
  require 'fileutils'
4
15
  require 'optparse'
5
16
  require 'json'
@@ -22,7 +33,7 @@ def help
22
33
  puts " / __ \\/ ____/ / | "
23
34
  puts " / / / / /_ / /| | Framework "
24
35
  puts "/ /_/ / __/ / ___ | Premium MVC "
25
- puts "\\____/_/ /_/ |_| v4.3.0 "
36
+ puts "\\____/_/ /_/ |_| v4.4.0 "
26
37
  puts " "
27
38
  puts "✨ One-For-All Framework CLI ✨"
28
39
  puts "-----------------------------"
@@ -33,6 +44,9 @@ def help
33
44
  puts " ofa g api NAME - Generate a new API controller (JSON)"
34
45
  puts " ofa g model NAME - Generate a new model"
35
46
  puts " ofa g migration NAME - Generate a new migration"
47
+ puts " ofa g mailer NAME ACTION - Generate a new mailer and template"
48
+ puts " ofa g task NAME - Generate a new background task"
49
+ puts " ofa g test NAME - Generate a new unit test"
36
50
  puts " ofa g post TITLE - Create a new post [args: --category, --author, --image]"
37
51
  puts " ofa feature ACTION F - Toggle features: action=enable/disable, F=cms/auth/rich_text"
38
52
  puts " ofa type NAME - Set application type: portfolio, blog, landing_page, e_commerce"
@@ -47,6 +61,8 @@ def help
47
61
  puts " ofa doctor - Check system health (DB, config, gems)"
48
62
  puts " ofa routes - List all registered routes"
49
63
  puts " ofa swagger - Auto-generate OpenAPI/Swagger documentation"
64
+ puts " ofa task NAME - Run a background task"
65
+ puts " ofa test - Run all application tests"
50
66
  puts " ofa run - Start the application server"
51
67
  puts " ofa deploy - Deploy project to production"
52
68
  puts "-----------------------------"
@@ -231,7 +247,7 @@ when 'init'
231
247
  puts " / __ \\/ ____/ / | "
232
248
  puts " / / / / /_ / /| | Framework "
233
249
  puts "/ /_/ / __/ / ___ | Premium MVC "
234
- puts "\\____/_/ /_/ |_| v4.3.0 "
250
+ puts "\\____/_/ /_/ |_| v4.4.0 "
235
251
  puts " "
236
252
  puts "Initializing One-For-All project as '#{app_type}' in #{PROJECT_ROOT}..."
237
253
 
@@ -262,7 +278,7 @@ when 'init'
262
278
  puts "-----------------------------\n"
263
279
 
264
280
  # Create directories
265
- dirs = ['app/controllers', 'app/models', 'app/views', 'config', 'db/migrations', 'public/css', 'public/js', 'public/images', 'public/images/uploads']
281
+ dirs = ['app/controllers', 'app/models', 'app/views', 'app/mailers', 'lib/tasks', 'test', 'config', 'db/migrations', 'public/css', 'public/js', 'public/images', 'public/images/uploads']
266
282
  dirs.each { |dir| FileUtils.mkdir_p(File.join(PROJECT_ROOT, dir)) }
267
283
 
268
284
  # Copy framework core migrations
@@ -274,6 +290,21 @@ when 'init'
274
290
  end
275
291
  end
276
292
 
293
+ # Copy framework core helpers and test assets
294
+ ['lib/task_helper.rb', 'app/mailers/application_mailer.rb', 'test/test_helper.rb'].each do |file|
295
+ src = File.join(FRAMEWORK_ROOT, file)
296
+ dest = File.join(PROJECT_ROOT, file)
297
+ FileUtils.mkdir_p(File.dirname(dest))
298
+ if File.exist?(src)
299
+ content = File.read(src)
300
+ # Inject FRAMEWORK_ROOT into test_helper.rb
301
+ if file == 'test/test_helper.rb'
302
+ content.gsub!("require_relative '../config/boot'", "$LOAD_PATH.unshift '#{FRAMEWORK_ROOT}'\nrequire 'config/boot'")
303
+ end
304
+ File.write(dest, content)
305
+ end
306
+ end
307
+
277
308
  # Create .env
278
309
  env_content = []
279
310
  env_content << "EKS_ENV=development"
@@ -321,6 +352,8 @@ when 'init'
321
352
  gem 'mysql2'
322
353
  gem 'kramdown'
323
354
  gem 'kramdown-parser-gfm'
355
+ gem 'mail'
356
+ gem 'minitest'
324
357
  RUBY
325
358
  File.write(File.join(PROJECT_ROOT, 'Gemfile'), gemfile_content)
326
359
 
@@ -499,6 +532,58 @@ when 'g'
499
532
  File.write(file_name, content)
500
533
  puts "Created API Controller: #{file_name}"
501
534
  puts "💡 Don't forget to register it in config/routes.rb"
535
+ elsif sub == 'mailer' && name
536
+ action = ARGV.shift || 'welcome'
537
+ class_name = name.capitalize + "Mailer"
538
+ file_name = File.join(PROJECT_ROOT, "app/mailers/#{name.downcase}_mailer.rb")
539
+ content = <<~RUBY
540
+ class #{class_name} < ApplicationMailer
541
+ def #{action}(user)
542
+ mail(
543
+ to: user.email,
544
+ subject: "Welcome to our platform!",
545
+ template: "#{name.downcase}_mailer/#{action}",
546
+ locals: { user: user }
547
+ )
548
+ end
549
+ end
550
+ RUBY
551
+ File.write(file_name, content)
552
+
553
+ # Create template
554
+ template_dir = File.join(PROJECT_ROOT, "app/views/mailers/#{name.downcase}_mailer")
555
+ FileUtils.mkdir_p(template_dir)
556
+ template_file = File.join(template_dir, "#{action}.erb")
557
+ File.write(template_file, "<h1>Hello <%= @user.username %></h1>\n<p>Welcome to One-For-All Framework!</p>")
558
+
559
+ puts "Created Mailer: #{file_name}"
560
+ puts "Created Template: #{template_file}"
561
+ elsif sub == 'task' && name
562
+ dir_path = File.join(PROJECT_ROOT, "lib/tasks")
563
+ FileUtils.mkdir_p(dir_path)
564
+ file_name = File.join(dir_path, "#{name.downcase}.rb")
565
+ content = <<~RUBY
566
+ desc "Description for #{name} task"
567
+ task :#{name.downcase} do
568
+ puts "Running task: #{name}"
569
+ # Your background job logic here
570
+ end
571
+ RUBY
572
+ File.write(file_name, content)
573
+ puts "Created Task: #{file_name}"
574
+ elsif sub == 'test' && name
575
+ file_name = File.join(PROJECT_ROOT, "test/#{name.downcase}_test.rb")
576
+ content = <<~RUBY
577
+ require_relative 'test_helper'
578
+
579
+ class #{name.capitalize}Test < Minitest::Test
580
+ def test_example
581
+ assert true
582
+ end
583
+ end
584
+ RUBY
585
+ File.write(file_name, content)
586
+ puts "Created Test: #{file_name}"
502
587
  elsif sub == 'model' && name
503
588
  class_name = name.capitalize
504
589
  file_name = File.join(PROJECT_ROOT, "app/models/#{name.downcase}.rb")
@@ -535,7 +620,7 @@ when 'g'
535
620
  File.write(file_name, content)
536
621
  puts "Created post: #{file_name}"
537
622
  else
538
- puts "Usage: ofa g [controller|model|migration|post] NAME"
623
+ puts "Usage: ofa g [controller|api|model|migration|mailer|task|test|post] NAME"
539
624
  end
540
625
 
541
626
  when 'feature'
@@ -545,7 +630,8 @@ when 'feature'
545
630
  config_path = File.join(PROJECT_ROOT, 'config', 'features.json')
546
631
  config = JSON.parse(File.read(config_path))
547
632
  config[feature] = (action == 'enable')
548
- File.write(config_path, JSON.pretty_generate(config))
633
+ File.open("#{config_path}.tmp", 'w') { |f| f.write(JSON.pretty_generate(config)) }
634
+ File.rename("#{config_path}.tmp", config_path)
549
635
  puts "Feature '#{feature}' has been #{action}d."
550
636
 
551
637
  if action == 'enable' && feature == 'cms' && !config['auth']
@@ -564,7 +650,8 @@ when 'type'
564
650
  config_path = File.join(PROJECT_ROOT, 'config', 'features.json')
565
651
  config = JSON.parse(File.read(config_path))
566
652
  config['type'] = name
567
- File.write(config_path, JSON.pretty_generate(config))
653
+ File.open("#{config_path}.tmp", 'w') { |f| f.write(JSON.pretty_generate(config)) }
654
+ File.rename("#{config_path}.tmp", config_path)
568
655
  puts "Application type set to '#{name}'."
569
656
 
570
657
  when 'theme'
@@ -577,7 +664,8 @@ when 'theme'
577
664
  config_path = File.join(PROJECT_ROOT, 'config', 'features.json')
578
665
  config = JSON.parse(File.read(config_path))
579
666
  config['theme'] = name
580
- File.write(config_path, JSON.pretty_generate(config))
667
+ File.open("#{config_path}.tmp", 'w') { |f| f.write(JSON.pretty_generate(config)) }
668
+ File.rename("#{config_path}.tmp", config_path)
581
669
  puts "Application theme set to '#{name}'."
582
670
 
583
671
  when 'storage'
@@ -640,7 +728,8 @@ when 'db'
640
728
  env_lines = File.exist?(env_path) ? File.readlines(env_path) : []
641
729
  env_lines.reject! { |l| l.start_with?('DATABASE_URL=') }
642
730
  env_lines << "DATABASE_URL=#{db_name}\n"
643
- File.write(env_path, env_lines.join)
731
+ File.open("#{env_path}.tmp", 'w') { |f| f.write(env_lines.join) }
732
+ File.rename("#{env_path}.tmp", env_path)
644
733
  puts "✅ Connection string saved to .env"
645
734
  end
646
735
  when 'sqlite'
@@ -648,7 +737,8 @@ when 'db'
648
737
  else
649
738
  config = { "adapter" => type, "host" => "localhost", "user" => "root", "password" => "", "database" => db_name || "one_for_all" }
650
739
  end
651
- File.write(config_path, JSON.pretty_generate(config))
740
+ File.open("#{config_path}.tmp", 'w') { |f| f.write(JSON.pretty_generate(config)) }
741
+ File.rename("#{config_path}.tmp", config_path)
652
742
  puts "Switched to #{type} mode."
653
743
  when 'migrate'
654
744
  run_migrations
@@ -733,7 +823,7 @@ when 'console'
733
823
  puts " / __ \\/ ____/ / | "
734
824
  puts " / / / / /_ / /| | Framework "
735
825
  puts "/ /_/ / __/ / ___ | Console (REPL) "
736
- puts "\\____/_/ /_/ |_| v4.3.0 "
826
+ puts "\\____/_/ /_/ |_| v4.4.0 "
737
827
  puts " "
738
828
  puts "✨ Loading environment... (Type 'exit' to quit)"
739
829
 
@@ -845,6 +935,63 @@ when 'deploy'
845
935
  puts " This framework supports deployment via Docker or Git Push."
846
936
  end
847
937
 
938
+ when 'task'
939
+ ensure_initialized!
940
+ name = ARGV.shift
941
+ if name.nil?
942
+ puts "Usage: ofa task NAME"
943
+ # List available tasks
944
+ tasks_dir = File.join(PROJECT_ROOT, 'lib/tasks')
945
+ if Dir.exist?(tasks_dir)
946
+ puts "\nAvailable tasks:"
947
+ Dir.glob(File.join(tasks_dir, '*.rb')).each do |f|
948
+ puts " - #{File.basename(f, '.rb')}"
949
+ end
950
+ end
951
+ exit 1
952
+ end
953
+
954
+ # Load framework & app
955
+ Object.const_set(:APP_ROOT, PROJECT_ROOT) unless defined?(APP_ROOT)
956
+ require File.join(FRAMEWORK_ROOT, 'config', 'boot')
957
+ require File.join(FRAMEWORK_ROOT, 'lib', 'task_helper')
958
+
959
+ # Load all tasks in lib/tasks
960
+ Dir.glob(File.join(PROJECT_ROOT, 'lib/tasks/*.rb')).each { |f| require f }
961
+
962
+ run_task(name)
963
+
964
+ when 'test'
965
+ ensure_initialized!
966
+ puts "🧪 Running tests..."
967
+ puts "------------------------------------------"
968
+
969
+ # Set environment to test
970
+ ENV['EKS_ENV'] = 'test'
971
+ Object.const_set(:APP_ROOT, PROJECT_ROOT) unless defined?(APP_ROOT)
972
+
973
+ # Add project root to load path
974
+ $LOAD_PATH.unshift PROJECT_ROOT
975
+ $LOAD_PATH.unshift File.join(PROJECT_ROOT, 'test')
976
+
977
+ # Find all tests (support both _test.rb and _spec.rb)
978
+ if ARGV.empty?
979
+ test_files = Dir.glob(File.join(PROJECT_ROOT, 'test/**/{*_test.rb,*_spec.rb}'))
980
+ else
981
+ test_files = ARGV.map { |arg| File.expand_path(arg, PROJECT_ROOT) }
982
+ end
983
+
984
+ if test_files.empty?
985
+ puts "❌ No tests found in test/ directory."
986
+ exit 1
987
+ end
988
+
989
+ # Run them by requiring
990
+ test_files.each { |f| require f }
991
+
992
+ # Trigger EksaMination execution if available
993
+ EksaMination.run if defined?(EksaMination)
994
+
848
995
  else
849
996
  help
850
997
  end
data/config/boot.rb CHANGED
@@ -93,7 +93,7 @@ require_relative 'database'
93
93
 
94
94
  # Autoloading (Framework Core first, then APP_ROOT)
95
95
  framework_app = File.expand_path('../app', __dir__)
96
- ['controllers', 'models', 'middleware', 'helpers'].each do |folder|
96
+ ['controllers', 'models', 'middleware', 'helpers', 'mailers'].each do |folder|
97
97
  next if folder == 'models' && ENV['SKIP_MODELS']
98
98
  loaded = []
99
99
  # Load framework core
data/db/data.sqlite3 CHANGED
Binary file
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: 4.3.0
4
+ version: 4.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ishikawa Uta
@@ -210,6 +210,7 @@ files:
210
210
  - app/controllers/products_controller.rb
211
211
  - app/controllers/projects_controller.rb
212
212
  - app/helpers/cloudinary_helper.rb
213
+ - app/mailers/application_mailer.rb
213
214
  - app/middleware/auth_middleware.rb
214
215
  - app/middleware/csrf_middleware.rb
215
216
  - app/middleware/jwt_middleware.rb
@@ -254,7 +255,6 @@ files:
254
255
  - db/data.sqlite3
255
256
  - db/development.sqlite3
256
257
  - db/migrations/20260502000000_create_products.rb
257
- - db/target.sqlite3
258
258
  - ofa
259
259
  - public/css/cms.css
260
260
  - public/images/logo.jpg
data/db/target.sqlite3 DELETED
Binary file