eksa-framework 1.1.0 โ 1.2.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.
- checksums.yaml +4 -4
- data/Gemfile +5 -0
- data/Gemfile.lock +54 -0
- data/README.md +49 -49
- data/app/controllers/seo_controller.rb +31 -0
- data/app/models/pesan.rb +10 -0
- data/app/views/docs.html.erb +41 -7
- data/app/views/index.html.erb +2 -2
- data/app/views/layout.html.erb +23 -6
- data/config.ru +9 -4
- data/exe/eksa +129 -0
- data/lib/eksa/controller.rb +17 -1
- data/lib/eksa/model.rb +17 -12
- data/lib/eksa/version.rb +1 -1
- data/lib/eksa.rb +42 -10
- data/spec/application_spec.rb +29 -0
- data/spec/model_spec.rb +13 -0
- data/spec/spec_helper.rb +22 -0
- metadata +12 -5
- data/db/eksa_app.db +0 -0
- data/exe/eksa-init +0 -43
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8caab3a457d5024168c3ff86697057d413bb0bdef32351c4cee69cee1da5b2ba
|
|
4
|
+
data.tar.gz: 72efe953699777d97650f2f51a1753cc28fd8c1eed5efe7a990ac2775c90c0ee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 04aaab52ac31d9a29d7ee02010c11719be53fa6b0fc4d0e99ac464efccb5dacc3eb8ea122b0cf24c43d3e94e2945cddf75a8f1b2205d5be6b1e8ed7e9546bdf7
|
|
7
|
+
data.tar.gz: '08fdf429f48a8965f70f5e0adc3406224a386b17741a39fa29492eef65b0153501e9f450434231b20a25f98ea4918af524fe525c6ea654afaa0247b2c8dfe2e0'
|
data/Gemfile
CHANGED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
GEM
|
|
2
|
+
remote: https://rubygems.org/
|
|
3
|
+
specs:
|
|
4
|
+
diff-lcs (1.6.2)
|
|
5
|
+
nio4r (2.7.5)
|
|
6
|
+
puma (7.2.0)
|
|
7
|
+
nio4r (~> 2.0)
|
|
8
|
+
rack (3.2.5)
|
|
9
|
+
rack-test (2.2.0)
|
|
10
|
+
rack (>= 1.3)
|
|
11
|
+
rackup (2.3.1)
|
|
12
|
+
rack (>= 3)
|
|
13
|
+
rspec (3.13.2)
|
|
14
|
+
rspec-core (~> 3.13.0)
|
|
15
|
+
rspec-expectations (~> 3.13.0)
|
|
16
|
+
rspec-mocks (~> 3.13.0)
|
|
17
|
+
rspec-core (3.13.6)
|
|
18
|
+
rspec-support (~> 3.13.0)
|
|
19
|
+
rspec-expectations (3.13.5)
|
|
20
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
21
|
+
rspec-support (~> 3.13.0)
|
|
22
|
+
rspec-mocks (3.13.8)
|
|
23
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
24
|
+
rspec-support (~> 3.13.0)
|
|
25
|
+
rspec-support (3.13.7)
|
|
26
|
+
sqlite3 (2.9.1-x86_64-linux-gnu)
|
|
27
|
+
|
|
28
|
+
PLATFORMS
|
|
29
|
+
x86_64-linux-gnu
|
|
30
|
+
|
|
31
|
+
DEPENDENCIES
|
|
32
|
+
puma
|
|
33
|
+
rack
|
|
34
|
+
rack-test (~> 2.1)
|
|
35
|
+
rackup
|
|
36
|
+
rspec (~> 3.12)
|
|
37
|
+
sqlite3
|
|
38
|
+
|
|
39
|
+
CHECKSUMS
|
|
40
|
+
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
|
|
41
|
+
nio4r (2.7.5) sha256=6c90168e48fb5f8e768419c93abb94ba2b892a1d0602cb06eef16d8b7df1dca1
|
|
42
|
+
puma (7.2.0) sha256=bf8ef4ab514a4e6d4554cb4326b2004eba5036ae05cf765cfe51aba9706a72a8
|
|
43
|
+
rack (3.2.5) sha256=4cbd0974c0b79f7a139b4812004a62e4c60b145cba76422e288ee670601ed6d3
|
|
44
|
+
rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463
|
|
45
|
+
rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868
|
|
46
|
+
rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
|
|
47
|
+
rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
|
|
48
|
+
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
|
|
49
|
+
rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
|
|
50
|
+
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
|
|
51
|
+
sqlite3 (2.9.1-x86_64-linux-gnu) sha256=1cbb644204ed143e5c96f6d59b5c571ba6f18b18a9dc5aa11c101187ff227afd
|
|
52
|
+
|
|
53
|
+
BUNDLED WITH
|
|
54
|
+
4.0.4
|
data/README.md
CHANGED
|
@@ -10,96 +10,96 @@
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
## ๐ Fitur Unggulan
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
13
|
+
|
|
14
|
+
* ๐ **Modern Glassmorphism UI**: Tampilan transparan yang indah dengan Tailwind CSS & Lucide Icons.
|
|
15
|
+
* โก **Rack 3 & Middleware Support**: Mendukung standar terbaru dan pembuatan pipeline middleware kustom.
|
|
16
|
+
* ๐ ๏ธ **Powerful CLI**: Inisialisasi project (`eksa init`), jalankan server (`eksa run`), dan generate komponen secara instan.
|
|
17
|
+
* ๐พ **Dynamic Database Engine**: Database SQLite otomatis dengan schema yang ditentukan oleh model Anda sendiri.
|
|
18
|
+
* ๐งช **Built-in Testing**: Lingkungan pengujian otomatis siap pakai menggunakan RSpec dan `rack-test`.
|
|
19
|
+
* ๐จ **Asset Helpers**: Library bawaan untuk pengelolaan CSS dan JS yang lebih rapi.
|
|
20
|
+
* ๐ **Dynamic SEO Engine**: Penanganan otomatis file `robots.txt` dan `sitemap.xml`.
|
|
19
21
|
|
|
20
22
|
---
|
|
21
23
|
|
|
22
24
|
## ๐ ๏ธ Instalasi Cepat
|
|
23
25
|
|
|
24
26
|
### 1. Install via Gem
|
|
25
|
-
Anda bisa menginstal framework ini langsung dengan `gem`:
|
|
26
|
-
|
|
27
27
|
```bash
|
|
28
28
|
gem install eksa-framework
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
### 2. Inisialisasi Project Baru
|
|
32
|
-
|
|
33
|
-
Masuk ke folder baru, lalu jalankan generator Eksa:
|
|
34
|
-
|
|
35
32
|
```bash
|
|
36
|
-
mkdir my-
|
|
37
|
-
|
|
38
|
-
eksa-init
|
|
33
|
+
mkdir my-app && cd my-app
|
|
34
|
+
eksa init
|
|
39
35
|
```
|
|
40
36
|
|
|
41
37
|
### 3. Jalankan Server
|
|
42
|
-
|
|
43
38
|
```bash
|
|
44
39
|
bundle install
|
|
45
|
-
|
|
40
|
+
eksa run
|
|
46
41
|
```
|
|
47
42
|
|
|
48
|
-
Buka browser dan akses `http://localhost:9292`.
|
|
49
|
-
|
|
50
43
|
---
|
|
51
44
|
|
|
52
45
|
## ๐ป Panduan Pengembangan
|
|
53
46
|
|
|
54
|
-
### 1.
|
|
55
|
-
|
|
56
|
-
Eksa memisahkan data dari logika aplikasi dengan folder `db` di level root:
|
|
57
|
-
|
|
58
|
-
* `app/`: Logic & Views (Controllers, Models, Views).
|
|
59
|
-
* `db/`: File database SQLite `eksa_app.db` (Terpisah dari app).
|
|
60
|
-
* `lib/eksa/`: Core Engine (The magic happens here).
|
|
61
|
-
* `public/`: File statis (CSS, Images, JS).
|
|
62
|
-
|
|
63
|
-
### 2. Menambah Route & Controller
|
|
64
|
-
|
|
65
|
-
Daftarkan rute di `config.ru`:
|
|
47
|
+
### 1. Konfigurasi Aplikasi (`config.ru`)
|
|
48
|
+
Eksa kini menggunakan blok inisialisasi untuk konfigurasi yang lebih fleksibel:
|
|
66
49
|
|
|
67
50
|
```ruby
|
|
68
|
-
app.
|
|
51
|
+
app = Eksa::Application.new do |config|
|
|
52
|
+
config.config[:db_path] = "./db/production.db"
|
|
53
|
+
|
|
54
|
+
config.use Rack::Static, urls: ["/css", "/img"], root: "public"
|
|
55
|
+
config.use Rack::ShowExceptions
|
|
56
|
+
end
|
|
69
57
|
```
|
|
70
58
|
|
|
71
|
-
|
|
59
|
+
### 2. CLI Generator
|
|
60
|
+
Hemat waktu dengan menggunakan generator bawaan:
|
|
72
61
|
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
62
|
+
```bash
|
|
63
|
+
# Membuat controller dan view template
|
|
64
|
+
eksa g controller Blog
|
|
65
|
+
|
|
66
|
+
# Membuat model dan schema database
|
|
67
|
+
eksa g model Post
|
|
77
68
|
```
|
|
78
69
|
|
|
79
70
|
### 3. Database & Model
|
|
80
|
-
|
|
81
|
-
Eksa akan otomatis membuat tabel saat Anda mendefinisikannya di `lib/eksa/model.rb`. Untuk menggunakan data di controller:
|
|
71
|
+
Definisikan schema tabel Anda langsung di dalam model:
|
|
82
72
|
|
|
83
73
|
```ruby
|
|
84
|
-
|
|
74
|
+
class Post < Eksa::Model
|
|
75
|
+
def self.setup_schema
|
|
76
|
+
db.execute <<~SQL
|
|
77
|
+
CREATE TABLE IF NOT EXISTS posts (
|
|
78
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
79
|
+
title TEXT,
|
|
80
|
+
content TEXT
|
|
81
|
+
)
|
|
82
|
+
SQL
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
### 4. Asset Helpers
|
|
88
|
+
Gunakan helper di dalam view untuk menyisipkan asset:
|
|
88
89
|
|
|
89
|
-
|
|
90
|
+
```erb
|
|
91
|
+
<%= stylesheet_tag "style" %>
|
|
92
|
+
<%= javascript_tag "app" %>
|
|
93
|
+
```
|
|
90
94
|
|
|
91
|
-
|
|
95
|
+
### 5. Menjalankan Test
|
|
96
|
+
Pastikan aplikasi Anda berjalan dengan benar menggunakan RSpec:
|
|
92
97
|
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
background: rgba(255, 255, 255, 0.05);
|
|
96
|
-
backdrop-filter: blur(16px);
|
|
97
|
-
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
98
|
-
}
|
|
98
|
+
```bash
|
|
99
|
+
bundle exec rspec
|
|
99
100
|
```
|
|
100
101
|
|
|
101
102
|
---
|
|
102
103
|
|
|
103
104
|
## ๐ Lisensi
|
|
104
|
-
|
|
105
105
|
Proyek ini dilisensikan di bawah **MIT License**. Lihat file [LICENSE](LICENSE) untuk detail lebih lanjut.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
class SeoController < Eksa::Controller
|
|
2
|
+
def robots
|
|
3
|
+
content = <<~TEXT
|
|
4
|
+
User-agent: *
|
|
5
|
+
Allow: /
|
|
6
|
+
Disallow: /hapus
|
|
7
|
+
Disallow: /edit
|
|
8
|
+
|
|
9
|
+
Sitemap: https://#{request.host}/sitemap.xml
|
|
10
|
+
TEXT
|
|
11
|
+
[200, { "Content-Type" => "text/plain" }, [content]]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def sitemap
|
|
15
|
+
lastmod = Time.now.strftime("%Y-%m-%d")
|
|
16
|
+
|
|
17
|
+
xml = '<?xml version="1.0" encoding="UTF-8"?>'
|
|
18
|
+
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'
|
|
19
|
+
|
|
20
|
+
["/", "/about", "/docs", "/kontak"].each do |path|
|
|
21
|
+
xml += "<url>"
|
|
22
|
+
xml += "<loc>https://#{request.host}#{path}</loc>"
|
|
23
|
+
xml += "<lastmod>#{lastmod}</lastmod>"
|
|
24
|
+
xml += "<priority>0.8</priority>"
|
|
25
|
+
xml += "</url>"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
xml += '</urlset>'
|
|
29
|
+
[200, { "Content-Type" => "application/xml" }, [xml]]
|
|
30
|
+
end
|
|
31
|
+
end
|
data/app/models/pesan.rb
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
class Pesan < Eksa::Model
|
|
2
|
+
def self.setup_schema
|
|
3
|
+
db.execute <<-SQL
|
|
4
|
+
CREATE TABLE IF NOT EXISTS pesan (
|
|
5
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
6
|
+
konten TEXT,
|
|
7
|
+
pengirim TEXT
|
|
8
|
+
);
|
|
9
|
+
SQL
|
|
10
|
+
end
|
|
11
|
+
|
|
2
12
|
def self.semua
|
|
3
13
|
db.execute("SELECT * FROM pesan ORDER BY id DESC")
|
|
4
14
|
end
|
data/app/views/docs.html.erb
CHANGED
|
@@ -39,16 +39,48 @@
|
|
|
39
39
|
</h2>
|
|
40
40
|
<div class="bg-black/40 rounded-2xl p-6 font-mono text-sm border border-white/10 space-y-3">
|
|
41
41
|
<div>
|
|
42
|
-
<p class="text-white/30"># 1. Install gem
|
|
43
|
-
<p class="text-indigo-300">gem install
|
|
42
|
+
<p class="text-white/30"># 1. Install gem</p>
|
|
43
|
+
<p class="text-indigo-300">gem install eksa-framework</p>
|
|
44
44
|
</div>
|
|
45
45
|
<div>
|
|
46
46
|
<p class="text-white/30"># 2. Inisialisasi project baru</p>
|
|
47
|
-
<p class="text-indigo-300">eksa
|
|
47
|
+
<p class="text-indigo-300">eksa init</p>
|
|
48
48
|
</div>
|
|
49
49
|
<div>
|
|
50
|
-
<p class="text-white/30"># 3.
|
|
51
|
-
<p class="text-indigo-300">
|
|
50
|
+
<p class="text-white/30"># 3. Install dependensi</p>
|
|
51
|
+
<p class="text-indigo-300">bundle install</p>
|
|
52
|
+
</div>
|
|
53
|
+
<div>
|
|
54
|
+
<p class="text-white/30"># 4. Jalankan server</p>
|
|
55
|
+
<p class="text-indigo-300">eksa run</p>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</section>
|
|
59
|
+
|
|
60
|
+
<section id="struktur" class="mb-12 scroll-mt-24">
|
|
61
|
+
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
|
62
|
+
<i data-lucide="folder-tree" class="w-6 h-6 text-indigo-400"></i> Struktur Project
|
|
63
|
+
</h2>
|
|
64
|
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
65
|
+
<div class="bg-white/5 p-4 rounded-xl border border-white/10">
|
|
66
|
+
<code class="text-indigo-300 font-bold">app/</code>
|
|
67
|
+
<p class="text-xs text-white/50 mt-1">Berisi Controllers, Models, dan Views aplikasi Anda.</p>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="bg-white/5 p-4 rounded-xl border border-white/10">
|
|
70
|
+
<code class="text-indigo-300 font-bold">db/</code>
|
|
71
|
+
<p class="text-xs text-white/50 mt-1">Lokasi penyimpanan database SQLite (Terpisah dari app).</p>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="bg-white/5 p-4 rounded-xl border border-white/10">
|
|
74
|
+
<code class="text-indigo-300 font-bold">lib/eksa/</code>
|
|
75
|
+
<p class="text-xs text-white/50 mt-1">Mesin inti (Core Engine) dari Eksa Framework.</p>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="bg-white/5 p-4 rounded-xl border border-white/10">
|
|
78
|
+
<code class="text-indigo-300 font-bold">spec/</code>
|
|
79
|
+
<p class="text-xs text-white/50 mt-1">Folder testing otomatis menggunakan RSpec (disertakan saat <code class="text-indigo-200">init</code>).</p>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="bg-white/5 p-4 rounded-xl border border-white/10">
|
|
82
|
+
<code class="text-indigo-300 font-bold">exe/</code>
|
|
83
|
+
<p class="text-xs text-white/50 mt-1">Executable files seperti CLI <code class="text-indigo-200">eksa</code>.</p>
|
|
52
84
|
</div>
|
|
53
85
|
</div>
|
|
54
86
|
</section>
|
|
@@ -95,11 +127,13 @@
|
|
|
95
127
|
|
|
96
128
|
<section id="build" class="mb-12 scroll-mt-24">
|
|
97
129
|
<h2 class="text-2xl font-bold mb-4 flex items-center gap-2">
|
|
98
|
-
<i data-lucide="package-check" class="w-6 h-6 text-indigo-400"></i> Build
|
|
130
|
+
<i data-lucide="package-check" class="w-6 h-6 text-indigo-400"></i> Build & Publish
|
|
99
131
|
</h2>
|
|
100
132
|
<div class="bg-black/40 rounded-2xl p-6 font-mono text-sm border border-white/10">
|
|
133
|
+
<p class="text-white/30"># Build gem lokal</p>
|
|
101
134
|
<p class="text-indigo-300">gem build eksa-framework.gemspec</p>
|
|
102
|
-
<p class="text-
|
|
135
|
+
<p class="text-white/30 mt-4"># Publish ke RubyGems / GitHub Packages</p>
|
|
136
|
+
<p class="text-indigo-300">gem push eksa-framework-1.2.1.gem</p>
|
|
103
137
|
</div>
|
|
104
138
|
</section>
|
|
105
139
|
|
data/app/views/index.html.erb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<div class="flex flex-col md:flex-row justify-between items-start md:items-center mb-8 gap-4">
|
|
3
3
|
<div>
|
|
4
4
|
<span class="inline-block bg-indigo-500/20 text-indigo-200 text-xs font-bold px-3 py-1 rounded-full mb-3 border border-indigo-500/30">
|
|
5
|
-
|
|
5
|
+
v1.2.1 Alpha
|
|
6
6
|
</span>
|
|
7
7
|
<h1 class="text-4xl font-extrabold tracking-tight">
|
|
8
8
|
Halo, <span class="text-indigo-300"><%= @nama %></span>!
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
<p class="text-sm text-white/40 mb-4 italic">Menampilkan hasil pencarian untuk "<%= @keyword %>"</p>
|
|
63
63
|
<% end %>
|
|
64
64
|
|
|
65
|
-
<div class="space-y-4 overflow-y-auto max-h-[
|
|
65
|
+
<div class="space-y-4 overflow-y-auto max-h-[320px] pr-2 custom-scrollbar">
|
|
66
66
|
<% if @semua_pesan.any? %>
|
|
67
67
|
<% @semua_pesan.each do |p| %>
|
|
68
68
|
<div class="bg-white/5 border border-white/10 p-5 rounded-2xl flex justify-between items-start group hover:bg-white/10 transition duration-300 animate__animated animate__fadeInUp">
|
data/app/views/layout.html.erb
CHANGED
|
@@ -3,8 +3,13 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title
|
|
7
|
-
|
|
6
|
+
<title><%= @title || "Eksa Framework" %></title>
|
|
7
|
+
<meta name="description" content="Framework Ruby Modern dengan sentuhan Glassmorphism">
|
|
8
|
+
<meta name="robots" content="index, follow">
|
|
9
|
+
<link rel="canonical" href="https://<%= request.host %><%= request.path %>">
|
|
10
|
+
<meta property="og:title" content="Eksa Framework">
|
|
11
|
+
<meta property="og:description" content="Membangun web cepat dengan estetika transparan.">
|
|
12
|
+
<meta property="og:type" content="website">
|
|
8
13
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
14
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
15
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
|
@@ -54,16 +59,28 @@
|
|
|
54
59
|
width: 100%;
|
|
55
60
|
}
|
|
56
61
|
|
|
57
|
-
/* Custom Scrollbar untuk list pesan */
|
|
58
62
|
.custom-scrollbar::-webkit-scrollbar {
|
|
59
63
|
width: 6px;
|
|
60
64
|
}
|
|
65
|
+
|
|
61
66
|
.custom-scrollbar::-webkit-scrollbar-track {
|
|
62
|
-
background: rgba(255, 255, 255, 0.
|
|
67
|
+
background: rgba(255, 255, 255, 0.02);
|
|
68
|
+
border-radius: 10px;
|
|
63
69
|
}
|
|
70
|
+
|
|
64
71
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
|
65
|
-
background: rgba(
|
|
72
|
+
background: rgba(99, 102, 241, 0.2);
|
|
66
73
|
border-radius: 10px;
|
|
74
|
+
border: 1px solid rgba(255, 255, 255, 0.05);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
78
|
+
background: rgba(99, 102, 241, 0.5);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.custom-scrollbar {
|
|
82
|
+
scrollbar-width: thin;
|
|
83
|
+
scrollbar-color: rgba(99, 102, 241, 0.2) rgba(255, 255, 255, 0.02);
|
|
67
84
|
}
|
|
68
85
|
</style>
|
|
69
86
|
</head>
|
|
@@ -104,7 +121,7 @@
|
|
|
104
121
|
<li><a href="/" class="nav-link flex items-center gap-2"><i data-lucide="home" class="w-4 h-4"></i> Home</a></li>
|
|
105
122
|
<li><a href="/about" class="nav-link flex items-center gap-2"><i data-lucide="info" class="w-4 h-4"></i> About</a></li>
|
|
106
123
|
<li><a href="/docs" class="nav-link flex items-center gap-2"><i data-lucide="book-open" class="w-4 h-4"></i> Docs</a></li>
|
|
107
|
-
<li><a href="/kontak" class="nav-link flex items-center gap-2"><i data-lucide="message-circle" class="w-4 h-4"></i>
|
|
124
|
+
<li><a href="/kontak" class="nav-link flex items-center gap-2"><i data-lucide="message-circle" class="w-4 h-4"></i> Contact</a></li>
|
|
108
125
|
</ul>
|
|
109
126
|
|
|
110
127
|
<div class="flex items-center gap-4">
|
data/config.ru
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
require './lib/eksa'
|
|
2
|
-
require './app/controllers/pages_controller'
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
use Rack::ShowExceptions
|
|
3
|
+
Dir[File.join(__dir__, 'app/controllers/*.rb')].each { |file| require_relative file }
|
|
6
4
|
|
|
7
|
-
app = Eksa::Application.new
|
|
5
|
+
app = Eksa::Application.new do |config|
|
|
6
|
+
config.config[:db_path] = File.expand_path("./db/eksa_app.db")
|
|
7
|
+
|
|
8
|
+
config.use Rack::Static, urls: ["/css", "/img"], root: "public"
|
|
9
|
+
config.use Rack::ShowExceptions
|
|
10
|
+
end
|
|
8
11
|
|
|
9
12
|
app.add_route "/", PagesController, :index
|
|
10
13
|
app.add_route "/hapus", PagesController, :hapus_pesan
|
|
@@ -12,5 +15,7 @@ app.add_route "/edit", PagesController, :edit
|
|
|
12
15
|
app.add_route "/about", PagesController, :about
|
|
13
16
|
app.add_route "/docs", PagesController, :docs
|
|
14
17
|
app.add_route "/kontak", PagesController, :kontak
|
|
18
|
+
app.add_route "/robots.txt", SeoController, :robots
|
|
19
|
+
app.add_route "/sitemap.xml", SeoController, :sitemap
|
|
15
20
|
|
|
16
21
|
run app
|
data/exe/eksa
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'fileutils'
|
|
5
|
+
|
|
6
|
+
def usage
|
|
7
|
+
puts "\nโจ Eksa Framework CLI โจ"
|
|
8
|
+
puts "-----------------------------"
|
|
9
|
+
puts "Usage:"
|
|
10
|
+
puts " eksa init - Inisialisasi project baru"
|
|
11
|
+
puts " eksa g controller NAME - Generate controller baru"
|
|
12
|
+
puts " eksa g model NAME - Generate model baru"
|
|
13
|
+
puts " eksa run - Jalankan server aplikasi"
|
|
14
|
+
puts "-----------------------------\n"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def init_project
|
|
18
|
+
gem_root = File.expand_path("../..", __FILE__)
|
|
19
|
+
target_dir = Dir.pwd
|
|
20
|
+
|
|
21
|
+
puts "๐ Menginisialisasi project di: #{target_dir}"
|
|
22
|
+
|
|
23
|
+
items_to_init = ['app', 'lib', 'public', 'db', 'spec', 'config.ru', 'Gemfile', 'README.md']
|
|
24
|
+
|
|
25
|
+
items_to_init.each do |item|
|
|
26
|
+
source = File.join(gem_root, item)
|
|
27
|
+
destination = File.join(target_dir, item)
|
|
28
|
+
|
|
29
|
+
if File.exist?(source)
|
|
30
|
+
if File.directory?(source)
|
|
31
|
+
FileUtils.cp_r(source, target_dir)
|
|
32
|
+
puts " [OK] Folder '#{item}' berhasil disalin."
|
|
33
|
+
else
|
|
34
|
+
FileUtils.cp(source, destination)
|
|
35
|
+
puts " [OK] File '#{item}' berhasil disalin."
|
|
36
|
+
end
|
|
37
|
+
elsif item == 'db'
|
|
38
|
+
FileUtils.mkdir_p(destination)
|
|
39
|
+
puts " [OK] Folder 'db' baru berhasil dibuat."
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
puts "\nโ
Inisialisasi Selesai!"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def generate_controller(name)
|
|
46
|
+
name = name.downcase
|
|
47
|
+
class_name = name.split('_').map(&:capitalize).join
|
|
48
|
+
file_path = "app/controllers/#{name}_controller.rb"
|
|
49
|
+
view_dir = "app/views/#{name}"
|
|
50
|
+
|
|
51
|
+
puts "๐ ๏ธ Generating controller: #{class_name}Controller"
|
|
52
|
+
|
|
53
|
+
content = <<~RUBY
|
|
54
|
+
class #{class_name}Controller < Eksa::Controller
|
|
55
|
+
def index
|
|
56
|
+
render "#{name}/index"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
RUBY
|
|
60
|
+
|
|
61
|
+
FileUtils.mkdir_p("app/controllers")
|
|
62
|
+
File.write(file_path, content)
|
|
63
|
+
puts " [OK] Created #{file_path}"
|
|
64
|
+
|
|
65
|
+
FileUtils.mkdir_p(view_dir)
|
|
66
|
+
index_view = File.join(view_dir, "index.html.erb")
|
|
67
|
+
File.write(index_view, "<h1>#{class_name}#index</h1>\n<p>Temukan saya di #{index_view}</p>")
|
|
68
|
+
puts " [OK] Created #{index_view}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def generate_model(name)
|
|
72
|
+
name = name.downcase
|
|
73
|
+
class_name = name.split('_').map(&:capitalize).join
|
|
74
|
+
file_path = "app/models/#{name}.rb"
|
|
75
|
+
|
|
76
|
+
puts "๐ ๏ธ Generating model: #{class_name}"
|
|
77
|
+
|
|
78
|
+
content = <<~RUBY
|
|
79
|
+
class #{class_name} < Eksa::Model
|
|
80
|
+
def self.setup_schema
|
|
81
|
+
db.execute <<~SQL
|
|
82
|
+
CREATE TABLE IF NOT EXISTS #{name}s (
|
|
83
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
84
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
85
|
+
)
|
|
86
|
+
SQL
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def self.all
|
|
90
|
+
db.execute("SELECT * FROM #{name}s ORDER BY id DESC")
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
RUBY
|
|
94
|
+
|
|
95
|
+
FileUtils.mkdir_p("app/models")
|
|
96
|
+
File.write(file_path, content)
|
|
97
|
+
puts " [OK] Created #{file_path}"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def start_server
|
|
101
|
+
if File.exist?('config.ru')
|
|
102
|
+
puts "๐ Memulai Eksa Framework Server..."
|
|
103
|
+
exec "rackup config.ru"
|
|
104
|
+
else
|
|
105
|
+
puts "โ Error: config.ru tidak ditemukan. Pastikan Anda berada di root project Eksa."
|
|
106
|
+
exit 1
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
command = ARGV.shift
|
|
111
|
+
|
|
112
|
+
case command
|
|
113
|
+
when 'init'
|
|
114
|
+
init_project
|
|
115
|
+
when 'run'
|
|
116
|
+
start_server
|
|
117
|
+
when 'g', 'generate'
|
|
118
|
+
subcommand = ARGV.shift
|
|
119
|
+
name = ARGV.shift
|
|
120
|
+
if subcommand == 'controller' && name
|
|
121
|
+
generate_controller(name)
|
|
122
|
+
elsif subcommand == 'model' && name
|
|
123
|
+
generate_model(name)
|
|
124
|
+
else
|
|
125
|
+
usage
|
|
126
|
+
end
|
|
127
|
+
else
|
|
128
|
+
usage
|
|
129
|
+
end
|
data/lib/eksa/controller.rb
CHANGED
|
@@ -15,6 +15,18 @@ module Eksa
|
|
|
15
15
|
@request.params
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
def stylesheet_tag(filename)
|
|
19
|
+
"<link rel='stylesheet' href='/css/#{filename}.css'>"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def javascript_tag(filename)
|
|
23
|
+
"<script src='/js/#{filename}.js'></script>"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def asset_path(path)
|
|
27
|
+
path.start_with?('/') ? path : "/#{path}"
|
|
28
|
+
end
|
|
29
|
+
|
|
18
30
|
def redirect_to(url, notice: nil)
|
|
19
31
|
@status = 302
|
|
20
32
|
@redirect_url = url
|
|
@@ -36,7 +48,11 @@ module Eksa
|
|
|
36
48
|
@content
|
|
37
49
|
end
|
|
38
50
|
else
|
|
39
|
-
"
|
|
51
|
+
"<div class='glass' style='padding: 2rem; border-radius: 1rem; color: #ff5555; background: rgba(255,0,0,0.1); backdrop-filter: blur(10px);'>
|
|
52
|
+
<h2 style='margin-top:0;'>โ ๏ธ View Error</h2>
|
|
53
|
+
<p>Template <strong>#{template_name}</strong> tidak ditemukan di:</p>
|
|
54
|
+
<code style='display:block; background:rgba(0,0,0,0.2); padding:0.5rem; border-radius:0.5rem;'>#{content_path}</code>
|
|
55
|
+
</div>"
|
|
40
56
|
end
|
|
41
57
|
end
|
|
42
58
|
end
|
data/lib/eksa/model.rb
CHANGED
|
@@ -3,23 +3,28 @@ require 'fileutils'
|
|
|
3
3
|
|
|
4
4
|
module Eksa
|
|
5
5
|
class Model
|
|
6
|
+
class << self
|
|
7
|
+
attr_accessor :database_path
|
|
8
|
+
end
|
|
9
|
+
|
|
6
10
|
def self.db
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
path = database_path || default_db_path
|
|
12
|
+
db_dir = File.dirname(path)
|
|
9
13
|
FileUtils.mkdir_p(db_dir) unless Dir.exist?(db_dir)
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
|
|
15
|
+
@db ||= SQLite3::Database.new(path)
|
|
16
|
+
ensure_schema
|
|
12
17
|
@db
|
|
13
18
|
end
|
|
14
19
|
|
|
15
|
-
def self.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
def self.default_db_path
|
|
21
|
+
File.expand_path("../../db/eksa_app.db", __dir__)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.ensure_schema
|
|
25
|
+
return if @schema_initialized
|
|
26
|
+
@schema_initialized = true
|
|
27
|
+
setup_schema if respond_to?(:setup_schema)
|
|
23
28
|
end
|
|
24
29
|
end
|
|
25
30
|
end
|
data/lib/eksa/version.rb
CHANGED
data/lib/eksa.rb
CHANGED
|
@@ -5,15 +5,45 @@ require_relative 'eksa/model'
|
|
|
5
5
|
|
|
6
6
|
module Eksa
|
|
7
7
|
class Application
|
|
8
|
+
attr_reader :config, :middlewares
|
|
9
|
+
|
|
8
10
|
def initialize
|
|
9
11
|
@routes = {}
|
|
12
|
+
@middlewares = []
|
|
13
|
+
@config = {
|
|
14
|
+
db_path: File.expand_path("./db/eksa_app.db")
|
|
15
|
+
}
|
|
16
|
+
yield self if block_given?
|
|
17
|
+
configure_framework
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def configure_framework
|
|
21
|
+
Eksa::Model.database_path = @config[:db_path]
|
|
10
22
|
end
|
|
11
23
|
|
|
12
24
|
def add_route(path, controller_class, action)
|
|
13
25
|
@routes[path] = { controller: controller_class, action: action }
|
|
14
26
|
end
|
|
15
27
|
|
|
28
|
+
def use(middleware, *args, &block)
|
|
29
|
+
@middlewares << [middleware, args, block]
|
|
30
|
+
end
|
|
31
|
+
|
|
16
32
|
def call(env)
|
|
33
|
+
@app ||= build_app
|
|
34
|
+
@app.call(env)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def build_app
|
|
38
|
+
builder = Rack::Builder.new
|
|
39
|
+
@middlewares.each do |middleware, args, block|
|
|
40
|
+
builder.use(middleware, *args, &block)
|
|
41
|
+
end
|
|
42
|
+
builder.run(method(:core_call))
|
|
43
|
+
builder.to_app
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def core_call(env)
|
|
17
47
|
request = Rack::Request.new(env)
|
|
18
48
|
flash_message = request.cookies['eksa_flash']
|
|
19
49
|
route = @routes[request.path_info]
|
|
@@ -21,22 +51,24 @@ module Eksa
|
|
|
21
51
|
if route
|
|
22
52
|
controller_instance = route[:controller].new(request)
|
|
23
53
|
controller_instance.flash[:notice] = flash_message if flash_message
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if controller_instance.status == 302
|
|
30
|
-
response.redirect(controller_instance.redirect_url, 302)
|
|
54
|
+
response_data = controller_instance.send(route[:action])
|
|
55
|
+
if response_data.is_a?(Array) && response_data.size == 3
|
|
56
|
+
status, headers, body = response_data
|
|
57
|
+
response = Rack::Response.new(body, status, headers)
|
|
31
58
|
else
|
|
32
|
-
response.
|
|
33
|
-
|
|
59
|
+
response = Rack::Response.new
|
|
60
|
+
if controller_instance.status == 302
|
|
61
|
+
response.redirect(controller_instance.redirect_url, 302)
|
|
62
|
+
else
|
|
63
|
+
response.write(response_data)
|
|
64
|
+
response['content-type'] = 'text/html'
|
|
65
|
+
end
|
|
34
66
|
end
|
|
35
67
|
|
|
36
68
|
response.delete_cookie('eksa_flash') if flash_message
|
|
37
69
|
response.finish
|
|
38
70
|
else
|
|
39
|
-
[404, { 'content-type' => 'text/html' }, ["<
|
|
71
|
+
[404, { 'content-type' => 'text/html' }, ["<div style='font-family:sans-serif; text-align:center; padding-top:50px;'><h1>404</h1><p>Halaman tidak ditemukan di Eksa Framework.</p></div>"]]
|
|
40
72
|
end
|
|
41
73
|
end
|
|
42
74
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe Eksa::Application do
|
|
4
|
+
let(:app) do
|
|
5
|
+
Eksa::Application.new do |config|
|
|
6
|
+
config.config[:db_path] = ":memory:" # Use in-memory DB for tests
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
it "returns 404 for unknown routes" do
|
|
11
|
+
get '/unknown'
|
|
12
|
+
expect(last_response.status).to eq(404)
|
|
13
|
+
expect(last_response.body).to include("Halaman tidak ditemukan")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it "can add and reach routes" do
|
|
17
|
+
# Mock controller for testing
|
|
18
|
+
stub_const("TestController", Class.new(Eksa::Controller) do
|
|
19
|
+
def index
|
|
20
|
+
"Hello Test"
|
|
21
|
+
end
|
|
22
|
+
end)
|
|
23
|
+
|
|
24
|
+
app.add_route "/test", TestController, :index
|
|
25
|
+
get '/test'
|
|
26
|
+
expect(last_response.status).to eq(200)
|
|
27
|
+
expect(last_response.body).to eq("Hello Test")
|
|
28
|
+
end
|
|
29
|
+
end
|
data/spec/model_spec.rb
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
class RecurrentModel < Eksa::Model
|
|
4
|
+
def self.setup_schema
|
|
5
|
+
db.execute "CREATE TABLE IF NOT EXISTS tests (id INTEGER PRIMARY KEY)"
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
describe Eksa::Model do
|
|
10
|
+
it "does not cause infinite recursion when setup_schema calls db" do
|
|
11
|
+
expect { RecurrentModel.db }.not_to raise_error
|
|
12
|
+
end
|
|
13
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require 'rack/test'
|
|
2
|
+
require 'rspec'
|
|
3
|
+
|
|
4
|
+
# Load the framework
|
|
5
|
+
require_relative '../lib/eksa'
|
|
6
|
+
|
|
7
|
+
# Set environment to test
|
|
8
|
+
ENV['RACK_ENV'] = 'test'
|
|
9
|
+
|
|
10
|
+
RSpec.configure do |config|
|
|
11
|
+
config.include Rack::Test::Methods
|
|
12
|
+
|
|
13
|
+
config.expect_with :rspec do |expectations|
|
|
14
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
config.mock_with :rspec do |mocks|
|
|
18
|
+
mocks.verify_partial_doubles = true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
config.shared_context_metadata_behavior = :apply_to_host_groups
|
|
22
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: eksa-framework
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.1
|
|
4
|
+
version: 1.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- IshikawaUta
|
|
@@ -70,14 +70,16 @@ description: Framework MVC ringan dengan tema modern, sistem routing, dan auto-d
|
|
|
70
70
|
email:
|
|
71
71
|
- komikers09@gmail.com
|
|
72
72
|
executables:
|
|
73
|
-
- eksa
|
|
73
|
+
- eksa
|
|
74
74
|
extensions: []
|
|
75
75
|
extra_rdoc_files: []
|
|
76
76
|
files:
|
|
77
77
|
- Gemfile
|
|
78
|
+
- Gemfile.lock
|
|
78
79
|
- LICENSE
|
|
79
80
|
- README.md
|
|
80
81
|
- app/controllers/pages_controller.rb
|
|
82
|
+
- app/controllers/seo_controller.rb
|
|
81
83
|
- app/models/pesan.rb
|
|
82
84
|
- app/views/about.html.erb
|
|
83
85
|
- app/views/docs.html.erb
|
|
@@ -86,9 +88,8 @@ files:
|
|
|
86
88
|
- app/views/kontak.html.erb
|
|
87
89
|
- app/views/layout.html.erb
|
|
88
90
|
- config.ru
|
|
89
|
-
- db/eksa_app.db
|
|
90
91
|
- db/setup.rb
|
|
91
|
-
- exe/eksa
|
|
92
|
+
- exe/eksa
|
|
92
93
|
- lib/eksa.rb
|
|
93
94
|
- lib/eksa/controller.rb
|
|
94
95
|
- lib/eksa/model.rb
|
|
@@ -96,10 +97,16 @@ files:
|
|
|
96
97
|
- public/css/style.css
|
|
97
98
|
- public/img/favicon.ico
|
|
98
99
|
- public/img/logo.png
|
|
100
|
+
- spec/application_spec.rb
|
|
101
|
+
- spec/model_spec.rb
|
|
102
|
+
- spec/spec_helper.rb
|
|
99
103
|
homepage: https://github.com/IshikawaUta/eksa-framework
|
|
100
104
|
licenses:
|
|
101
105
|
- MIT
|
|
102
|
-
metadata:
|
|
106
|
+
metadata:
|
|
107
|
+
homepage_uri: https://github.com/IshikawaUta/eksa-framework
|
|
108
|
+
source_code_uri: https://github.com/IshikawaUta/eksa-framework
|
|
109
|
+
changelog_uri: https://github.com/IshikawaUta/eksa-framework/releases
|
|
103
110
|
rdoc_options: []
|
|
104
111
|
require_paths:
|
|
105
112
|
- lib
|
data/db/eksa_app.db
DELETED
|
Binary file
|
data/exe/eksa-init
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require 'fileutils'
|
|
5
|
-
|
|
6
|
-
gem_root = File.expand_path("../..", __FILE__)
|
|
7
|
-
target_dir = Dir.pwd
|
|
8
|
-
|
|
9
|
-
puts "\nโจ Eksa Framework Generator โจ"
|
|
10
|
-
puts "-----------------------------"
|
|
11
|
-
puts "๐ Menginisialisasi project di: #{target_dir}"
|
|
12
|
-
|
|
13
|
-
items_to_init = ['app', 'lib', 'public', 'db', 'config.ru', 'Gemfile', 'README.md']
|
|
14
|
-
|
|
15
|
-
items_to_init.each do |item|
|
|
16
|
-
source = File.join(gem_root, item)
|
|
17
|
-
destination = File.join(target_dir, item)
|
|
18
|
-
|
|
19
|
-
if File.exist?(source)
|
|
20
|
-
if File.directory?(source)
|
|
21
|
-
FileUtils.cp_r(source, target_dir)
|
|
22
|
-
puts " [OK] Folder '#{item}' berhasil disalin."
|
|
23
|
-
else
|
|
24
|
-
FileUtils.cp(source, destination)
|
|
25
|
-
puts " [OK] File '#{item}' berhasil disalin."
|
|
26
|
-
end
|
|
27
|
-
else
|
|
28
|
-
if item == 'db'
|
|
29
|
-
FileUtils.mkdir_p(destination)
|
|
30
|
-
puts " [OK] Folder 'db' baru berhasil dibuat."
|
|
31
|
-
else
|
|
32
|
-
puts " [!] Warning: Sumber '#{item}' tidak ditemukan."
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
puts "\nโ
Inisialisasi Selesai!"
|
|
38
|
-
puts "-----------------------------"
|
|
39
|
-
puts "Instruksi selanjutnya:"
|
|
40
|
-
puts "1. Jalankan: bundle install"
|
|
41
|
-
puts "2. Jalankan server: rackup config.ru"
|
|
42
|
-
puts "3. Buka browser: http://localhost:9292"
|
|
43
|
-
puts "-----------------------------\n"
|