tayo 0.1.4 β 0.1.6
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/lib/tayo/commands/init.rb +19 -15
- data/lib/tayo/version.rb +1 -1
- data/repomix-output.xml +1690 -0
- metadata +2 -1
data/repomix-output.xml
ADDED
@@ -0,0 +1,1690 @@
|
|
1
|
+
This file is a merged representation of the entire codebase, combined into a single document by Repomix.
|
2
|
+
|
3
|
+
<file_summary>
|
4
|
+
This section contains a summary of this file.
|
5
|
+
|
6
|
+
<purpose>
|
7
|
+
This file contains a packed representation of the entire repository's contents.
|
8
|
+
It is designed to be easily consumable by AI systems for analysis, code review,
|
9
|
+
or other automated processes.
|
10
|
+
</purpose>
|
11
|
+
|
12
|
+
<file_format>
|
13
|
+
The content is organized as follows:
|
14
|
+
1. This summary section
|
15
|
+
2. Repository information
|
16
|
+
3. Directory structure
|
17
|
+
4. Repository files (if enabled)
|
18
|
+
5. Multiple file entries, each consisting of:
|
19
|
+
- File path as an attribute
|
20
|
+
- Full contents of the file
|
21
|
+
</file_format>
|
22
|
+
|
23
|
+
<usage_guidelines>
|
24
|
+
- This file should be treated as read-only. Any changes should be made to the
|
25
|
+
original repository files, not this packed version.
|
26
|
+
- When processing this file, use the file path to distinguish
|
27
|
+
between different files in the repository.
|
28
|
+
- Be aware that this file may contain sensitive information. Handle it with
|
29
|
+
the same level of security as you would the original repository.
|
30
|
+
</usage_guidelines>
|
31
|
+
|
32
|
+
<notes>
|
33
|
+
- Some files may have been excluded based on .gitignore rules and Repomix's configuration
|
34
|
+
- Binary files are not included in this packed representation. Please refer to the Repository Structure section for a complete list of file paths, including binary files
|
35
|
+
- Files matching patterns in .gitignore are excluded
|
36
|
+
- Files matching default ignore patterns are excluded
|
37
|
+
- Files are sorted by Git change count (files with more changes are at the bottom)
|
38
|
+
</notes>
|
39
|
+
|
40
|
+
</file_summary>
|
41
|
+
|
42
|
+
<directory_structure>
|
43
|
+
.claude/
|
44
|
+
settings.local.json
|
45
|
+
bin/
|
46
|
+
console
|
47
|
+
setup
|
48
|
+
exe/
|
49
|
+
tayo
|
50
|
+
lib/
|
51
|
+
tayo/
|
52
|
+
commands/
|
53
|
+
cf.rb
|
54
|
+
gh.rb
|
55
|
+
init.rb
|
56
|
+
cli.rb
|
57
|
+
version.rb
|
58
|
+
tayo.rb
|
59
|
+
sig/
|
60
|
+
tayo.rbs
|
61
|
+
.gitignore
|
62
|
+
Gemfile
|
63
|
+
Rakefile
|
64
|
+
README.md
|
65
|
+
tayo.gemspec
|
66
|
+
</directory_structure>
|
67
|
+
|
68
|
+
<files>
|
69
|
+
This section contains the contents of the repository's files.
|
70
|
+
|
71
|
+
<file path=".claude/settings.local.json">
|
72
|
+
{
|
73
|
+
"permissions": {
|
74
|
+
"allow": [
|
75
|
+
"Bash(rg:*)",
|
76
|
+
"Bash(mv:*)",
|
77
|
+
"Bash(bundle update:*)",
|
78
|
+
"Bash(chmod:*)",
|
79
|
+
"Bash(ls:*)",
|
80
|
+
"Bash(git init:*)",
|
81
|
+
"Bash(git add:*)",
|
82
|
+
"Bash(rake build)",
|
83
|
+
"Bash(git commit:*)",
|
84
|
+
"Bash(gh repo create:*)",
|
85
|
+
"Bash(gem push:*)",
|
86
|
+
"Bash(gem owner:*)",
|
87
|
+
"Bash(gem signout:*)",
|
88
|
+
"Bash(gem help:*)",
|
89
|
+
"Bash(gem:*)",
|
90
|
+
"Bash(mkdir:*)",
|
91
|
+
"Bash(git push:*)"
|
92
|
+
],
|
93
|
+
"deny": []
|
94
|
+
}
|
95
|
+
}
|
96
|
+
</file>
|
97
|
+
|
98
|
+
<file path="bin/console">
|
99
|
+
#!/usr/bin/env ruby
|
100
|
+
# frozen_string_literal: true
|
101
|
+
|
102
|
+
require "bundler/setup"
|
103
|
+
require "tayo"
|
104
|
+
|
105
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
106
|
+
# with your gem easier. You can also use a different console, if you like.
|
107
|
+
|
108
|
+
require "irb"
|
109
|
+
IRB.start(__FILE__)
|
110
|
+
</file>
|
111
|
+
|
112
|
+
<file path="bin/setup">
|
113
|
+
#!/usr/bin/env bash
|
114
|
+
set -euo pipefail
|
115
|
+
IFS=$'\n\t'
|
116
|
+
set -vx
|
117
|
+
|
118
|
+
bundle install
|
119
|
+
|
120
|
+
# Do any other automated setup that you need to do here
|
121
|
+
</file>
|
122
|
+
|
123
|
+
<file path="exe/tayo">
|
124
|
+
#!/usr/bin/env ruby
|
125
|
+
|
126
|
+
require "tayo"
|
127
|
+
|
128
|
+
Tayo::CLI.start(ARGV)
|
129
|
+
</file>
|
130
|
+
|
131
|
+
<file path="lib/tayo/cli.rb">
|
132
|
+
# frozen_string_literal: true
|
133
|
+
|
134
|
+
require "thor"
|
135
|
+
require "colorize"
|
136
|
+
require_relative "commands/init"
|
137
|
+
require_relative "commands/gh"
|
138
|
+
require_relative "commands/cf"
|
139
|
+
|
140
|
+
module Tayo
|
141
|
+
class CLI < Thor
|
142
|
+
desc "init", "Rails νλ‘μ νΈμ Tayoλ₯Ό μ€μ ν©λλ€"
|
143
|
+
def init
|
144
|
+
Commands::Init.new.execute
|
145
|
+
end
|
146
|
+
|
147
|
+
desc "gh", "GitHub μ μ₯μμ 컨ν
μ΄λ λ μ§μ€νΈλ¦¬λ₯Ό μ€μ ν©λλ€"
|
148
|
+
def gh
|
149
|
+
Commands::Gh.new.execute
|
150
|
+
end
|
151
|
+
|
152
|
+
desc "cf", "Cloudflare DNSλ₯Ό μ€μ νμ¬ νμλ²μ λλ©μΈμ μ°κ²°ν©λλ€"
|
153
|
+
def cf
|
154
|
+
Commands::Cf.new.execute
|
155
|
+
end
|
156
|
+
|
157
|
+
desc "version", "Tayo λ²μ μ νμν©λλ€"
|
158
|
+
def version
|
159
|
+
puts "Tayo #{VERSION}"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
</file>
|
164
|
+
|
165
|
+
<file path="lib/tayo.rb">
|
166
|
+
# frozen_string_literal: true
|
167
|
+
|
168
|
+
require_relative "tayo/version"
|
169
|
+
require_relative "tayo/cli"
|
170
|
+
|
171
|
+
module Tayo
|
172
|
+
class Error < StandardError; end
|
173
|
+
end
|
174
|
+
</file>
|
175
|
+
|
176
|
+
<file path="sig/tayo.rbs">
|
177
|
+
module Tayo
|
178
|
+
VERSION: String
|
179
|
+
# See the writing guide of rbs: https://github.com/ruby/rbs#guides
|
180
|
+
end
|
181
|
+
</file>
|
182
|
+
|
183
|
+
<file path=".gitignore">
|
184
|
+
*.gem
|
185
|
+
*.rbc
|
186
|
+
/.config
|
187
|
+
/coverage/
|
188
|
+
/InstalledFiles
|
189
|
+
/pkg/
|
190
|
+
/spec/reports/
|
191
|
+
/spec/examples.txt
|
192
|
+
/test/tmp/
|
193
|
+
/test/version_tmp/
|
194
|
+
/tmp/
|
195
|
+
.DS_Store
|
196
|
+
|
197
|
+
# Used by dotenv library to load environment variables.
|
198
|
+
# .env
|
199
|
+
|
200
|
+
# Ignore Byebug command history file.
|
201
|
+
.byebug_history
|
202
|
+
|
203
|
+
## Specific to RubyMotion:
|
204
|
+
.dat*
|
205
|
+
.repl_history
|
206
|
+
build/
|
207
|
+
*.bridgesupport
|
208
|
+
build-iPhoneOS/
|
209
|
+
build-iPhoneSimulator/
|
210
|
+
|
211
|
+
## Documentation cache and generated files:
|
212
|
+
/.yardoc/
|
213
|
+
/_yardoc/
|
214
|
+
/doc/
|
215
|
+
/rdoc/
|
216
|
+
|
217
|
+
## Environment normalization:
|
218
|
+
/.bundle/
|
219
|
+
/vendor/bundle
|
220
|
+
/lib/bundler/man/
|
221
|
+
|
222
|
+
# for a library or gem, you might want to ignore these files since the code is
|
223
|
+
# intended to run in multiple environments; otherwise, check them in:
|
224
|
+
# Gemfile.lock
|
225
|
+
# .ruby-version
|
226
|
+
# .ruby-gemset
|
227
|
+
|
228
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
229
|
+
.rvmrc
|
230
|
+
|
231
|
+
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
|
232
|
+
# .rubocop-https?--*
|
233
|
+
</file>
|
234
|
+
|
235
|
+
<file path="Gemfile">
|
236
|
+
# frozen_string_literal: true
|
237
|
+
|
238
|
+
source "https://rubygems.org"
|
239
|
+
|
240
|
+
# Specify your gem's dependencies in tayo.gemspec
|
241
|
+
gemspec
|
242
|
+
|
243
|
+
gem "irb"
|
244
|
+
gem "rake", "~> 13.0"
|
245
|
+
</file>
|
246
|
+
|
247
|
+
<file path="Rakefile">
|
248
|
+
# frozen_string_literal: true
|
249
|
+
|
250
|
+
require "bundler/gem_tasks"
|
251
|
+
task default: %i[]
|
252
|
+
</file>
|
253
|
+
|
254
|
+
<file path="README.md">
|
255
|
+
# Tayo
|
256
|
+
|
257
|
+
Rails μ ν리μΌμ΄μ
μ νμλ²μ λ°°ν¬νκΈ° μν λꡬμ
λλ€.
|
258
|
+
|
259
|
+
## μ€μΉ
|
260
|
+
|
261
|
+
μμ€ν
μμ΄λλ‘ μ€μΉ:
|
262
|
+
|
263
|
+
```bash
|
264
|
+
gem install tayo
|
265
|
+
```
|
266
|
+
|
267
|
+
## μ¬μ©λ²
|
268
|
+
|
269
|
+
### 1. `tayo init` - Rails νλ‘μ νΈ μ΄κΈ°ν
|
270
|
+
|
271
|
+
Rails νλ‘μ νΈλ₯Ό νμλ² λ°°ν¬λ₯Ό μν΄ μ€λΉν©λλ€.
|
272
|
+
|
273
|
+
```bash
|
274
|
+
tayo init
|
275
|
+
```
|
276
|
+
|
277
|
+
μ΄ λͺ
λ Ήμ΄λ λ€μ μμ
λ€μ μνν©λλ€:
|
278
|
+
|
279
|
+
- **OrbStack μ€μΉ νμΈ**: Docker 컨ν
μ΄λλ₯Ό μ€ννκΈ° μν OrbStackμ΄ μ€μΉλμ΄ μλμ§ νμΈν©λλ€
|
280
|
+
- **Gemfile μμ **: development κ·Έλ£Ήμ tayo gemμ μΆκ°ν©λλ€
|
281
|
+
- **Bundle μ€μΉ**: μμ‘΄μ±μ μ€μΉν©λλ€
|
282
|
+
- **Linux νλ«νΌ μΆκ°**: `x86_64-linux`μ `aarch64-linux` νλ«νΌμ Gemfile.lockμ μΆκ°ν©λλ€
|
283
|
+
- **Dockerfile μμ±**: Rails 7 κΈ°λ³Έ Dockerfileμ΄ μμΌλ©΄ μμ±ν©λλ€
|
284
|
+
- **Welcome νμ΄μ§ μμ±**:
|
285
|
+
- `app/controllers/welcome_controller.rb` 컨νΈλ‘€λ¬ μμ±
|
286
|
+
- `app/views/welcome/index.html.erb` λ·° νμΌ μμ± (μ λλ©μ΄μ
μ΄ μλ μμ λλ© νμ΄μ§)
|
287
|
+
- `config/routes.rb`μ `root 'welcome#index'` μ€μ μΆκ°
|
288
|
+
- **Git 컀λ°**: λ³κ²½μ¬νμ μλμΌλ‘ 컀λ°ν©λλ€
|
289
|
+
- **Docker μΊμ μ 리**: λμ€ν¬ κ³΅κ° ν보λ₯Ό μν΄ Docker μΊμλ₯Ό μ 리ν©λλ€
|
290
|
+
|
291
|
+
### 2. `tayo gh` - GitHub μ μ₯μ λ° Container Registry μ€μ
|
292
|
+
|
293
|
+
GitHub μ μ₯μλ₯Ό μμ±νκ³ Container Registryλ₯Ό μ€μ ν©λλ€.
|
294
|
+
|
295
|
+
```bash
|
296
|
+
tayo gh
|
297
|
+
```
|
298
|
+
|
299
|
+
μ΄ λͺ
λ Ήμ΄λ λ€μ μμ
λ€μ μνν©λλ€:
|
300
|
+
|
301
|
+
- **GitHub CLI μ€μΉ νμΈ**: `gh` λͺ
λ Ήμ΄κ° μ€μΉλμ΄ μλμ§ νμΈν©λλ€
|
302
|
+
- **GitHub μΈμ¦ νμΈ**:
|
303
|
+
- GitHubμ λ‘κ·ΈμΈλμ΄ μλμ§ νμΈ
|
304
|
+
- νμν κΆν(repo, read:org, write:packages) νμΈ
|
305
|
+
- κΆνμ΄ μμΌλ©΄ λΈλΌμ°μ μμ ν ν° μμ± νμ΄μ§λ₯Ό μ½λλ€
|
306
|
+
- **Git μ μ₯μ μ΄κΈ°ν**: μμ§ git μ μ₯μκ° μλλ©΄ μ΄κΈ°νν©λλ€
|
307
|
+
- **GitHub μ격 μ μ₯μ μ€μ **:
|
308
|
+
- κΈ°μ‘΄ μ격 μ μ₯μκ° μμΌλ©΄ μ¬μ©
|
309
|
+
- μμΌλ©΄ μ μ μ₯μ μμ± (public/private μ ν κ°λ₯)
|
310
|
+
- μ½λλ₯Ό GitHubμ νΈμ
|
311
|
+
- **GitHub Container Registry μ€μ **:
|
312
|
+
- Registry URL μμ±: `ghcr.io/username/repository-name`
|
313
|
+
- Dockerλ‘ μλ λ‘κ·ΈμΈ μ€ν
|
314
|
+
- **λ°°ν¬ μ€μ νμΌ μμ±**:
|
315
|
+
- `config/deploy.yml` νμΌ μμ± λλ μ
λ°μ΄νΈ
|
316
|
+
- μλ² IP, λλ©μΈ, λ°μ΄ν°λ² μ΄μ€ λ± μ€μ ν¬ν¨
|
317
|
+
- **νκ²½ λ³μ νμΌ μ€λΉ**:
|
318
|
+
- `.env.production` νμΌ μμ±
|
319
|
+
- `.gitignore`μ μΆκ°νμ¬ λ³΄μ μ μ§
|
320
|
+
|
321
|
+
### 3. `tayo cf` - Cloudflare DNS μ€μ
|
322
|
+
|
323
|
+
Cloudflareλ₯Ό ν΅ν΄ λλ©μΈμ νμλ² IPμ μ°κ²°ν©λλ€.
|
324
|
+
|
325
|
+
```bash
|
326
|
+
tayo cf
|
327
|
+
```
|
328
|
+
|
329
|
+
μ΄ λͺ
λ Ήμ΄λ λ€μ μμ
λ€μ μνν©λλ€:
|
330
|
+
|
331
|
+
- **μ€μ νμΌ νμΈ**: `config/deploy.yml` νμΌμμ μλ² IPμ λλ©μΈ μ 보λ₯Ό μ½μ΅λλ€
|
332
|
+
- **Cloudflare μΈμ¦**:
|
333
|
+
- API ν ν° μ
λ ₯ μμ² (μ²μ μ€ν μ)
|
334
|
+
- ν ν°μ μμ νκ² μ μ₯ (macOS Keychain μ¬μ©)
|
335
|
+
- **λλ©μΈ Zone νμΈ**:
|
336
|
+
- Cloudflare κ³μ μμ λλ©μΈμ μ°Ύμ΅λλ€
|
337
|
+
- Zone IDλ₯Ό μλμΌλ‘ κ°μ Έμ΅λλ€
|
338
|
+
- **DNS λ μ½λ μμ±/μ
λ°μ΄νΈ**:
|
339
|
+
- A λ μ½λ μμ±: λλ©μΈμ μλ² IPμ μ°κ²°
|
340
|
+
- κΈ°μ‘΄ λ μ½λκ° μμΌλ©΄ μ
λ°μ΄νΈ
|
341
|
+
- Proxied μ€μ (Cloudflare CDN μ¬μ©)
|
342
|
+
- **μ€μ μλ£ νμΈ**:
|
343
|
+
- DNS μ€μ μ΄ μλ£λλ©΄ μ±κ³΅ λ©μμ§ νμ
|
344
|
+
- λλ©μΈμΌλ‘ μ μ κ°λ₯ν¨μ μλ΄
|
345
|
+
|
346
|
+
κ° λͺ
λ Ήμ΄λ λ¨κ³λ³λ‘ μ§ν μν©μ νμνλ©°, μ€λ₯κ° λ°μνλ©΄ μΉμ ν μλ΄ λ©μμ§λ₯Ό μ 곡ν©λλ€.
|
347
|
+
|
348
|
+
rails new λ‘ νλ‘μ νΈ μμ± ν
|
349
|
+
bundle exec tayo init
|
350
|
+
bundle exec tayo gh
|
351
|
+
bundle exec tayo cf
|
352
|
+
|
353
|
+
μμΌλ‘ μ§ν ν
|
354
|
+
bin/kamal setup μΌλ‘ λ°°ν¬ μ§ν
|
355
|
+
</file>
|
356
|
+
|
357
|
+
<file path="lib/tayo/commands/cf.rb">
|
358
|
+
# frozen_string_literal: true
|
359
|
+
|
360
|
+
require "colorize"
|
361
|
+
require "tty-prompt"
|
362
|
+
require "net/http"
|
363
|
+
require "json"
|
364
|
+
require "uri"
|
365
|
+
|
366
|
+
module Tayo
|
367
|
+
module Commands
|
368
|
+
class Cf
|
369
|
+
def execute
|
370
|
+
puts "βοΈ Cloudflare DNS μ€μ μ μμν©λλ€...".colorize(:green)
|
371
|
+
|
372
|
+
unless rails_project?
|
373
|
+
puts "β Rails νλ‘μ νΈκ° μλλλ€. Rails νλ‘μ νΈ λ£¨νΈμμ μ€νν΄μ£ΌμΈμ.".colorize(:red)
|
374
|
+
return
|
375
|
+
end
|
376
|
+
|
377
|
+
# 1. λλ©μΈ μ
λ ₯λ°κΈ°
|
378
|
+
domain_info = get_domain_input
|
379
|
+
|
380
|
+
# 2. Cloudflare ν ν° μμ± νμ΄μ§ μ΄κΈ° λ° κΆν μλ΄
|
381
|
+
open_token_creation_page
|
382
|
+
|
383
|
+
# 3. ν ν° μ
λ ₯λ°κΈ°
|
384
|
+
token = get_cloudflare_token
|
385
|
+
|
386
|
+
# 4. Cloudflare APIλ‘ λλ©μΈ λͺ©λ‘ μ‘°ν λ° μ ν
|
387
|
+
selected_zone = select_cloudflare_zone(token)
|
388
|
+
|
389
|
+
# 5. λ£¨νΈ λλ©μΈ λ μ½λ νμΈ
|
390
|
+
existing_records = check_existing_records(token, selected_zone, domain_info)
|
391
|
+
|
392
|
+
# 6. DNS λ μ½λ μΆκ°/μμ
|
393
|
+
setup_dns_record(token, selected_zone, domain_info, existing_records)
|
394
|
+
|
395
|
+
# 7. config/deploy.yml μ
λ°μ΄νΈ
|
396
|
+
update_deploy_config(domain_info)
|
397
|
+
|
398
|
+
puts "\nπ Cloudflare DNS μ€μ μ΄ μλ£λμμ΅λλ€!".colorize(:green)
|
399
|
+
|
400
|
+
# λ³κ²½μ¬ν 컀λ°
|
401
|
+
commit_cloudflare_changes(domain_info)
|
402
|
+
end
|
403
|
+
|
404
|
+
private
|
405
|
+
|
406
|
+
def rails_project?
|
407
|
+
File.exist?("Gemfile") && File.exist?("config/application.rb")
|
408
|
+
end
|
409
|
+
|
410
|
+
def get_domain_input
|
411
|
+
prompt = TTY::Prompt.new
|
412
|
+
|
413
|
+
puts "\nπ λ°°ν¬ν λλ©μΈμ μ€μ ν©λλ€.".colorize(:yellow)
|
414
|
+
|
415
|
+
domain = prompt.ask("λ°°ν¬ν λλ©μΈμ μ
λ ₯νμΈμ (μ: myapp.com, api.example.com):") do |q|
|
416
|
+
q.validate(/\A[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\z/, "μ¬λ°λ₯Έ λλ©μΈ νμμ μ
λ ₯ν΄μ£ΌμΈμ (μ: myapp.com)")
|
417
|
+
end
|
418
|
+
|
419
|
+
# λλ©μΈμ΄ 루νΈμΈμ§ μλΈλλ©μΈμΈμ§ νλ¨
|
420
|
+
parts = domain.split('.')
|
421
|
+
if parts.length == 2
|
422
|
+
{ type: :root, domain: domain, zone: domain }
|
423
|
+
else
|
424
|
+
zone = parts[-2..-1].join('.')
|
425
|
+
{ type: :subdomain, domain: domain, zone: zone, subdomain: parts[0..-3].join('.') }
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
def open_token_creation_page
|
430
|
+
puts "\nπ Cloudflare API ν ν°μ΄ νμν©λλ€.".colorize(:yellow)
|
431
|
+
puts "ν ν° μμ± νμ΄μ§λ₯Ό μ½λλ€...".colorize(:cyan)
|
432
|
+
|
433
|
+
# Cloudflare API ν ν° μμ± νμ΄μ§ μ΄κΈ°
|
434
|
+
system("open 'https://dash.cloudflare.com/profile/api-tokens'")
|
435
|
+
|
436
|
+
puts "\nλ€μ κΆνμΌλ‘ ν ν°μ μμ±ν΄μ£ΌμΈμ:".colorize(:yellow)
|
437
|
+
puts ""
|
438
|
+
puts "νκ΅μ΄ νλ©΄:".colorize(:gray)
|
439
|
+
puts "β’ μμ β DNS β μ½κΈ°".colorize(:white)
|
440
|
+
puts "β’ μμ β DNS β νΈμ§".colorize(:white)
|
441
|
+
puts " (μμ 리μμ€λ 'λͺ¨λ μμ' μ ν)".colorize(:gray)
|
442
|
+
puts ""
|
443
|
+
puts "English:".colorize(:gray)
|
444
|
+
puts "β’ Zone β DNS β Read".colorize(:white)
|
445
|
+
puts "β’ Zone β DNS β Edit".colorize(:white)
|
446
|
+
puts " (Zone Resources: Select 'All zones')".colorize(:gray)
|
447
|
+
puts ""
|
448
|
+
end
|
449
|
+
|
450
|
+
def get_cloudflare_token
|
451
|
+
prompt = TTY::Prompt.new
|
452
|
+
|
453
|
+
token = prompt.mask("μμ±λ Cloudflare API ν ν°μ λΆμ¬λ£μΌμΈμ:")
|
454
|
+
|
455
|
+
if token.nil? || token.strip.empty?
|
456
|
+
puts "β ν ν°μ΄ μ
λ ₯λμ§ μμμ΅λλ€.".colorize(:red)
|
457
|
+
exit 1
|
458
|
+
end
|
459
|
+
|
460
|
+
# ν ν° μ ν¨μ± κ°λ¨ νμΈ
|
461
|
+
if test_cloudflare_token(token.strip)
|
462
|
+
puts "β
ν ν°μ΄ νμΈλμμ΅λλ€.".colorize(:green)
|
463
|
+
return token.strip
|
464
|
+
else
|
465
|
+
puts "β ν ν°μ΄ μ¬λ°λ₯΄μ§ μκ±°λ κΆνμ΄ λΆμ‘±ν©λλ€.".colorize(:red)
|
466
|
+
exit 1
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
def test_cloudflare_token(token)
|
471
|
+
uri = URI('https://api.cloudflare.com/client/v4/user/tokens/verify')
|
472
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
473
|
+
http.use_ssl = true
|
474
|
+
|
475
|
+
request = Net::HTTP::Get.new(uri)
|
476
|
+
request['Authorization'] = "Bearer #{token}"
|
477
|
+
request['Content-Type'] = 'application/json'
|
478
|
+
|
479
|
+
response = http.request(request)
|
480
|
+
return response.code == '200'
|
481
|
+
rescue
|
482
|
+
return false
|
483
|
+
end
|
484
|
+
|
485
|
+
def select_cloudflare_zone(token)
|
486
|
+
puts "\nπ Cloudflare λλ©μΈ λͺ©λ‘μ μ‘°νν©λλ€...".colorize(:yellow)
|
487
|
+
|
488
|
+
zones = get_cloudflare_zones(token)
|
489
|
+
|
490
|
+
if zones.empty?
|
491
|
+
puts "β Cloudflareμ λ±λ‘λ λλ©μΈμ΄ μμ΅λλ€.".colorize(:red)
|
492
|
+
puts "λ¨Όμ https://dash.cloudflare.com μμ λλ©μΈμ μΆκ°ν΄μ£ΌμΈμ.".colorize(:cyan)
|
493
|
+
exit 1
|
494
|
+
end
|
495
|
+
|
496
|
+
prompt = TTY::Prompt.new
|
497
|
+
zone_choices = zones.map { |zone| "#{zone['name']} (#{zone['status']})" }
|
498
|
+
|
499
|
+
selected = prompt.select("λλ©μΈμ μ ννμΈμ:", zone_choices)
|
500
|
+
zone_name = selected.split(' ').first
|
501
|
+
|
502
|
+
selected_zone = zones.find { |zone| zone['name'] == zone_name }
|
503
|
+
puts "β
μ νλ λλ©μΈ: #{zone_name}".colorize(:green)
|
504
|
+
|
505
|
+
return selected_zone
|
506
|
+
end
|
507
|
+
|
508
|
+
def get_cloudflare_zones(token)
|
509
|
+
uri = URI('https://api.cloudflare.com/client/v4/zones')
|
510
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
511
|
+
http.use_ssl = true
|
512
|
+
|
513
|
+
request = Net::HTTP::Get.new(uri)
|
514
|
+
request['Authorization'] = "Bearer #{token}"
|
515
|
+
request['Content-Type'] = 'application/json'
|
516
|
+
|
517
|
+
response = http.request(request)
|
518
|
+
|
519
|
+
if response.code == '200'
|
520
|
+
data = JSON.parse(response.body)
|
521
|
+
return data['result'] || []
|
522
|
+
else
|
523
|
+
puts "β λλ©μΈ λͺ©λ‘ μ‘°νμ μ€ν¨νμ΅λλ€: #{response.code}".colorize(:red)
|
524
|
+
exit 1
|
525
|
+
end
|
526
|
+
rescue => e
|
527
|
+
puts "β API μμ² μ€ μ€λ₯κ° λ°μνμ΅λλ€: #{e.message}".colorize(:red)
|
528
|
+
exit 1
|
529
|
+
end
|
530
|
+
|
531
|
+
def check_existing_records(token, zone, domain_info)
|
532
|
+
puts "\nπ κΈ°μ‘΄ DNS λ μ½λλ₯Ό νμΈν©λλ€...".colorize(:yellow)
|
533
|
+
|
534
|
+
zone_id = zone['id']
|
535
|
+
zone_name = zone['name']
|
536
|
+
|
537
|
+
# λ£¨νΈ λλ©μΈμ A/CNAME λ μ½λ νμΈ
|
538
|
+
records = get_dns_records(token, zone_id, zone_name, ['A', 'CNAME'])
|
539
|
+
|
540
|
+
puts "κΈ°μ‘΄ λ μ½λ: #{records.length}κ° λ°κ²¬".colorize(:gray)
|
541
|
+
|
542
|
+
return records
|
543
|
+
end
|
544
|
+
|
545
|
+
def get_dns_records(token, zone_id, name, types)
|
546
|
+
records = []
|
547
|
+
|
548
|
+
types.each do |type|
|
549
|
+
uri = URI("https://api.cloudflare.com/client/v4/zones/#{zone_id}/dns_records")
|
550
|
+
uri.query = URI.encode_www_form({
|
551
|
+
type: type,
|
552
|
+
name: name
|
553
|
+
})
|
554
|
+
|
555
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
556
|
+
http.use_ssl = true
|
557
|
+
|
558
|
+
request = Net::HTTP::Get.new(uri)
|
559
|
+
request['Authorization'] = "Bearer #{token}"
|
560
|
+
request['Content-Type'] = 'application/json'
|
561
|
+
|
562
|
+
response = http.request(request)
|
563
|
+
|
564
|
+
if response.code == '200'
|
565
|
+
data = JSON.parse(response.body)
|
566
|
+
records.concat(data['result'] || [])
|
567
|
+
end
|
568
|
+
end
|
569
|
+
|
570
|
+
return records
|
571
|
+
rescue => e
|
572
|
+
puts "β DNS λ μ½λ μ‘°ν μ€ μ€λ₯: #{e.message}".colorize(:red)
|
573
|
+
return []
|
574
|
+
end
|
575
|
+
|
576
|
+
def setup_dns_record(token, zone, domain_info, existing_records)
|
577
|
+
puts "\nβοΈ DNS λ μ½λλ₯Ό μ€μ ν©λλ€...".colorize(:yellow)
|
578
|
+
|
579
|
+
# νμλ² IP/URL μ
λ ₯λ°κΈ°
|
580
|
+
prompt = TTY::Prompt.new
|
581
|
+
|
582
|
+
server_info = prompt.ask("νμλ² IP λλ λλ©μΈμ μ
λ ₯νμΈμ:") do |q|
|
583
|
+
q.validate(/\A.+\z/, "μλ² μ 보λ₯Ό μ
λ ₯ν΄μ£ΌμΈμ")
|
584
|
+
end
|
585
|
+
|
586
|
+
# SSH μ¬μ©μ κ³μ μ
λ ₯λ°κΈ°
|
587
|
+
ssh_user = prompt.ask("SSH μ¬μ©μ κ³μ μ μ
λ ₯νμΈμ:", default: "root")
|
588
|
+
|
589
|
+
# IPμΈμ§ λλ©μΈμΈμ§ νλ¨
|
590
|
+
is_ip = server_info.match?(/\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/)
|
591
|
+
record_type = is_ip ? 'A' : 'CNAME'
|
592
|
+
|
593
|
+
zone_id = zone['id']
|
594
|
+
zone_name = zone['name']
|
595
|
+
|
596
|
+
# λλ©μΈ μ 보μ λ°λΌ λ μ½λ μ€μ
|
597
|
+
final_domain = determine_final_domain(domain_info, zone_name, existing_records)
|
598
|
+
|
599
|
+
# λμ λλ©μΈμ λͺ¨λ A/CNAME λ μ½λ νμΈ
|
600
|
+
all_records = get_dns_records(token, zone_id, final_domain[:name], ['A', 'CNAME'])
|
601
|
+
|
602
|
+
if all_records.any?
|
603
|
+
existing_record = all_records.first
|
604
|
+
|
605
|
+
# λμΌν νμ
μ΄κ³ κ°μ κ°μ΄λ©΄ 건λλ°κΈ°
|
606
|
+
if existing_record['type'] == record_type && existing_record['content'] == server_info
|
607
|
+
puts "β
DNS λ μ½λκ° μ΄λ―Έ μ¬λ°λ₯΄κ² μ€μ λμ΄ μμ΅λλ€.".colorize(:green)
|
608
|
+
puts " #{final_domain[:full_domain]} β #{server_info} (#{record_type} λ μ½λ)".colorize(:gray)
|
609
|
+
else
|
610
|
+
# νμ
μ΄ λ€λ₯΄κ±°λ κ°μ΄ λ€λ₯Έ κ²½μ° μμ ν μ¬μμ±
|
611
|
+
puts "β οΈ κΈ°μ‘΄ λ μ½λλ₯Ό μμ νκ³ μλ‘ μμ±ν©λλ€.".colorize(:yellow)
|
612
|
+
puts " κΈ°μ‘΄: #{existing_record['content']} (#{existing_record['type']}) β μλ‘μ΄: #{server_info} (#{record_type})".colorize(:gray)
|
613
|
+
|
614
|
+
# κΈ°μ‘΄ λ μ½λ μμ
|
615
|
+
delete_dns_record(token, zone_id, existing_record['id'])
|
616
|
+
|
617
|
+
# μ λ μ½λ μμ±
|
618
|
+
create_dns_record(token, zone_id, final_domain[:name], record_type, server_info)
|
619
|
+
end
|
620
|
+
else
|
621
|
+
# DNS λ μ½λ μμ±
|
622
|
+
create_dns_record(token, zone_id, final_domain[:name], record_type, server_info)
|
623
|
+
end
|
624
|
+
|
625
|
+
# μ΅μ’
λλ©μΈ μ 보 μ μ₯
|
626
|
+
@final_domain = final_domain[:full_domain]
|
627
|
+
@server_info = server_info
|
628
|
+
@ssh_user = ssh_user
|
629
|
+
end
|
630
|
+
|
631
|
+
def determine_final_domain(domain_info, zone_name, existing_records)
|
632
|
+
case domain_info[:type]
|
633
|
+
when :root
|
634
|
+
if existing_records.any?
|
635
|
+
puts "β οΈ λ£¨νΈ λλ©μΈμ μ΄λ―Έ λ μ½λκ° μμ΅λλ€. app.#{zone_name}μ μ¬μ©ν©λλ€.".colorize(:yellow)
|
636
|
+
{ name: "app.#{zone_name}", full_domain: "app.#{zone_name}" }
|
637
|
+
else
|
638
|
+
{ name: zone_name, full_domain: zone_name }
|
639
|
+
end
|
640
|
+
when :subdomain
|
641
|
+
{ name: domain_info[:domain], full_domain: domain_info[:domain] }
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
def create_dns_record(token, zone_id, name, type, content)
|
646
|
+
uri = URI("https://api.cloudflare.com/client/v4/zones/#{zone_id}/dns_records")
|
647
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
648
|
+
http.use_ssl = true
|
649
|
+
|
650
|
+
request = Net::HTTP::Post.new(uri)
|
651
|
+
request['Authorization'] = "Bearer #{token}"
|
652
|
+
request['Content-Type'] = 'application/json'
|
653
|
+
|
654
|
+
data = {
|
655
|
+
type: type,
|
656
|
+
name: name,
|
657
|
+
content: content,
|
658
|
+
ttl: 300
|
659
|
+
}
|
660
|
+
|
661
|
+
request.body = data.to_json
|
662
|
+
response = http.request(request)
|
663
|
+
|
664
|
+
if response.code == '200'
|
665
|
+
puts "β
DNS λ μ½λκ° μμ±λμμ΅λλ€.".colorize(:green)
|
666
|
+
puts " #{name} β #{content} (#{type} λ μ½λ)".colorize(:gray)
|
667
|
+
else
|
668
|
+
puts "β DNS λ μ½λ μμ±μ μ€ν¨νμ΅λλ€: #{response.code}".colorize(:red)
|
669
|
+
puts response.body
|
670
|
+
exit 1
|
671
|
+
end
|
672
|
+
rescue => e
|
673
|
+
puts "β DNS λ μ½λ μμ± μ€ μ€λ₯: #{e.message}".colorize(:red)
|
674
|
+
exit 1
|
675
|
+
end
|
676
|
+
|
677
|
+
def delete_dns_record(token, zone_id, record_id)
|
678
|
+
uri = URI("https://api.cloudflare.com/client/v4/zones/#{zone_id}/dns_records/#{record_id}")
|
679
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
680
|
+
http.use_ssl = true
|
681
|
+
|
682
|
+
request = Net::HTTP::Delete.new(uri)
|
683
|
+
request['Authorization'] = "Bearer #{token}"
|
684
|
+
request['Content-Type'] = 'application/json'
|
685
|
+
|
686
|
+
response = http.request(request)
|
687
|
+
|
688
|
+
if response.code == '200'
|
689
|
+
puts "β
κΈ°μ‘΄ DNS λ μ½λκ° μμ λμμ΅λλ€.".colorize(:green)
|
690
|
+
else
|
691
|
+
puts "β DNS λ μ½λ μμ μ μ€ν¨νμ΅λλ€: #{response.code}".colorize(:red)
|
692
|
+
puts response.body
|
693
|
+
exit 1
|
694
|
+
end
|
695
|
+
rescue => e
|
696
|
+
puts "β DNS λ μ½λ μμ μ€ μ€λ₯: #{e.message}".colorize(:red)
|
697
|
+
exit 1
|
698
|
+
end
|
699
|
+
|
700
|
+
def update_deploy_config(domain_info)
|
701
|
+
puts "\nπ λ°°ν¬ μ€μ μ μ
λ°μ΄νΈν©λλ€...".colorize(:yellow)
|
702
|
+
|
703
|
+
config_file = "config/deploy.yml"
|
704
|
+
|
705
|
+
unless File.exist?(config_file)
|
706
|
+
puts "β οΈ config/deploy.yml νμΌμ΄ μμ΅λλ€.".colorize(:yellow)
|
707
|
+
return
|
708
|
+
end
|
709
|
+
|
710
|
+
content = File.read(config_file)
|
711
|
+
|
712
|
+
# proxy.host μ€μ μ
λ°μ΄νΈ
|
713
|
+
if content.include?("proxy:")
|
714
|
+
content.gsub!(/(\s+host:\s+).*$/, "\\1#{@final_domain}")
|
715
|
+
else
|
716
|
+
# proxy μΉμ
μ΄ μμΌλ©΄ μΆκ°
|
717
|
+
proxy_config = "\n# Proxy configuration\nproxy:\n ssl: true\n host: #{@final_domain}\n"
|
718
|
+
content += proxy_config
|
719
|
+
end
|
720
|
+
|
721
|
+
# servers μ€μ μ
λ°μ΄νΈ
|
722
|
+
if content.match?(/servers:\s*\n\s*web:\s*\n\s*-\s*/)
|
723
|
+
content.gsub!(/(\s*servers:\s*\n\s*web:\s*\n\s*-\s*)[\d.]+/, "\\1#{@server_info}")
|
724
|
+
end
|
725
|
+
|
726
|
+
# ssh user μ€μ μ
λ°μ΄νΈ
|
727
|
+
if @ssh_user && @ssh_user != "root"
|
728
|
+
if content.match?(/^ssh:/)
|
729
|
+
# κΈ°μ‘΄ ssh μΉμ
μ
λ°μ΄νΈ
|
730
|
+
content.gsub!(/^ssh:\s*\n\s*user:\s*\w+/, "ssh:\n user: #{@ssh_user}")
|
731
|
+
else
|
732
|
+
# ssh μΉμ
μΆκ° (accessories μΉμ
μμ μΆκ°)
|
733
|
+
if content.match?(/^# Use accessory services/)
|
734
|
+
content.gsub!(/^# Use accessory services/, "# Use a different ssh user than root\nssh:\n user: #{@ssh_user}\n\n# Use accessory services")
|
735
|
+
else
|
736
|
+
# νμΌ λμ μΆκ°
|
737
|
+
content += "\n# Use a different ssh user than root\nssh:\n user: #{@ssh_user}\n"
|
738
|
+
end
|
739
|
+
end
|
740
|
+
end
|
741
|
+
|
742
|
+
File.write(config_file, content)
|
743
|
+
puts "β
config/deploy.ymlμ΄ μ
λ°μ΄νΈλμμ΅λλ€.".colorize(:green)
|
744
|
+
puts " proxy.host: #{@final_domain}".colorize(:gray)
|
745
|
+
puts " servers.web: #{@server_info}".colorize(:gray)
|
746
|
+
puts " ssh.user: #{@ssh_user}".colorize(:gray) if @ssh_user && @ssh_user != "root"
|
747
|
+
end
|
748
|
+
|
749
|
+
def commit_cloudflare_changes(domain_info)
|
750
|
+
puts "\nπ λ³κ²½μ¬νμ Gitμ 컀λ°ν©λλ€...".colorize(:yellow)
|
751
|
+
|
752
|
+
# λ³κ²½λ νμΌμ΄ μλμ§ νμΈ
|
753
|
+
status_output = `git status --porcelain`.strip
|
754
|
+
|
755
|
+
if status_output.empty?
|
756
|
+
puts "βΉοΈ 컀λ°ν λ³κ²½μ¬νμ΄ μμ΅λλ€.".colorize(:yellow)
|
757
|
+
return
|
758
|
+
end
|
759
|
+
|
760
|
+
# Git add
|
761
|
+
system("git add -A")
|
762
|
+
|
763
|
+
# Commit λ©μμ§ μμ±
|
764
|
+
commit_message = "Configure Cloudflare DNS settings\n\n- Setup DNS for domain: #{domain_info[:domain]}\n- Configure server IP: #{domain_info[:server_ip]}\n- Update deployment configuration\n- Add proxy host settings\n\nπ€ Generated with Tayo"
|
765
|
+
|
766
|
+
# Commit μ€ν
|
767
|
+
if system("git commit -m \"#{commit_message}\"")
|
768
|
+
puts "β
λ³κ²½μ¬νμ΄ μ±κ³΅μ μΌλ‘ 컀λ°λμμ΅λλ€.".colorize(:green)
|
769
|
+
|
770
|
+
# GitHubμ νΈμ
|
771
|
+
if system("git push", out: File::NULL, err: File::NULL)
|
772
|
+
puts "β
λ³κ²½μ¬νμ΄ GitHubμ νΈμλμμ΅λλ€.".colorize(:green)
|
773
|
+
else
|
774
|
+
puts "β οΈ GitHub νΈμμ μ€ν¨νμ΅λλ€. μλμΌλ‘ 'git push'λ₯Ό μ€νν΄μ£ΌμΈμ.".colorize(:yellow)
|
775
|
+
end
|
776
|
+
else
|
777
|
+
puts "β Git 컀λ°μ μ€ν¨νμ΅λλ€.".colorize(:red)
|
778
|
+
end
|
779
|
+
end
|
780
|
+
end
|
781
|
+
end
|
782
|
+
end
|
783
|
+
</file>
|
784
|
+
|
785
|
+
<file path="lib/tayo/commands/gh.rb">
|
786
|
+
# frozen_string_literal: true
|
787
|
+
|
788
|
+
require "colorize"
|
789
|
+
require "json"
|
790
|
+
require "git"
|
791
|
+
require "yaml"
|
792
|
+
require "tty-prompt"
|
793
|
+
|
794
|
+
module Tayo
|
795
|
+
module Commands
|
796
|
+
class Gh
|
797
|
+
def execute
|
798
|
+
puts "π GitHub μ μ₯μ λ° μ»¨ν
μ΄λ λ μ§μ€νΈλ¦¬ μ€μ μ μμν©λλ€...".colorize(:green)
|
799
|
+
|
800
|
+
unless rails_project?
|
801
|
+
puts "β Rails νλ‘μ νΈκ° μλλλ€. Rails νλ‘μ νΈ λ£¨νΈμμ μ€νν΄μ£ΌμΈμ.".colorize(:red)
|
802
|
+
return
|
803
|
+
end
|
804
|
+
|
805
|
+
puts "\n[1/7] GitHub CLI μ€μΉ νμΈ".colorize(:blue)
|
806
|
+
check_github_cli
|
807
|
+
|
808
|
+
puts "\n[2/7] GitHub λ‘κ·ΈμΈ νμΈ".colorize(:blue)
|
809
|
+
check_github_auth
|
810
|
+
|
811
|
+
puts "\n[3/7] 컨ν
μ΄λ λ μ§μ€νΈλ¦¬ κΆν νμΈ".colorize(:blue)
|
812
|
+
check_container_registry_permission
|
813
|
+
|
814
|
+
puts "\n[4/7] Git μ μ₯μ μ΄κΈ°ν".colorize(:blue)
|
815
|
+
init_git_repo
|
816
|
+
|
817
|
+
puts "\n[5/7] GitHub μ μ₯μ μμ±".colorize(:blue)
|
818
|
+
create_github_repository
|
819
|
+
|
820
|
+
puts "\n[6/7] 컨ν
μ΄λ λ μ§μ€νΈλ¦¬ μ€μ ".colorize(:blue)
|
821
|
+
create_container_registry
|
822
|
+
|
823
|
+
puts "\n[7/7] λ°°ν¬ μ€μ νμΌ μμ±".colorize(:blue)
|
824
|
+
create_deploy_config
|
825
|
+
|
826
|
+
puts "\nπ λͺ¨λ μ€μ μ΄ μλ£λμμ΅λλ€!".colorize(:green)
|
827
|
+
puts "\nλ€μ μ λ³΄κ° μ€μ λμμ΅λλ€:".colorize(:yellow)
|
828
|
+
puts "β’ GitHub μ μ₯μ: https://github.com/#{@username}/#{@repo_name}".colorize(:cyan)
|
829
|
+
puts "β’ Container Registry: #{@registry_url}".colorize(:cyan)
|
830
|
+
puts "β’ λ°°ν¬ μ€μ : config/deploy.yml".colorize(:cyan)
|
831
|
+
|
832
|
+
# λ³κ²½μ¬ν 컀λ°
|
833
|
+
commit_github_changes
|
834
|
+
end
|
835
|
+
|
836
|
+
private
|
837
|
+
|
838
|
+
def rails_project?
|
839
|
+
File.exist?("Gemfile") && File.exist?("config/application.rb")
|
840
|
+
end
|
841
|
+
|
842
|
+
def check_github_cli
|
843
|
+
if system("gh --version", out: File::NULL, err: File::NULL)
|
844
|
+
puts "β
GitHub CLIκ° μ΄λ―Έ μ€μΉλμ΄ μμ΅λλ€.".colorize(:green)
|
845
|
+
else
|
846
|
+
puts "π¦ GitHub CLIλ₯Ό μ€μΉν©λλ€...".colorize(:yellow)
|
847
|
+
system("brew install gh")
|
848
|
+
puts "β
GitHub CLI μ€μΉ μλ£.".colorize(:green)
|
849
|
+
end
|
850
|
+
end
|
851
|
+
|
852
|
+
def check_github_auth
|
853
|
+
auth_status = `gh auth status 2>&1`
|
854
|
+
|
855
|
+
unless $?.success?
|
856
|
+
puts "π GitHub λ‘κ·ΈμΈμ΄ νμν©λλ€.".colorize(:yellow)
|
857
|
+
puts "λ€μ λͺ
λ Ήμ΄λ₯Ό μ€ννμ¬ λ‘κ·ΈμΈν΄μ£ΌμΈμ:".colorize(:yellow)
|
858
|
+
puts "gh auth login".colorize(:cyan)
|
859
|
+
exit 1
|
860
|
+
end
|
861
|
+
|
862
|
+
# ν ν° λ§λ£ νμΈ
|
863
|
+
if auth_status.include?("Token has expired") || auth_status.include?("authentication failed")
|
864
|
+
puts "β οΈ GitHub ν ν°μ΄ λ§λ£λμμ΅λλ€.".colorize(:yellow)
|
865
|
+
puts "λ€μ λ‘κ·ΈμΈν΄μ£ΌμΈμ:".colorize(:yellow)
|
866
|
+
puts "gh auth login".colorize(:cyan)
|
867
|
+
exit 1
|
868
|
+
end
|
869
|
+
|
870
|
+
puts "β
GitHubμ λ‘κ·ΈμΈλμ΄ μμ΅λλ€.".colorize(:green)
|
871
|
+
end
|
872
|
+
|
873
|
+
def check_container_registry_permission
|
874
|
+
scopes = `gh auth status -t 2>&1`
|
875
|
+
|
876
|
+
# ν ν° λ§λ£ νμΈ (κΆν μ²΄ν¬ μμλ)
|
877
|
+
if scopes.include?("Token has expired") || scopes.include?("authentication failed")
|
878
|
+
puts "β οΈ GitHub ν ν°μ΄ λ§λ£λμμ΅λλ€.".colorize(:yellow)
|
879
|
+
puts "λ€μ λ‘κ·ΈμΈν΄μ£ΌμΈμ:".colorize(:yellow)
|
880
|
+
puts "gh auth login".colorize(:cyan)
|
881
|
+
exit 1
|
882
|
+
end
|
883
|
+
|
884
|
+
unless scopes.include?("write:packages") || scopes.include?("admin:packages")
|
885
|
+
puts "β οΈ μ»¨ν
μ΄λ λ μ§μ€νΈλ¦¬ κΆνμ΄ μμ΅λλ€.".colorize(:yellow)
|
886
|
+
puts "\nTayoκ° μ μ μλνκΈ° μν΄ λ€μ κΆνλ€μ΄ νμν©λλ€:".colorize(:yellow)
|
887
|
+
puts "β’ repo - GitHub μ μ₯μ μμ± λ° κ΄λ¦¬".colorize(:yellow)
|
888
|
+
puts "β’ read:org - μ‘°μ§ μ 보 μ½κΈ°".colorize(:yellow)
|
889
|
+
puts "β’ write:packages - Docker μ΄λ―Έμ§λ₯Ό Container Registryμ νΈμ".colorize(:yellow)
|
890
|
+
puts "\nν ν° μμ± νμ΄μ§λ₯Ό μ½λλ€...".colorize(:cyan)
|
891
|
+
|
892
|
+
project_name = File.basename(Dir.pwd)
|
893
|
+
token_description = "Tayo%20-%20#{project_name}"
|
894
|
+
token_url = "https://github.com/settings/tokens/new?scopes=repo,read:org,write:packages&description=#{token_description}"
|
895
|
+
system("open '#{token_url}'")
|
896
|
+
|
897
|
+
puts "\nβ
λΈλΌμ°μ μμ GitHub ν ν° μμ± νμ΄μ§κ° μ΄λ Έμ΅λλ€.".colorize(:green)
|
898
|
+
puts "π νμν κΆνλ€μ΄ μ΄λ―Έ 체ν¬λμ΄ μμ΅λλ€:".colorize(:green)
|
899
|
+
puts " β’ repo - μ μ₯μ μμ± λ° κ΄λ¦¬".colorize(:gray)
|
900
|
+
puts " β’ read:org - μ‘°μ§ μ 보 μ½κΈ°".colorize(:gray)
|
901
|
+
puts " β’ write:packages - Container Registry μ κ·Ό".colorize(:gray)
|
902
|
+
|
903
|
+
puts "\nλ€μ λ¨κ³λ₯Ό λ°λΌμ£ΌμΈμ:".colorize(:yellow)
|
904
|
+
puts "1. νμ΄μ§ νλ¨μ 'Generate token' λ²νΌμ ν΄λ¦νμΈμ".colorize(:cyan)
|
905
|
+
puts "2. μμ±λ ν ν°μ 볡μ¬νμΈμ".colorize(:cyan)
|
906
|
+
puts "3. μλμ ν ν°μ λΆμ¬λ£μΌμΈμ:".colorize(:cyan)
|
907
|
+
|
908
|
+
print "\nν ν° μ
λ ₯: ".colorize(:yellow)
|
909
|
+
token = STDIN.gets.chomp
|
910
|
+
|
911
|
+
if token.empty?
|
912
|
+
puts "β ν ν°μ΄ μ
λ ₯λμ§ μμμ΅λλ€.".colorize(:red)
|
913
|
+
exit 1
|
914
|
+
end
|
915
|
+
|
916
|
+
# ν ν°μ μμ νμΌμ μ μ₯νκ³ gh auth login μ€ν
|
917
|
+
require 'tempfile'
|
918
|
+
Tempfile.create('github_token') do |f|
|
919
|
+
f.write(token)
|
920
|
+
f.flush
|
921
|
+
|
922
|
+
puts "\nπ GitHubμ λ‘κ·ΈμΈ μ€...".colorize(:yellow)
|
923
|
+
if system("gh auth login --with-token < #{f.path}")
|
924
|
+
puts "β
GitHub λ‘κ·ΈμΈμ μ±κ³΅νμ΅λλ€!".colorize(:green)
|
925
|
+
puts "\nλ€μ 'tayo gh' λͺ
λ Ήμ μ€νν΄μ£ΌμΈμ.".colorize(:cyan)
|
926
|
+
else
|
927
|
+
puts "β GitHub λ‘κ·ΈμΈμ μ€ν¨νμ΅λλ€.".colorize(:red)
|
928
|
+
end
|
929
|
+
end
|
930
|
+
|
931
|
+
exit 0
|
932
|
+
end
|
933
|
+
|
934
|
+
puts "β
컨ν
μ΄λ λ μ§μ€νΈλ¦¬ κΆνμ΄ νμΈλμμ΅λλ€.".colorize(:green)
|
935
|
+
end
|
936
|
+
|
937
|
+
def init_git_repo
|
938
|
+
unless Dir.exist?(".git")
|
939
|
+
Git.init(".")
|
940
|
+
puts "β
Git μ μ₯μλ₯Ό μ΄κΈ°ννμ΅λλ€.".colorize(:green)
|
941
|
+
else
|
942
|
+
puts "βΉοΈ Git μ μ₯μκ° μ΄λ―Έ μ΄κΈ°νλμ΄ μμ΅λλ€.".colorize(:yellow)
|
943
|
+
end
|
944
|
+
|
945
|
+
git = Git.open(".")
|
946
|
+
|
947
|
+
# HEAD 컀λ°μ΄ μλμ§ νμΈ
|
948
|
+
has_commits = begin
|
949
|
+
git.log.count > 0
|
950
|
+
rescue Git::GitExecuteError
|
951
|
+
false
|
952
|
+
end
|
953
|
+
|
954
|
+
# git statusλ‘ λ³κ²½μ¬ν νμΈ (HEADκ° μμΌλ©΄ λ€λ₯Έ λ°©λ² μ¬μ©)
|
955
|
+
has_changes = if has_commits
|
956
|
+
git.status.untracked.any? || git.status.changed.any?
|
957
|
+
else
|
958
|
+
# HEADκ° μμ λλ μνΉ λλ ν 리μ νμΌμ΄ μλμ§ νμΈ
|
959
|
+
Dir.glob("*", File::FNM_DOTMATCH).reject { |f| f == "." || f == ".." || f == ".git" }.any?
|
960
|
+
end
|
961
|
+
|
962
|
+
if has_changes
|
963
|
+
git.add(all: true)
|
964
|
+
git.commit("init")
|
965
|
+
puts "β
μ΄κΈ° 컀λ°μ μμ±νμ΅λλ€.".colorize(:green)
|
966
|
+
else
|
967
|
+
puts "βΉοΈ 컀λ°ν λ³κ²½μ¬νμ΄ μμ΅λλ€.".colorize(:yellow)
|
968
|
+
end
|
969
|
+
end
|
970
|
+
|
971
|
+
def create_github_repository
|
972
|
+
repo_name = File.basename(Dir.pwd)
|
973
|
+
username = `gh api user -q .login`.strip
|
974
|
+
|
975
|
+
# μ‘°μ§ λͺ©λ‘ κ°μ Έμ€κΈ°
|
976
|
+
orgs_json = `gh api user/orgs -q '.[].login' 2>/dev/null`
|
977
|
+
orgs = orgs_json.strip.split("\n").reject(&:empty?)
|
978
|
+
|
979
|
+
owner = username
|
980
|
+
|
981
|
+
if orgs.any?
|
982
|
+
prompt = TTY::Prompt.new
|
983
|
+
choices = ["#{username} (κ°μΈ κ³μ )"] + orgs.map { |org| "#{org} (μ‘°μ§)" }
|
984
|
+
|
985
|
+
selection = prompt.select("π’ μ μ₯μλ₯Ό μμ±ν μμΉλ₯Ό μ ννμΈμ:", choices)
|
986
|
+
|
987
|
+
if selection != "#{username} (κ°μΈ κ³μ )"
|
988
|
+
owner = selection.split(" ").first
|
989
|
+
end
|
990
|
+
end
|
991
|
+
|
992
|
+
# μ μ₯μ μ‘΄μ¬ μ¬λΆ νμΈ
|
993
|
+
repo_exists = system("gh repo view #{owner}/#{repo_name}", out: File::NULL, err: File::NULL)
|
994
|
+
|
995
|
+
if repo_exists
|
996
|
+
puts "βΉοΈ GitHub μ μ₯μκ° μ΄λ―Έ μ‘΄μ¬ν©λλ€: https://github.com/#{owner}/#{repo_name}".colorize(:yellow)
|
997
|
+
@repo_name = repo_name
|
998
|
+
@username = owner
|
999
|
+
else
|
1000
|
+
create_cmd = if owner == username
|
1001
|
+
"gh repo create #{repo_name} --private --source=. --remote=origin --push"
|
1002
|
+
else
|
1003
|
+
"gh repo create #{owner}/#{repo_name} --private --source=. --remote=origin --push"
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
result = system(create_cmd)
|
1007
|
+
|
1008
|
+
if result
|
1009
|
+
puts "β
GitHub μ μ₯μλ₯Ό μμ±νμ΅λλ€: https://github.com/#{owner}/#{repo_name}".colorize(:green)
|
1010
|
+
@repo_name = repo_name
|
1011
|
+
@username = owner
|
1012
|
+
else
|
1013
|
+
puts "β GitHub μ μ₯μ μμ±μ μ€ν¨νμ΅λλ€.".colorize(:red)
|
1014
|
+
exit 1
|
1015
|
+
end
|
1016
|
+
end
|
1017
|
+
end
|
1018
|
+
|
1019
|
+
def create_container_registry
|
1020
|
+
# Docker μ΄λ―Έμ§ νκ·Έλ μλ¬Έμμ¬μΌ ν¨
|
1021
|
+
registry_url = "ghcr.io/#{@username.downcase}/#{@repo_name.downcase}"
|
1022
|
+
@registry_url = registry_url
|
1023
|
+
|
1024
|
+
puts "β
컨ν
μ΄λ λ μ§μ€νΈλ¦¬κ° μ€μ λμμ΅λλ€.".colorize(:green)
|
1025
|
+
puts " URL: #{registry_url}".colorize(:gray)
|
1026
|
+
puts " βΉοΈ 컨ν
μ΄λ λ μ§μ€νΈλ¦¬λ 첫 μ΄λ―Έμ§ νΈμ μ μλμΌλ‘ μμ±λ©λλ€.".colorize(:gray)
|
1027
|
+
|
1028
|
+
# Dockerλ‘ GitHub Container Registryμ λ‘κ·ΈμΈ
|
1029
|
+
puts "\nπ³ Dockerλ‘ GitHub Container Registryμ λ‘κ·ΈμΈν©λλ€...".colorize(:yellow)
|
1030
|
+
|
1031
|
+
# νμ¬ GitHub ν ν° κ°μ Έμ€κΈ°
|
1032
|
+
token = `gh auth token`.strip
|
1033
|
+
|
1034
|
+
if token.empty?
|
1035
|
+
puts "β GitHub ν ν°μ κ°μ Έμ¬ μ μμ΅λλ€.".colorize(:red)
|
1036
|
+
return
|
1037
|
+
end
|
1038
|
+
|
1039
|
+
# Docker login μ€ν
|
1040
|
+
login_cmd = "echo #{token} | docker login ghcr.io -u #{@username} --password-stdin"
|
1041
|
+
|
1042
|
+
if system(login_cmd)
|
1043
|
+
puts "β
Docker λ‘κ·ΈμΈμ μ±κ³΅νμ΅λλ€!".colorize(:green)
|
1044
|
+
puts " Registry: ghcr.io".colorize(:gray)
|
1045
|
+
puts " Username: #{@username}".colorize(:gray)
|
1046
|
+
else
|
1047
|
+
puts "β Docker λ‘κ·ΈμΈμ μ€ν¨νμ΅λλ€.".colorize(:red)
|
1048
|
+
puts " μλμΌλ‘ λ€μ λͺ
λ Ήμ μ€νν΄μ£ΌμΈμ:".colorize(:yellow)
|
1049
|
+
puts " docker login ghcr.io".colorize(:cyan)
|
1050
|
+
end
|
1051
|
+
end
|
1052
|
+
|
1053
|
+
def create_deploy_config
|
1054
|
+
config_dir = "config"
|
1055
|
+
Dir.mkdir(config_dir) unless Dir.exist?(config_dir)
|
1056
|
+
|
1057
|
+
if File.exist?("config/deploy.yml")
|
1058
|
+
puts "βΉοΈ κΈ°μ‘΄ config/deploy.yml νμΌμ μ
λ°μ΄νΈν©λλ€.".colorize(:yellow)
|
1059
|
+
update_kamal_config
|
1060
|
+
else
|
1061
|
+
puts "β
config/deploy.yml νμΌμ μμ±νμ΅λλ€.".colorize(:green)
|
1062
|
+
create_tayo_config
|
1063
|
+
end
|
1064
|
+
end
|
1065
|
+
|
1066
|
+
private
|
1067
|
+
|
1068
|
+
def update_kamal_config
|
1069
|
+
content = File.read("config/deploy.yml")
|
1070
|
+
|
1071
|
+
# μ΄λ―Έμ§ μ€μ μ
λ°μ΄νΈ (ghcr.io μ€λ³΅ μ κ±°)
|
1072
|
+
# @registry_urlμ μ΄λ―Έ ghcr.ioλ₯Ό ν¬ν¨νκ³ μμΌλ―λ‘, κ·Έλλ‘ μ¬μ©
|
1073
|
+
content.gsub!(/^image:\s+.*$/, "image: #{@registry_url}")
|
1074
|
+
|
1075
|
+
# registry μΉμ
μ
λ°μ΄νΈ
|
1076
|
+
if content.include?("registry:")
|
1077
|
+
# κΈ°μ‘΄ registry μΉμ
μμ
|
1078
|
+
# server λΌμΈμ΄ μ£Όμμ²λ¦¬λμ΄ μλμ§ νμΈ
|
1079
|
+
if content.match?(/^\s*#\s*server:/)
|
1080
|
+
content.gsub!(/^\s*#\s*server:\s*.*$/, " server: ghcr.io")
|
1081
|
+
elsif content.match?(/^\s*server:/)
|
1082
|
+
content.gsub!(/^\s*server:\s*.*$/, " server: ghcr.io")
|
1083
|
+
else
|
1084
|
+
# server λΌμΈμ΄ μμΌλ©΄ username μμ μΆκ°
|
1085
|
+
content.gsub!(/(\s*username:\s+)/, " server: ghcr.io\n\\1")
|
1086
|
+
end
|
1087
|
+
# usernameλ μλ¬Έμλ‘ λ³ν
|
1088
|
+
content.gsub!(/^\s*username:\s+.*$/, " username: #{@username.downcase}")
|
1089
|
+
else
|
1090
|
+
# registry μΉμ
μΆκ°
|
1091
|
+
registry_config = "\n# Container registry configuration\nregistry:\n server: ghcr.io\n username: #{@username.downcase}\n password:\n - KAMAL_REGISTRY_PASSWORD\n"
|
1092
|
+
content.gsub!(/^# Credentials for your image host\.\nregistry:.*?^$/m, registry_config)
|
1093
|
+
end
|
1094
|
+
|
1095
|
+
File.write("config/deploy.yml", content)
|
1096
|
+
|
1097
|
+
# GitHub ν ν°μ Kamal secrets νμΌμ μ€μ
|
1098
|
+
setup_kamal_secrets
|
1099
|
+
|
1100
|
+
puts "β
Container Registry μ€μ μ΄ μ
λ°μ΄νΈλμμ΅λλ€:".colorize(:green)
|
1101
|
+
puts " β’ μ΄λ―Έμ§: #{@registry_url}".colorize(:gray)
|
1102
|
+
puts " β’ λ μ§μ€νΈλ¦¬ μλ²: ghcr.io".colorize(:gray)
|
1103
|
+
puts " β’ μ¬μ©μλͺ
: #{@username}".colorize(:gray)
|
1104
|
+
end
|
1105
|
+
|
1106
|
+
def setup_kamal_secrets
|
1107
|
+
# .kamal λλ ν 리 μμ±
|
1108
|
+
Dir.mkdir(".kamal") unless Dir.exist?(".kamal")
|
1109
|
+
|
1110
|
+
# νμ¬ GitHub ν ν° κ°μ Έμ€κΈ°
|
1111
|
+
token_output = `gh auth token 2>/dev/null`
|
1112
|
+
|
1113
|
+
if $?.success? && !token_output.strip.empty?
|
1114
|
+
token = token_output.strip
|
1115
|
+
secrets_file = ".kamal/secrets"
|
1116
|
+
|
1117
|
+
# κΈ°μ‘΄ secrets νμΌ μ½κΈ° (μλ€λ©΄)
|
1118
|
+
existing_content = File.exist?(secrets_file) ? File.read(secrets_file) : ""
|
1119
|
+
|
1120
|
+
# KAMAL_REGISTRY_PASSWORDκ° μ΄λ―Έ μλμ§ νμΈ
|
1121
|
+
if existing_content.include?("KAMAL_REGISTRY_PASSWORD")
|
1122
|
+
# κΈ°μ‘΄ κ° μ
λ°μ΄νΈ
|
1123
|
+
updated_content = existing_content.gsub(/^KAMAL_REGISTRY_PASSWORD=.*$/, "KAMAL_REGISTRY_PASSWORD=#{token}")
|
1124
|
+
else
|
1125
|
+
# μλ‘ μΆκ°
|
1126
|
+
updated_content = existing_content.empty? ? "KAMAL_REGISTRY_PASSWORD=#{token}\n" : "#{existing_content.chomp}\nKAMAL_REGISTRY_PASSWORD=#{token}\n"
|
1127
|
+
end
|
1128
|
+
|
1129
|
+
File.write(secrets_file, updated_content)
|
1130
|
+
puts "β
GitHub ν ν°μ΄ .kamal/secretsμ μ€μ λμμ΅λλ€.".colorize(:green)
|
1131
|
+
|
1132
|
+
# .gitignoreμ secrets νμΌ μΆκ°
|
1133
|
+
add_to_gitignore(".kamal/secrets")
|
1134
|
+
else
|
1135
|
+
puts "β οΈ GitHub ν ν°μ κ°μ Έμ¬ μ μμ΅λλ€. μλμΌλ‘ μ€μ ν΄μ£ΌμΈμ:".colorize(:yellow)
|
1136
|
+
puts " echo 'KAMAL_REGISTRY_PASSWORD=your_github_token' >> .kamal/secrets".colorize(:cyan)
|
1137
|
+
end
|
1138
|
+
end
|
1139
|
+
|
1140
|
+
def add_to_gitignore(file_path)
|
1141
|
+
gitignore_file = ".gitignore"
|
1142
|
+
|
1143
|
+
if File.exist?(gitignore_file)
|
1144
|
+
content = File.read(gitignore_file)
|
1145
|
+
unless content.include?(file_path)
|
1146
|
+
File.write(gitignore_file, "#{content.chomp}\n#{file_path}\n")
|
1147
|
+
puts "β
.gitignoreμ #{file_path}λ₯Ό μΆκ°νμ΅λλ€.".colorize(:green)
|
1148
|
+
end
|
1149
|
+
else
|
1150
|
+
File.write(gitignore_file, "#{file_path}\n")
|
1151
|
+
puts "β
.gitignore νμΌμ μμ±νκ³ #{file_path}λ₯Ό μΆκ°νμ΅λλ€.".colorize(:green)
|
1152
|
+
end
|
1153
|
+
end
|
1154
|
+
|
1155
|
+
def create_tayo_config
|
1156
|
+
deploy_config = {
|
1157
|
+
"production" => {
|
1158
|
+
"registry" => @registry_url,
|
1159
|
+
"repository" => "https://github.com/#{@username}/#{@repo_name}",
|
1160
|
+
"server" => {
|
1161
|
+
"host" => "your-home-server.local",
|
1162
|
+
"user" => "deploy",
|
1163
|
+
"port" => 22
|
1164
|
+
},
|
1165
|
+
"environment" => {
|
1166
|
+
"RAILS_ENV" => "production",
|
1167
|
+
"RAILS_MASTER_KEY" => "your-master-key"
|
1168
|
+
}
|
1169
|
+
}
|
1170
|
+
}
|
1171
|
+
|
1172
|
+
File.write("config/deploy.yml", deploy_config.to_yaml)
|
1173
|
+
puts " β οΈ μλ² μ 보μ νκ²½ λ³μλ₯Ό μ€μ ν΄μ£ΌμΈμ.".colorize(:yellow)
|
1174
|
+
end
|
1175
|
+
|
1176
|
+
def commit_github_changes
|
1177
|
+
puts "\nπ λ³κ²½μ¬νμ Gitμ 컀λ°ν©λλ€...".colorize(:yellow)
|
1178
|
+
|
1179
|
+
# λ³κ²½λ νμΌμ΄ μλμ§ νμΈ
|
1180
|
+
status_output = `git status --porcelain`.strip
|
1181
|
+
|
1182
|
+
if status_output.empty?
|
1183
|
+
puts "βΉοΈ 컀λ°ν λ³κ²½μ¬νμ΄ μμ΅λλ€.".colorize(:yellow)
|
1184
|
+
return
|
1185
|
+
end
|
1186
|
+
|
1187
|
+
# Git add
|
1188
|
+
system("git add -A")
|
1189
|
+
|
1190
|
+
# Commit λ©μμ§ μμ±
|
1191
|
+
commit_message = "Add GitHub Container Registry configuration\n\n- Setup GitHub repository: #{@repo_name}\n- Configure container registry: #{@registry_url}\n- Add deployment configuration files\n- Setup environment variables\n\nπ€ Generated with Tayo"
|
1192
|
+
|
1193
|
+
# Commit μ€ν
|
1194
|
+
if system("git commit -m \"#{commit_message}\"")
|
1195
|
+
puts "β
λ³κ²½μ¬νμ΄ μ±κ³΅μ μΌλ‘ 컀λ°λμμ΅λλ€.".colorize(:green)
|
1196
|
+
|
1197
|
+
# GitHubμ νΈμ
|
1198
|
+
if system("git push", out: File::NULL, err: File::NULL)
|
1199
|
+
puts "β
λ³κ²½μ¬νμ΄ GitHubμ νΈμλμμ΅λλ€.".colorize(:green)
|
1200
|
+
else
|
1201
|
+
puts "β οΈ GitHub νΈμμ μ€ν¨νμ΅λλ€. μλμΌλ‘ 'git push'λ₯Ό μ€νν΄μ£ΌμΈμ.".colorize(:yellow)
|
1202
|
+
end
|
1203
|
+
else
|
1204
|
+
puts "β Git 컀λ°μ μ€ν¨νμ΅λλ€.".colorize(:red)
|
1205
|
+
end
|
1206
|
+
end
|
1207
|
+
end
|
1208
|
+
end
|
1209
|
+
end
|
1210
|
+
</file>
|
1211
|
+
|
1212
|
+
<file path="tayo.gemspec">
|
1213
|
+
# frozen_string_literal: true
|
1214
|
+
|
1215
|
+
require_relative "lib/tayo/version"
|
1216
|
+
|
1217
|
+
Gem::Specification.new do |spec|
|
1218
|
+
spec.name = "tayo"
|
1219
|
+
spec.version = Tayo::VERSION
|
1220
|
+
spec.authors = ["μ΄μμwonsup Lee/Alfonso"]
|
1221
|
+
spec.email = ["onesup.lee@gmail.com"]
|
1222
|
+
|
1223
|
+
spec.summary = "Rails deployment tool for home servers"
|
1224
|
+
spec.description = "Tayo is a deployment tool for Rails applications to home servers using GitHub Container Registry and Cloudflare CLI."
|
1225
|
+
spec.homepage = "https://github.com/TeamMilestone/tayo"
|
1226
|
+
spec.required_ruby_version = ">= 3.1.0"
|
1227
|
+
|
1228
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
1229
|
+
spec.metadata["source_code_uri"] = "https://github.com/TeamMilestone/tayo"
|
1230
|
+
spec.metadata["changelog_uri"] = "https://github.com/TeamMilestone/tayo/blob/main/CHANGELOG.md"
|
1231
|
+
|
1232
|
+
# Specify which files should be added to the gem when it is released.
|
1233
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
1234
|
+
gemspec = File.basename(__FILE__)
|
1235
|
+
spec.files = IO.popen(%w[git ls-files -z], chdir: __dir__, err: IO::NULL) do |ls|
|
1236
|
+
ls.readlines("\x0", chomp: true).reject do |f|
|
1237
|
+
(f == gemspec) ||
|
1238
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile])
|
1239
|
+
end
|
1240
|
+
end
|
1241
|
+
spec.bindir = "exe"
|
1242
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
1243
|
+
spec.require_paths = ["lib"]
|
1244
|
+
|
1245
|
+
spec.add_dependency "thor", "~> 1.3"
|
1246
|
+
spec.add_dependency "git", "~> 1.18"
|
1247
|
+
spec.add_dependency "colorize", "~> 1.1"
|
1248
|
+
spec.add_dependency "tty-prompt", "~> 0.23"
|
1249
|
+
|
1250
|
+
# For more information and examples about making a new gem, check out our
|
1251
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
1252
|
+
end
|
1253
|
+
</file>
|
1254
|
+
|
1255
|
+
<file path="lib/tayo/commands/init.rb">
|
1256
|
+
# frozen_string_literal: true
|
1257
|
+
|
1258
|
+
require "colorize"
|
1259
|
+
|
1260
|
+
module Tayo
|
1261
|
+
module Commands
|
1262
|
+
class Init
|
1263
|
+
def execute
|
1264
|
+
puts "π Tayo μ΄κΈ°νλ₯Ό μμν©λλ€...".colorize(:green)
|
1265
|
+
|
1266
|
+
unless rails_project?
|
1267
|
+
puts "β Rails νλ‘μ νΈκ° μλλλ€. Rails νλ‘μ νΈ λ£¨νΈμμ μ€νν΄μ£ΌμΈμ.".colorize(:red)
|
1268
|
+
return
|
1269
|
+
end
|
1270
|
+
|
1271
|
+
check_orbstack
|
1272
|
+
add_to_gemfile
|
1273
|
+
bundle_install
|
1274
|
+
add_linux_platform
|
1275
|
+
create_welcome_page
|
1276
|
+
commit_changes
|
1277
|
+
clear_docker_cache
|
1278
|
+
|
1279
|
+
puts "β
Tayoκ° μ±κ³΅μ μΌλ‘ μ€μ λμμ΅λλ€!".colorize(:green)
|
1280
|
+
end
|
1281
|
+
|
1282
|
+
private
|
1283
|
+
|
1284
|
+
def rails_project?
|
1285
|
+
File.exist?("Gemfile") && File.exist?("config/application.rb")
|
1286
|
+
end
|
1287
|
+
|
1288
|
+
def check_orbstack
|
1289
|
+
puts "π³ OrbStack μνλ₯Ό νμΈν©λλ€...".colorize(:yellow)
|
1290
|
+
|
1291
|
+
# OrbStack μ€ν μν νμΈ
|
1292
|
+
orbstack_running = system("pgrep -x OrbStack > /dev/null 2>&1")
|
1293
|
+
|
1294
|
+
if orbstack_running
|
1295
|
+
puts "β
OrbStackμ΄ μ€ν μ€μ
λλ€.".colorize(:green)
|
1296
|
+
else
|
1297
|
+
puts "π OrbStackμ μμν©λλ€...".colorize(:yellow)
|
1298
|
+
|
1299
|
+
# OrbStack μ€ν
|
1300
|
+
if system("open -a OrbStack")
|
1301
|
+
puts "β
OrbStackμ΄ μμλμμ΅λλ€.".colorize(:green)
|
1302
|
+
|
1303
|
+
# OrbStackμ΄ μμ ν μμλ λκΉμ§ μ μ λκΈ°
|
1304
|
+
print "Docker μλΉμ€κ° μ€λΉλ λκΉμ§ λκΈ° μ€".colorize(:yellow)
|
1305
|
+
5.times do
|
1306
|
+
sleep 1
|
1307
|
+
print ".".colorize(:yellow)
|
1308
|
+
end
|
1309
|
+
puts ""
|
1310
|
+
|
1311
|
+
# Dockerκ° μ€λΉλμλμ§ νμΈ
|
1312
|
+
if system("docker ps > /dev/null 2>&1")
|
1313
|
+
puts "β
Dockerκ° μ€λΉλμμ΅λλ€.".colorize(:green)
|
1314
|
+
else
|
1315
|
+
puts "β οΈ Dockerκ° μμ§ μ€λΉλμ§ μμμ΅λλ€. μ μ ν λ€μ μλν΄μ£ΌμΈμ.".colorize(:yellow)
|
1316
|
+
end
|
1317
|
+
else
|
1318
|
+
puts "β OrbStackμ μμν μ μμ΅λλ€.".colorize(:red)
|
1319
|
+
puts "OrbStackμ΄ μ€μΉλμ΄ μλμ§ νμΈν΄μ£ΌμΈμ.".colorize(:yellow)
|
1320
|
+
puts "https://orbstack.dev μμ λ€μ΄λ‘λν μ μμ΅λλ€.".colorize(:cyan)
|
1321
|
+
end
|
1322
|
+
end
|
1323
|
+
end
|
1324
|
+
|
1325
|
+
def add_to_gemfile
|
1326
|
+
gemfile_content = File.read("Gemfile")
|
1327
|
+
|
1328
|
+
if gemfile_content.include?("tayo")
|
1329
|
+
puts "βΉοΈ Tayoκ° μ΄λ―Έ Gemfileμ μμ΅λλ€.".colorize(:yellow)
|
1330
|
+
return
|
1331
|
+
end
|
1332
|
+
|
1333
|
+
development_group = gemfile_content.match(/group :development do\n(.*?)\nend/m)
|
1334
|
+
|
1335
|
+
if development_group
|
1336
|
+
updated_content = gemfile_content.sub(
|
1337
|
+
/group :development do\n/,
|
1338
|
+
"group :development do\n gem 'tayo'\n"
|
1339
|
+
)
|
1340
|
+
else
|
1341
|
+
updated_content = gemfile_content + "\n\ngroup :development do\n gem 'tayo'\nend\n"
|
1342
|
+
end
|
1343
|
+
|
1344
|
+
File.write("Gemfile", updated_content)
|
1345
|
+
puts "β
Gemfileμ Tayoλ₯Ό μΆκ°νμ΅λλ€.".colorize(:green)
|
1346
|
+
end
|
1347
|
+
|
1348
|
+
def bundle_install
|
1349
|
+
puts "π¦ bundle installμ μ€νν©λλ€...".colorize(:yellow)
|
1350
|
+
system("bundle install")
|
1351
|
+
end
|
1352
|
+
|
1353
|
+
def add_linux_platform
|
1354
|
+
puts "π§ Linux νλ«νΌμ νμΈνκ³ μΆκ°ν©λλ€...".colorize(:yellow)
|
1355
|
+
|
1356
|
+
# Gemfile.lock νμΌ νμΈ
|
1357
|
+
unless File.exist?("Gemfile.lock")
|
1358
|
+
puts "β οΈ Gemfile.lock νμΌμ΄ μμ΅λλ€. bundle installμ λ¨Όμ μ€νν΄μ£ΌμΈμ.".colorize(:yellow)
|
1359
|
+
return
|
1360
|
+
end
|
1361
|
+
|
1362
|
+
gemfile_lock_content = File.read("Gemfile.lock")
|
1363
|
+
platforms_needed = []
|
1364
|
+
|
1365
|
+
# νμν νλ«νΌ νμΈ
|
1366
|
+
unless gemfile_lock_content.include?("x86_64-linux")
|
1367
|
+
platforms_needed << "x86_64-linux"
|
1368
|
+
end
|
1369
|
+
|
1370
|
+
unless gemfile_lock_content.include?("aarch64-linux")
|
1371
|
+
platforms_needed << "aarch64-linux"
|
1372
|
+
end
|
1373
|
+
|
1374
|
+
if platforms_needed.empty?
|
1375
|
+
puts "β
νμν Linux νλ«νΌμ΄ μ΄λ―Έ μΆκ°λμ΄ μμ΅λλ€.".colorize(:green)
|
1376
|
+
return
|
1377
|
+
end
|
1378
|
+
|
1379
|
+
# νλ«νΌ μΆκ°
|
1380
|
+
platforms_needed.each do |platform|
|
1381
|
+
puts "π¦ #{platform} νλ«νΌμ μΆκ°ν©λλ€...".colorize(:yellow)
|
1382
|
+
if system("bundle lock --add-platform #{platform}")
|
1383
|
+
puts "β
#{platform} νλ«νΌμ΄ μΆκ°λμμ΅λλ€.".colorize(:green)
|
1384
|
+
else
|
1385
|
+
puts "β #{platform} νλ«νΌ μΆκ°μ μ€ν¨νμ΅λλ€.".colorize(:red)
|
1386
|
+
end
|
1387
|
+
end
|
1388
|
+
|
1389
|
+
# Dockerfile νμΈ λ° μμ±
|
1390
|
+
ensure_dockerfile_exists
|
1391
|
+
end
|
1392
|
+
|
1393
|
+
def ensure_dockerfile_exists
|
1394
|
+
dockerfile_created = false
|
1395
|
+
|
1396
|
+
unless File.exist?("Dockerfile")
|
1397
|
+
puts "π³ Dockerfileμ΄ μμ΅λλ€. κΈ°λ³Έ Dockerfileμ μμ±ν©λλ€...".colorize(:yellow)
|
1398
|
+
|
1399
|
+
# Rails 7μ κΈ°λ³Έ Dockerfile μμ±
|
1400
|
+
if system("rails app:update:bin")
|
1401
|
+
system("./bin/rails generate dockerfile")
|
1402
|
+
puts "β
Dockerfileμ΄ μμ±λμμ΅λλ€.".colorize(:green)
|
1403
|
+
dockerfile_created = true
|
1404
|
+
else
|
1405
|
+
puts "β οΈ Dockerfile μμ±μ μ€ν¨νμ΅λλ€. μλμΌλ‘ μμ±ν΄μ£ΌμΈμ.".colorize(:yellow)
|
1406
|
+
puts " λ€μ λͺ
λ Ήμ΄λ₯Ό μ€ννμΈμ: ./bin/rails generate dockerfile".colorize(:cyan)
|
1407
|
+
return
|
1408
|
+
end
|
1409
|
+
else
|
1410
|
+
puts "β
Dockerfileμ΄ μ΄λ―Έ μ‘΄μ¬ν©λλ€.".colorize(:green)
|
1411
|
+
end
|
1412
|
+
|
1413
|
+
# Dockerfileμμ bootsnap precompile λΆλΆ μ κ±° (λΉλ λ¬Έμ ν΄κ²°)
|
1414
|
+
fix_dockerfile_bootsnap_issue
|
1415
|
+
end
|
1416
|
+
|
1417
|
+
def fix_dockerfile_bootsnap_issue
|
1418
|
+
return unless File.exist?("Dockerfile")
|
1419
|
+
|
1420
|
+
dockerfile_content = File.read("Dockerfile")
|
1421
|
+
original_content = dockerfile_content.dup
|
1422
|
+
|
1423
|
+
# Dockerfileμ λΌμΈλ³λ‘ μ²λ¦¬
|
1424
|
+
lines = dockerfile_content.split("\n")
|
1425
|
+
filtered_lines = []
|
1426
|
+
skip_next = false
|
1427
|
+
|
1428
|
+
lines.each_with_index do |line, index|
|
1429
|
+
# bootsnap κ΄λ ¨ μ£Όμ μ°ΎκΈ°
|
1430
|
+
if line.match?(/^\s*#.*bootsnap.*faster boot times/i)
|
1431
|
+
skip_next = true # λ€μ λΌμΈλ μ κ±°ν μ€λΉ
|
1432
|
+
next # μ΄ λΌμΈμ μ κ±°
|
1433
|
+
end
|
1434
|
+
|
1435
|
+
# bootsnap precompile RUN λͺ
λ Ή μ°ΎκΈ°
|
1436
|
+
if line.match?(/^\s*RUN.*bootsnap\s+precompile/i)
|
1437
|
+
skip_next = false # 리μ
|
1438
|
+
next # μ΄ λΌμΈμ μ κ±°
|
1439
|
+
end
|
1440
|
+
|
1441
|
+
# skip_nextκ° trueμ΄κ³ νμ¬ λΌμΈμ΄ RUN bootsnapμ΄λ©΄ μ κ±°
|
1442
|
+
if skip_next && line.match?(/^\s*RUN.*bootsnap/i)
|
1443
|
+
skip_next = false
|
1444
|
+
next
|
1445
|
+
end
|
1446
|
+
|
1447
|
+
skip_next = false
|
1448
|
+
filtered_lines << line
|
1449
|
+
end
|
1450
|
+
|
1451
|
+
new_content = filtered_lines.join("\n") + "\n"
|
1452
|
+
|
1453
|
+
if new_content != original_content
|
1454
|
+
File.write("Dockerfile", new_content)
|
1455
|
+
puts "β
Dockerfileμμ bootsnap precompile λΆλΆμ μ κ±°νμ΅λλ€. (λΉλ λ¬Έμ ν΄κ²°)".colorize(:green)
|
1456
|
+
puts " - μ΄λ μλ €μ§ λΉλ λ¬Έμ λ₯Ό λ°©μ§νκΈ° μν¨μ
λλ€.".colorize(:gray)
|
1457
|
+
end
|
1458
|
+
end
|
1459
|
+
|
1460
|
+
def create_welcome_page
|
1461
|
+
# Welcome 컨νΈλ‘€λ¬κ° μ΄λ―Έ μλμ§ νμΈ
|
1462
|
+
if File.exist?("app/controllers/welcome_controller.rb")
|
1463
|
+
puts "βΉοΈ Welcome νμ΄μ§κ° μ΄λ―Έ μ‘΄μ¬ν©λλ€.".colorize(:yellow)
|
1464
|
+
@welcome_page_created = false
|
1465
|
+
return
|
1466
|
+
end
|
1467
|
+
|
1468
|
+
puts "π¨ Welcome νμ΄μ§λ₯Ό μμ±ν©λλ€...".colorize(:yellow)
|
1469
|
+
|
1470
|
+
# Welcome 컨νΈλ‘€λ¬ μμ±
|
1471
|
+
system("rails generate controller Welcome index --skip-routes --no-helper --no-assets")
|
1472
|
+
|
1473
|
+
# νλ‘μ νΈ μ΄λ¦ κ°μ Έμ€κΈ°
|
1474
|
+
project_name = File.basename(Dir.pwd).gsub(/[-_]/, ' ').split.map(&:capitalize).join(' ')
|
1475
|
+
|
1476
|
+
# Welcome νμ΄μ§ HTML μμ±
|
1477
|
+
welcome_html = <<~HTML
|
1478
|
+
<!DOCTYPE html>
|
1479
|
+
<html>
|
1480
|
+
<head>
|
1481
|
+
<title>#{project_name} - Welcome</title>
|
1482
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
1483
|
+
<style>
|
1484
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
1485
|
+
body {
|
1486
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
1487
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
1488
|
+
min-height: 100vh;
|
1489
|
+
display: flex;
|
1490
|
+
align-items: center;
|
1491
|
+
justify-content: center;
|
1492
|
+
color: white;
|
1493
|
+
}
|
1494
|
+
.container {
|
1495
|
+
text-align: center;
|
1496
|
+
padding: 2rem;
|
1497
|
+
max-width: 800px;
|
1498
|
+
animation: fadeIn 1s ease-out;
|
1499
|
+
}
|
1500
|
+
h1 {
|
1501
|
+
font-size: 4rem;
|
1502
|
+
margin-bottom: 1rem;
|
1503
|
+
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
1504
|
+
animation: slideDown 0.8s ease-out;
|
1505
|
+
}
|
1506
|
+
.subtitle {
|
1507
|
+
font-size: 1.5rem;
|
1508
|
+
margin-bottom: 3rem;
|
1509
|
+
opacity: 0.9;
|
1510
|
+
animation: slideUp 0.8s ease-out 0.2s both;
|
1511
|
+
}
|
1512
|
+
.info-grid {
|
1513
|
+
display: grid;
|
1514
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
1515
|
+
gap: 2rem;
|
1516
|
+
margin-top: 3rem;
|
1517
|
+
}
|
1518
|
+
.info-card {
|
1519
|
+
background: rgba(255,255,255,0.1);
|
1520
|
+
backdrop-filter: blur(10px);
|
1521
|
+
padding: 2rem;
|
1522
|
+
border-radius: 10px;
|
1523
|
+
border: 1px solid rgba(255,255,255,0.2);
|
1524
|
+
animation: fadeIn 0.8s ease-out 0.4s both;
|
1525
|
+
}
|
1526
|
+
.info-card h3 {
|
1527
|
+
font-size: 1.2rem;
|
1528
|
+
margin-bottom: 0.5rem;
|
1529
|
+
}
|
1530
|
+
.info-card p {
|
1531
|
+
opacity: 0.8;
|
1532
|
+
font-size: 0.9rem;
|
1533
|
+
}
|
1534
|
+
.deploy-badge {
|
1535
|
+
display: inline-block;
|
1536
|
+
background: rgba(255,255,255,0.2);
|
1537
|
+
padding: 0.5rem 1rem;
|
1538
|
+
border-radius: 20px;
|
1539
|
+
margin-top: 2rem;
|
1540
|
+
font-size: 0.9rem;
|
1541
|
+
animation: pulse 2s infinite;
|
1542
|
+
}
|
1543
|
+
@keyframes fadeIn {
|
1544
|
+
from { opacity: 0; transform: translateY(20px); }
|
1545
|
+
to { opacity: 1; transform: translateY(0); }
|
1546
|
+
}
|
1547
|
+
@keyframes slideDown {
|
1548
|
+
from { opacity: 0; transform: translateY(-30px); }
|
1549
|
+
to { opacity: 1; transform: translateY(0); }
|
1550
|
+
}
|
1551
|
+
@keyframes slideUp {
|
1552
|
+
from { opacity: 0; transform: translateY(30px); }
|
1553
|
+
to { opacity: 1; transform: translateY(0); }
|
1554
|
+
}
|
1555
|
+
@keyframes pulse {
|
1556
|
+
0%, 100% { opacity: 1; }
|
1557
|
+
50% { opacity: 0.8; }
|
1558
|
+
}
|
1559
|
+
</style>
|
1560
|
+
</head>
|
1561
|
+
<body>
|
1562
|
+
<div class="container">
|
1563
|
+
<h1>π #{project_name}</h1>
|
1564
|
+
<p class="subtitle">Welcome to your Tayo-powered Rails application!</p>
|
1565
|
+
|
1566
|
+
<div class="info-grid">
|
1567
|
+
<div class="info-card">
|
1568
|
+
<h3>π¦ Container Ready</h3>
|
1569
|
+
<p>Your app is configured for container deployment</p>
|
1570
|
+
</div>
|
1571
|
+
<div class="info-card">
|
1572
|
+
<h3>π GitHub Integration</h3>
|
1573
|
+
<p>Ready to push to GitHub Container Registry</p>
|
1574
|
+
</div>
|
1575
|
+
<div class="info-card">
|
1576
|
+
<h3>βοΈ Cloudflare DNS</h3>
|
1577
|
+
<p>Domain management simplified</p>
|
1578
|
+
</div>
|
1579
|
+
</div>
|
1580
|
+
|
1581
|
+
<div class="deploy-badge">
|
1582
|
+
Deployed with Tayo π
|
1583
|
+
</div>
|
1584
|
+
</div>
|
1585
|
+
</body>
|
1586
|
+
</html>
|
1587
|
+
HTML
|
1588
|
+
|
1589
|
+
# Welcome λ·° νμΌμ μ μ₯
|
1590
|
+
welcome_view_path = "app/views/welcome/index.html.erb"
|
1591
|
+
File.write(welcome_view_path, welcome_html)
|
1592
|
+
|
1593
|
+
# routes.rb μ
λ°μ΄νΈ
|
1594
|
+
routes_file = "config/routes.rb"
|
1595
|
+
routes_content = File.read(routes_file)
|
1596
|
+
|
1597
|
+
# root κ²½λ‘ μ€μ - welcome#indexκ° μ΄λ―Έ μλμ§ νμΈ
|
1598
|
+
unless routes_content.include?("welcome#index")
|
1599
|
+
if routes_content.match?(/^\s*root\s+/)
|
1600
|
+
# κΈ°μ‘΄ root μ€μ μ΄ μμΌλ©΄ κ΅μ²΄
|
1601
|
+
routes_content.gsub!(/^\s*root\s+.*$/, " root 'welcome#index'")
|
1602
|
+
else
|
1603
|
+
# root μ€μ μ΄ μμΌλ©΄ μΆκ°
|
1604
|
+
routes_content.gsub!(/Rails\.application\.routes\.draw do\s*\n/, "Rails.application.routes.draw do\n root 'welcome#index'\n")
|
1605
|
+
end
|
1606
|
+
|
1607
|
+
File.write(routes_file, routes_content)
|
1608
|
+
puts " β
routes.rbμ root κ²½λ‘λ₯Ό μ€μ νμ΅λλ€.".colorize(:green)
|
1609
|
+
else
|
1610
|
+
puts " βΉοΈ routes.rbμ welcome#indexκ° μ΄λ―Έ μ€μ λμ΄ μμ΅λλ€.".colorize(:yellow)
|
1611
|
+
end
|
1612
|
+
|
1613
|
+
puts "β
Welcome νμ΄μ§κ° μμ±λμμ΅λλ€!".colorize(:green)
|
1614
|
+
puts " κ²½λ‘: /".colorize(:gray)
|
1615
|
+
puts " 컨νΈλ‘€λ¬: app/controllers/welcome_controller.rb".colorize(:gray)
|
1616
|
+
puts " λ·°: app/views/welcome/index.html.erb".colorize(:gray)
|
1617
|
+
|
1618
|
+
@welcome_page_created = true
|
1619
|
+
end
|
1620
|
+
|
1621
|
+
def commit_changes
|
1622
|
+
# Git μ μ₯μμΈμ§ νμΈ
|
1623
|
+
unless Dir.exist?(".git")
|
1624
|
+
puts "β οΈ Git μ μ₯μκ° μλλλ€. 컀λ°μ 건λλλλ€.".colorize(:yellow)
|
1625
|
+
return
|
1626
|
+
end
|
1627
|
+
|
1628
|
+
# Welcome νμ΄μ§κ° μλ‘ μμ±λ κ²½μ°μλ§ μ»€λ°
|
1629
|
+
unless @welcome_page_created
|
1630
|
+
puts "βΉοΈ μλ‘μ΄ λ³κ²½μ¬νμ΄ μμ΄ μ»€λ°μ 건λλλλ€.".colorize(:yellow)
|
1631
|
+
return
|
1632
|
+
end
|
1633
|
+
|
1634
|
+
puts "π λ³κ²½μ¬νμ Gitμ 컀λ°ν©λλ€...".colorize(:yellow)
|
1635
|
+
|
1636
|
+
# Git μν νμΈ
|
1637
|
+
git_status = `git status --porcelain`
|
1638
|
+
|
1639
|
+
if git_status.strip.empty?
|
1640
|
+
puts "βΉοΈ 컀λ°ν λ³κ²½μ¬νμ΄ μμ΅λλ€.".colorize(:yellow)
|
1641
|
+
return
|
1642
|
+
end
|
1643
|
+
|
1644
|
+
# λ³κ²½μ¬ν μ€ν
μ΄μ§
|
1645
|
+
system("git add .")
|
1646
|
+
|
1647
|
+
# 컀λ°
|
1648
|
+
commit_message = "Add Tayo configuration and Welcome page"
|
1649
|
+
if system("git commit -m '#{commit_message}'")
|
1650
|
+
puts "β
λ³κ²½μ¬νμ΄ μ»€λ°λμμ΅λλ€.".colorize(:green)
|
1651
|
+
puts " μ»€λ° λ©μμ§: #{commit_message}".colorize(:gray)
|
1652
|
+
else
|
1653
|
+
puts "β οΈ μ»€λ°μ μ€ν¨νμ΅λλ€.".colorize(:yellow)
|
1654
|
+
end
|
1655
|
+
end
|
1656
|
+
|
1657
|
+
def clear_docker_cache
|
1658
|
+
puts "π§Ή Docker μΊμλ₯Ό μ 리ν©λλ€...".colorize(:yellow)
|
1659
|
+
|
1660
|
+
# Docker system prune
|
1661
|
+
if system("docker system prune -f > /dev/null 2>&1")
|
1662
|
+
puts "β
Docker μμ€ν
μΊμκ° μ 리λμμ΅λλ€.".colorize(:green)
|
1663
|
+
else
|
1664
|
+
puts "β οΈ Docker μμ€ν
μ 리μ μ€ν¨νμ΅λλ€.".colorize(:yellow)
|
1665
|
+
end
|
1666
|
+
|
1667
|
+
# Kamal build cache clear
|
1668
|
+
if File.exist?("config/deploy.yml")
|
1669
|
+
puts "π’ Kamal λΉλ μΊμλ₯Ό μ 리ν©λλ€...".colorize(:yellow)
|
1670
|
+
if system("kamal build --clear-cache > /dev/null 2>&1")
|
1671
|
+
puts "β
Kamal λΉλ μΊμκ° μ 리λμμ΅λλ€.".colorize(:green)
|
1672
|
+
else
|
1673
|
+
puts "β οΈ Kamal λΉλ μΊμ μ 리μ μ€ν¨νμ΅λλ€.".colorize(:yellow)
|
1674
|
+
end
|
1675
|
+
end
|
1676
|
+
end
|
1677
|
+
end
|
1678
|
+
end
|
1679
|
+
end
|
1680
|
+
</file>
|
1681
|
+
|
1682
|
+
<file path="lib/tayo/version.rb">
|
1683
|
+
# frozen_string_literal: true
|
1684
|
+
|
1685
|
+
module Tayo
|
1686
|
+
VERSION = "0.1.5"
|
1687
|
+
end
|
1688
|
+
</file>
|
1689
|
+
|
1690
|
+
</files>
|