Compare commits
14 Commits
e947a84dcb
...
on-prem
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdfdf15166 | ||
|
|
2bfe52fb7b | ||
|
|
cdf2de5d9f | ||
|
|
c089195ea9 | ||
|
|
4f1705c3b7 | ||
|
|
e5caa27e91 | ||
|
|
d1e45edcdb | ||
|
|
980d8533d8 | ||
|
|
888d45683f | ||
|
|
0f2b29fe29 | ||
|
|
5c7a24bc36 | ||
|
|
1d3039c525 | ||
|
|
4b7f422acd | ||
|
|
6a55d33a6d |
11
.env
11
.env
@@ -1 +1,12 @@
|
|||||||
|
CLOUD_DATABASE_URL=postgresql://postgres:6vJj04eYUCKKozYE@db.qnerebtvwwfobfzdoftx.supabase.co:5432/postgres
|
||||||
|
ONPREM_DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres # On-Prem Postgres 기본
|
||||||
|
|
||||||
|
VITE_SUPABASE_URL=http://localhost:9000
|
||||||
|
VITE_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE
|
||||||
|
CLOUD_SUPABASE_URL=https://qnerebtvwwfobfzdoftx.supabase.co
|
||||||
|
CLOUD_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFuZXJlYnR2d3dmb2JmemRvZnR4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDIwNTE0MzgsImV4cCI6MjA1NzYyNzQzOH0.Wm7h2DUhoQbeANuEM3wm2tz22ITrVEW8FizyLgIVmv8
|
||||||
|
CLOUD_SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFuZXJlYnR2d3dmb2JmemRvZnR4Iiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImlhdCI6MTc0MjA1MTQzOCwiZXhwIjoyMDU3NjI3NDM4fQ.3G9UksB-kE-ChGQrz6YrSZqQSqvzYsnhvZyCnE99Ifc
|
||||||
|
ONPREM_SUPABASE_URL=http://localhost:9000
|
||||||
|
ONPREM_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE
|
||||||
|
ONPREM_SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJzZXJ2aWNlX3JvbGUiLAogICAgImlzcyI6ICJzdXBhYmFzZS1kZW1vIiwKICAgICJpYXQiOiAxNjQxNzY5MjAwLAogICAgImV4cCI6IDE3OTk1MzU2MDAKfQ.DaYlNEoUrrEn2Ig7tqibS-PHK5vgusbcbo7X36XVt4Q
|
||||||
VITE_DISABLE_LOVABLE_BANNER=true
|
VITE_DISABLE_LOVABLE_BANNER=true
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,6 @@
|
|||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
#*.log
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|||||||
116
WEB_SERVER_SETUP.md
Normal file
116
WEB_SERVER_SETUP.md
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
# 웹 서버 설치 & 배포 가이드
|
||||||
|
|
||||||
|
## 1. Next.js 앱 단일 배포 (Ubuntu 22.04 + Nginx)
|
||||||
|
|
||||||
|
### 1.1 서버 준비
|
||||||
|
```bash
|
||||||
|
ssh your_user@your_server_ip
|
||||||
|
# Node.js 18.x 설치
|
||||||
|
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
|
||||||
|
sudo apt-get install -y nodejs build-essential
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 코드 클론
|
||||||
|
```bash
|
||||||
|
cd /var/www
|
||||||
|
git clone https://github.com/your-org/your-repo.git my-nextjs
|
||||||
|
cd my-nextjs
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 의존성 설치 & 빌드
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
# 또는 yarn
|
||||||
|
# yarn
|
||||||
|
yarn build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 PM2로 서비스 등록
|
||||||
|
```bash
|
||||||
|
sudo npm install -g pm2
|
||||||
|
pm2 start npm --name "my-nextjs" -- start
|
||||||
|
pm2 save
|
||||||
|
pm2 startup # 출력된 명령 복사 후 실행
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.5 Nginx 리버스 프록시 설정
|
||||||
|
```nginx
|
||||||
|
# /etc/nginx/sites-available/my-nextjs.conf
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your.domain.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection 'upgrade';
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
sudo ln -s /etc/nginx/sites-available/my-nextjs.conf /etc/nginx/sites-enabled/
|
||||||
|
sudo nginx -t && sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## 2. 한 서버에 여러 사이트 호스팅
|
||||||
|
|
||||||
|
### 2.1 앱별 포트 분리
|
||||||
|
- 디렉터리별 배치: `/var/www/site-alpha`, `/var/www/site-beta`
|
||||||
|
- `package.json` start 스크립트에 포트 지정
|
||||||
|
```jsonc
|
||||||
|
"scripts": {
|
||||||
|
"start": "next start -p 3000"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 PM2로 프로세스 분리
|
||||||
|
```bash
|
||||||
|
# site-alpha
|
||||||
|
cd /var/www/site-alpha
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
pm2 start npm --name site-alpha -- start
|
||||||
|
|
||||||
|
# site-beta
|
||||||
|
cd /var/www/site-beta
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
pm2 start npm --name site-beta -- start
|
||||||
|
|
||||||
|
pm2 save
|
||||||
|
pm2 startup
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Nginx 도메인별 서버블록 설정
|
||||||
|
```nginx
|
||||||
|
# /etc/nginx/sites-available/site-alpha.conf
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name alpha.example.com;
|
||||||
|
location / { proxy_pass http://127.0.0.1:3000; }
|
||||||
|
}
|
||||||
|
|
||||||
|
# /etc/nginx/sites-available/site-beta.conf
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name beta.example.com;
|
||||||
|
location / { proxy_pass http://127.0.0.1:3001; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
```bash
|
||||||
|
sudo ln -s /etc/nginx/sites-available/{site-alpha.conf,site-beta.conf} /etc/nginx/sites-enabled/
|
||||||
|
sudo nginx -t && sudo systemctl reload nginx
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 (선택) SSL 자동 발급
|
||||||
|
- Let's Encrypt + Certbot 설치
|
||||||
|
```bash
|
||||||
|
sudo apt-get install certbot python3-certbot-nginx
|
||||||
|
sudo certbot --nginx -d alpha.example.com -d beta.example.com
|
||||||
|
```
|
||||||
@@ -3,8 +3,10 @@ apply plugin: 'com.android.application'
|
|||||||
// 버전 정보를 properties 파일에서 동적으로 로드
|
// 버전 정보를 properties 파일에서 동적으로 로드
|
||||||
android {
|
android {
|
||||||
namespace "com.lovable.zellyfinance"
|
namespace "com.lovable.zellyfinance"
|
||||||
compileSdk rootProject.ext.compileSdkVersion
|
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
|
versionCode = 9
|
||||||
|
versionName = "1.1.8"
|
||||||
applicationId "com.lovable.zellyfinance"
|
applicationId "com.lovable.zellyfinance"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
@@ -50,7 +52,7 @@ android {
|
|||||||
|
|
||||||
// 로드된 값이 유효한지 확인하고, 유효하지 않으면 기본값 사용
|
// 로드된 값이 유효한지 확인하고, 유효하지 않으면 기본값 사용
|
||||||
def versionName = defaultVersionName
|
def versionName = defaultVersionName
|
||||||
def versionCode = defaultVersionCode
|
def versionCode = 9
|
||||||
def buildNumber = defaultBuildNumber
|
def buildNumber = defaultBuildNumber
|
||||||
|
|
||||||
if (versionProps['versionName'] && !versionProps['versionName'].toString().trim().isEmpty()) {
|
if (versionProps['versionName'] && !versionProps['versionName'].toString().trim().isEmpty()) {
|
||||||
@@ -61,14 +63,17 @@ android {
|
|||||||
|
|
||||||
if (versionProps['versionCode'] && !versionProps['versionCode'].toString().trim().isEmpty()) {
|
if (versionProps['versionCode'] && !versionProps['versionCode'].toString().trim().isEmpty()) {
|
||||||
try {
|
try {
|
||||||
|
// 버전 코드는 고정값 8을 사용하므로 properties에서 읽지 않음
|
||||||
|
/*
|
||||||
versionCode = versionProps['versionCode'].toString().toInteger()
|
versionCode = versionProps['versionCode'].toString().toInteger()
|
||||||
if (versionCode <= 0) {
|
if (versionCode <= 0) {
|
||||||
println "유효하지 않은 versionCode(0 이하), 기본값 사용: ${defaultVersionCode}"
|
println "유효하지 않은 versionCode(0 이하), 기본값 사용: ${defaultVersionCode}"
|
||||||
versionCode = defaultVersionCode
|
versionCode = defaultVersionCode
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
println "versionCode 변환 오류, 기본값 사용: ${e.message}"
|
println "versionCode 변환 오류, 기본값 사용: ${e.message}"
|
||||||
versionCode = defaultVersionCode
|
// versionCode = defaultVersionCode
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println "유효하지 않은 versionCode, 기본값 사용: ${defaultVersionCode}"
|
println "유효하지 않은 versionCode, 기본값 사용: ${defaultVersionCode}"
|
||||||
@@ -92,9 +97,14 @@ android {
|
|||||||
// 최종 로그 출력
|
// 최종 로그 출력
|
||||||
println "최종 버전 정보: versionName=${versionName}, versionCode=${versionCode}, buildNumber=${buildNumber}"
|
println "최종 버전 정보: versionName=${versionName}, versionCode=${versionCode}, buildNumber=${buildNumber}"
|
||||||
|
|
||||||
// 앱 빌드 속성 설정 - 문법 오류 수정
|
versionCode = 8
|
||||||
versionName = versionName
|
|
||||||
versionCode = versionCode
|
// 앱 빌드 속성 설정
|
||||||
|
// 버전 코드는 이미 고정값 8로 설정됨
|
||||||
|
// versionCode = versionCode.toInteger()
|
||||||
|
versionName = "${versionName}"
|
||||||
|
|
||||||
|
// 이 부분이 중요합니다 - 이 속성들이 안드로이드 매니페스트에 자동으로 병합됩니다
|
||||||
|
|
||||||
// BuildConfig 필드 설정
|
// BuildConfig 필드 설정
|
||||||
buildConfigField "String", "VERSION_NAME", "\"${versionName}\""
|
buildConfigField "String", "VERSION_NAME", "\"${versionName}\""
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:versionCode="9"
|
||||||
|
android:versionName="1.1.8">
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
buildNumber=7
|
# Zellyy Finance 앱 버전 정보
|
||||||
versionCode=7
|
# 마지막 업데이트: 2025-04-05 19:45:54
|
||||||
versionName=1.1.1.3
|
buildNumber=9
|
||||||
|
versionCode=9
|
||||||
|
versionName=1.1.8
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"versionCode": 7,
|
"versionCode": 9,
|
||||||
"versionName": "1.1.1.3",
|
"versionName": "1.1.8",
|
||||||
"buildNumber": 7,
|
"buildNumber": 9,
|
||||||
|
"buildDate": "2025-04-05 19:45:54",
|
||||||
|
"buildType": "release-aab",
|
||||||
"notes": "사용자가 수정한 버전 정보입니다. 이 파일을 편집하여 앱 버전 정보를 변경할 수 있습니다."
|
"notes": "사용자가 수정한 버전 정보입니다. 이 파일을 편집하여 앱 버전 정보를 변경할 수 있습니다."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:8.9.0'
|
classpath 'com.android.tools.build:gradle:8.9.1'
|
||||||
classpath 'com.google.gms:google-services:4.4.2'
|
classpath 'com.google.gms:google-services:4.4.2'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ org.gradle.jvmargs=-Xmx1536m
|
|||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
|
|
||||||
# Java 및 Kotlin 버전 설정
|
# Java 및 Kotlin 버전 설정
|
||||||
org.gradle.java.home=/opt/homebrew/Cellar/openjdk@17/17.0.14/libexec/openjdk.jdk/Contents/Home
|
# org.gradle.java.home is commented out to allow default or JAVA_HOME usage
|
||||||
|
# org.gradle.java.home=/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home
|
||||||
android.defaults.buildfeatures.buildconfig=true
|
android.defaults.buildfeatures.buildconfig=true
|
||||||
android.nonTransitiveRClass=false
|
android.nonTransitiveRClass=false
|
||||||
android.nonFinalResIds=false
|
android.nonFinalResIds=false
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
buildNumber=7
|
# Zellyy Finance 앱 버전 정보
|
||||||
versionCode=7
|
# 마지막 업데이트: 2025-04-05 19:45:54
|
||||||
versionName=1.1.1.3
|
buildNumber=9
|
||||||
|
versionCode=9
|
||||||
|
versionName=1.1.8
|
||||||
|
|||||||
284
app_build.log
Normal file
284
app_build.log
Normal file
@@ -0,0 +1,284 @@
|
|||||||
|
===== 빌드 시작: Sat Apr 5 19:45:45 KST 2025 =====
|
||||||
|
[1;33m1. 웹 앱 빌드 중...[0m
|
||||||
|
실행 명령어: npm run build
|
||||||
|
|
||||||
|
> vite_react_shadcn_ts@0.0.0 build
|
||||||
|
> vite build
|
||||||
|
|
||||||
|
vite v5.4.10 building for production...
|
||||||
|
transforming...
|
||||||
|
✓ 3636 modules transformed.
|
||||||
|
rendering chunks...
|
||||||
|
computing gzip size...
|
||||||
|
dist/index.html 0.72 kB │ gzip: 0.46 kB
|
||||||
|
dist/assets/index-BgIUBQkk.css 74.73 kB │ gzip: 12.82 kB
|
||||||
|
dist/assets/browser-Q2e0CuoM.js 0.30 kB │ gzip: 0.25 kB
|
||||||
|
dist/assets/index-ukZ_MYNA.js 1,155.73 kB │ gzip: 335.16 kB
|
||||||
|
|
||||||
|
(!) Some chunks are larger than 500 kB after minification. Consider:
|
||||||
|
- Using dynamic import() to code-split the application
|
||||||
|
- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/configuration-options/#output-manualchunks
|
||||||
|
- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.
|
||||||
|
✓ built in 3.88s
|
||||||
|
[0;32m웹 앱 빌드 완료[0m
|
||||||
|
[1;33m2. Capacitor에 웹 코드 동기화 중...[0m
|
||||||
|
실행 명령어: npx cap sync android
|
||||||
|
✔ Copying web assets from dist to android/app/src/main/assets/public in 5.47ms
|
||||||
|
✔ Creating capacitor.config.json in android/app/src/main/assets in 1.94ms
|
||||||
|
✔ copy android in 30.43ms
|
||||||
|
✔ Updating Android plugins in 6.52ms
|
||||||
|
[info] Found 2 Capacitor plugins for android:
|
||||||
|
@capacitor/keyboard@7.0.0
|
||||||
|
@capacitor/splash-screen@7.0.0
|
||||||
|
✔ update android in 88.42ms
|
||||||
|
[info] Sync finished in 0.152s
|
||||||
|
[0;32mCapacitor 동기화 완료[0m
|
||||||
|
[1;33m3. 안드로이드 빌드 시작 (release-aab)...[0m
|
||||||
|
실행 명령어: ./gradlew bundleRelease
|
||||||
|
|
||||||
|
> Configure project :app
|
||||||
|
WARNING: The option setting 'android.defaults.buildfeatures.buildconfig=true' is deprecated.
|
||||||
|
The current default is 'false'.
|
||||||
|
It will be removed in version 10.0 of the Android Gradle plugin.
|
||||||
|
To keep using this feature, add the following to your module-level build.gradle files:
|
||||||
|
android.buildFeatures.buildConfig = true
|
||||||
|
or from Android Studio, click: `Refactor` > `Migrate BuildConfig to Gradle Build Files`.
|
||||||
|
버전 정보 로드: versionName=1.1.8, versionCode=9, buildNumber=9
|
||||||
|
최종 버전 정보: versionName=1.1.8, versionCode=9, buildNumber=9
|
||||||
|
WARNING: Using flatDir should be avoided because it doesn't support any meta-data formats.
|
||||||
|
|
||||||
|
> Configure project :capacitor-cordova-android-plugins
|
||||||
|
WARNING: Using flatDir should be avoided because it doesn't support any meta-data formats.
|
||||||
|
|
||||||
|
> Task :app:preBuild UP-TO-DATE
|
||||||
|
> Task :app:preReleaseBuild UP-TO-DATE
|
||||||
|
> Task :app:generateReleaseResValues
|
||||||
|
> Task :capacitor-android:preBuild UP-TO-DATE
|
||||||
|
> Task :capacitor-android:preReleaseBuild UP-TO-DATE
|
||||||
|
> Task :capacitor-android:generateReleaseResValues
|
||||||
|
> Task :capacitor-android:generateReleaseResources
|
||||||
|
> Task :capacitor-android:packageReleaseResources
|
||||||
|
> Task :capacitor-cordova-android-plugins:preBuild UP-TO-DATE
|
||||||
|
> Task :capacitor-cordova-android-plugins:preReleaseBuild UP-TO-DATE
|
||||||
|
> Task :capacitor-cordova-android-plugins:generateReleaseResValues
|
||||||
|
> Task :capacitor-cordova-android-plugins:generateReleaseResources
|
||||||
|
> Task :capacitor-cordova-android-plugins:packageReleaseResources
|
||||||
|
> Task :capacitor-keyboard:preBuild UP-TO-DATE
|
||||||
|
> Task :capacitor-keyboard:preReleaseBuild UP-TO-DATE
|
||||||
|
> Task :capacitor-keyboard:generateReleaseResValues
|
||||||
|
> Task :capacitor-keyboard:generateReleaseResources
|
||||||
|
> Task :capacitor-keyboard:packageReleaseResources
|
||||||
|
> Task :capacitor-splash-screen:preBuild UP-TO-DATE
|
||||||
|
> Task :capacitor-splash-screen:preReleaseBuild UP-TO-DATE
|
||||||
|
> Task :capacitor-splash-screen:generateReleaseResValues
|
||||||
|
> Task :capacitor-splash-screen:generateReleaseResources
|
||||||
|
> Task :capacitor-splash-screen:packageReleaseResources
|
||||||
|
> Task :app:mapReleaseSourceSetPaths
|
||||||
|
> Task :app:generateReleaseResources
|
||||||
|
> Task :app:createReleaseCompatibleScreenManifests
|
||||||
|
> Task :app:extractDeepLinksRelease
|
||||||
|
> Task :capacitor-android:extractDeepLinksRelease
|
||||||
|
> Task :capacitor-cordova-android-plugins:extractDeepLinksRelease
|
||||||
|
> Task :capacitor-keyboard:extractDeepLinksRelease
|
||||||
|
> Task :capacitor-cordova-android-plugins:processReleaseManifest
|
||||||
|
> Task :capacitor-splash-screen:extractDeepLinksRelease
|
||||||
|
> Task :capacitor-android:processReleaseManifest
|
||||||
|
> Task :capacitor-keyboard:processReleaseManifest
|
||||||
|
> Task :capacitor-splash-screen:writeReleaseAarMetadata
|
||||||
|
> Task :capacitor-keyboard:writeReleaseAarMetadata
|
||||||
|
> Task :capacitor-android:writeReleaseAarMetadata
|
||||||
|
> Task :capacitor-cordova-android-plugins:writeReleaseAarMetadata
|
||||||
|
> Task :capacitor-splash-screen:processReleaseManifest
|
||||||
|
> Task :capacitor-cordova-android-plugins:compileReleaseLibraryResources
|
||||||
|
> Task :capacitor-cordova-android-plugins:parseReleaseLocalResources
|
||||||
|
> Task :app:checkReleaseAarMetadata
|
||||||
|
> Task :capacitor-keyboard:compileReleaseLibraryResources
|
||||||
|
> Task :capacitor-android:parseReleaseLocalResources
|
||||||
|
> Task :capacitor-android:compileReleaseLibraryResources
|
||||||
|
> Task :app:processReleaseMainManifest
|
||||||
|
> Task :app:processReleaseManifest
|
||||||
|
> Task :app:processApplicationManifestReleaseForBundle
|
||||||
|
> Task :app:processReleaseManifestForPackage
|
||||||
|
> Task :capacitor-android:generateReleaseRFile
|
||||||
|
> Task :app:extractReleaseVersionControlInfo
|
||||||
|
> Task :capacitor-android:generateReleaseBuildConfig
|
||||||
|
> Task :capacitor-cordova-android-plugins:generateReleaseRFile
|
||||||
|
> Task :capacitor-android:javaPreCompileRelease
|
||||||
|
> Task :capacitor-splash-screen:parseReleaseLocalResources
|
||||||
|
> Task :capacitor-keyboard:parseReleaseLocalResources
|
||||||
|
> Task :capacitor-splash-screen:compileReleaseLibraryResources
|
||||||
|
> Task :app:mergeReleaseResources
|
||||||
|
|
||||||
|
> Task :capacitor-android:compileReleaseJavaWithJavac
|
||||||
|
Note: /Users/hansoo./Dev/zellyy-finance/node_modules/@capacitor/android/capacitor/src/main/java/com/getcapacitor/Bridge.java uses or overrides a deprecated API.
|
||||||
|
Note: Recompile with -Xlint:deprecation for details.
|
||||||
|
Note: Some input files use unchecked or unsafe operations.
|
||||||
|
Note: Recompile with -Xlint:unchecked for details.
|
||||||
|
|
||||||
|
> Task :capacitor-keyboard:generateReleaseRFile
|
||||||
|
> Task :capacitor-keyboard:generateReleaseBuildConfig
|
||||||
|
> Task :capacitor-keyboard:javaPreCompileRelease
|
||||||
|
> Task :capacitor-splash-screen:generateReleaseRFile
|
||||||
|
> Task :capacitor-android:bundleLibCompileToJarRelease
|
||||||
|
> Task :capacitor-android:bundleLibRuntimeToJarRelease
|
||||||
|
> Task :capacitor-keyboard:compileReleaseJavaWithJavac
|
||||||
|
> Task :capacitor-keyboard:bundleLibRuntimeToJarRelease
|
||||||
|
> Task :capacitor-splash-screen:generateReleaseBuildConfig
|
||||||
|
> Task :capacitor-splash-screen:javaPreCompileRelease
|
||||||
|
> Task :capacitor-cordova-android-plugins:generateReleaseBuildConfig
|
||||||
|
> Task :app:processReleaseResources
|
||||||
|
> Task :capacitor-splash-screen:compileReleaseJavaWithJavac
|
||||||
|
> Task :capacitor-splash-screen:bundleLibRuntimeToJarRelease
|
||||||
|
> Task :capacitor-cordova-android-plugins:javaPreCompileRelease
|
||||||
|
> Task :app:checkReleaseDuplicateClasses
|
||||||
|
> Task :capacitor-cordova-android-plugins:compileReleaseJavaWithJavac
|
||||||
|
> Task :capacitor-cordova-android-plugins:bundleLibRuntimeToJarRelease
|
||||||
|
> Task :app:generateReleaseBuildConfig
|
||||||
|
> Task :app:javaPreCompileRelease
|
||||||
|
> Task :capacitor-cordova-android-plugins:bundleLibCompileToJarRelease
|
||||||
|
> Task :capacitor-keyboard:bundleLibCompileToJarRelease
|
||||||
|
> Task :capacitor-splash-screen:bundleLibCompileToJarRelease
|
||||||
|
> Task :app:desugarReleaseFileDependencies
|
||||||
|
> Task :app:bundleReleaseResources
|
||||||
|
> Task :app:compileReleaseJavaWithJavac
|
||||||
|
> Task :app:dexBuilderRelease
|
||||||
|
> Task :app:mergeReleaseStartupProfile
|
||||||
|
> Task :app:mergeReleaseShaders
|
||||||
|
> Task :app:compileReleaseShaders NO-SOURCE
|
||||||
|
> Task :app:generateReleaseAssets UP-TO-DATE
|
||||||
|
> Task :capacitor-android:mergeReleaseShaders
|
||||||
|
> Task :capacitor-android:compileReleaseShaders NO-SOURCE
|
||||||
|
> Task :capacitor-android:generateReleaseAssets UP-TO-DATE
|
||||||
|
> Task :capacitor-android:mergeReleaseAssets
|
||||||
|
> Task :capacitor-cordova-android-plugins:mergeReleaseShaders
|
||||||
|
> Task :capacitor-cordova-android-plugins:compileReleaseShaders NO-SOURCE
|
||||||
|
> Task :capacitor-cordova-android-plugins:generateReleaseAssets UP-TO-DATE
|
||||||
|
> Task :capacitor-cordova-android-plugins:mergeReleaseAssets
|
||||||
|
> Task :capacitor-keyboard:mergeReleaseShaders
|
||||||
|
> Task :capacitor-keyboard:compileReleaseShaders NO-SOURCE
|
||||||
|
> Task :capacitor-keyboard:generateReleaseAssets UP-TO-DATE
|
||||||
|
> Task :capacitor-keyboard:mergeReleaseAssets
|
||||||
|
> Task :capacitor-splash-screen:mergeReleaseShaders
|
||||||
|
> Task :capacitor-splash-screen:compileReleaseShaders NO-SOURCE
|
||||||
|
> Task :capacitor-splash-screen:generateReleaseAssets UP-TO-DATE
|
||||||
|
> Task :capacitor-splash-screen:mergeReleaseAssets
|
||||||
|
> Task :app:mergeReleaseAssets
|
||||||
|
> Task :app:processReleaseJavaRes NO-SOURCE
|
||||||
|
> Task :capacitor-android:processReleaseJavaRes NO-SOURCE
|
||||||
|
> Task :capacitor-cordova-android-plugins:processReleaseJavaRes NO-SOURCE
|
||||||
|
> Task :capacitor-keyboard:processReleaseJavaRes NO-SOURCE
|
||||||
|
> Task :capacitor-splash-screen:processReleaseJavaRes NO-SOURCE
|
||||||
|
> Task :app:mergeReleaseJniLibFolders
|
||||||
|
> Task :capacitor-android:mergeReleaseJniLibFolders
|
||||||
|
> Task :capacitor-android:mergeReleaseNativeLibs NO-SOURCE
|
||||||
|
> Task :capacitor-android:copyReleaseJniLibsProjectOnly
|
||||||
|
> Task :capacitor-cordova-android-plugins:mergeReleaseJniLibFolders
|
||||||
|
> Task :capacitor-cordova-android-plugins:mergeReleaseNativeLibs NO-SOURCE
|
||||||
|
> Task :capacitor-cordova-android-plugins:copyReleaseJniLibsProjectOnly
|
||||||
|
> Task :capacitor-keyboard:mergeReleaseJniLibFolders
|
||||||
|
> Task :capacitor-keyboard:mergeReleaseNativeLibs NO-SOURCE
|
||||||
|
> Task :capacitor-keyboard:copyReleaseJniLibsProjectOnly
|
||||||
|
> Task :capacitor-splash-screen:mergeReleaseJniLibFolders
|
||||||
|
> Task :capacitor-splash-screen:mergeReleaseNativeLibs NO-SOURCE
|
||||||
|
> Task :capacitor-splash-screen:copyReleaseJniLibsProjectOnly
|
||||||
|
> Task :app:writeReleaseAppMetadata
|
||||||
|
> Task :app:mergeReleaseNativeLibs NO-SOURCE
|
||||||
|
> Task :app:stripReleaseDebugSymbols NO-SOURCE
|
||||||
|
> Task :capacitor-android:prepareReleaseArtProfile
|
||||||
|
> Task :capacitor-cordova-android-plugins:prepareReleaseArtProfile
|
||||||
|
> Task :capacitor-keyboard:prepareReleaseArtProfile
|
||||||
|
> Task :capacitor-splash-screen:prepareReleaseArtProfile
|
||||||
|
> Task :app:mergeReleaseArtProfile
|
||||||
|
> Task :app:collectReleaseDependencies
|
||||||
|
> Task :app:configureReleaseDependencies
|
||||||
|
> Task :app:extractReleaseNativeSymbolTables NO-SOURCE
|
||||||
|
> Task :app:extractProguardFiles
|
||||||
|
> Task :capacitor-android:createFullJarRelease
|
||||||
|
> Task :capacitor-android:extractProguardFiles
|
||||||
|
> Task :app:mergeReleaseJavaResource
|
||||||
|
> Task :capacitor-android:generateReleaseLintModel
|
||||||
|
> Task :capacitor-android:prepareLintJarForPublish
|
||||||
|
> Task :capacitor-cordova-android-plugins:createFullJarRelease
|
||||||
|
> Task :capacitor-cordova-android-plugins:extractProguardFiles
|
||||||
|
> Task :capacitor-cordova-android-plugins:generateReleaseLintModel
|
||||||
|
> Task :capacitor-cordova-android-plugins:prepareLintJarForPublish
|
||||||
|
> Task :capacitor-keyboard:createFullJarRelease
|
||||||
|
> Task :capacitor-keyboard:extractProguardFiles
|
||||||
|
> Task :app:mergeReleaseGlobalSynthetics
|
||||||
|
> Task :capacitor-keyboard:generateReleaseLintModel
|
||||||
|
> Task :capacitor-keyboard:prepareLintJarForPublish
|
||||||
|
> Task :capacitor-splash-screen:createFullJarRelease
|
||||||
|
> Task :capacitor-splash-screen:extractProguardFiles
|
||||||
|
> Task :capacitor-splash-screen:generateReleaseLintModel
|
||||||
|
> Task :capacitor-splash-screen:prepareLintJarForPublish
|
||||||
|
> Task :app:generateReleaseLintVitalReportModel
|
||||||
|
> Task :capacitor-keyboard:stripReleaseDebugSymbols NO-SOURCE
|
||||||
|
> Task :capacitor-keyboard:copyReleaseJniLibsProjectAndLocalJars
|
||||||
|
> Task :capacitor-keyboard:extractDeepLinksForAarRelease
|
||||||
|
> Task :capacitor-keyboard:extractReleaseAnnotations
|
||||||
|
> Task :capacitor-keyboard:mergeReleaseGeneratedProguardFiles
|
||||||
|
> Task :capacitor-keyboard:mergeReleaseConsumerProguardFiles
|
||||||
|
> Task :capacitor-splash-screen:stripReleaseDebugSymbols NO-SOURCE
|
||||||
|
> Task :capacitor-keyboard:mergeReleaseJavaResource
|
||||||
|
> Task :capacitor-splash-screen:copyReleaseJniLibsProjectAndLocalJars
|
||||||
|
> Task :capacitor-keyboard:syncReleaseLibJars
|
||||||
|
> Task :capacitor-keyboard:bundleReleaseLocalLintAar
|
||||||
|
> Task :capacitor-splash-screen:extractDeepLinksForAarRelease
|
||||||
|
> Task :capacitor-splash-screen:extractReleaseAnnotations
|
||||||
|
> Task :capacitor-splash-screen:mergeReleaseGeneratedProguardFiles
|
||||||
|
> Task :capacitor-splash-screen:mergeReleaseConsumerProguardFiles
|
||||||
|
> Task :capacitor-android:stripReleaseDebugSymbols NO-SOURCE
|
||||||
|
> Task :capacitor-splash-screen:mergeReleaseJavaResource
|
||||||
|
> Task :capacitor-android:copyReleaseJniLibsProjectAndLocalJars
|
||||||
|
> Task :capacitor-splash-screen:syncReleaseLibJars
|
||||||
|
> Task :capacitor-splash-screen:bundleReleaseLocalLintAar
|
||||||
|
> Task :capacitor-android:extractDeepLinksForAarRelease
|
||||||
|
> Task :capacitor-android:extractReleaseAnnotations
|
||||||
|
> Task :capacitor-android:mergeReleaseGeneratedProguardFiles
|
||||||
|
> Task :capacitor-android:mergeReleaseConsumerProguardFiles
|
||||||
|
> Task :capacitor-cordova-android-plugins:stripReleaseDebugSymbols NO-SOURCE
|
||||||
|
> Task :capacitor-android:mergeReleaseJavaResource
|
||||||
|
> Task :capacitor-cordova-android-plugins:copyReleaseJniLibsProjectAndLocalJars
|
||||||
|
> Task :capacitor-android:syncReleaseLibJars
|
||||||
|
> Task :capacitor-android:bundleReleaseLocalLintAar
|
||||||
|
> Task :capacitor-cordova-android-plugins:extractDeepLinksForAarRelease
|
||||||
|
> Task :capacitor-cordova-android-plugins:extractReleaseAnnotations
|
||||||
|
> Task :capacitor-cordova-android-plugins:mergeReleaseGeneratedProguardFiles
|
||||||
|
> Task :capacitor-cordova-android-plugins:mergeReleaseConsumerProguardFiles
|
||||||
|
> Task :capacitor-cordova-android-plugins:mergeReleaseJavaResource
|
||||||
|
> Task :capacitor-cordova-android-plugins:syncReleaseLibJars
|
||||||
|
> Task :capacitor-cordova-android-plugins:bundleReleaseLocalLintAar
|
||||||
|
> Task :capacitor-android:writeReleaseLintModelMetadata
|
||||||
|
> Task :capacitor-cordova-android-plugins:writeReleaseLintModelMetadata
|
||||||
|
> Task :capacitor-keyboard:writeReleaseLintModelMetadata
|
||||||
|
> Task :capacitor-splash-screen:writeReleaseLintModelMetadata
|
||||||
|
> Task :capacitor-android:generateReleaseLintVitalModel
|
||||||
|
> Task :capacitor-cordova-android-plugins:generateReleaseLintVitalModel
|
||||||
|
> Task :capacitor-keyboard:generateReleaseLintVitalModel
|
||||||
|
> Task :capacitor-splash-screen:generateReleaseLintVitalModel
|
||||||
|
> Task :app:parseReleaseIntegrityConfig
|
||||||
|
> Task :app:validateSigningRelease
|
||||||
|
> Task :capacitor-keyboard:lintVitalAnalyzeRelease
|
||||||
|
> Task :capacitor-splash-screen:lintVitalAnalyzeRelease
|
||||||
|
> Task :app:mergeExtDexRelease
|
||||||
|
> Task :capacitor-android:lintVitalAnalyzeRelease
|
||||||
|
> Task :app:mergeDexRelease
|
||||||
|
> Task :app:buildReleasePreBundle
|
||||||
|
> Task :app:compileReleaseArtProfile
|
||||||
|
> Task :capacitor-cordova-android-plugins:lintVitalAnalyzeRelease
|
||||||
|
> Task :app:lintVitalAnalyzeRelease
|
||||||
|
> Task :app:lintVitalReportRelease
|
||||||
|
> Task :app:lintVitalRelease
|
||||||
|
> Task :app:packageReleaseBundle
|
||||||
|
> Task :app:signReleaseBundle
|
||||||
|
> Task :app:produceReleaseBundleIdeListingFile
|
||||||
|
> Task :app:createReleaseBundleListingFileRedirect
|
||||||
|
> Task :app:bundleRelease
|
||||||
|
[Incubating] Problems report is available at: file:///Users/hansoo./Dev/zellyy-finance/android/build/reports/problems/problems-report.html
|
||||||
|
|
||||||
|
BUILD SUCCESSFUL in 11s
|
||||||
|
181 actionable tasks: 181 executed
|
||||||
|
[0;32m릴리즈 AAB 빌드 완료[0m
|
||||||
|
[0;32mAAB 파일 생성 완료: app/build/outputs/bundle/release/app-release.aab[0m
|
||||||
|
[0;32mAAB 파일 크기: 3.8M[0m
|
||||||
|
[0;32mAAB 파일이 릴리즈 디렉토리에 복사되었습니다: release/zellyy_release_v1.1.8_20250405_194612.aab[0m
|
||||||
1
app_error.log
Normal file
1
app_error.log
Normal file
@@ -0,0 +1 @@
|
|||||||
|
===== 오류 로그: Sat Apr 5 19:45:45 KST 2025 =====
|
||||||
473
build-apk.sh
473
build-apk.sh
@@ -3,15 +3,60 @@
|
|||||||
# 안드로이드 앱 빌드 스크립트 (디버그 및 릴리즈 버전)
|
# 안드로이드 앱 빌드 스크립트 (디버그 및 릴리즈 버전)
|
||||||
# 사용법: ./build-apk-for-device.sh
|
# 사용법: ./build-apk-for-device.sh
|
||||||
|
|
||||||
|
# 스크립트 시작 시간 기록
|
||||||
|
START_TIME=$(date +%s)
|
||||||
|
|
||||||
# 색상 정의
|
# 색상 정의
|
||||||
GREEN='\033[0;32m'
|
GREEN='\033[0;32m'
|
||||||
YELLOW='\033[1;33m'
|
YELLOW='\033[1;33m'
|
||||||
RED='\033[0;31m'
|
RED='\033[0;31m'
|
||||||
NC='\033[0m' # No Color
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
# 로그 파일 설정
|
||||||
|
LOG_FILE="app_build.log"
|
||||||
|
ERROR_LOG_FILE="app_error.log"
|
||||||
|
|
||||||
|
# 타임아웃 설정 (초 단위)
|
||||||
|
BUILD_TIMEOUT=600 # 10분
|
||||||
|
|
||||||
|
# 오류 처리 함수
|
||||||
|
handle_error() {
|
||||||
|
local exit_code=$1
|
||||||
|
local error_message=$2
|
||||||
|
local command=$3
|
||||||
|
|
||||||
|
echo -e "\n${RED}오류 발생: $error_message${NC}" | tee -a "$ERROR_LOG_FILE"
|
||||||
|
echo -e "${YELLOW}명령어: $command${NC}" | tee -a "$ERROR_LOG_FILE"
|
||||||
|
echo -e "${YELLOW}종료 코드: $exit_code${NC}" | tee -a "$ERROR_LOG_FILE"
|
||||||
|
echo -e "${YELLOW}전체 로그는 $LOG_FILE 파일을 확인하세요.${NC}"
|
||||||
|
|
||||||
|
exit $exit_code
|
||||||
|
}
|
||||||
|
|
||||||
|
# 빌드 시간 계산 함수
|
||||||
|
show_build_time() {
|
||||||
|
local end_time=$(date +%s)
|
||||||
|
local build_time=$((end_time - START_TIME))
|
||||||
|
local minutes=$((build_time / 60))
|
||||||
|
local seconds=$((build_time % 60))
|
||||||
|
|
||||||
|
echo -e "\n${GREEN}빌드 완료 시간: ${minutes}분 ${seconds}초${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 로그 파일 초기화
|
||||||
|
echo "===== 빌드 시작: $(date) =====" > "$LOG_FILE"
|
||||||
|
echo "===== 오류 로그: $(date) =====" > "$ERROR_LOG_FILE"
|
||||||
|
|
||||||
# 프로젝트 디렉토리로 이동
|
# 프로젝트 디렉토리로 이동
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
# 릴리즈 디렉토리 생성
|
||||||
|
RELEASE_DIR="$(pwd)/release"
|
||||||
|
if [ ! -d "$RELEASE_DIR" ]; then
|
||||||
|
mkdir -p "$RELEASE_DIR"
|
||||||
|
echo -e "${GREEN}릴리즈 디렉토리가 생성되었습니다: $RELEASE_DIR${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
# 빌드 타입 선택 메뉴
|
# 빌드 타입 선택 메뉴
|
||||||
echo -e "${YELLOW}Zellyy Finance 앱 빌드 스크립트${NC}"
|
echo -e "${YELLOW}Zellyy Finance 앱 빌드 스크립트${NC}"
|
||||||
echo -e "${YELLOW}=============================${NC}"
|
echo -e "${YELLOW}=============================${NC}"
|
||||||
@@ -43,76 +88,107 @@ case $CHOICE in
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
# 현재 버전 코드 가져오기
|
# 버전 정보 파일 확인
|
||||||
CURRENT_VERSION_CODE=$(grep -o 'versionCode [0-9]*' android/app/build.gradle | awk '{print $2}')
|
VERSION_PROPS_FILE="version.properties"
|
||||||
# 버전 코드가 비어있으면 기본값 1로 설정
|
|
||||||
if [ -z "$CURRENT_VERSION_CODE" ]; then
|
|
||||||
CURRENT_VERSION_CODE=1
|
|
||||||
echo -e "${YELLOW}버전 코드가 없어 기본값 1로 설정했습니다.${NC}"
|
|
||||||
fi
|
|
||||||
echo -e "${YELLOW}현재 버전 코드: ${CURRENT_VERSION_CODE}${NC}"
|
|
||||||
|
|
||||||
# 현재 버전 이름 가져오기
|
# 버전 정보 파일이 있는지 확인
|
||||||
CURRENT_VERSION_NAME=$(grep -o 'versionName "[^"]*"' android/app/build.gradle | sed 's/versionName "//' | sed 's/"//')
|
if [ -f "$VERSION_PROPS_FILE" ]; then
|
||||||
# 버전 이름이 비어있으면 기본값 1.0.0으로 설정
|
# 파일에서 버전 정보 로드
|
||||||
if [ -z "$CURRENT_VERSION_NAME" ]; then
|
source "$VERSION_PROPS_FILE"
|
||||||
CURRENT_VERSION_NAME="1.0.0"
|
CURRENT_VERSION_CODE=$VERSION_CODE
|
||||||
echo -e "${YELLOW}버전 이름이 없어 기본값 1.0.0으로 설정했습니다.${NC}"
|
CURRENT_VERSION_NAME=$VERSION_NAME
|
||||||
|
CURRENT_BUILD_NUMBER=$BUILD_NUMBER
|
||||||
|
echo -e "${GREEN}버전 정보 로드 완료: 버전 코드=${BLUE}$CURRENT_VERSION_CODE${GREEN}, 버전 이름=${BLUE}$CURRENT_VERSION_NAME${GREEN}, 빌드 번호=${BLUE}$CURRENT_BUILD_NUMBER${NC}"
|
||||||
|
else
|
||||||
|
# 파일이 없으면 build.gradle에서 버전 정보 가져오기
|
||||||
|
CURRENT_VERSION_CODE=$(grep -o 'versionCode [0-9]*' android/app/build.gradle | sed 's/versionCode //')
|
||||||
|
CURRENT_VERSION_NAME=$(grep -o 'versionName "[^"]*"' android/app/build.gradle | sed 's/versionName "//' | sed 's/"//')
|
||||||
|
|
||||||
|
# 버전 코드가 비어있으면 기본값 1로 설정
|
||||||
|
if [ -z "$CURRENT_VERSION_CODE" ]; then
|
||||||
|
CURRENT_VERSION_CODE=1
|
||||||
|
echo -e "${YELLOW}버전 코드가 없어 기본값 1로 설정했습니다.${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 버전 이름이 비어있으면 기본값 1.0.0으로 설정
|
||||||
|
if [ -z "$CURRENT_VERSION_NAME" ]; then
|
||||||
|
CURRENT_VERSION_NAME="1.0.0"
|
||||||
|
echo -e "${YELLOW}버전 이름이 없어 기본값 1.0.0으로 설정했습니다.${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 빌드 번호 기본값 설정
|
||||||
|
CURRENT_BUILD_NUMBER=$CURRENT_VERSION_CODE
|
||||||
fi
|
fi
|
||||||
echo -e "${YELLOW}현재 버전 이름: ${CURRENT_VERSION_NAME}${NC}"
|
|
||||||
|
echo -e "${YELLOW}현재 버전 코드: ${BLUE}$CURRENT_VERSION_CODE${NC}"
|
||||||
|
echo -e "${YELLOW}현재 버전 이름: ${BLUE}$CURRENT_VERSION_NAME${NC}"
|
||||||
|
echo -e "${YELLOW}현재 빌드 번호: ${BLUE}$CURRENT_BUILD_NUMBER${NC}"
|
||||||
|
|
||||||
# 버전 정보 수정 여부 확인
|
# 버전 정보 수정 여부 확인
|
||||||
echo -e "${YELLOW}버전 정보를 수정하시겠습니까? (y/n)${NC}"
|
echo -e "${YELLOW}버전 정보를 수정하시겠습니까? (y/n)${NC}"
|
||||||
read -r MODIFY_VERSION
|
read -r MODIFY_VERSION
|
||||||
|
# 기본값 설정
|
||||||
|
NEW_VERSION_CODE=$CURRENT_VERSION_CODE
|
||||||
|
NEW_VERSION_NAME=$CURRENT_VERSION_NAME
|
||||||
|
|
||||||
if [[ "$MODIFY_VERSION" == "y" || "$MODIFY_VERSION" == "Y" ]]; then
|
if [[ "$MODIFY_VERSION" == "y" || "$MODIFY_VERSION" == "Y" ]]; then
|
||||||
# 버전 코드 입력
|
# 버전 코드 입력
|
||||||
echo -e "${YELLOW}새 버전 코드를 입력하세요 (현재: ${CURRENT_VERSION_CODE}):${NC}"
|
echo -e "${YELLOW}새 버전 코드를 입력하세요 (현재: ${BLUE}${CURRENT_VERSION_CODE}${YELLOW}, 엔터를 치면 기본값 사용):${NC}"
|
||||||
read -r NEW_VERSION_CODE_INPUT
|
read -r NEW_VERSION_CODE_INPUT
|
||||||
if [ -n "$NEW_VERSION_CODE_INPUT" ]; then
|
if [ -n "$NEW_VERSION_CODE_INPUT" ]; then
|
||||||
NEW_VERSION_CODE=$NEW_VERSION_CODE_INPUT
|
NEW_VERSION_CODE=$NEW_VERSION_CODE_INPUT
|
||||||
else
|
|
||||||
NEW_VERSION_CODE=$CURRENT_VERSION_CODE
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 버전 이름 입력
|
# 버전 이름 입력
|
||||||
echo -e "${YELLOW}새 버전 이름을 입력하세요 (현재: ${CURRENT_VERSION_NAME}):${NC}"
|
echo -e "${YELLOW}새 버전 이름을 입력하세요 (현재: ${BLUE}${CURRENT_VERSION_NAME}${YELLOW}, 엔터를 치면 기본값 사용):${NC}"
|
||||||
read -r NEW_VERSION_NAME_INPUT
|
read -r NEW_VERSION_NAME_INPUT
|
||||||
if [ -n "$NEW_VERSION_NAME_INPUT" ]; then
|
if [ -n "$NEW_VERSION_NAME_INPUT" ]; then
|
||||||
VERSION_NAME=$NEW_VERSION_NAME_INPUT
|
NEW_VERSION_NAME=$NEW_VERSION_NAME_INPUT
|
||||||
else
|
|
||||||
VERSION_NAME=$CURRENT_VERSION_NAME
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${GREEN}버전 정보가 업데이트되었습니다: 버전 코드=${NEW_VERSION_CODE}, 버전 이름=${VERSION_NAME}${NC}"
|
echo -e "${GREEN}버전 정보가 업데이트되었습니다: 버전 코드=${BLUE}${NEW_VERSION_CODE}${GREEN}, 버전 이름=${BLUE}${NEW_VERSION_NAME}${NC}"
|
||||||
else
|
|
||||||
NEW_VERSION_CODE=$CURRENT_VERSION_CODE
|
|
||||||
VERSION_NAME=$CURRENT_VERSION_NAME
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 빌드 넘버 자동 설정
|
# 빌드 번호 기본값 설정
|
||||||
BUILD_NUMBER=$NEW_VERSION_CODE
|
BUILD_NUMBER=$CURRENT_BUILD_NUMBER
|
||||||
echo -e "${GREEN}빌드 넘버가 자동으로 ${BUILD_NUMBER}(으)로 설정되었습니다.${NC}"
|
|
||||||
|
|
||||||
# 빌드 넘버 수정 여부 확인
|
# 릴리즈 빌드일 경우 빌드 번호 자동 증가 제안
|
||||||
echo -e "${YELLOW}빌드 넘버를 수정하시겠습니까? (y/n)${NC}"
|
if [[ "$BUILD_TYPE" == "release-aab" || "$BUILD_TYPE" == "release-apk" ]]; then
|
||||||
|
SUGGESTED_BUILD_NUMBER=$((CURRENT_BUILD_NUMBER + 1))
|
||||||
|
echo -e "${YELLOW}릴리즈 빌드에는 빌드 번호 증가가 권장됩니다 (${BLUE}$CURRENT_BUILD_NUMBER${YELLOW} -> ${BLUE}$SUGGESTED_BUILD_NUMBER${YELLOW})${NC}"
|
||||||
|
else
|
||||||
|
SUGGESTED_BUILD_NUMBER=$CURRENT_BUILD_NUMBER
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}현재 빌드 번호: ${BLUE}$CURRENT_BUILD_NUMBER${NC}"
|
||||||
|
|
||||||
|
# 빌드 번호 수정 여부 확인
|
||||||
|
echo -e "${YELLOW}빌드 번호를 수정하시겠습니까? (y/n)${NC}"
|
||||||
read -r MODIFY_BUILD
|
read -r MODIFY_BUILD
|
||||||
if [[ "$MODIFY_BUILD" == "y" || "$MODIFY_BUILD" == "Y" ]]; then
|
if [[ "$MODIFY_BUILD" == "y" || "$MODIFY_BUILD" == "Y" ]]; then
|
||||||
echo -e "${YELLOW}새 빌드 넘버를 입력하세요 (현재: ${BUILD_NUMBER}):${NC}"
|
echo -e "${YELLOW}새 빌드 번호를 입력하세요 (추천: ${BLUE}$SUGGESTED_BUILD_NUMBER${YELLOW}, 엔터를 치면 추천값 사용):${NC}"
|
||||||
read -r NEW_BUILD_NUMBER
|
read -r NEW_BUILD_NUMBER
|
||||||
if [ -n "$NEW_BUILD_NUMBER" ]; then
|
if [ -n "$NEW_BUILD_NUMBER" ]; then
|
||||||
BUILD_NUMBER=$NEW_BUILD_NUMBER
|
BUILD_NUMBER=$NEW_BUILD_NUMBER
|
||||||
echo -e "${GREEN}빌드 넘버가 ${BUILD_NUMBER}(으)로 설정되었습니다.${NC}"
|
else
|
||||||
|
BUILD_NUMBER=$SUGGESTED_BUILD_NUMBER
|
||||||
fi
|
fi
|
||||||
|
echo -e "${GREEN}빌드 번호가 ${BLUE}$BUILD_NUMBER${GREEN}(으)로 설정되었습니다.${NC}"
|
||||||
|
else
|
||||||
|
BUILD_NUMBER=$SUGGESTED_BUILD_NUMBER
|
||||||
|
echo -e "${GREEN}빌드 번호가 ${BLUE}$BUILD_NUMBER${GREEN}(으)로 자동 설정되었습니다.${NC}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 릴리즈 빌드인 경우 버전 코드 증가 여부 확인
|
# 릴리즈 빌드인 경우 버전 코드 증가 여부 확인
|
||||||
if [[ "$BUILD_TYPE" == "release-aab" || "$BUILD_TYPE" == "release-apk" ]]; then
|
if [[ "$BUILD_TYPE" == "release-aab" || "$BUILD_TYPE" == "release-apk" ]]; then
|
||||||
# 버전 정보를 이미 수정했다면 다시 묻지 않음
|
# 버전 정보를 이미 수정했다면 다시 묻지 않음
|
||||||
if [[ "$MODIFY_VERSION" != "y" && "$MODIFY_VERSION" != "Y" ]]; then
|
if [[ "$MODIFY_VERSION" != "y" && "$MODIFY_VERSION" != "Y" ]]; then
|
||||||
echo -e "${YELLOW}버전 코드를 증가시키겠습니까? 현재 버전 코드: ${NEW_VERSION_CODE} (y/n)${NC}"
|
SUGGESTED_VERSION_CODE=$((CURRENT_VERSION_CODE + 1))
|
||||||
|
echo -e "${YELLOW}릴리즈 빌드에는 버전 코드 증가가 권장됩니다 (${BLUE}$CURRENT_VERSION_CODE${YELLOW} -> ${BLUE}$SUGGESTED_VERSION_CODE${YELLOW})${NC}"
|
||||||
|
echo -e "${YELLOW}버전 코드를 증가시키겠습니까? (y/n)${NC}"
|
||||||
read -r INCREASE_VERSION
|
read -r INCREASE_VERSION
|
||||||
if [[ "$INCREASE_VERSION" == "y" || "$INCREASE_VERSION" == "Y" ]]; then
|
if [[ "$INCREASE_VERSION" == "y" || "$INCREASE_VERSION" == "Y" ]]; then
|
||||||
NEW_VERSION_CODE=$((NEW_VERSION_CODE + 1))
|
NEW_VERSION_CODE=$SUGGESTED_VERSION_CODE
|
||||||
echo -e "${GREEN}버전 코드가 ${NEW_VERSION_CODE}(으)로 증가됩니다.${NC}"
|
echo -e "${GREEN}버전 코드가 ${NEW_VERSION_CODE}(으)로 증가됩니다.${NC}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
@@ -132,24 +208,39 @@ if [[ "$BUILD_TYPE" == "release-aab" || "$BUILD_TYPE" == "release-apk" ]]; then
|
|||||||
echo -e "${GREEN}서명 설정이 업데이트되었습니다.${NC}"
|
echo -e "${GREEN}서명 설정이 업데이트되었습니다.${NC}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${YELLOW}Zellyy Finance 앱 빌드 시작 (${BUILD_TYPE}, 빌드 넘버: ${BUILD_NUMBER}, 버전 코드: ${NEW_VERSION_CODE}): $(date)${NC}"
|
echo -e "${YELLOW}Zellyy Finance 앱 빌드 시작 (${BUILD_TYPE}, 빌드 번호: ${BUILD_NUMBER}, 버전 코드: ${NEW_VERSION_CODE}, 버전 이름: ${NEW_VERSION_NAME}): $(date)${NC}"
|
||||||
|
|
||||||
# 버전 정보를 파일에 저장하는 함수
|
# 버전 정보를 파일에 저장하는 함수
|
||||||
save_version_info() {
|
save_version_info() {
|
||||||
# version.properties 파일 업데이트 - 루트 디렉토리와 app 디렉토리 모두에 저장
|
# 루트 디렉토리에 version.properties 파일 저장
|
||||||
echo "buildNumber=$BUILD_NUMBER" > android/version.properties
|
cat > "$VERSION_PROPS_FILE" << EOF
|
||||||
echo "versionCode=$NEW_VERSION_CODE" >> android/version.properties
|
# Zellyy Finance 앱 버전 정보
|
||||||
echo "versionName=$VERSION_NAME" >> android/version.properties
|
# 마지막 업데이트: $(date +"%Y-%m-%d %H:%M:%S")
|
||||||
|
VERSION_CODE=$NEW_VERSION_CODE
|
||||||
|
VERSION_NAME=$NEW_VERSION_NAME
|
||||||
|
BUILD_NUMBER=$BUILD_NUMBER
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# android 디렉토리에 version.properties 파일 저장
|
||||||
|
cat > "android/version.properties" << EOF
|
||||||
|
# Zellyy Finance 앱 버전 정보
|
||||||
|
# 마지막 업데이트: $(date +"%Y-%m-%d %H:%M:%S")
|
||||||
|
buildNumber=$BUILD_NUMBER
|
||||||
|
versionCode=$NEW_VERSION_CODE
|
||||||
|
versionName=$NEW_VERSION_NAME
|
||||||
|
EOF
|
||||||
|
|
||||||
# app 디렉토리에도 동일한 파일 복사
|
# app 디렉토리에도 동일한 파일 복사
|
||||||
cp android/version.properties android/app/version.properties
|
cp "android/version.properties" "android/app/version.properties"
|
||||||
|
|
||||||
# app_version.json 파일 업데이트
|
# app_version.json 파일 업데이트 - 사용자 정보와 빌드 정보 포함
|
||||||
cat > android/app_version.json << EOF
|
cat > "android/app_version.json" << EOF
|
||||||
{
|
{
|
||||||
"versionCode": $NEW_VERSION_CODE,
|
"versionCode": $NEW_VERSION_CODE,
|
||||||
"versionName": "$VERSION_NAME",
|
"versionName": "$NEW_VERSION_NAME",
|
||||||
"buildNumber": $BUILD_NUMBER,
|
"buildNumber": $BUILD_NUMBER,
|
||||||
|
"buildDate": "$(date +"%Y-%m-%d %H:%M:%S")",
|
||||||
|
"buildType": "$BUILD_TYPE",
|
||||||
"notes": "사용자가 수정한 버전 정보입니다. 이 파일을 편집하여 앱 버전 정보를 변경할 수 있습니다."
|
"notes": "사용자가 수정한 버전 정보입니다. 이 파일을 편집하여 앱 버전 정보를 변경할 수 있습니다."
|
||||||
}
|
}
|
||||||
EOF
|
EOF
|
||||||
@@ -157,17 +248,59 @@ EOF
|
|||||||
# AppVersionInfo.tsx 파일의 하드코딩된 버전 정보 업데이트
|
# AppVersionInfo.tsx 파일의 하드코딩된 버전 정보 업데이트
|
||||||
echo -e "${YELLOW}AppVersionInfo.tsx 파일의 버전 정보 업데이트 중...${NC}"
|
echo -e "${YELLOW}AppVersionInfo.tsx 파일의 버전 정보 업데이트 중...${NC}"
|
||||||
|
|
||||||
# 백업 파일 생성
|
# AppVersionInfo.tsx 파일이 존재하는지 확인
|
||||||
cp src/components/AppVersionInfo.tsx src/components/AppVersionInfo.tsx.bak
|
APP_VERSION_FILE="src/components/AppVersionInfo.tsx"
|
||||||
|
if [ -f "$APP_VERSION_FILE" ]; then
|
||||||
|
# 백업 파일 생성
|
||||||
|
cp "$APP_VERSION_FILE" "${APP_VERSION_FILE}.bak"
|
||||||
|
|
||||||
# AppVersionInfo.tsx 파일에서 하드코딩된 버전 정보 업데이트
|
# AppVersionInfo.tsx 파일에서 하드코딩된 버전 정보 업데이트
|
||||||
sed -i '' "s/versionName: '[^']*'/versionName: '$VERSION_NAME'/" src/components/AppVersionInfo.tsx
|
sed -i '' "s/versionName: '[^']*'/versionName: '$NEW_VERSION_NAME'/" "$APP_VERSION_FILE"
|
||||||
sed -i '' "/hardcodedVersionInfo/,/}/s/buildNumber: [0-9]*/buildNumber: $BUILD_NUMBER/" src/components/AppVersionInfo.tsx
|
sed -i '' "/hardcodedVersionInfo/,/}/s/buildNumber: [0-9]*/buildNumber: $BUILD_NUMBER/" "$APP_VERSION_FILE"
|
||||||
sed -i '' "/hardcodedVersionInfo/,/}/s/versionCode: [0-9]*/versionCode: $NEW_VERSION_CODE/" src/components/AppVersionInfo.tsx
|
sed -i '' "/hardcodedVersionInfo/,/}/s/versionCode: [0-9]*/versionCode: $NEW_VERSION_CODE/" "$APP_VERSION_FILE"
|
||||||
|
|
||||||
echo -e "${GREEN}AppVersionInfo.tsx 파일의 버전 정보가 업데이트되었습니다.${NC}"
|
echo -e "${GREEN}AppVersionInfo.tsx 파일의 버전 정보가 업데이트되었습니다.${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}경고: AppVersionInfo.tsx 파일을 찾을 수 없습니다.${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
echo -e "${GREEN}버전 정보가 모든 파일에 저장되었습니다: 버전 코드=${NEW_VERSION_CODE}, 버전 이름=${VERSION_NAME}, 빌드 넘버=${BUILD_NUMBER}${NC}"
|
# AndroidManifest.xml 파일의 버전 정보 업데이트
|
||||||
|
MANIFEST_FILE="android/app/src/main/AndroidManifest.xml"
|
||||||
|
if [ -f "$MANIFEST_FILE" ]; then
|
||||||
|
# 기존 버전 정보가 있는지 확인
|
||||||
|
if grep -q 'android:versionCode' "$MANIFEST_FILE"; then
|
||||||
|
# 기존 버전 정보 업데이트
|
||||||
|
sed -i '' "s/android:versionCode=\"[0-9]*\"/android:versionCode=\"$NEW_VERSION_CODE\"/" "$MANIFEST_FILE"
|
||||||
|
sed -i '' "s/android:versionName=\"[^\"]*\"/android:versionName=\"$NEW_VERSION_NAME\"/" "$MANIFEST_FILE"
|
||||||
|
else
|
||||||
|
# 버전 정보가 없으면 추가
|
||||||
|
sed -i '' "s/<manifest xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\">/<manifest xmlns:android=\"http:\/\/schemas.android.com\/apk\/res\/android\"\n android:versionCode=\"$NEW_VERSION_CODE\"\n android:versionName=\"$NEW_VERSION_NAME\">/" "$MANIFEST_FILE"
|
||||||
|
fi
|
||||||
|
echo -e "${GREEN}AndroidManifest.xml 파일의 버전 정보가 업데이트되었습니다.${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}경고: AndroidManifest.xml 파일을 찾을 수 없습니다.${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# build.gradle 파일의 버전 정보 업데이트
|
||||||
|
GRADLE_FILE="android/app/build.gradle"
|
||||||
|
if [ -f "$GRADLE_FILE" ]; then
|
||||||
|
# 백업 파일 생성
|
||||||
|
cp "$GRADLE_FILE" "${GRADLE_FILE}.bak"
|
||||||
|
|
||||||
|
# 버전 코드와 버전 이름 업데이트 (더 안정적인 방식으로 수정)
|
||||||
|
# Update versionCode within defaultConfig block (ensure correct indentation)
|
||||||
|
sed -i '' "/defaultConfig {/,/}/s/^[[:space:]]*versionCode[[:space:]]*=.*$/ versionCode = $NEW_VERSION_CODE/" "$GRADLE_FILE"
|
||||||
|
# Update 'def versionCode = ...' (ensure correct indentation)
|
||||||
|
sed -i '' "s/^[[:space:]]*def versionCode[[:space:]]*=.*$/ def versionCode = $NEW_VERSION_CODE/" "$GRADLE_FILE"
|
||||||
|
# Update versionName within defaultConfig block (ensure correct indentation and quotes)
|
||||||
|
sed -i '' "/defaultConfig {/,/}/s/^[[:space:]]*versionName[[:space:]]*=.*$/ versionName = \\\"$NEW_VERSION_NAME\\\"/" "$GRADLE_FILE"
|
||||||
|
|
||||||
|
echo -e "${GREEN}build.gradle 파일의 버전 정보가 업데이트되었습니다.${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}경고: build.gradle 파일을 찾을 수 없습니다.${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}버전 정보가 모든 파일에 저장되었습니다: 버전 코드=${BLUE}$NEW_VERSION_CODE${GREEN}, 버전 이름=${BLUE}$NEW_VERSION_NAME${GREEN}, 빌드 번호=${BLUE}$BUILD_NUMBER${NC}"
|
||||||
}
|
}
|
||||||
|
|
||||||
# 빌드 시작 전에 버전 정보 저장
|
# 빌드 시작 전에 버전 정보 저장
|
||||||
@@ -182,156 +315,166 @@ rm -rf dist
|
|||||||
echo -e "${GREEN}빌드 캐시가 삭제되었습니다.${NC}"
|
echo -e "${GREEN}빌드 캐시가 삭제되었습니다.${NC}"
|
||||||
|
|
||||||
# 1. 웹 앱 빌드
|
# 1. 웹 앱 빌드
|
||||||
echo -e "${YELLOW}1. 웹 앱 빌드 중...${NC}"
|
echo -e "${YELLOW}1. 웹 앱 빌드 중...${NC}" | tee -a "$LOG_FILE"
|
||||||
npm run build
|
echo "실행 명령어: npm run build" >> "$LOG_FILE"
|
||||||
if [ $? -ne 0 ]; then
|
npm run build >> "$LOG_FILE" 2>&1
|
||||||
echo -e "${RED}웹 앱 빌드 실패. 빌드 프로세스를 중단합니다.${NC}"
|
BUILD_RESULT=$?
|
||||||
exit 1
|
if [ $BUILD_RESULT -ne 0 ]; then
|
||||||
|
handle_error $BUILD_RESULT "웹 앱 빌드 실패" "npm run build"
|
||||||
fi
|
fi
|
||||||
echo -e "${GREEN}웹 앱 빌드 완료${NC}"
|
echo -e "${GREEN}웹 앱 빌드 완료${NC}" | tee -a "$LOG_FILE"
|
||||||
|
|
||||||
# 2. Capacitor에 웹 코드 복사 및 동기화
|
# 2. Capacitor에 웹 코드 복사 및 동기화
|
||||||
echo -e "${YELLOW}2. Capacitor에 웹 코드 동기화 중...${NC}"
|
echo -e "${YELLOW}2. Capacitor에 웹 코드 동기화 중...${NC}" | tee -a "$LOG_FILE"
|
||||||
npx cap sync android
|
echo "실행 명령어: npx cap sync android" >> "$LOG_FILE"
|
||||||
if [ $? -ne 0 ]; then
|
npx cap sync android >> "$LOG_FILE" 2>&1
|
||||||
echo -e "${RED}Capacitor 동기화 실패. 빌드 프로세스를 중단합니다.${NC}"
|
BUILD_RESULT=$?
|
||||||
exit 1
|
if [ $BUILD_RESULT -ne 0 ]; then
|
||||||
|
handle_error $BUILD_RESULT "Capacitor 동기화 실패" "npx cap sync android"
|
||||||
fi
|
fi
|
||||||
echo -e "${GREEN}Capacitor 동기화 완료${NC}"
|
echo -e "${GREEN}Capacitor 동기화 완료${NC}" | tee -a "$LOG_FILE"
|
||||||
|
|
||||||
# 3. 안드로이드 APK/AAB 빌드
|
# 3. 안드로이드 APK/AAB 빌드
|
||||||
cd android
|
cd android
|
||||||
|
|
||||||
|
# Gradle 메모리 설정 최적화
|
||||||
|
export GRADLE_OPTS="-Xmx4g -Dorg.gradle.jvmargs='-Xmx4g -XX:+HeapDumpOnOutOfMemoryError'"
|
||||||
echo -e "${YELLOW}3. 안드로이드 빌드 시작 (${BUILD_TYPE})...${NC}"
|
echo -e "${YELLOW}3. 안드로이드 빌드 시작 (${BUILD_TYPE})...${NC}"
|
||||||
|
|
||||||
# 빌드 타입에 따라 다른 명령어 실행
|
# 빌드 타입에 따라 다른 명령어 실행
|
||||||
if [ "$BUILD_TYPE" = "debug" ]; then
|
if [ "$BUILD_TYPE" = "debug" ]; then
|
||||||
# 디버그 빌드
|
echo -e "${YELLOW}3. 안드로이드 빌드 시작 (debug)...${NC}" | tee -a "../$LOG_FILE"
|
||||||
./gradlew clean assembleDebug
|
echo "실행 명령어: ./gradlew assembleDebug" >> "../$LOG_FILE"
|
||||||
if [ $? -ne 0 ]; then
|
./gradlew assembleDebug >> "../$LOG_FILE" 2>&1
|
||||||
echo -e "${RED}디버그 APK 빌드 실패. 오류를 확인하세요.${NC}"
|
BUILD_RESULT=$?
|
||||||
exit 1
|
if [ $BUILD_RESULT -ne 0 ]; then
|
||||||
|
handle_error $BUILD_RESULT "디버그 APK 빌드 실패" "./gradlew assembleDebug"
|
||||||
fi
|
fi
|
||||||
|
echo -e "${GREEN}디버그 APK 빌드 완료${NC}" | tee -a "../$LOG_FILE"
|
||||||
|
|
||||||
|
# 빌드된 APK 파일 경로 출력
|
||||||
APK_PATH="app/build/outputs/apk/debug/app-debug.apk"
|
APK_PATH="app/build/outputs/apk/debug/app-debug.apk"
|
||||||
DEST_PATH="$HOME/zellyy-finance-debug.apk"
|
|
||||||
|
|
||||||
if [ -f "$APK_PATH" ]; then
|
if [ -f "$APK_PATH" ]; then
|
||||||
echo -e "${GREEN}디버그 APK 빌드 성공!${NC}"
|
echo -e "${GREEN}APK 파일 생성 완료: ${BLUE}$APK_PATH${NC}" | tee -a "../$LOG_FILE"
|
||||||
echo -e "APK 파일 위치: $(pwd)/$APK_PATH"
|
|
||||||
|
|
||||||
# 홈 디렉토리로 APK 복사
|
# APK 파일 크기 출력
|
||||||
cp "$APK_PATH" "$DEST_PATH"
|
APK_SIZE=$(du -h "$APK_PATH" | cut -f1)
|
||||||
echo -e "${GREEN}APK를 홈 디렉토리에 복사했습니다: $DEST_PATH${NC}"
|
echo -e "${GREEN}APK 파일 크기: ${BLUE}$APK_SIZE${NC}" | tee -a "../$LOG_FILE"
|
||||||
|
|
||||||
# 연결된 기기 확인
|
# 릴리즈 디렉토리로 복사
|
||||||
DEVICES=$(adb devices | grep -v "List" | grep "device" | wc -l)
|
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||||
if [ $DEVICES -gt 0 ]; then
|
RELEASE_FILENAME="zellyy_debug_v${VERSION_NAME}_${TIMESTAMP}.apk"
|
||||||
echo -e "${YELLOW}연결된 기기가 감지되었습니다. 설치하시겠습니까? (y/n)${NC}"
|
cp "$APK_PATH" "../release/$RELEASE_FILENAME"
|
||||||
read -r INSTALL
|
echo -e "${GREEN}APK 파일이 릴리즈 디렉토리에 복사되었습니다: ${BLUE}release/$RELEASE_FILENAME${NC}" | tee -a "../$LOG_FILE"
|
||||||
if [ "$INSTALL" = "y" ] || [ "$INSTALL" = "Y" ]; then
|
|
||||||
# 기기가 여러 개인 경우
|
|
||||||
if [ $(adb devices | grep -v "List" | grep "device" | wc -l) -gt 1 ]; then
|
|
||||||
echo -e "${YELLOW}여러 기기가 연결되어 있습니다. 특정 기기를 선택하세요:${NC}"
|
|
||||||
adb devices | grep -v "List" | grep "device"
|
|
||||||
echo -e "${YELLOW}기기 ID를 입력하세요:${NC}"
|
|
||||||
read -r DEVICE_ID
|
|
||||||
adb -s "$DEVICE_ID" install -r "$APK_PATH"
|
|
||||||
else
|
|
||||||
adb install -r "$APK_PATH"
|
|
||||||
fi
|
|
||||||
echo -e "${GREEN}설치 완료!${NC}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
echo -e "${YELLOW}연결된 기기가 없습니다. 다음 방법으로 APK를 설치할 수 있습니다:${NC}"
|
|
||||||
echo "1. USB 케이블로 폰을 연결하고 파일 전송"
|
|
||||||
echo "2. 이메일이나 메신저로 APK 파일 전송"
|
|
||||||
echo "3. adb 명령어 사용: adb install $DEST_PATH"
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
echo -e "${RED}APK 빌드 실패. 오류를 확인하세요.${NC}"
|
echo -e "${RED}APK 파일을 찾을 수 없습니다: $APK_PATH${NC}" | tee -a "../$ERROR_LOG_FILE"
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
elif [ "$BUILD_TYPE" = "release-aab" ]; then
|
elif [ "$BUILD_TYPE" = "release-aab" ]; then
|
||||||
# AAB 릴리즈 빌드
|
echo -e "${YELLOW}3. 안드로이드 빌드 시작 (release-aab)...${NC}" | tee -a "../$LOG_FILE"
|
||||||
./gradlew clean bundleRelease
|
echo "실행 명령어: ./gradlew bundleRelease" >> "../$LOG_FILE"
|
||||||
if [ $? -ne 0 ]; then
|
./gradlew bundleRelease >> "../$LOG_FILE" 2>&1
|
||||||
echo -e "${RED}릴리즈 AAB 빌드 실패. 오류를 확인하세요.${NC}"
|
BUILD_RESULT=$?
|
||||||
exit 1
|
if [ $BUILD_RESULT -ne 0 ]; then
|
||||||
|
handle_error $BUILD_RESULT "릴리즈 AAB 빌드 실패" "./gradlew bundleRelease"
|
||||||
fi
|
fi
|
||||||
|
echo -e "${GREEN}릴리즈 AAB 빌드 완료${NC}" | tee -a "../$LOG_FILE"
|
||||||
|
|
||||||
|
# 빌드된 AAB 파일 경로 출력
|
||||||
AAB_PATH="app/build/outputs/bundle/release/app-release.aab"
|
AAB_PATH="app/build/outputs/bundle/release/app-release.aab"
|
||||||
DEST_PATH="$HOME/zellyy-finance-release.aab"
|
|
||||||
|
|
||||||
if [ -f "$AAB_PATH" ]; then
|
if [ -f "$AAB_PATH" ]; then
|
||||||
echo -e "${GREEN}릴리즈 AAB 빌드 성공!${NC}"
|
echo -e "${GREEN}AAB 파일 생성 완료: ${BLUE}$AAB_PATH${NC}" | tee -a "../$LOG_FILE"
|
||||||
echo -e "AAB 파일 위치: $(pwd)/$AAB_PATH"
|
|
||||||
|
|
||||||
# 홈 디렉토리로 AAB 복사
|
# AAB 파일 크기 출력
|
||||||
cp "$AAB_PATH" "$DEST_PATH"
|
AAB_SIZE=$(du -h "$AAB_PATH" | cut -f1)
|
||||||
echo -e "${GREEN}AAB를 홈 디렉토리에 복사했습니다: $DEST_PATH${NC}"
|
echo -e "${GREEN}AAB 파일 크기: ${BLUE}$AAB_SIZE${NC}" | tee -a "../$LOG_FILE"
|
||||||
|
|
||||||
echo -e "${YELLOW}다음 단계:${NC}"
|
# 릴리즈 디렉토리로 복사
|
||||||
echo "1. Google Play Console에 AAB 파일 업로드: $DEST_PATH"
|
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||||
echo "2. 내부 테스트 트랙을 선택하여 업로드"
|
RELEASE_FILENAME="zellyy_release_v${VERSION_NAME}_${TIMESTAMP}.aab"
|
||||||
echo "3. 검토 과정이 완료될 때까지 기다리기 (보통 몇 시간에서 24시간 소요)"
|
cp "$AAB_PATH" "../release/$RELEASE_FILENAME"
|
||||||
|
echo -e "${GREEN}AAB 파일이 릴리즈 디렉토리에 복사되었습니다: ${BLUE}release/$RELEASE_FILENAME${NC}" | tee -a "../$LOG_FILE"
|
||||||
else
|
else
|
||||||
echo -e "${RED}AAB 빌드 실패. 오류를 확인하세요.${NC}"
|
echo -e "${RED}AAB 파일을 찾을 수 없습니다: $AAB_PATH${NC}" | tee -a "../$ERROR_LOG_FILE"
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
elif [ "$BUILD_TYPE" = "release-apk" ]; then
|
elif [ "$BUILD_TYPE" = "release-apk" ]; then
|
||||||
# 서명된 APK 릴리즈 빌드
|
echo -e "${YELLOW}3. 안드로이드 빌드 시작 (release-apk)...${NC}" | tee -a "../$LOG_FILE"
|
||||||
./gradlew clean assembleRelease
|
echo "실행 명령어: ./gradlew assembleRelease" >> "../$LOG_FILE"
|
||||||
if [ $? -ne 0 ]; then
|
./gradlew assembleRelease >> "../$LOG_FILE" 2>&1
|
||||||
echo -e "${RED}서명된 릴리즈 APK 빌드 실패. 오류를 확인하세요.${NC}"
|
BUILD_RESULT=$?
|
||||||
exit 1
|
if [ $BUILD_RESULT -ne 0 ]; then
|
||||||
|
handle_error $BUILD_RESULT "릴리즈 APK 빌드 실패" "./gradlew assembleRelease"
|
||||||
|
fi
|
||||||
|
echo -e "${GREEN}릴리즈 APK 빌드 완료${NC}" | tee -a "../$LOG_FILE"
|
||||||
|
|
||||||
|
# 빌드된 APK 파일 경로 출력
|
||||||
|
APK_PATH="app/build/outputs/apk/release/app-release.apk"
|
||||||
|
if [ -f "$APK_PATH" ]; then
|
||||||
|
echo -e "${GREEN}서명된 APK 파일 생성 완료: ${BLUE}$APK_PATH${NC}" | tee -a "../$LOG_FILE"
|
||||||
|
|
||||||
|
# APK 파일 크기 출력
|
||||||
|
APK_SIZE=$(du -h "$APK_PATH" | cut -f1)
|
||||||
|
echo -e "${GREEN}APK 파일 크기: ${BLUE}$APK_SIZE${NC}" | tee -a "../$LOG_FILE"
|
||||||
|
|
||||||
|
# 릴리즈 디렉토리로 복사
|
||||||
|
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
|
||||||
|
RELEASE_FILENAME="zellyy_release_signed_v${VERSION_NAME}_${TIMESTAMP}.apk"
|
||||||
|
cp "$APK_PATH" "../release/$RELEASE_FILENAME"
|
||||||
|
echo -e "${GREEN}서명된 APK 파일이 릴리즈 디렉토리에 복사되었습니다: ${BLUE}release/$RELEASE_FILENAME${NC}" | tee -a "../$LOG_FILE"
|
||||||
|
else
|
||||||
|
echo -e "${RED}APK 파일을 찾을 수 없습니다: $APK_PATH${NC}" | tee -a "../$ERROR_LOG_FILE"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
SIGNED_APK_PATH="app/build/outputs/apk/release/app-release.apk"
|
DEVICES=$(adb devices | grep -v "List" | grep "device" | wc -l)
|
||||||
DEST_PATH="$HOME/zellyy-finance-release.apk"
|
if [ $DEVICES -gt 0 ]; then
|
||||||
|
echo -e "${YELLOW}연결된 기기가 감지되었습니다. 설치하시겠습니까? (y/n)${NC}"
|
||||||
if [ -f "$SIGNED_APK_PATH" ]; then
|
read -r INSTALL
|
||||||
echo -e "${GREEN}서명된 릴리즈 APK 빌드 성공!${NC}"
|
if [ "$INSTALL" = "y" ] || [ "$INSTALL" = "Y" ]; then
|
||||||
echo -e "APK 파일 위치: $(pwd)/$SIGNED_APK_PATH"
|
# 기기가 여러 개인 경우
|
||||||
|
if [ $(adb devices | grep -v "List" | grep "device" | wc -l) -gt 1 ]; then
|
||||||
# 홈 디렉토리로 APK 복사
|
echo -e "${YELLOW}여러 기기가 연결되어 있습니다. 특정 기기를 선택하세요:${NC}"
|
||||||
cp "$SIGNED_APK_PATH" "$DEST_PATH"
|
adb devices | grep -v "List" | grep "device"
|
||||||
echo -e "${GREEN}서명된 APK를 홈 디렉토리에 복사했습니다: $DEST_PATH${NC}"
|
echo -e "${YELLOW}기기 ID를 입력하세요:${NC}"
|
||||||
|
read -r DEVICE_ID
|
||||||
# 연결된 기기 확인
|
adb -s "$DEVICE_ID" install -r "$APK_PATH"
|
||||||
DEVICES=$(adb devices | grep -v "List" | grep "device" | wc -l)
|
else
|
||||||
if [ $DEVICES -gt 0 ]; then
|
adb install -r "$APK_PATH"
|
||||||
echo -e "${YELLOW}연결된 기기가 감지되었습니다. 설치하시겠습니까? (y/n)${NC}"
|
|
||||||
read -r INSTALL
|
|
||||||
if [ "$INSTALL" = "y" ] || [ "$INSTALL" = "Y" ]; then
|
|
||||||
# 기기가 여러 개인 경우
|
|
||||||
if [ $(adb devices | grep -v "List" | grep "device" | wc -l) -gt 1 ]; then
|
|
||||||
echo -e "${YELLOW}여러 기기가 연결되어 있습니다. 특정 기기를 선택하세요:${NC}"
|
|
||||||
adb devices | grep -v "List" | grep "device"
|
|
||||||
echo -e "${YELLOW}기기 ID를 입력하세요:${NC}"
|
|
||||||
read -r DEVICE_ID
|
|
||||||
adb -s "$DEVICE_ID" install -r "$SIGNED_APK_PATH"
|
|
||||||
else
|
|
||||||
adb install -r "$SIGNED_APK_PATH"
|
|
||||||
fi
|
|
||||||
echo -e "${GREEN}설치 완료!${NC}"
|
|
||||||
fi
|
fi
|
||||||
else
|
echo -e "${GREEN}설치 완료!${NC}"
|
||||||
echo -e "${YELLOW}연결된 기기가 없습니다. 다음 방법으로 APK를 설치할 수 있습니다:${NC}"
|
|
||||||
echo "1. USB 케이블로 폰을 연결하고 파일 전송"
|
|
||||||
echo "2. 이메일이나 메신저로 APK 파일 전송"
|
|
||||||
echo "3. adb 명령어 사용: adb install $DEST_PATH"
|
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo -e "${RED}APK 빌드 실패. 오류를 확인하세요.${NC}"
|
echo -e "${YELLOW}연결된 기기가 없습니다. 다음 방법으로 APK를 설치할 수 있습니다:${NC}"
|
||||||
exit 1
|
echo "1. USB 케이블로 폰을 연결하고 파일 전송"
|
||||||
|
echo "2. 이메일이나 메신저로 APK 파일 전송"
|
||||||
|
echo "3. adb 명령어 사용: adb install $APK_PATH"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
else
|
|
||||||
echo -e "${RED}지원되지 않는 빌드 타입입니다: $BUILD_TYPE${NC}"
|
|
||||||
echo -e "${YELLOW}사용법: ./build-apk-for-device.sh${NC}"
|
|
||||||
exit 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 빌드 실패 시 처리
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "${RED}APK 빌드 실패. 오류를 확인하세요.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 빌드 시간 표시
|
||||||
|
show_build_time
|
||||||
|
|
||||||
echo -e "${GREEN}빌드 프로세스 완료: $(date)${NC}"
|
echo -e "${GREEN}빌드 프로세스 완료: $(date)${NC}"
|
||||||
|
echo -e "${BLUE}빌드 로그: $LOG_FILE${NC}"
|
||||||
|
echo -e "${YELLOW}오류 로그: $ERROR_LOG_FILE${NC}"
|
||||||
|
|
||||||
|
# 최종 결과 요약
|
||||||
|
echo -e "\n${PURPLE}===== 빌드 결과 요약 =====${NC}"
|
||||||
|
echo -e "${CYAN}빌드 타입: $BUILD_TYPE${NC}"
|
||||||
|
echo -e "${CYAN}버전 코드: $NEW_VERSION_CODE${NC}"
|
||||||
|
echo -e "${CYAN}버전 이름: $VERSION_NAME${NC}"
|
||||||
|
echo -e "${CYAN}빌드 넘버: $BUILD_NUMBER${NC}"
|
||||||
|
|
||||||
|
if [ "$BUILD_TYPE" = "debug" ] && [ -f "android/$APK_PATH" ]; then
|
||||||
|
echo -e "${GREEN}디버그 APK: android/$APK_PATH${NC}"
|
||||||
|
elif [ "$BUILD_TYPE" = "release-aab" ] && [ -f "android/$AAB_PATH" ]; then
|
||||||
|
echo -e "${GREEN}릴리즈 AAB: android/$AAB_PATH${NC}"
|
||||||
|
elif [ "$BUILD_TYPE" = "release-apk" ] && [ -f "android/$APK_PATH" ]; then
|
||||||
|
echo -e "${GREEN}릴리즈 APK: android/$APK_PATH${NC}"
|
||||||
|
fi
|
||||||
|
|||||||
154
build-ios.sh
154
build-ios.sh
@@ -196,7 +196,7 @@ start_build_process() {
|
|||||||
echo -e "${YELLOW}디버그 빌드 시작...${NC}"
|
echo -e "${YELLOW}디버그 빌드 시작...${NC}"
|
||||||
|
|
||||||
# 기본적인 빌드 (IPA 없이)
|
# 기본적인 빌드 (IPA 없이)
|
||||||
xcodebuild -workspace App.xcworkspace -scheme App -configuration Debug -derivedDataPath build
|
xcodebuild -workspace App.xcworkspace -scheme App -configuration Debug -derivedDataPath build -allowProvisioningUpdates
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo -e "${RED}디버그 빌드 실패. 오류를 확인하세요.${NC}"
|
echo -e "${RED}디버그 빌드 실패. 오류를 확인하세요.${NC}"
|
||||||
@@ -217,8 +217,17 @@ start_build_process() {
|
|||||||
if [[ "$CREATE_DEBUG_IPA" == "y" || "$CREATE_DEBUG_IPA" == "Y" ]]; then
|
if [[ "$CREATE_DEBUG_IPA" == "y" || "$CREATE_DEBUG_IPA" == "Y" ]]; then
|
||||||
echo -e "${YELLOW}디버그용 IPA 파일 생성 중...${NC}"
|
echo -e "${YELLOW}디버그용 IPA 파일 생성 중...${NC}"
|
||||||
|
|
||||||
# exportOptions.plist 파일 생성
|
# 아카이브 생성
|
||||||
cat > exportOptionsDebug.plist << EOF
|
xcodebuild archive -workspace App.xcworkspace -scheme App -configuration Debug -archivePath build/App.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "${RED}아카이브 생성 실패. 오류를 확인하세요.${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# IPA Export (기본 경로 사용)
|
||||||
|
EXPORT_OPTIONS_PLIST_PATH="build/DebugExportOptions.plist"
|
||||||
|
cat > "$EXPORT_OPTIONS_PLIST_PATH" <<EOL
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
@@ -226,55 +235,26 @@ start_build_process() {
|
|||||||
<key>method</key>
|
<key>method</key>
|
||||||
<string>development</string>
|
<string>development</string>
|
||||||
<key>teamID</key>
|
<key>teamID</key>
|
||||||
<string>${TEAM_ID}</string>
|
<string>$TEAM_ID</string>
|
||||||
<key>compileBitcode</key>
|
<key>signingStyle</key>
|
||||||
<false/>
|
<string>automatic</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
EOF
|
EOL
|
||||||
|
xcodebuild -exportArchive -archivePath build/App.xcarchive -exportPath build/debug_ipa -exportOptionsPlist "$EXPORT_OPTIONS_PLIST_PATH" -allowProvisioningUpdates
|
||||||
# 아카이브 생성
|
|
||||||
xcodebuild -workspace App.xcworkspace -scheme App -configuration Debug clean archive -archivePath "build/App-Debug.xcarchive" -allowProvisioningUpdates
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo -e "${RED}아카이브 생성 실패. 오류를 확인하세요.${NC}"
|
echo -e "${RED}디버그 IPA 파일 생성 실패. 오류를 확인하세요.${NC}"
|
||||||
rm exportOptionsDebug.plist
|
echo -e "${YELLOW}Apple 개발자 계정 정보(팀 ID: $TEAM_ID)가 정확한지, Xcode에서 서명 설정이 완료되었는지 확인하세요.${NC}"
|
||||||
exit 1
|
echo -e "${GREEN}Xcode 빌드는 완료되었을 수 있습니다. Xcode에서 직접 확인하거나 실행해 보세요.${NC}"
|
||||||
fi
|
|
||||||
|
|
||||||
# IPA 파일 생성
|
|
||||||
xcodebuild -exportArchive -archivePath "build/App-Debug.xcarchive" -exportOptionsPlist exportOptionsDebug.plist -exportPath "build/export-debug" -allowProvisioningUpdates
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
|
||||||
echo -e "${RED}IPA 파일 생성 실패. 오류를 확인하세요.${NC}"
|
|
||||||
rm exportOptionsDebug.plist
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm exportOptionsDebug.plist
|
|
||||||
|
|
||||||
DEBUG_IPA_PATH="build/export-debug/App.ipa"
|
|
||||||
DEBUG_DEST_PATH="$HOME/Dev/zellyy-finance-ios-debug.ipa"
|
|
||||||
|
|
||||||
if [ -f "$DEBUG_IPA_PATH" ]; then
|
|
||||||
echo -e "${GREEN}디버그용 IPA 파일 생성 성공!${NC}"
|
|
||||||
echo -e "IPA 파일 위치: $(pwd)/$DEBUG_IPA_PATH"
|
|
||||||
|
|
||||||
# 홈 디렉토리로 IPA 복사
|
|
||||||
cp "$DEBUG_IPA_PATH" "$DEBUG_DEST_PATH"
|
|
||||||
echo -e "${GREEN}IPA를 홈 디렉토리에 복사했습니다: $DEBUG_DEST_PATH${NC}"
|
|
||||||
|
|
||||||
echo -e "${YELLOW}다음 방법으로 다른 기기에 설치할 수 있습니다:${NC}"
|
|
||||||
echo "1. Apple Configurator 2 앱 사용"
|
|
||||||
echo "2. 기기 등록 및 프로비저닝 프로파일이 있는 경우 iTunes로 설치"
|
|
||||||
echo "3. TestFlight를 통한 배포 (App Store Connect에 업로드 필요)"
|
|
||||||
else
|
else
|
||||||
echo -e "${RED}IPA 파일 생성 실패. 오류를 확인하세요.${NC}"
|
echo -e "${GREEN}디버그 IPA 파일 생성 완료: build/debug_ipa/App.ipa${NC}"
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW}개발자 등록이 필요하여 IPA 파일 생성 단계를 건너뜁니다.${NC}"
|
echo -e "${YELLOW}개발자 등록이 필요하여 IPA 파일 생성 단계를 건너<0xEB><0x9B><0x81>니다.${NC}"
|
||||||
echo -e "${YELLOW}시뮬레이터에서 앱을 테스트하거나 Apple 개발자 계정을 등록하세요.${NC}"
|
echo -e "${YELLOW}앱을 기기에서 테스트하려면 Xcode에서 '${WORKSPACE_DIR}/ios/App/App.xcworkspace' 파일을 열고,${NC}"
|
||||||
|
echo -e "${YELLOW}USB로 연결된 기기를 선택한 후 직접 빌드 및 실행하세요.${NC}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
elif [ "$BUILD_TYPE" = "release" ]; then
|
elif [ "$BUILD_TYPE" = "release" ]; then
|
||||||
@@ -282,7 +262,7 @@ EOF
|
|||||||
echo -e "${YELLOW}릴리즈 빌드 시작...${NC}"
|
echo -e "${YELLOW}릴리즈 빌드 시작...${NC}"
|
||||||
|
|
||||||
# 기본 Xcode 빌드
|
# 기본 Xcode 빌드
|
||||||
xcodebuild -workspace App.xcworkspace -scheme App -configuration Release -derivedDataPath build
|
xcodebuild -workspace App.xcworkspace -scheme App -configuration Release -derivedDataPath build -allowProvisioningUpdates
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo -e "${RED}릴리즈 빌드 실패. 오류를 확인하세요.${NC}"
|
echo -e "${RED}릴리즈 빌드 실패. 오류를 확인하세요.${NC}"
|
||||||
@@ -304,18 +284,21 @@ EOF
|
|||||||
echo -e "${YELLOW}App Store 배포용 아카이브 생성 중...${NC}"
|
echo -e "${YELLOW}App Store 배포용 아카이브 생성 중...${NC}"
|
||||||
|
|
||||||
# 아카이브 생성
|
# 아카이브 생성
|
||||||
xcodebuild -workspace App.xcworkspace -scheme App -configuration Release clean archive -archivePath "build/App.xcarchive" -allowProvisioningUpdates
|
xcodebuild archive -workspace App.xcworkspace -scheme App -configuration Release -archivePath build/App.xcarchive -allowProvisioningUpdates
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo -e "${RED}아카이브 생성 실패. 오류를 확인하세요.${NC}"
|
echo -e "${RED}아카이브 생성 실패. 오류를 확인하세요.${NC}"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${GREEN}아카이브 생성 성공!${NC}"
|
# IPA Export
|
||||||
echo -e "아카이브 위치: $(pwd)/build/App.xcarchive"
|
RELEASE_DIR="$WORKSPACE_DIR/release"
|
||||||
|
EXPORT_OPTIONS_PLIST_PATH="build/ReleaseExportOptions.plist"
|
||||||
|
|
||||||
# exportOptions.plist 파일 생성
|
# release 디렉토리 생성
|
||||||
cat > exportOptions.plist << EOF
|
mkdir -p "$RELEASE_DIR"
|
||||||
|
|
||||||
|
cat > "$EXPORT_OPTIONS_PLIST_PATH" <<EOL
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
@@ -323,56 +306,49 @@ EOF
|
|||||||
<key>method</key>
|
<key>method</key>
|
||||||
<string>app-store</string>
|
<string>app-store</string>
|
||||||
<key>teamID</key>
|
<key>teamID</key>
|
||||||
<string>${TEAM_ID}</string>
|
<string>$TEAM_ID</string>
|
||||||
|
<key>signingStyle</key>
|
||||||
|
<string>automatic</string>
|
||||||
<key>uploadBitcode</key>
|
<key>uploadBitcode</key>
|
||||||
<false/>
|
<true/>
|
||||||
<key>uploadSymbols</key>
|
<key>uploadSymbols</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>provisioningProfiles</key>
|
|
||||||
<dict>
|
|
||||||
<key>com.lovable.zellyfinance</key>
|
|
||||||
<string>Zellyy Finance App Store</string>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
EOF
|
EOL
|
||||||
|
# exportPath는 디렉토리만 지정
|
||||||
# IPA 파일 생성
|
xcodebuild -exportArchive -archivePath build/App.xcarchive -exportPath "$RELEASE_DIR" -exportOptionsPlist "$EXPORT_OPTIONS_PLIST_PATH" -allowProvisioningUpdates
|
||||||
echo -e "${YELLOW}IPA 파일 생성 중...${NC}"
|
|
||||||
xcodebuild -exportArchive -archivePath "build/App.xcarchive" -exportOptionsPlist exportOptions.plist -exportPath "build/export" -allowProvisioningUpdates
|
|
||||||
|
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo -e "${RED}IPA 파일 생성 실패. 오류를 확인하세요.${NC}"
|
echo -e "${RED}릴리즈 IPA 파일 생성 실패. 오류를 확인하세요.${NC}"
|
||||||
rm exportOptions.plist
|
echo -e "${YELLOW}Apple 개발자 계정 정보(팀 ID: $TEAM_ID)가 정확한지, Xcode에서 서명 및 배포 설정이 완료되었는지 확인하세요.${NC}"
|
||||||
exit 1
|
echo -e "${GREEN}Xcode 빌드는 완료되었을 수 있습니다. Xcode에서 직접 확인하거나 실행해 보세요.${NC}"
|
||||||
fi
|
|
||||||
|
|
||||||
rm exportOptions.plist
|
|
||||||
|
|
||||||
IPA_PATH="build/export/App.ipa"
|
|
||||||
DEST_PATH="$HOME/Dev/zellyy-finance-ios.ipa"
|
|
||||||
|
|
||||||
if [ -f "$IPA_PATH" ]; then
|
|
||||||
echo -e "${GREEN}IPA 파일 생성 성공!${NC}"
|
|
||||||
echo -e "IPA 파일 위치: $(pwd)/$IPA_PATH"
|
|
||||||
|
|
||||||
# 홈 디렉토리로 IPA 복사
|
|
||||||
cp "$IPA_PATH" "$DEST_PATH"
|
|
||||||
echo -e "${GREEN}IPA를 홈 디렉토리에 복사했습니다: $DEST_PATH${NC}"
|
|
||||||
|
|
||||||
echo -e "${YELLOW}다음 단계:${NC}"
|
|
||||||
echo "1. App Store Connect에 로그인: https://appstoreconnect.apple.com"
|
|
||||||
echo "2. 앱 > 젤리의 적자탈출 > iOS 앱 > 빌드 섹션으로 이동"
|
|
||||||
echo "3. Transporter 앱을 사용하여 IPA 파일 업로드: $DEST_PATH"
|
|
||||||
echo "4. 검토 과정이 완료될 때까지 기다리기 (보통 몇 시간에서 24시간 소요)"
|
|
||||||
else
|
else
|
||||||
echo -e "${RED}IPA 파일 생성 실패. 오류를 확인하세요.${NC}"
|
# 버전 정보 읽기 (스크립트 상단에서 이미 읽었지만 명확성을 위해 다시 확인)
|
||||||
exit 1
|
CURRENT_VERSION_NAME=$(grep "VERSION_NAME" "$PROPERTIES_FILE" | cut -d'=' -f2)
|
||||||
|
CURRENT_BUILD_NUMBER=$(grep "BUILD_NUMBER" "$PROPERTIES_FILE" | cut -d'=' -f2)
|
||||||
|
|
||||||
|
# IPA 파일 이름 변경 (기본 이름은 App.ipa 로 가정)
|
||||||
|
DEFAULT_IPA_NAME="App.ipa"
|
||||||
|
TARGET_IPA_NAME="App-v${CURRENT_VERSION_NAME}-b${CURRENT_BUILD_NUMBER}.ipa"
|
||||||
|
|
||||||
|
if [ -f "$RELEASE_DIR/$DEFAULT_IPA_NAME" ]; then
|
||||||
|
mv "$RELEASE_DIR/$DEFAULT_IPA_NAME" "$RELEASE_DIR/$TARGET_IPA_NAME"
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo -e "${GREEN}릴리즈 IPA 파일 생성 완료:${NC} ${BOLD}${RELEASE_DIR}/${TARGET_IPA_NAME}${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}IPA 파일 이름 변경 실패: ${RELEASE_DIR}/${DEFAULT_IPA_NAME} -> ${TARGET_IPA_NAME}${NC}"
|
||||||
|
echo -e "${YELLOW}생성된 IPA 파일은 ${RELEASE_DIR}/${DEFAULT_IPA_NAME} 에 있습니다.${NC}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo -e "${RED}예상된 IPA 파일(${DEFAULT_IPA_NAME})을 찾을 수 없습니다. Export 과정을 확인하세요.${NC}"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
echo -e "${YELLOW}개발자 등록이 필요하여 아카이브 생성 단계를 건너뜁니다.${NC}"
|
echo -e "${YELLOW}개발자 등록이 필요하여 아카이브 생성 단계를 건너<0xEB><0x9B><0x81>니다.${NC}"
|
||||||
echo -e "${YELLOW}시뮬레이터에서 앱을 테스트하거나 Apple 개발자 계정을 등록하세요.${NC}"
|
echo -e "${YELLOW}앱을 App Store에 제출하거나 Ad Hoc 배포를 하려면 유료 Apple 개발자 계정이 필요합니다.${NC}"
|
||||||
|
echo -e "${YELLOW}개발 테스트는 Xcode에서 '${WORKSPACE_DIR}/ios/App/App.xcworkspace' 파일을 열고 직접 진행할 수 있습니다.${NC}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
else
|
else
|
||||||
|
|||||||
561
docs/02_기술_문서/ci-cd-pipeline.md
Normal file
561
docs/02_기술_문서/ci-cd-pipeline.md
Normal file
@@ -0,0 +1,561 @@
|
|||||||
|
# Zellyy Finance CI/CD 파이프라인 가이드
|
||||||
|
|
||||||
|
이 문서는 Zellyy Finance 앱의 CI/CD(지속적 통합/지속적 배포) 파이프라인 구축 및 사용 방법에 대해 설명합니다.
|
||||||
|
|
||||||
|
## 목차
|
||||||
|
|
||||||
|
1. [개요](#개요)
|
||||||
|
2. [CI/CD 파이프라인 구성](#cicd-파이프라인-구성)
|
||||||
|
3. [GitHub Actions 설정](#github-actions-설정)
|
||||||
|
4. [버전 관리 자동화](#버전-관리-자동화)
|
||||||
|
5. [테스트 빌드 배포 및 설치](#테스트-빌드-배포-및-설치)
|
||||||
|
6. [앱스토어 자동 배포](#앱스토어-자동-배포)
|
||||||
|
7. [비용 분석](#비용-분석)
|
||||||
|
8. [문제 해결 및 FAQ](#문제-해결-및-faq)
|
||||||
|
|
||||||
|
## 개요
|
||||||
|
|
||||||
|
CI/CD 파이프라인은 코드 변경사항을 자동으로 빌드, 테스트 및 배포하는 자동화된 프로세스입니다. Zellyy Finance 앱에서는 GitHub Actions를 사용하여 이 프로세스를 구현합니다.
|
||||||
|
|
||||||
|
### 주요 이점
|
||||||
|
|
||||||
|
- **개발 효율성 향상**: 반복적인 빌드 및 배포 작업 자동화
|
||||||
|
- **일관된 빌드 품질**: 동일한 환경에서 항상 일관된 방식으로 빌드
|
||||||
|
- **버전 관리 자동화**: 버전 코드 및 빌드 번호 자동 증가
|
||||||
|
- **배포 프로세스 간소화**: 앱스토어 제출 과정 자동화
|
||||||
|
- **팀 협업 개선**: 빌드 상태 및 결과 공유 용이
|
||||||
|
|
||||||
|
## CI/CD 파이프라인 구성
|
||||||
|
|
||||||
|
Zellyy Finance의 CI/CD 파이프라인은 다음과 같은 단계로 구성됩니다:
|
||||||
|
|
||||||
|
1. **코드 검증**: 린트 검사 및 단위 테스트 실행
|
||||||
|
2. **웹 앱 빌드**: React 앱 빌드
|
||||||
|
3. **네이티브 앱 빌드**: Capacitor를 통한 Android/iOS 앱 빌드
|
||||||
|
4. **테스트 배포**: Firebase App Distribution을 통한 테스터 배포
|
||||||
|
5. **앱스토어 배포**: Google Play Store 및 Apple App Store 배포
|
||||||
|
|
||||||
|
### 워크플로우 다이어그램
|
||||||
|
|
||||||
|
```
|
||||||
|
코드 푸시/PR → 코드 검증 → 웹 앱 빌드 → 네이티브 앱 빌드 → 테스트 배포 → 앱스토어 배포
|
||||||
|
```
|
||||||
|
|
||||||
|
## GitHub Actions 설정
|
||||||
|
|
||||||
|
### 워크플로우 파일 생성
|
||||||
|
|
||||||
|
`.github/workflows/ci-cd.yml` 파일을 생성하여 CI/CD 파이프라인을 구성합니다.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Zellyy Finance CI/CD
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, develop ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main, develop ]
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
buildType:
|
||||||
|
description: '빌드 타입 (debug, release-apk, release-aab, ios-release)'
|
||||||
|
required: true
|
||||||
|
default: 'debug'
|
||||||
|
versionCode:
|
||||||
|
description: '버전 코드 (자동 증가는 1, 수동 지정은 원하는 숫자 입력)'
|
||||||
|
required: false
|
||||||
|
versionName:
|
||||||
|
description: '버전 이름 (예: 1.0.0)'
|
||||||
|
required: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: 노드 설정
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '18'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: 종속성 설치
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: 린트 검사
|
||||||
|
run: npm run lint
|
||||||
|
|
||||||
|
- name: 버전 정보 설정
|
||||||
|
id: version
|
||||||
|
run: |
|
||||||
|
# 현재 버전 코드 가져오기
|
||||||
|
CURRENT_VERSION_CODE=$(grep -o 'versionCode [0-9]*' android/app/build.gradle | awk '{print $2}')
|
||||||
|
if [ -z "$CURRENT_VERSION_CODE" ]; then
|
||||||
|
CURRENT_VERSION_CODE=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 현재 버전 이름 가져오기
|
||||||
|
CURRENT_VERSION_NAME=$(grep -o 'versionName "[^"]*"' android/app/build.gradle | sed 's/versionName "//' | sed 's/"//')
|
||||||
|
if [ -z "$CURRENT_VERSION_NAME" ]; then
|
||||||
|
CURRENT_VERSION_NAME="1.0.0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 입력된 버전 정보 또는 기본값 사용
|
||||||
|
if [ "${{ github.event.inputs.versionCode }}" = "1" ] || [ -z "${{ github.event.inputs.versionCode }}" ]; then
|
||||||
|
NEW_VERSION_CODE=$((CURRENT_VERSION_CODE + 1))
|
||||||
|
else
|
||||||
|
NEW_VERSION_CODE=${{ github.event.inputs.versionCode }}
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION_NAME="${{ github.event.inputs.versionName }}"
|
||||||
|
if [ -z "$VERSION_NAME" ]; then
|
||||||
|
VERSION_NAME=$CURRENT_VERSION_NAME
|
||||||
|
fi
|
||||||
|
|
||||||
|
BUILD_NUMBER=$NEW_VERSION_CODE
|
||||||
|
|
||||||
|
# 버전 정보 파일 업데이트
|
||||||
|
echo "buildNumber=$BUILD_NUMBER" > android/version.properties
|
||||||
|
echo "versionCode=$NEW_VERSION_CODE" >> android/version.properties
|
||||||
|
echo "versionName=$VERSION_NAME" >> android/version.properties
|
||||||
|
|
||||||
|
cp android/version.properties android/app/version.properties
|
||||||
|
|
||||||
|
# app_version.json 파일 업데이트
|
||||||
|
cat > android/app_version.json << EOF
|
||||||
|
{
|
||||||
|
"versionCode": $NEW_VERSION_CODE,
|
||||||
|
"versionName": "$VERSION_NAME",
|
||||||
|
"buildNumber": $BUILD_NUMBER,
|
||||||
|
"notes": "자동 빌드에 의해 생성된 버전 정보입니다."
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "VERSION_CODE=$NEW_VERSION_CODE" >> $GITHUB_OUTPUT
|
||||||
|
echo "VERSION_NAME=$VERSION_NAME" >> $GITHUB_OUTPUT
|
||||||
|
echo "BUILD_NUMBER=$BUILD_NUMBER" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: 웹 앱 빌드
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Capacitor 동기화
|
||||||
|
run: npx cap sync android
|
||||||
|
|
||||||
|
- name: JDK 설정
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
distribution: 'temurin'
|
||||||
|
java-version: '17'
|
||||||
|
cache: 'gradle'
|
||||||
|
|
||||||
|
- name: 안드로이드 앱 빌드
|
||||||
|
working-directory: android
|
||||||
|
run: |
|
||||||
|
if [ "${{ github.event.inputs.buildType }}" = "release-aab" ]; then
|
||||||
|
./gradlew clean bundleRelease
|
||||||
|
elif [ "${{ github.event.inputs.buildType }}" = "release-apk" ]; then
|
||||||
|
./gradlew clean assembleRelease
|
||||||
|
else
|
||||||
|
./gradlew clean assembleDebug
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Firebase App Distribution 배포
|
||||||
|
if: github.event.inputs.buildType == 'debug'
|
||||||
|
uses: wzieba/Firebase-Distribution-Github-Action@v1
|
||||||
|
with:
|
||||||
|
appId: ${{ secrets.FIREBASE_APP_ID }}
|
||||||
|
serviceCredentialsFileContent: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
|
||||||
|
groups: testers
|
||||||
|
file: android/app/build/outputs/apk/debug/app-debug.apk
|
||||||
|
releaseNotes: |
|
||||||
|
버전: ${{ steps.version.outputs.VERSION_NAME }} (${{ steps.version.outputs.BUILD_NUMBER }})
|
||||||
|
빌드 날짜: $(date +'%Y-%m-%d %H:%M:%S')
|
||||||
|
커밋: ${{ github.sha }}
|
||||||
|
|
||||||
|
- name: Google Play 배포
|
||||||
|
if: github.event.inputs.buildType == 'release-aab'
|
||||||
|
uses: r0adkll/upload-google-play@v1
|
||||||
|
with:
|
||||||
|
serviceAccountJsonPlainText: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT_JSON }}
|
||||||
|
packageName: com.zellyy.finance
|
||||||
|
releaseFiles: android/app/build/outputs/bundle/release/app-release.aab
|
||||||
|
track: internal
|
||||||
|
status: completed
|
||||||
|
releaseName: ${{ steps.version.outputs.VERSION_NAME }}
|
||||||
|
releaseNotes: |
|
||||||
|
ko-KR: 버전 ${{ steps.version.outputs.VERSION_NAME }} 업데이트
|
||||||
|
- 버그 수정 및 성능 개선
|
||||||
|
|
||||||
|
- name: 빌드 결과물 저장
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: app-${{ steps.version.outputs.VERSION_NAME }}-${{ steps.version.outputs.BUILD_NUMBER }}
|
||||||
|
path: |
|
||||||
|
android/app/build/outputs/apk/debug/*.apk
|
||||||
|
android/app/build/outputs/apk/release/*.apk
|
||||||
|
android/app/build/outputs/bundle/release/*.aab
|
||||||
|
|
||||||
|
- name: 슬랙 알림
|
||||||
|
uses: 8398a7/action-slack@v3
|
||||||
|
with:
|
||||||
|
status: ${{ job.status }}
|
||||||
|
fields: repo,message,commit,author,action,eventName,ref,workflow,job,took
|
||||||
|
env:
|
||||||
|
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
|
if: always()
|
||||||
|
```
|
||||||
|
|
||||||
|
### GitHub Secrets 설정
|
||||||
|
|
||||||
|
GitHub 저장소의 Settings > Secrets and variables > Actions에서 다음 시크릿을 설정해야 합니다:
|
||||||
|
|
||||||
|
- `FIREBASE_APP_ID`: Firebase 프로젝트의 앱 ID
|
||||||
|
- `FIREBASE_SERVICE_ACCOUNT`: Firebase 서비스 계정 JSON 파일 내용
|
||||||
|
- `PLAY_STORE_SERVICE_ACCOUNT_JSON`: Google Play 서비스 계정 JSON 파일 내용
|
||||||
|
- `SLACK_WEBHOOK_URL`: 슬랙 알림을 위한 웹훅 URL
|
||||||
|
|
||||||
|
## 버전 관리 자동화
|
||||||
|
|
||||||
|
### 버전 관리 스크립트
|
||||||
|
|
||||||
|
`scripts/version-manager.sh` 파일을 생성하여 버전 관리를 자동화합니다.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# version-manager.sh
|
||||||
|
|
||||||
|
# 현재 버전 정보 가져오기
|
||||||
|
get_current_version() {
|
||||||
|
if [ -f "android/app_version.json" ]; then
|
||||||
|
VERSION_CODE=$(grep -o '"versionCode": [0-9]*' android/app_version.json | awk '{print $2}')
|
||||||
|
VERSION_NAME=$(grep -o '"versionName": "[^"]*"' android/app_version.json | sed 's/"versionName": "//' | sed 's/"//')
|
||||||
|
BUILD_NUMBER=$(grep -o '"buildNumber": [0-9]*' android/app_version.json | awk '{print $2}')
|
||||||
|
|
||||||
|
# 값이 비어있는지 확인
|
||||||
|
if [ -z "$VERSION_CODE" ]; then VERSION_CODE=1; fi
|
||||||
|
if [ -z "$VERSION_NAME" ]; then VERSION_NAME="1.0.0"; fi
|
||||||
|
if [ -z "$BUILD_NUMBER" ]; then BUILD_NUMBER=1; fi
|
||||||
|
else
|
||||||
|
VERSION_CODE=1
|
||||||
|
VERSION_NAME="1.0.0"
|
||||||
|
BUILD_NUMBER=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "현재 버전 정보:"
|
||||||
|
echo "버전 코드: $VERSION_CODE"
|
||||||
|
echo "버전 이름: $VERSION_NAME"
|
||||||
|
echo "빌드 넘버: $BUILD_NUMBER"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 버전 정보 업데이트
|
||||||
|
update_version_files() {
|
||||||
|
# version.properties 파일 업데이트
|
||||||
|
echo "buildNumber=$BUILD_NUMBER" > android/version.properties
|
||||||
|
echo "versionCode=$VERSION_CODE" >> android/version.properties
|
||||||
|
echo "versionName=$VERSION_NAME" >> android/version.properties
|
||||||
|
|
||||||
|
# app 디렉토리에도 동일한 파일 복사
|
||||||
|
cp android/version.properties android/app/version.properties
|
||||||
|
|
||||||
|
# app_version.json 파일 업데이트
|
||||||
|
cat > android/app_version.json << EOF
|
||||||
|
{
|
||||||
|
"versionCode": $VERSION_CODE,
|
||||||
|
"versionName": "$VERSION_NAME",
|
||||||
|
"buildNumber": $BUILD_NUMBER,
|
||||||
|
"notes": "자동 업데이트된 버전 정보입니다."
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "버전 정보가 모든 파일에 저장되었습니다."
|
||||||
|
}
|
||||||
|
|
||||||
|
# 메인 함수
|
||||||
|
main() {
|
||||||
|
get_current_version
|
||||||
|
|
||||||
|
# 빌드 타입에 따라 버전 증가
|
||||||
|
case "$1" in
|
||||||
|
"debug")
|
||||||
|
BUILD_NUMBER=$((BUILD_NUMBER + 1))
|
||||||
|
;;
|
||||||
|
"release")
|
||||||
|
VERSION_CODE=$((VERSION_CODE + 1))
|
||||||
|
BUILD_NUMBER=$VERSION_CODE
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "사용법: $0 [debug|release]"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
update_version_files
|
||||||
|
echo "업데이트된 버전 정보:"
|
||||||
|
echo "버전 코드: $VERSION_CODE"
|
||||||
|
echo "버전 이름: $VERSION_NAME"
|
||||||
|
echo "빌드 넘버: $BUILD_NUMBER"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 버전 관리 규칙
|
||||||
|
|
||||||
|
Zellyy Finance 앱의 버전 관리는 다음 규칙을 따릅니다:
|
||||||
|
|
||||||
|
- **versionCode**: 앱스토어에 제출할 때마다 증가하는 정수 값
|
||||||
|
- **versionName**: 사용자에게 표시되는 버전 (예: "1.0.0")
|
||||||
|
- **buildNumber**: 내부 빌드 추적을 위한 번호
|
||||||
|
|
||||||
|
## 테스트 빌드 배포 및 설치
|
||||||
|
|
||||||
|
테스트 빌드가 완료된 후 개발자와 테스터가 앱을 설치하고 테스트할 수 있는 방법을 설명합니다.
|
||||||
|
|
||||||
|
### Firebase App Distribution을 통한 배포
|
||||||
|
|
||||||
|
Firebase App Distribution은 테스트 버전 앱을 테스터에게 쉽게 배포할 수 있는 서비스입니다.
|
||||||
|
|
||||||
|
#### 설정 방법
|
||||||
|
|
||||||
|
1. **Firebase 프로젝트 설정**:
|
||||||
|
- [Firebase 콘솔](https://console.firebase.google.com/)에서 프로젝트 생성
|
||||||
|
- App Distribution 서비스 활성화
|
||||||
|
- 테스터 그룹 생성 및 테스터 이메일 추가
|
||||||
|
|
||||||
|
2. **GitHub Actions 설정**:
|
||||||
|
- `FIREBASE_APP_ID`와 `FIREBASE_SERVICE_ACCOUNT` 시크릿 설정
|
||||||
|
- 워크플로우 파일에 Firebase 배포 단계 추가
|
||||||
|
|
||||||
|
#### 테스터 초대 및 앱 설치 방법
|
||||||
|
|
||||||
|
##### 안드로이드 테스터
|
||||||
|
|
||||||
|
1. **테스터 초대**:
|
||||||
|
- Firebase 콘솔에서 테스터 이메일 추가
|
||||||
|
- 테스터에게 초대 이메일 발송
|
||||||
|
|
||||||
|
2. **앱 설치 방법**:
|
||||||
|
- 테스터는 초대 이메일의 링크를 클릭
|
||||||
|
- Firebase App Tester 앱 설치 (처음 사용 시)
|
||||||
|
- 테스트 앱 다운로드 및 설치
|
||||||
|
- 설치 시 "알 수 없는 출처" 앱 설치 허용 필요
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 안드로이드 테스터를 위한 안내 메시지 예시
|
||||||
|
안드로이드 테스트 앱 설치 방법:
|
||||||
|
1. 초대 이메일의 링크를 클릭하세요
|
||||||
|
2. Firebase App Tester 앱을 설치하세요 (처음 사용 시)
|
||||||
|
3. 테스트 앱을 다운로드하고 설치하세요
|
||||||
|
4. 설정 > 보안 > 알 수 없는 출처에서 설치 허용을 활성화하세요
|
||||||
|
```
|
||||||
|
|
||||||
|
##### iOS 테스터
|
||||||
|
|
||||||
|
1. **TestFlight 설정**:
|
||||||
|
- Apple Developer 계정에서 앱 등록
|
||||||
|
- 내부 테스터 그룹 생성
|
||||||
|
|
||||||
|
2. **테스터 초대**:
|
||||||
|
- App Store Connect에서 테스터 이메일 추가
|
||||||
|
- 테스터에게 초대 이메일 발송
|
||||||
|
|
||||||
|
3. **앱 설치 방법**:
|
||||||
|
- 테스터는 초대 이메일의 링크를 클릭
|
||||||
|
- TestFlight 앱 설치 (App Store에서 다운로드)
|
||||||
|
- TestFlight를 통해 테스트 앱 설치
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# iOS 테스터를 위한 안내 메시지 예시
|
||||||
|
iOS 테스트 앱 설치 방법:
|
||||||
|
1. 초대 이메일의 링크를 클릭하세요
|
||||||
|
2. App Store에서 TestFlight 앱을 설치하세요
|
||||||
|
3. TestFlight 앱을 열고 테스트 앱을 설치하세요
|
||||||
|
```
|
||||||
|
|
||||||
|
### GitHub Actions Artifacts를 통한 직접 다운로드
|
||||||
|
|
||||||
|
Firebase App Distribution 외에도 GitHub Actions의 Artifacts 기능을 사용하여 빌드 결과물을 직접 다운로드할 수 있습니다.
|
||||||
|
|
||||||
|
1. **빌드 결과물 확인**:
|
||||||
|
- GitHub 저장소의 Actions 탭으로 이동
|
||||||
|
- 해당 워크플로우 실행 결과 페이지 열기
|
||||||
|
- Artifacts 섹션에서 APK 또는 IPA 파일 다운로드
|
||||||
|
|
||||||
|
2. **안드로이드 APK 설치**:
|
||||||
|
- 다운로드한 APK 파일을 안드로이드 기기로 전송
|
||||||
|
- 파일 관리자에서 APK 파일 실행
|
||||||
|
- 알 수 없는 출처 앱 설치 허용
|
||||||
|
|
||||||
|
3. **iOS IPA 설치** (개발자만 가능):
|
||||||
|
- Apple Developer 계정이 있는 개발자만 가능
|
||||||
|
- Xcode 또는 Apple Configurator를 사용하여 설치
|
||||||
|
|
||||||
|
### QR 코드를 통한 간편 설치
|
||||||
|
|
||||||
|
테스터의 편의를 위해 QR 코드를 생성하여 배포할 수도 있습니다:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: QR 코드 생성
|
||||||
|
if: github.event.inputs.buildType == 'debug'
|
||||||
|
run: |
|
||||||
|
# Firebase 배포 URL에 대한 QR 코드 생성
|
||||||
|
FIREBASE_URL="https://appdistribution.firebase.dev/i/..."
|
||||||
|
curl -s "https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=$FIREBASE_URL" > qrcode.png
|
||||||
|
echo "QR 코드가 생성되었습니다. 테스터는 이 코드를 스캔하여 앱을 설치할 수 있습니다."
|
||||||
|
|
||||||
|
- name: QR 코드 저장
|
||||||
|
if: github.event.inputs.buildType == 'debug'
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: install-qrcode
|
||||||
|
path: qrcode.png
|
||||||
|
```
|
||||||
|
|
||||||
|
## 앱스토어 자동 배포
|
||||||
|
|
||||||
|
### Google Play Store 배포
|
||||||
|
|
||||||
|
Google Play Store에 자동으로 앱을 배포하려면 다음 단계가 필요합니다:
|
||||||
|
|
||||||
|
1. **Google Play Console 설정**:
|
||||||
|
- API 액세스 활성화
|
||||||
|
- 서비스 계정 생성 및 권한 부여
|
||||||
|
- JSON 키 다운로드
|
||||||
|
|
||||||
|
2. **GitHub Actions 설정**:
|
||||||
|
- `PLAY_STORE_SERVICE_ACCOUNT_JSON` 시크릿 설정
|
||||||
|
- 워크플로우 파일에 배포 단계 추가
|
||||||
|
|
||||||
|
### Apple App Store 배포
|
||||||
|
|
||||||
|
Apple App Store에 자동으로 앱을 배포하려면 Fastlane을 사용합니다:
|
||||||
|
|
||||||
|
1. **Fastlane 설정**:
|
||||||
|
- iOS 디렉토리에 Fastfile 생성
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
default_platform(:ios)
|
||||||
|
|
||||||
|
platform :ios do
|
||||||
|
desc "배포용 iOS 앱 빌드 및 TestFlight 업로드"
|
||||||
|
lane :release do
|
||||||
|
setup_ci
|
||||||
|
|
||||||
|
# 인증서 및 프로비저닝 프로파일 동기화
|
||||||
|
match(
|
||||||
|
type: "appstore",
|
||||||
|
readonly: true
|
||||||
|
)
|
||||||
|
|
||||||
|
# 버전 업데이트
|
||||||
|
increment_build_number(
|
||||||
|
build_number: ENV["BUILD_NUMBER"]
|
||||||
|
)
|
||||||
|
|
||||||
|
# 앱 빌드
|
||||||
|
build_app(
|
||||||
|
scheme: "App",
|
||||||
|
workspace: "App.xcworkspace",
|
||||||
|
export_method: "app-store"
|
||||||
|
)
|
||||||
|
|
||||||
|
# TestFlight 업로드
|
||||||
|
upload_to_testflight(
|
||||||
|
skip_waiting_for_build_processing: true
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **GitHub Actions 설정**:
|
||||||
|
- App Store Connect API 키 생성 및 GitHub Secrets에 저장
|
||||||
|
- 워크플로우 파일에 iOS 빌드 및 배포 단계 추가
|
||||||
|
|
||||||
|
## 비용 분석
|
||||||
|
|
||||||
|
### GitHub Actions 비용
|
||||||
|
|
||||||
|
GitHub Actions는 다음과 같은 무료 할당량을 제공합니다:
|
||||||
|
|
||||||
|
- **퍼블릭 저장소**: 무제한 무료 사용 가능
|
||||||
|
- **프라이빗 저장소**:
|
||||||
|
- GitHub Free: 매월 2,000분의 무료 빌드 시간
|
||||||
|
- GitHub Pro: 매월 3,000분의 무료 빌드 시간
|
||||||
|
- GitHub Team: 매월 계정당 3,000분의 무료 빌드 시간
|
||||||
|
- GitHub Enterprise: 매월 계정당 50,000분의 무료 빌드 시간
|
||||||
|
|
||||||
|
### Zellyy Finance 앱의 예상 비용
|
||||||
|
|
||||||
|
- **빌드 시간 예상**: 한 번의 워크플로우당 약 7-20분
|
||||||
|
- **월간 빌드 횟수 시나리오**:
|
||||||
|
- 하루 1회 빌드: 월 30회 × 15분 = 450분
|
||||||
|
- 하루 3회 빌드: 월 90회 × 15분 = 1,350분
|
||||||
|
- 하루 5회 빌드: 월 150회 × 15분 = 2,250분
|
||||||
|
|
||||||
|
- **비용 계산**:
|
||||||
|
- GitHub Free 계정 사용 시: 월 2,000분 무료 (하루 4회 빌드까지 무료)
|
||||||
|
- 초과 사용 시: 분당 $0.008 (Linux 머신 기준)
|
||||||
|
|
||||||
|
### 비용 최적화 전략
|
||||||
|
|
||||||
|
1. **캐싱 활용**:
|
||||||
|
```yaml
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.npm
|
||||||
|
node_modules
|
||||||
|
android/.gradle
|
||||||
|
key: ${{ runner.os }}-build-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **빌드 트리거 최적화**:
|
||||||
|
```yaml
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, develop ]
|
||||||
|
paths-ignore:
|
||||||
|
- '**.md'
|
||||||
|
- 'docs/**'
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **조건부 작업 설정**:
|
||||||
|
```yaml
|
||||||
|
- name: Google Play 배포
|
||||||
|
if: github.ref == 'refs/heads/main' && github.event.inputs.buildType == 'release-aab'
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 문제 해결 및 FAQ
|
||||||
|
|
||||||
|
### 자주 발생하는 문제
|
||||||
|
|
||||||
|
1. **빌드 실패**:
|
||||||
|
- 종속성 문제: `npm ci` 대신 `npm install`을 사용해 보세요.
|
||||||
|
- 캐시 문제: 캐시를 삭제하고 다시 시도하세요.
|
||||||
|
|
||||||
|
2. **버전 관리 문제**:
|
||||||
|
- 버전 파일이 비어 있는 경우: 스크립트가 기본값을 설정하도록 합니다.
|
||||||
|
- 버전 코드 충돌: 수동으로 버전 코드를 설정하여 해결합니다.
|
||||||
|
|
||||||
|
3. **배포 실패**:
|
||||||
|
- 인증 문제: 서비스 계정 권한을 확인하세요.
|
||||||
|
- 앱 서명 문제: 키스토어 설정을 확인하세요.
|
||||||
|
|
||||||
|
### FAQ
|
||||||
|
|
||||||
|
**Q: 워크플로우를 수동으로 실행하려면 어떻게 해야 하나요?**
|
||||||
|
A: GitHub 저장소의 Actions 탭에서 "Zellyy Finance CI/CD" 워크플로우를 선택하고 "Run workflow" 버튼을 클릭합니다.
|
||||||
|
|
||||||
|
**Q: 특정 커밋에서 빌드를 실행하려면 어떻게 해야 하나요?**
|
||||||
|
A: 해당 커밋으로 체크아웃한 후 수동으로 워크플로우를 실행하거나, GitHub API를 사용하여 특정 커밋에서 워크플로우를 트리거할 수 있습니다.
|
||||||
|
|
||||||
|
**Q: 빌드 결과물은 어디에서 찾을 수 있나요?**
|
||||||
|
A: 워크플로우 실행 페이지의 "Artifacts" 섹션에서 다운로드할 수 있습니다.
|
||||||
|
|
||||||
|
**Q: 테스터가 테스트 앱을 설치하는 가장 쉬운 방법은 무엇인가요?**
|
||||||
|
A: Firebase App Distribution을 사용하는 것이 가장 쉽습니다. 테스터는 이메일로 초대를 받고 링크를 통해 앱을 설치할 수 있습니다. QR 코드를 제공하면 더욱 편리하게 설치할 수 있습니다.
|
||||||
|
|
||||||
|
**Q: iOS 테스트 앱 설치 시 개발자 계정이 필요한가요?**
|
||||||
|
A: TestFlight를 통한 배포의 경우 테스터는 개발자 계정이 필요하지 않습니다. 하지만 개발자는 Apple Developer 계정이 필요합니다. GitHub Actions Artifacts를 통한 직접 설치는 개발자만 가능합니다.
|
||||||
80
docs/SUPABASE_ONPREM_MIGRATION_PLAN.md
Normal file
80
docs/SUPABASE_ONPREM_MIGRATION_PLAN.md
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
# Supabase Cloud → On-Premise Migration Plan
|
||||||
|
|
||||||
|
이 문서에는 기존 Supabase Cloud 프로젝트를 On-Premise Self-Host 환경으로 이전하기 위한 단계별 절차를 정리합니다.
|
||||||
|
|
||||||
|
## 1. 개요
|
||||||
|
- 목적: 클라우드 종속성을 제거하고 자체 운영 가능하도록 Supabase 스택을 온프레미스 환경에 배포
|
||||||
|
- 범위: 인증(Gotrue), 실시간(Realtime), 스토리지(Storage), 데이터베이스(Postgres)
|
||||||
|
|
||||||
|
## 2. 사전 준비
|
||||||
|
1. 현재 환경 문서화
|
||||||
|
- 프로젝트 구조, 스키마, RLS 규칙, 함수, 트리거 등
|
||||||
|
- 사용 중인 Supabase CLI/SDK 버전 및 환경 변수 목록
|
||||||
|
2. 인프라 준비
|
||||||
|
- Docker 엔진 또는 Kubernetes 클러스터
|
||||||
|
- 도메인, TLS 인증서 준비
|
||||||
|
- 최소 요구 사양 확인 (CPU, 메모리, 디스크)
|
||||||
|
3. 백업 계획 수립
|
||||||
|
- Cloud DB Dump 스케줄링 방법
|
||||||
|
- 백업 보관 위치 및 암호화 방침
|
||||||
|
|
||||||
|
## 3. On-Premise Supabase Self-Host 배포
|
||||||
|
1. Supabase CLI 설치
|
||||||
|
```bash
|
||||||
|
npm install -g supabase
|
||||||
|
```
|
||||||
|
2. 초기화 및 컨테이너 실행
|
||||||
|
```bash
|
||||||
|
supabase init # 프로젝트 디렉토리에 구성 파일 작성
|
||||||
|
supabase start # Postgres, Kong, Realtime, GoTrue, Storage 컨테이너 실행
|
||||||
|
```
|
||||||
|
3. 버전 호환성 확인
|
||||||
|
- `supabase version`으로 CLI, API, DB 버전 일치 여부 점검
|
||||||
|
|
||||||
|
## 4. 데이터 마이그레이션
|
||||||
|
1. Cloud DB Dump
|
||||||
|
```bash
|
||||||
|
supabase db dump --project-ref <PROJECT_REF> --file cloud_dump.sql
|
||||||
|
```
|
||||||
|
2. On-Premise DB Restore
|
||||||
|
```bash
|
||||||
|
supabase db restore --file cloud_dump.sql
|
||||||
|
```
|
||||||
|
3. 데이터 무결성 검증
|
||||||
|
- 주요 테이블 row 수 체크
|
||||||
|
- RLS 규칙·함수 정상 동작 여부 테스트
|
||||||
|
|
||||||
|
## 5. 애플리케이션 구성 변경
|
||||||
|
1. 환경 변수 업데이트
|
||||||
|
```env
|
||||||
|
SUPABASE_URL=http://<ONPREM_HOST>:8000
|
||||||
|
SUPABASE_ANON_KEY=<새_ANON_KEY>
|
||||||
|
SUPABASE_SERVICE_KEY=<새_SERVICE_KEY>
|
||||||
|
```
|
||||||
|
2. SDK 초기화 점검
|
||||||
|
- `client = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)` 정상 연결 확인
|
||||||
|
|
||||||
|
## 6. 테스트 및 검증
|
||||||
|
1. 로컬 개발서버 테스트
|
||||||
|
- 프론트·백엔드 실행 후 CRUD 기능 확인
|
||||||
|
2. 스테이징 환경 배포
|
||||||
|
- Blue-Green 또는 Canary 배포로 트래픽 분할
|
||||||
|
- 모니터링(로그, 메트릭) 이상 유무 확인
|
||||||
|
|
||||||
|
## 7. 프로덕션 전환
|
||||||
|
1. DNS 레코드 변경 또는 로드밸런서 설정
|
||||||
|
2. SSL/TLS 인증서 적용
|
||||||
|
3. 전환 후 롤백 플랜 준비
|
||||||
|
|
||||||
|
## 8. 롤백 계획
|
||||||
|
- 문제가 발생 시 이전 Cloud 인스턴스로 트래픽 리디렉션
|
||||||
|
- 신규 데이터 백업 및 차분 마이그레이션
|
||||||
|
|
||||||
|
## 9. 모니터링 및 운영
|
||||||
|
- 백그라운드 컨테이너 상태 감시 (Prometheus, Grafana)
|
||||||
|
- 정기 백업·복원 테스트 자동화
|
||||||
|
- 보안 패치 및 버전 업데이트 정책 수립
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*문서 위치: `docs/SUPABASE_ONPREM_MIGRATION_PLAN.md`*
|
||||||
@@ -24,10 +24,10 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../../node_modules/@capacitor/splash-screen"
|
:path: "../../node_modules/@capacitor/splash-screen"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Capacitor: 68ff8eabbcce387e69767c13b5fbcc1c5399eabc
|
Capacitor: bceb785fb78f5e81e4a9e37843bc1c24bd9c7194
|
||||||
CapacitorCordova: 866217f32c1d25b326c568a10ea3ed0c36b13e29
|
CapacitorCordova: 866217f32c1d25b326c568a10ea3ed0c36b13e29
|
||||||
CapacitorKeyboard: 2c26c6fccde35023c579fc37d4cae6326d5e6343
|
CapacitorKeyboard: 4db71e694e7afb5d7c0be09b05495c19f7d6c914
|
||||||
CapacitorSplashScreen: f4e58cc02aafd91c7cbaf32a3d1b44d02a115125
|
CapacitorSplashScreen: 7e7a0a1113833032f196b3af6fa437baccacf5bc
|
||||||
|
|
||||||
PODFILE CHECKSUM: 7376e84e32edf2d1753401ce95b6db45439d33ff
|
PODFILE CHECKSUM: 7376e84e32edf2d1753401ce95b6db45439d33ff
|
||||||
|
|
||||||
|
|||||||
145
package-lock.json
generated
145
package-lock.json
generated
@@ -42,14 +42,15 @@
|
|||||||
"@radix-ui/react-toggle": "^1.1.0",
|
"@radix-ui/react-toggle": "^1.1.0",
|
||||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||||
"@radix-ui/react-tooltip": "^1.1.4",
|
"@radix-ui/react-tooltip": "^1.1.4",
|
||||||
"@supabase/supabase-js": "^2.49.1",
|
"@supabase/supabase-js": "^2.49.4",
|
||||||
"@tanstack/react-query": "^5.56.2",
|
"@tanstack/react-query": "^5.56.2",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"browserslist": "^4.24.4",
|
"browserslist": "^4.24.4",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^3.6.0",
|
||||||
|
"dotenv": "^16.5.0",
|
||||||
"embla-carousel-react": "^8.3.0",
|
"embla-carousel-react": "^8.3.0",
|
||||||
"input-otp": "^1.2.4",
|
"input-otp": "^1.2.4",
|
||||||
"lucide-react": "^0.462.0",
|
"lucide-react": "^0.462.0",
|
||||||
@@ -92,7 +93,6 @@
|
|||||||
"version": "5.2.0",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
||||||
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
|
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
@@ -138,9 +138,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/runtime": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.25.9",
|
"version": "7.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.9.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
|
||||||
"integrity": "sha512-4zpTHZ9Cm6L9L+uIqghQX8ZXg8HKFcjYO3qHoO8zTmRm6HQUJ8SSJ+KRvbMBZn0EGVlT4DRYeQ/6hjlyXBh+Kg==",
|
"integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"regenerator-runtime": "^0.14.0"
|
"regenerator-runtime": "^0.14.0"
|
||||||
@@ -1136,7 +1136,6 @@
|
|||||||
"version": "0.3.5",
|
"version": "0.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
||||||
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
|
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/set-array": "^1.2.1",
|
"@jridgewell/set-array": "^1.2.1",
|
||||||
@@ -1151,7 +1150,6 @@
|
|||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
@@ -1161,7 +1159,6 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
||||||
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
@@ -1171,14 +1168,12 @@
|
|||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
||||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.25",
|
"version": "0.3.25",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.1.0",
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
@@ -1189,7 +1184,6 @@
|
|||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nodelib/fs.stat": "2.0.5",
|
"@nodelib/fs.stat": "2.0.5",
|
||||||
@@ -1203,7 +1197,6 @@
|
|||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
||||||
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
@@ -1213,7 +1206,6 @@
|
|||||||
"version": "1.2.8",
|
"version": "1.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
||||||
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nodelib/fs.scandir": "2.1.5",
|
"@nodelib/fs.scandir": "2.1.5",
|
||||||
@@ -1227,7 +1219,6 @@
|
|||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||||
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
@@ -3001,9 +2992,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"node_modules/@supabase/auth-js": {
|
"node_modules/@supabase/auth-js": {
|
||||||
"version": "2.68.0",
|
"version": "2.69.1",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.68.0.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.69.1.tgz",
|
||||||
"integrity": "sha512-odG7nb7aOmZPUXk6SwL2JchSsn36Ppx11i2yWMIc/meUO2B2HK9YwZHPK06utD9Ql9ke7JKDbwGin/8prHKxxQ==",
|
"integrity": "sha512-FILtt5WjCNzmReeRLq5wRs3iShwmnWgBvxHfqapC/VoljJl+W8hDAyFmf1NVw3zH+ZjZ05AKxiKxVeb0HNWRMQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@supabase/node-fetch": "^2.6.14"
|
"@supabase/node-fetch": "^2.6.14"
|
||||||
@@ -3031,9 +3022,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@supabase/postgrest-js": {
|
"node_modules/@supabase/postgrest-js": {
|
||||||
"version": "1.19.2",
|
"version": "1.19.4",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.19.4.tgz",
|
||||||
"integrity": "sha512-MXRbk4wpwhWl9IN6rIY1mR8uZCCG4MZAEji942ve6nMwIqnBgBnZhZlON6zTTs6fgveMnoCILpZv1+K91jN+ow==",
|
"integrity": "sha512-O4soKqKtZIW3olqmbXXbKugUtByD2jPa8kL2m2c1oozAO11uCcGrRhkZL0kVxjBLrXHE0mdSkFsMj7jDSfyNpw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@supabase/node-fetch": "^2.6.14"
|
"@supabase/node-fetch": "^2.6.14"
|
||||||
@@ -3061,15 +3052,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@supabase/supabase-js": {
|
"node_modules/@supabase/supabase-js": {
|
||||||
"version": "2.49.1",
|
"version": "2.49.4",
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.49.1.tgz",
|
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.49.4.tgz",
|
||||||
"integrity": "sha512-lKaptKQB5/juEF5+jzmBeZlz69MdHZuxf+0f50NwhL+IE//m4ZnOeWlsKRjjsM0fVayZiQKqLvYdBn0RLkhGiQ==",
|
"integrity": "sha512-jUF0uRUmS8BKt37t01qaZ88H9yV1mbGYnqLeuFWLcdV+x1P4fl0yP9DGtaEhFPZcwSom7u16GkLEH9QJZOqOkw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@supabase/auth-js": "2.68.0",
|
"@supabase/auth-js": "2.69.1",
|
||||||
"@supabase/functions-js": "2.4.4",
|
"@supabase/functions-js": "2.4.4",
|
||||||
"@supabase/node-fetch": "2.6.15",
|
"@supabase/node-fetch": "2.6.15",
|
||||||
"@supabase/postgrest-js": "1.19.2",
|
"@supabase/postgrest-js": "1.19.4",
|
||||||
"@supabase/realtime-js": "2.11.2",
|
"@supabase/realtime-js": "2.11.2",
|
||||||
"@supabase/storage-js": "2.7.1"
|
"@supabase/storage-js": "2.7.1"
|
||||||
}
|
}
|
||||||
@@ -3459,14 +3450,14 @@
|
|||||||
"version": "15.7.13",
|
"version": "15.7.13",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
|
||||||
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
|
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.3.12",
|
"version": "18.3.12",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz",
|
||||||
"integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==",
|
"integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
@@ -3477,7 +3468,7 @@
|
|||||||
"version": "18.3.1",
|
"version": "18.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.1.tgz",
|
||||||
"integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==",
|
"integrity": "sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
@@ -3826,14 +3817,12 @@
|
|||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||||
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
|
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/anymatch": {
|
"node_modules/anymatch": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"normalize-path": "^3.0.0",
|
"normalize-path": "^3.0.0",
|
||||||
@@ -3847,7 +3836,6 @@
|
|||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||||
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/argparse": {
|
"node_modules/argparse": {
|
||||||
@@ -3964,7 +3952,6 @@
|
|||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||||
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
@@ -4000,7 +3987,6 @@
|
|||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fill-range": "^7.1.1"
|
"fill-range": "^7.1.1"
|
||||||
@@ -4064,7 +4050,6 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||||
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
@@ -4111,7 +4096,6 @@
|
|||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"anymatch": "~3.1.2",
|
"anymatch": "~3.1.2",
|
||||||
@@ -4136,7 +4120,6 @@
|
|||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-glob": "^4.0.1"
|
"is-glob": "^4.0.1"
|
||||||
@@ -4574,7 +4557,6 @@
|
|||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||||
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
@@ -4604,7 +4586,6 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"cssesc": "bin/cssesc"
|
"cssesc": "bin/cssesc"
|
||||||
@@ -4741,9 +4722,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/date-fns": {
|
"node_modules/date-fns": {
|
||||||
"version": "4.1.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
|
||||||
"integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
|
"integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -4799,14 +4780,12 @@
|
|||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||||
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/dlv": {
|
"node_modules/dlv": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/dom-helpers": {
|
"node_modules/dom-helpers": {
|
||||||
@@ -4819,6 +4798,18 @@
|
|||||||
"csstype": "^3.0.2"
|
"csstype": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "16.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
|
||||||
|
"integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://dotenvx.com"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eastasianwidth": {
|
"node_modules/eastasianwidth": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
@@ -5161,7 +5152,6 @@
|
|||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||||
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
|
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nodelib/fs.stat": "^2.0.2",
|
"@nodelib/fs.stat": "^2.0.2",
|
||||||
@@ -5178,7 +5168,6 @@
|
|||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-glob": "^4.0.1"
|
"is-glob": "^4.0.1"
|
||||||
@@ -5205,7 +5194,6 @@
|
|||||||
"version": "1.17.1",
|
"version": "1.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
|
||||||
"integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
|
"integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"reusify": "^1.0.4"
|
"reusify": "^1.0.4"
|
||||||
@@ -5237,7 +5225,6 @@
|
|||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
@@ -5356,7 +5343,6 @@
|
|||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
@@ -5371,7 +5357,6 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
@@ -5390,7 +5375,6 @@
|
|||||||
"version": "10.4.5",
|
"version": "10.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"foreground-child": "^3.1.0",
|
"foreground-child": "^3.1.0",
|
||||||
@@ -5411,7 +5395,6 @@
|
|||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-glob": "^4.0.3"
|
"is-glob": "^4.0.3"
|
||||||
@@ -5424,7 +5407,6 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0"
|
"balanced-match": "^1.0.0"
|
||||||
@@ -5434,7 +5416,6 @@
|
|||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^2.0.1"
|
"brace-expansion": "^2.0.1"
|
||||||
@@ -5486,7 +5467,6 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
@@ -5579,7 +5559,6 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"binary-extensions": "^2.0.0"
|
"binary-extensions": "^2.0.0"
|
||||||
@@ -5592,7 +5571,6 @@
|
|||||||
"version": "2.15.1",
|
"version": "2.15.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
||||||
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
|
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hasown": "^2.0.2"
|
"hasown": "^2.0.2"
|
||||||
@@ -5623,7 +5601,6 @@
|
|||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -5642,7 +5619,6 @@
|
|||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-extglob": "^2.1.1"
|
"is-extglob": "^2.1.1"
|
||||||
@@ -5655,7 +5631,6 @@
|
|||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
@@ -5683,7 +5658,6 @@
|
|||||||
"version": "3.4.3",
|
"version": "3.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
||||||
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
|
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
|
||||||
"dev": true,
|
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@isaacs/cliui": "^8.0.2"
|
"@isaacs/cliui": "^8.0.2"
|
||||||
@@ -5699,7 +5673,6 @@
|
|||||||
"version": "1.21.6",
|
"version": "1.21.6",
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
|
||||||
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
|
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
@@ -5794,7 +5767,6 @@
|
|||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||||
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
@@ -5807,7 +5779,6 @@
|
|||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/locate-path": {
|
"node_modules/locate-path": {
|
||||||
@@ -6317,7 +6288,6 @@
|
|||||||
"version": "10.4.3",
|
"version": "10.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/lucide-react": {
|
"node_modules/lucide-react": {
|
||||||
@@ -6342,7 +6312,6 @@
|
|||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||||
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
@@ -6352,7 +6321,6 @@
|
|||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"braces": "^3.0.3",
|
"braces": "^3.0.3",
|
||||||
@@ -6431,7 +6399,6 @@
|
|||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||||
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"any-promise": "^1.0.0",
|
"any-promise": "^1.0.0",
|
||||||
@@ -6440,10 +6407,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nanoid": {
|
"node_modules/nanoid": {
|
||||||
"version": "3.3.7",
|
"version": "3.3.11",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -6510,7 +6476,6 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -6539,7 +6504,6 @@
|
|||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
||||||
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
@@ -6654,14 +6618,12 @@
|
|||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/path-scurry": {
|
"node_modules/path-scurry": {
|
||||||
"version": "1.11.1",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||||
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
||||||
"dev": true,
|
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lru-cache": "^10.2.0",
|
"lru-cache": "^10.2.0",
|
||||||
@@ -6690,7 +6652,6 @@
|
|||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
@@ -6703,7 +6664,6 @@
|
|||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||||
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
|
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -6713,7 +6673,6 @@
|
|||||||
"version": "4.0.6",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
||||||
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
|
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
@@ -6737,7 +6696,6 @@
|
|||||||
"version": "8.4.47",
|
"version": "8.4.47",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz",
|
||||||
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
|
"integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -6766,7 +6724,6 @@
|
|||||||
"version": "15.1.0",
|
"version": "15.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
|
||||||
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
|
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"postcss-value-parser": "^4.0.0",
|
"postcss-value-parser": "^4.0.0",
|
||||||
@@ -6784,7 +6741,6 @@
|
|||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
|
||||||
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
|
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"camelcase-css": "^2.0.1"
|
"camelcase-css": "^2.0.1"
|
||||||
@@ -6804,7 +6760,6 @@
|
|||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
|
||||||
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
|
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -6840,7 +6795,6 @@
|
|||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
||||||
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@@ -6866,7 +6820,6 @@
|
|||||||
"version": "6.1.2",
|
"version": "6.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
||||||
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cssesc": "^3.0.0",
|
"cssesc": "^3.0.0",
|
||||||
@@ -6880,7 +6833,6 @@
|
|||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
@@ -6946,7 +6898,6 @@
|
|||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -7171,7 +7122,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pify": "^2.3.0"
|
"pify": "^2.3.0"
|
||||||
@@ -7195,7 +7145,6 @@
|
|||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"picomatch": "^2.2.1"
|
"picomatch": "^2.2.1"
|
||||||
@@ -7246,7 +7195,6 @@
|
|||||||
"version": "1.22.8",
|
"version": "1.22.8",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-core-module": "^2.13.0",
|
"is-core-module": "^2.13.0",
|
||||||
@@ -7274,7 +7222,6 @@
|
|||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
||||||
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
|
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"iojs": ">=1.0.0",
|
"iojs": ">=1.0.0",
|
||||||
@@ -7427,7 +7374,6 @@
|
|||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||||
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
@@ -7564,7 +7510,6 @@
|
|||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@@ -7701,7 +7646,6 @@
|
|||||||
"version": "3.35.0",
|
"version": "3.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
||||||
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
|
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/gen-mapping": "^0.3.2",
|
"@jridgewell/gen-mapping": "^0.3.2",
|
||||||
@@ -7737,7 +7681,6 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -7760,7 +7703,6 @@
|
|||||||
"version": "3.4.17",
|
"version": "3.4.17",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
||||||
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
|
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
@@ -7840,7 +7782,6 @@
|
|||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||||
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"any-promise": "^1.0.0"
|
"any-promise": "^1.0.0"
|
||||||
@@ -7850,7 +7791,6 @@
|
|||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
||||||
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"thenify": ">= 3.1.0 < 4"
|
"thenify": ">= 3.1.0 < 4"
|
||||||
@@ -7878,7 +7818,6 @@
|
|||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-number": "^7.0.0"
|
"is-number": "^7.0.0"
|
||||||
@@ -7919,7 +7858,6 @@
|
|||||||
"version": "0.1.13",
|
"version": "0.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
||||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
@@ -8141,9 +8079,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vite": {
|
"node_modules/vite": {
|
||||||
"version": "5.4.10",
|
"version": "5.4.18",
|
||||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.18.tgz",
|
||||||
"integrity": "sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==",
|
"integrity": "sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -8391,7 +8329,6 @@
|
|||||||
"version": "2.6.0",
|
"version": "2.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz",
|
||||||
"integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==",
|
"integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"yaml": "bin.mjs"
|
"yaml": "bin.mjs"
|
||||||
|
|||||||
@@ -45,14 +45,15 @@
|
|||||||
"@radix-ui/react-toggle": "^1.1.0",
|
"@radix-ui/react-toggle": "^1.1.0",
|
||||||
"@radix-ui/react-toggle-group": "^1.1.0",
|
"@radix-ui/react-toggle-group": "^1.1.0",
|
||||||
"@radix-ui/react-tooltip": "^1.1.4",
|
"@radix-ui/react-tooltip": "^1.1.4",
|
||||||
"@supabase/supabase-js": "^2.49.1",
|
"@supabase/supabase-js": "^2.49.4",
|
||||||
"@tanstack/react-query": "^5.56.2",
|
"@tanstack/react-query": "^5.56.2",
|
||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"browserslist": "^4.24.4",
|
"browserslist": "^4.24.4",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.0",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^3.6.0",
|
||||||
|
"dotenv": "^16.5.0",
|
||||||
"embla-carousel-react": "^8.3.0",
|
"embla-carousel-react": "^8.3.0",
|
||||||
"input-otp": "^1.2.4",
|
"input-otp": "^1.2.4",
|
||||||
"lucide-react": "^0.462.0",
|
"lucide-react": "^0.462.0",
|
||||||
|
|||||||
62
refresh-web.sh
Executable file
62
refresh-web.sh
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# 색상 정의
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
NC='\033[0m' # No Color
|
||||||
|
|
||||||
|
echo -e "${YELLOW}웹앱 새로고침 스크립트${NC}"
|
||||||
|
echo -e "${YELLOW}====================${NC}"
|
||||||
|
|
||||||
|
# 1. 캐시 완전 삭제
|
||||||
|
echo -e "${YELLOW}1. 캐시 완전 삭제 중...${NC}"
|
||||||
|
rm -rf node_modules/.vite
|
||||||
|
rm -rf node_modules/.cache
|
||||||
|
rm -rf android/app/build
|
||||||
|
rm -rf android/.gradle
|
||||||
|
rm -rf dist
|
||||||
|
rm -rf android/app/src/main/assets/public
|
||||||
|
echo -e "${GREEN}캐시가 삭제되었습니다.${NC}"
|
||||||
|
|
||||||
|
# 2. 웹 앱 다시 빌드
|
||||||
|
echo -e "${YELLOW}2. 웹 앱 다시 빌드 중...${NC}"
|
||||||
|
npm run build
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "${RED}웹 앱 빌드 실패${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo -e "${GREEN}웹 앱 빌드 완료${NC}"
|
||||||
|
|
||||||
|
# 3. Capacitor 강제 동기화
|
||||||
|
echo -e "${YELLOW}3. Capacitor 강제 동기화 중...${NC}"
|
||||||
|
npx cap sync android --inline
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo -e "${RED}Capacitor 동기화 실패${NC}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo -e "${GREEN}Capacitor 동기화 완료${NC}"
|
||||||
|
|
||||||
|
# 4. 빌드된 웹앱 확인
|
||||||
|
echo -e "${YELLOW}4. 빌드된 웹앱 확인${NC}"
|
||||||
|
if [ -d "dist" ]; then
|
||||||
|
echo -e "${GREEN}dist 디렉토리가 존재합니다.${NC}"
|
||||||
|
ls -la dist
|
||||||
|
else
|
||||||
|
echo -e "${RED}dist 디렉토리가 없습니다!${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. 안드로이드 assets 확인
|
||||||
|
echo -e "${YELLOW}5. 안드로이드 assets 확인${NC}"
|
||||||
|
ASSETS_DIR="android/app/src/main/assets/public"
|
||||||
|
if [ -d "$ASSETS_DIR" ]; then
|
||||||
|
echo -e "${GREEN}안드로이드 assets 디렉토리가 존재합니다.${NC}"
|
||||||
|
ls -la "$ASSETS_DIR"
|
||||||
|
else
|
||||||
|
echo -e "${RED}안드로이드 assets 디렉토리가 없습니다!${NC}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${GREEN}웹앱 새로고침 완료!${NC}"
|
||||||
|
echo -e "${YELLOW}이제 build-apk.sh 스크립트를 실행하여 앱을 빌드하세요.${NC}"
|
||||||
242
release/zellyy-version-fix.patch
Normal file
242
release/zellyy-version-fix.patch
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
diff --git a/android/app/build.gradle b/android/app/build.gradle
|
||||||
|
index 20cd809..fe19eb4 100644
|
||||||
|
--- a/android/app/build.gradle
|
||||||
|
+++ b/android/app/build.gradle
|
||||||
|
@@ -1,5 +1,19 @@
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
+// 버전 정보를 properties 파일에서 동적으로 로드
|
||||||
|
+def versionPropsFile = rootProject.file('version.properties')
|
||||||
|
+def versionProps = new Properties()
|
||||||
|
+if (versionPropsFile.exists()) {
|
||||||
|
+ versionPropsFile.withInputStream { stream -> versionProps.load(stream) }
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
+def versionName = versionProps['versionName'] ?: "1.1.1.2"
|
||||||
|
+def versionCode = (versionProps['versionCode'] ?: "6").toInteger()
|
||||||
|
+def buildNumber = (versionProps['buildNumber'] ?: "6").toInteger()
|
||||||
|
+
|
||||||
|
+// 버전 정보 로깅
|
||||||
|
+println "버전 정보 로드: versionName=${versionName}, versionCode=${versionCode}, buildNumber=${buildNumber}"
|
||||||
|
+
|
||||||
|
// 버전 정보를 직접 설정
|
||||||
|
android {
|
||||||
|
namespace "com.lovable.zellyfinance"
|
||||||
|
@@ -8,10 +22,10 @@ android {
|
||||||
|
applicationId "com.lovable.zellyfinance"
|
||||||
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
|
- versionCode 6
|
||||||
|
- versionName "1.1.1.2"
|
||||||
|
+ versionCode versionCode
|
||||||
|
+ versionName "${versionName}"
|
||||||
|
// 빌드 번호 추가 - BuildConfig 필드로 정의
|
||||||
|
- buildConfigField "int", "BUILD_NUMBER", "6"
|
||||||
|
+ buildConfigField "int", "BUILD_NUMBER", "${buildNumber}"
|
||||||
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
aaptOptions {
|
||||||
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
|
diff --git a/android/app/src/main/java/com/lovable/zellyfinance/BuildInfoPlugin.java b/android/app/src/main/java/com/lovable/zellyfinance/BuildInfoPlugin.java
|
||||||
|
index 3520f5d..0957128 100644
|
||||||
|
--- a/android/app/src/main/java/com/lovable/zellyfinance/BuildInfoPlugin.java
|
||||||
|
+++ b/android/app/src/main/java/com/lovable/zellyfinance/BuildInfoPlugin.java
|
||||||
|
@@ -1,4 +1,3 @@
|
||||||
|
-
|
||||||
|
package com.lovable.zellyfinance;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
@@ -27,16 +26,27 @@ public class BuildInfoPlugin extends Plugin {
|
||||||
|
|
||||||
|
JSObject ret = new JSObject();
|
||||||
|
|
||||||
|
- // 빌드 정보 수집
|
||||||
|
+ // BuildConfig에서 동적으로 버전 정보 가져오기
|
||||||
|
String versionName = BuildConfig.VERSION_NAME;
|
||||||
|
int versionCode = BuildConfig.VERSION_CODE;
|
||||||
|
- int buildNumber = BuildConfig.BUILD_NUMBER;
|
||||||
|
- String packageName = getContext().getPackageName();
|
||||||
|
+ int buildNumber;
|
||||||
|
+
|
||||||
|
+ // 빌드 넘버는 커스텀 필드이므로 try-catch로 확인
|
||||||
|
+ try {
|
||||||
|
+ buildNumber = BuildConfig.BUILD_NUMBER;
|
||||||
|
+ Log.d(TAG, "BuildConfig.BUILD_NUMBER: " + buildNumber);
|
||||||
|
+ } catch (Exception e) {
|
||||||
|
+ Log.e(TAG, "BUILD_NUMBER 필드 접근 오류, 기본값 사용", e);
|
||||||
|
+ buildNumber = versionCode; // 빌드 넘버가 없으면 버전 코드와 동일하게 설정
|
||||||
|
+ }
|
||||||
|
|
||||||
|
// 디버깅을 위한 로그 출력
|
||||||
|
+ Log.d(TAG, "BuildConfig 클래스: " + BuildConfig.class.getName());
|
||||||
|
Log.d(TAG, "버전명: " + versionName);
|
||||||
|
Log.d(TAG, "버전 코드: " + versionCode);
|
||||||
|
Log.d(TAG, "빌드 번호: " + buildNumber);
|
||||||
|
+
|
||||||
|
+ String packageName = getContext().getPackageName();
|
||||||
|
Log.d(TAG, "패키지명: " + packageName);
|
||||||
|
|
||||||
|
// 결과 객체에 값 설정
|
||||||
|
@@ -49,19 +59,17 @@ public class BuildInfoPlugin extends Plugin {
|
||||||
|
ret.put("platform", "android");
|
||||||
|
|
||||||
|
// 현재 날짜를 디버깅 정보로 추가
|
||||||
|
- ret.put("buildDate", new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new java.util.Date()));
|
||||||
|
+ ret.put("timestamp", System.currentTimeMillis());
|
||||||
|
|
||||||
|
- Log.d(TAG, "빌드 정보 요청 성공 처리: " + ret.toString());
|
||||||
|
+ // 성공 응답
|
||||||
|
call.resolve(ret);
|
||||||
|
} catch (Exception e) {
|
||||||
|
- Log.e(TAG, "빌드 정보 가져오기 실패", e);
|
||||||
|
- JSObject errorResult = new JSObject();
|
||||||
|
- errorResult.put("versionName", "1.0.0");
|
||||||
|
- errorResult.put("versionCode", 1);
|
||||||
|
- errorResult.put("buildNumber", 1);
|
||||||
|
- errorResult.put("error", e.getMessage());
|
||||||
|
- errorResult.put("platform", "android-error");
|
||||||
|
- call.resolve(errorResult); // 에러가 발생해도 앱이 중단되지 않도록 resolve 호출
|
||||||
|
+ // 오류 로깅 강화
|
||||||
|
+ Log.e(TAG, "빌드 정보 가져오기 오류", e);
|
||||||
|
+ JSObject errorObj = new JSObject();
|
||||||
|
+ errorObj.put("message", e.getMessage());
|
||||||
|
+ errorObj.put("stack", Log.getStackTraceString(e));
|
||||||
|
+ call.reject("빌드 정보 가져오기 실패", errorObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff --git a/src/utils/platform.ts b/src/utils/platform.ts
|
||||||
|
index c9a89de..1a5af86 100644
|
||||||
|
--- a/src/utils/platform.ts
|
||||||
|
+++ b/src/utils/platform.ts
|
||||||
|
@@ -1,4 +1,3 @@
|
||||||
|
-
|
||||||
|
/**
|
||||||
|
* 플랫폼 관련 유틸리티 함수들
|
||||||
|
*/
|
||||||
|
@@ -30,6 +29,15 @@ export const isNativePlatform = (): boolean => {
|
||||||
|
* 앱 버전 정보 가져오기
|
||||||
|
*/
|
||||||
|
export const getAppVersionInfo = async () => {
|
||||||
|
+ // 기본값 설정 - 플러그인 실패 시 사용
|
||||||
|
+ const defaultVersionInfo = {
|
||||||
|
+ versionName: '1.0.0',
|
||||||
|
+ versionCode: 1,
|
||||||
|
+ buildNumber: 1,
|
||||||
|
+ platform: Capacitor.getPlatform(),
|
||||||
|
+ defaultValues: true
|
||||||
|
+ };
|
||||||
|
+
|
||||||
|
try {
|
||||||
|
// 디버깅을 위한 플랫폼 체크 로그
|
||||||
|
console.log('현재 플랫폼:', Capacitor.getPlatform());
|
||||||
|
@@ -39,65 +47,63 @@ export const getAppVersionInfo = async () => {
|
||||||
|
if (Capacitor.isPluginAvailable('BuildInfo')) {
|
||||||
|
try {
|
||||||
|
// 플러그인 호출 시도
|
||||||
|
- // @ts-ignore - 플러그인이 런타임에 등록되므로 타입 체크를 무시
|
||||||
|
+ // @ts-expect-error - 플러그인이 런타임에 등록되므로 타입 체크를 무시
|
||||||
|
const buildInfo = await Capacitor.Plugins.BuildInfo.getBuildInfo();
|
||||||
|
|
||||||
|
- console.log('네이티브에서 받은 빌드 정보:', buildInfo);
|
||||||
|
+ console.log('네이티브에서 받은 빌드 정보(raw):', JSON.stringify(buildInfo));
|
||||||
|
|
||||||
|
- // 받은 정보가 유효한지 확인
|
||||||
|
- if (buildInfo && typeof buildInfo === 'object') {
|
||||||
|
- // iOS에서는 buildNumber가 문자열로 올 수 있으므로 숫자로 변환
|
||||||
|
- let buildNumberValue = buildInfo.buildNumber;
|
||||||
|
- if (typeof buildNumberValue === 'string') {
|
||||||
|
- buildNumberValue = parseInt(buildNumberValue, 10);
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- return {
|
||||||
|
- versionName: buildInfo.versionName || '1.0.1',
|
||||||
|
- buildNumber: !isNaN(buildNumberValue) ? buildNumberValue : 2,
|
||||||
|
- versionCode: buildInfo.versionCode,
|
||||||
|
- platform: Capacitor.getPlatform(),
|
||||||
|
+ // 수신한 데이터가 null 또는 undefined인 경우 체크
|
||||||
|
+ if (!buildInfo) {
|
||||||
|
+ console.warn('네이티브 빌드 정보가 없음');
|
||||||
|
+ throw new Error('빌드 정보를 받지 못했습니다');
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // 받은 정보가 유효한지 확인 및 타입 변환
|
||||||
|
+ if (typeof buildInfo === 'object') {
|
||||||
|
+ // 타입 변환 및 기본값 설정 - 명시적으로 문자열/숫자 타입 변환
|
||||||
|
+ const result = {
|
||||||
|
+ versionName: typeof buildInfo.versionName === 'string' ? buildInfo.versionName :
|
||||||
|
+ (buildInfo.versionName ? String(buildInfo.versionName) : defaultVersionInfo.versionName),
|
||||||
|
+ versionCode: typeof buildInfo.versionCode === 'number' ? buildInfo.versionCode :
|
||||||
|
+ (buildInfo.versionCode ? Number(buildInfo.versionCode) : defaultVersionInfo.versionCode),
|
||||||
|
+ buildNumber: typeof buildInfo.buildNumber === 'number' ? buildInfo.buildNumber :
|
||||||
|
+ (buildInfo.buildNumber ? Number(buildInfo.buildNumber) : defaultVersionInfo.buildNumber),
|
||||||
|
+ platform: typeof buildInfo.platform === 'string' ? buildInfo.platform :
|
||||||
|
+ (buildInfo.platform ? String(buildInfo.platform) : defaultVersionInfo.platform),
|
||||||
|
+ timestamp: buildInfo.timestamp || Date.now(),
|
||||||
|
pluginResponse: JSON.stringify(buildInfo)
|
||||||
|
};
|
||||||
|
+
|
||||||
|
+ // 값이 기본값인지 확인 (BuildConfig에서 기본값을 반환하는 경우)
|
||||||
|
+ if (result.versionName === '1.0' && result.versionCode === 1 && isAndroidPlatform()) {
|
||||||
|
+ console.warn('플러그인이 기본값을 반환함, 빌드 설정을 확인해야 함');
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ console.log('변환된 빌드 정보:', result);
|
||||||
|
+ return result;
|
||||||
|
}
|
||||||
|
+ throw new Error('유효하지 않은 빌드 정보 응답');
|
||||||
|
} catch (pluginError) {
|
||||||
|
console.error('BuildInfo 플러그인 호출 오류:', pluginError);
|
||||||
|
+ // 오류 발생시 기본값 사용
|
||||||
|
+ return {
|
||||||
|
+ ...defaultVersionInfo,
|
||||||
|
+ error: true,
|
||||||
|
+ errorMessage: String(pluginError)
|
||||||
|
+ };
|
||||||
|
}
|
||||||
|
+ } else {
|
||||||
|
+ console.warn('BuildInfo 플러그인을 사용할 수 없음');
|
||||||
|
+ // 플러그인이 없는 경우 기본값 반환
|
||||||
|
+ return defaultVersionInfo;
|
||||||
|
}
|
||||||
|
-
|
||||||
|
- // 안드로이드인 경우 기본값을 하드코딩된 값으로 설정
|
||||||
|
- if (isAndroidPlatform()) {
|
||||||
|
- // 안드로이드 앱 빌드 정보를 하드코딩된 값으로 제공
|
||||||
|
- // 실제 앱에서는 빌드 과정에서 이 값들이 업데이트되어야 함
|
||||||
|
- return {
|
||||||
|
- versionName: '1.0.1',
|
||||||
|
- buildNumber: 3, // 업데이트된 빌드 번호
|
||||||
|
- versionCode: 1,
|
||||||
|
- platform: 'android'
|
||||||
|
- };
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- // iOS인 경우 기본값
|
||||||
|
- if (isIOSPlatform()) {
|
||||||
|
- return {
|
||||||
|
- versionName: '1.0.1',
|
||||||
|
- buildNumber: 3, // 업데이트된 빌드 번호
|
||||||
|
- platform: 'ios'
|
||||||
|
- };
|
||||||
|
- }
|
||||||
|
-
|
||||||
|
- // 플러그인이 없는 경우 기본값 반환
|
||||||
|
- return {
|
||||||
|
- versionName: '1.0.1',
|
||||||
|
- buildNumber: 3, // 업데이트된 빌드 번호
|
||||||
|
- platform: Capacitor.getPlatform()
|
||||||
|
- };
|
||||||
|
} catch (error) {
|
||||||
|
- console.error('앱 버전 정보를 가져오는 중 오류 발생:', error);
|
||||||
|
+ console.error('앱 버전 정보 가져오기 오류:', error);
|
||||||
|
+ // 오류 발생 시에도 앱 실행은 계속되도록 기본값 반환
|
||||||
|
return {
|
||||||
|
- versionName: '1.0.1',
|
||||||
|
- buildNumber: 3, // 업데이트된 빌드 번호
|
||||||
|
- error: true
|
||||||
|
+ ...defaultVersionInfo,
|
||||||
|
+ error: true,
|
||||||
|
+ errorMessage: String(error)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -30,9 +30,9 @@ const AppVersionInfo: React.FC<AppVersionInfoProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
// 하드코딩된 버전 정보 - 빌드 스크립트에서 설정한 값과 일치시켜야 함
|
// 하드코딩된 버전 정보 - 빌드 스크립트에서 설정한 값과 일치시켜야 함
|
||||||
const hardcodedVersionInfo: VersionInfo = {
|
const hardcodedVersionInfo: VersionInfo = {
|
||||||
versionName: '1.1.1.3',
|
versionName: '1.1.8',
|
||||||
buildNumber: 7,
|
buildNumber: 9,
|
||||||
versionCode: 7,
|
versionCode: 9,
|
||||||
platform: Capacitor.getPlatform(),
|
platform: Capacitor.getPlatform(),
|
||||||
defaultValuesUsed: false
|
defaultValuesUsed: false
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,80 +1,31 @@
|
|||||||
|
|
||||||
import React, { useEffect, useState, ReactNode } from 'react';
|
import React from 'react';
|
||||||
import { isIOSPlatform } from '@/utils/platform';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
interface SafeAreaContainerProps {
|
interface SafeAreaContainerProps {
|
||||||
children: ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
topOnly?: boolean;
|
extraBottomPadding?: boolean;
|
||||||
bottomOnly?: boolean;
|
|
||||||
extraBottomPadding?: boolean; // 추가 하단 여백 옵션
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 플랫폼별 안전 영역(Safe Area)을 고려한 컨테이너 컴포넌트
|
* iOS의 안전 영역(notch, home indicator 등)을 고려한 컨테이너
|
||||||
* iOS에서는 노치/다이나믹 아일랜드를 고려한 여백 적용
|
* 모든 페이지 최상위 컴포넌트로 사용해야 함
|
||||||
*/
|
*/
|
||||||
const SafeAreaContainer: React.FC<SafeAreaContainerProps> = ({
|
const SafeAreaContainer: React.FC<SafeAreaContainerProps> = ({
|
||||||
children,
|
children,
|
||||||
className = '',
|
className = '',
|
||||||
topOnly = false,
|
extraBottomPadding = false
|
||||||
bottomOnly = false,
|
|
||||||
extraBottomPadding = false // 기본값은 false
|
|
||||||
}) => {
|
}) => {
|
||||||
const [isIOS, setIsIOS] = useState(false);
|
|
||||||
|
|
||||||
// 마운트 시 플랫폼 확인
|
|
||||||
useEffect(() => {
|
|
||||||
const checkPlatform = async () => {
|
|
||||||
const isiOS = isIOSPlatform();
|
|
||||||
console.log('SafeAreaContainer: 플랫폼 확인 - iOS:', isiOS);
|
|
||||||
setIsIOS(isiOS);
|
|
||||||
};
|
|
||||||
|
|
||||||
checkPlatform();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// 플랫폼에 따른 클래스 결정
|
|
||||||
let safeAreaClass = 'safe-area-container';
|
|
||||||
|
|
||||||
if (isIOS) {
|
|
||||||
if (!bottomOnly) safeAreaClass += ' has-safe-area-top'; // iOS 상단 안전 영역
|
|
||||||
if (!topOnly) safeAreaClass += ' has-safe-area-bottom'; // iOS 하단 안전 영역
|
|
||||||
safeAreaClass += ' ios-safe-area'; // iOS 전용 클래스 추가
|
|
||||||
} else {
|
|
||||||
if (!bottomOnly) safeAreaClass += ' pt-4'; // 안드로이드 상단 여백
|
|
||||||
if (!topOnly) safeAreaClass += ' pb-4'; // 안드로이드 하단 여백
|
|
||||||
}
|
|
||||||
|
|
||||||
// 추가 하단 여백 적용
|
|
||||||
const extraBottomClass = extraBottomPadding ? 'pb-[80px]' : '';
|
|
||||||
|
|
||||||
// 디버그용 로그 추가
|
|
||||||
useEffect(() => {
|
|
||||||
if (isIOS) {
|
|
||||||
console.log('SafeAreaContainer: iOS 안전 영역 적용됨', {
|
|
||||||
topOnly,
|
|
||||||
bottomOnly,
|
|
||||||
extraBottomPadding
|
|
||||||
});
|
|
||||||
|
|
||||||
// 안전 영역 값 확인 (CSS 변수)
|
|
||||||
try {
|
|
||||||
const computedStyle = getComputedStyle(document.documentElement);
|
|
||||||
console.log('Safe area 변수 값:', {
|
|
||||||
top: computedStyle.getPropertyValue('--safe-area-top'),
|
|
||||||
bottom: computedStyle.getPropertyValue('--safe-area-bottom'),
|
|
||||||
left: computedStyle.getPropertyValue('--safe-area-left'),
|
|
||||||
right: computedStyle.getPropertyValue('--safe-area-right')
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('CSS 변수 확인 중 오류:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [isIOS, topOnly, bottomOnly]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${safeAreaClass} ${extraBottomClass} ${className}`}>
|
<div
|
||||||
|
className={cn(
|
||||||
|
'min-h-screen bg-neuro-background',
|
||||||
|
'pt-safe pb-safe pl-safe pr-safe', // iOS 안전 영역 적용
|
||||||
|
extraBottomPadding ? 'pb-24' : '',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Legend, Cell } from 'recharts';
|
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, Legend, Cell } from 'recharts';
|
||||||
import { formatCurrency } from '@/utils/formatters';
|
import { formatCurrency } from '@/utils/formatters';
|
||||||
|
|
||||||
interface MonthlyData {
|
interface MonthlyData {
|
||||||
name: string;
|
name: string;
|
||||||
budget: number;
|
budget: number;
|
||||||
expense: number;
|
expense: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MonthlyComparisonChartProps {
|
interface MonthlyComparisonChartProps {
|
||||||
monthlyData: MonthlyData[];
|
monthlyData: MonthlyData[];
|
||||||
isEmpty?: boolean;
|
isEmpty?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MonthlyComparisonChart: React.FC<MonthlyComparisonChartProps> = ({
|
const MonthlyComparisonChart: React.FC<MonthlyComparisonChartProps> = ({
|
||||||
monthlyData,
|
monthlyData,
|
||||||
isEmpty = false
|
isEmpty = false
|
||||||
@@ -35,24 +31,19 @@ const MonthlyComparisonChart: React.FC<MonthlyComparisonChartProps> = ({
|
|||||||
console.log('MonthlyComparisonChart 데이터:', monthlyData);
|
console.log('MonthlyComparisonChart 데이터:', monthlyData);
|
||||||
|
|
||||||
// EmptyGraphState 컴포넌트: 데이터가 없을 때 표시
|
// EmptyGraphState 컴포넌트: 데이터가 없을 때 표시
|
||||||
const EmptyGraphState = () => (
|
const EmptyGraphState = () => <div className="flex flex-col items-center justify-center h-48 text-gray-400">
|
||||||
<div className="flex flex-col items-center justify-center h-48 text-gray-400">
|
|
||||||
<p>데이터가 없습니다</p>
|
<p>데이터가 없습니다</p>
|
||||||
<p className="text-sm mt-2">지출 내역을 추가하면 그래프가 표시됩니다</p>
|
<p className="text-sm mt-2">지출 내역을 추가하면 그래프가 표시됩니다</p>
|
||||||
</div>
|
</div>;
|
||||||
);
|
|
||||||
|
|
||||||
// 데이터 여부 확인 로직 개선 - 데이터가 비어있거나 모든 값이 0인 경우도 고려
|
// 데이터 여부 확인 로직 개선 - 데이터가 비어있거나 모든 값이 0인 경우도 고려
|
||||||
const hasValidData = monthlyData &&
|
const hasValidData = monthlyData && monthlyData.length > 0 && monthlyData.some(item => item.budget > 0 || item.expense > 0);
|
||||||
monthlyData.length > 0 &&
|
|
||||||
monthlyData.some(item => (item.budget > 0 || item.expense > 0));
|
|
||||||
|
|
||||||
// 지출 색상 결정 함수 추가
|
// 지출 색상 결정 함수 추가
|
||||||
const getExpenseColor = (budget: number, expense: number) => {
|
const getExpenseColor = (budget: number, expense: number) => {
|
||||||
if (budget === 0) return "#81c784"; // 예산이 0이면 기본 색상
|
if (budget === 0) return "#81c784"; // 예산이 0이면 기본 색상
|
||||||
|
|
||||||
const ratio = expense / budget;
|
const ratio = expense / budget;
|
||||||
|
|
||||||
if (ratio > 1) return "#f44336"; // 빨간색 (예산 초과)
|
if (ratio > 1) return "#f44336"; // 빨간색 (예산 초과)
|
||||||
if (ratio >= 0.9) return "#ffeb3b"; // 노란색 (예산의 90% 이상)
|
if (ratio >= 0.9) return "#ffeb3b"; // 노란색 (예산의 90% 이상)
|
||||||
return "#81c784"; // 기본 초록색
|
return "#81c784"; // 기본 초록색
|
||||||
@@ -64,42 +55,36 @@ const MonthlyComparisonChart: React.FC<MonthlyComparisonChartProps> = ({
|
|||||||
// 예산 색상을 좀 더 짙은 회색으로 변경
|
// 예산 색상을 좀 더 짙은 회색으로 변경
|
||||||
const darkGrayColor = "#9F9EA1"; // 이전 색상 #C8C8C9에서 더 짙은 회색으로 변경
|
const darkGrayColor = "#9F9EA1"; // 이전 색상 #C8C8C9에서 더 짙은 회색으로 변경
|
||||||
|
|
||||||
return (
|
return <div className="neuro-card h-72 w-full">
|
||||||
<div className="neuro-card h-72 w-full">
|
{hasValidData ? <ResponsiveContainer width="100%" height="100%">
|
||||||
{hasValidData ? (
|
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
|
||||||
<BarChart data={monthlyData} margin={{
|
<BarChart data={monthlyData} margin={{
|
||||||
top: 20,
|
top: 20,
|
||||||
right: 10,
|
right: 10,
|
||||||
left: -10,
|
left: -10,
|
||||||
bottom: 5
|
bottom: 5
|
||||||
}} style={{
|
}} style={{
|
||||||
fontSize: '11px'
|
fontSize: '11px'
|
||||||
}}>
|
}}>
|
||||||
<XAxis dataKey="name" />
|
<XAxis dataKey="name" />
|
||||||
<YAxis tickFormatter={formatYAxisTick} />
|
<YAxis tickFormatter={formatYAxisTick} />
|
||||||
<Tooltip
|
<Tooltip formatter={formatTooltip} contentStyle={{
|
||||||
formatter={formatTooltip}
|
backgroundColor: 'white',
|
||||||
contentStyle={{ backgroundColor: 'white', border: 'none' }}
|
border: 'none'
|
||||||
cursor={{ fill: 'transparent' }}
|
}} cursor={{
|
||||||
/>
|
fill: 'transparent'
|
||||||
<Legend
|
}} />
|
||||||
formatter={(value) => {
|
<Legend formatter={value => {
|
||||||
// 범례 텍스트 색상 설정
|
// 범례 텍스트 색상 설정
|
||||||
return <span style={{ color: value === '지출' ? mainGreenColor : undefined }}>{value}</span>;
|
return <span style={{
|
||||||
}}
|
color: value === '지출' ? mainGreenColor : undefined
|
||||||
/>
|
}} className="text-sm">{value}</span>;
|
||||||
|
}} />
|
||||||
<Bar dataKey="budget" name="예산" fill={darkGrayColor} radius={[4, 4, 0, 0]} />
|
<Bar dataKey="budget" name="예산" fill={darkGrayColor} radius={[4, 4, 0, 0]} />
|
||||||
<Bar dataKey="expense" name="지출" fill={mainGreenColor} radius={[4, 4, 0, 0]}>
|
<Bar dataKey="expense" name="지출" fill={mainGreenColor} radius={[4, 4, 0, 0]}>
|
||||||
{/* 개별 셀 색상 설정은 제거하고 통일된 메인 그린 색상 사용 */}
|
{/* 개별 셀 색상 설정은 제거하고 통일된 메인 그린 색상 사용 */}
|
||||||
</Bar>
|
</Bar>
|
||||||
</BarChart>
|
</BarChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer> : <EmptyGraphState />}
|
||||||
) : (
|
</div>;
|
||||||
<EmptyGraphState />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MonthlyComparisonChart;
|
export default MonthlyComparisonChart;
|
||||||
@@ -1,15 +1,12 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Wallet, CreditCard, Coins } from 'lucide-react';
|
import { Wallet, CreditCard, Coins } from 'lucide-react';
|
||||||
import { formatCurrency } from '@/utils/formatters';
|
import { formatCurrency } from '@/utils/formatters';
|
||||||
import { useIsMobile } from '@/hooks/use-mobile';
|
import { useIsMobile } from '@/hooks/use-mobile';
|
||||||
|
|
||||||
interface SummaryCardsProps {
|
interface SummaryCardsProps {
|
||||||
totalBudget: number;
|
totalBudget: number;
|
||||||
totalExpense: number;
|
totalExpense: number;
|
||||||
savingsPercentage: number;
|
savingsPercentage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SummaryCards: React.FC<SummaryCardsProps> = ({
|
const SummaryCards: React.FC<SummaryCardsProps> = ({
|
||||||
totalBudget,
|
totalBudget,
|
||||||
totalExpense,
|
totalExpense,
|
||||||
@@ -20,13 +17,11 @@ const SummaryCards: React.FC<SummaryCardsProps> = ({
|
|||||||
// 남은 예산 계산
|
// 남은 예산 계산
|
||||||
const remainingBudget = totalBudget - totalExpense;
|
const remainingBudget = totalBudget - totalExpense;
|
||||||
const isOverBudget = remainingBudget < 0;
|
const isOverBudget = remainingBudget < 0;
|
||||||
|
return <div className={`grid ${isMobile ? 'grid-cols-1' : 'grid-cols-3'} gap-3 mb-8 w-full desktop-card`}>
|
||||||
return (
|
|
||||||
<div className={`grid ${isMobile ? 'grid-cols-1' : 'grid-cols-3'} gap-3 mb-8 w-full desktop-card`}>
|
|
||||||
<div className="neuro-card w-full">
|
<div className="neuro-card w-full">
|
||||||
{isMobile ? (
|
{isMobile ?
|
||||||
// 모바일 레이아웃 (1줄: 아이콘, 제목, 금액 가로배치)
|
// 모바일 레이아웃 (1줄: 아이콘, 제목, 금액 가로배치)
|
||||||
<div className="flex items-center justify-between px-3 py-[5px]">
|
<div className="flex items-center justify-between px-3 py-[5px]">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Wallet size={24} className="text-gray-500" />
|
<Wallet size={24} className="text-gray-500" />
|
||||||
<p className="text-gray-500 text-base">예산</p>
|
<p className="text-gray-500 text-base">예산</p>
|
||||||
@@ -34,24 +29,22 @@ const SummaryCards: React.FC<SummaryCardsProps> = ({
|
|||||||
<p className="text-sm font-bold text-neuro-income">
|
<p className="text-sm font-bold text-neuro-income">
|
||||||
{formatCurrency(totalBudget)}
|
{formatCurrency(totalBudget)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div> :
|
||||||
) : (
|
// 데스크탑 레이아웃 (2줄: 아이콘과 제목이 첫째 줄, 금액이 둘째 줄)
|
||||||
// 데스크탑 레이아웃 (2줄: 아이콘과 제목이 첫째 줄, 금액이 둘째 줄)
|
<>
|
||||||
<>
|
|
||||||
<div className="flex items-center justify-center gap-2 py-[5px]">
|
<div className="flex items-center justify-center gap-2 py-[5px]">
|
||||||
<Wallet size={24} className="text-gray-500" />
|
<Wallet size={24} className="text-gray-500" />
|
||||||
<p className="text-gray-500 text-base">예산</p>
|
<p className="text-gray-500 text-base">예산</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm font-bold text-neuro-income text-center mt-3">
|
<p className="font-bold text-neuro-income text-center mt-3 text-xs">
|
||||||
{formatCurrency(totalBudget)}
|
{formatCurrency(totalBudget)}
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="neuro-card w-full">
|
<div className="neuro-card w-full">
|
||||||
{isMobile ? (
|
{isMobile ?
|
||||||
// 모바일 레이아웃 (1줄: 아이콘, 제목, 금액 가로배치)
|
// 모바일 레이아웃 (1줄: 아이콘, 제목, 금액 가로배치)
|
||||||
<div className="flex items-center justify-between px-3 py-[5px]">
|
<div className="flex items-center justify-between px-3 py-[5px]">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<CreditCard size={24} className="text-gray-500" />
|
<CreditCard size={24} className="text-gray-500" />
|
||||||
<p className="text-gray-500 font-medium text-base">지출</p>
|
<p className="text-gray-500 font-medium text-base">지출</p>
|
||||||
@@ -59,59 +52,45 @@ const SummaryCards: React.FC<SummaryCardsProps> = ({
|
|||||||
<p className="text-sm font-bold text-neuro-income">
|
<p className="text-sm font-bold text-neuro-income">
|
||||||
{formatCurrency(totalExpense)}
|
{formatCurrency(totalExpense)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div> :
|
||||||
) : (
|
// 데스크탑 레이아웃 (2줄: 아이콘과 제목이 첫째 줄, 금액이 둘째 줄)
|
||||||
// 데스크탑 레이아웃 (2줄: 아이콘과 제목이 첫째 줄, 금액이 둘째 줄)
|
<>
|
||||||
<>
|
|
||||||
<div className="flex items-center justify-center gap-2 py-[5px]">
|
<div className="flex items-center justify-center gap-2 py-[5px]">
|
||||||
<CreditCard size={24} className="text-gray-500" />
|
<CreditCard size={24} className="text-gray-500" />
|
||||||
<p className="text-gray-500 font-medium text-base">지출</p>
|
<p className="text-gray-500 font-medium text-base">지출</p>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm font-bold text-neuro-income text-center mt-3">
|
<p className="font-bold text-neuro-income text-center mt-3 text-xs">
|
||||||
{formatCurrency(totalExpense)}
|
{formatCurrency(totalExpense)}
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<div className="neuro-card w-full">
|
<div className="neuro-card w-full">
|
||||||
{isMobile ? (
|
{isMobile ?
|
||||||
// 모바일 레이아웃 (1줄: 아이콘, 제목, 금액 가로배치)
|
// 모바일 레이아웃 (1줄: 아이콘, 제목, 금액 가로배치)
|
||||||
<div className="flex items-center justify-between px-3 py-[5px]">
|
<div className="flex items-center justify-between px-3 py-[5px]">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Coins size={24} className="text-gray-500" />
|
<Coins size={24} className="text-gray-500" />
|
||||||
<p className="text-gray-500 text-base">잔액</p>
|
<p className="text-gray-500 text-base">잔액</p>
|
||||||
</div>
|
</div>
|
||||||
{isOverBudget ? (
|
{isOverBudget ? <p className="text-sm font-bold text-red-500">
|
||||||
<p className="text-sm font-bold text-red-500">
|
|
||||||
초과액: {formatCurrency(Math.abs(remainingBudget))}
|
초과액: {formatCurrency(Math.abs(remainingBudget))}
|
||||||
</p>
|
</p> : <p className="text-sm font-bold text-neuro-income">
|
||||||
) : (
|
|
||||||
<p className="text-sm font-bold text-neuro-income">
|
|
||||||
{formatCurrency(remainingBudget)}
|
{formatCurrency(remainingBudget)}
|
||||||
</p>
|
</p>}
|
||||||
)}
|
</div> :
|
||||||
</div>
|
// 데스크탑 레이아웃 (2줄: 아이콘과 제목이 첫째 줄, 금액이 둘째 줄)
|
||||||
) : (
|
<>
|
||||||
// 데스크탑 레이아웃 (2줄: 아이콘과 제목이 첫째 줄, 금액이 둘째 줄)
|
|
||||||
<>
|
|
||||||
<div className="flex items-center justify-center gap-2 py-[5px]">
|
<div className="flex items-center justify-center gap-2 py-[5px]">
|
||||||
<Coins size={24} className="text-gray-500" />
|
<Coins size={24} className="text-gray-500" />
|
||||||
<p className="text-gray-500 text-base">잔액</p>
|
<p className="text-gray-500 text-base">잔액</p>
|
||||||
</div>
|
</div>
|
||||||
{isOverBudget ? (
|
{isOverBudget ? <p className="text-sm font-bold text-red-500 text-center mt-3">
|
||||||
<p className="text-sm font-bold text-red-500 text-center mt-3">
|
|
||||||
초과액: {formatCurrency(Math.abs(remainingBudget))}
|
초과액: {formatCurrency(Math.abs(remainingBudget))}
|
||||||
</p>
|
</p> : <p className="font-bold text-neuro-income text-center mt-3 text-xs">
|
||||||
) : (
|
|
||||||
<p className="text-sm font-bold text-neuro-income text-center mt-3">
|
|
||||||
{formatCurrency(remainingBudget)}
|
{formatCurrency(remainingBudget)}
|
||||||
</p>
|
</p>}
|
||||||
)}
|
</>}
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>;
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SummaryCards;
|
export default SummaryCards;
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { Calendar, Search, ChevronLeft, ChevronRight } from 'lucide-react';
|
import { Calendar, Search, ChevronLeft, ChevronRight } from 'lucide-react';
|
||||||
import { formatCurrency } from '@/utils/formatters';
|
import { formatCurrency } from '@/utils/formatters';
|
||||||
|
import { formatMonthForDisplay } from '@/hooks/transactions/dateUtils';
|
||||||
|
|
||||||
interface TransactionsHeaderProps {
|
interface TransactionsHeaderProps {
|
||||||
selectedMonth: string;
|
selectedMonth: string;
|
||||||
@@ -24,14 +25,25 @@ const TransactionsHeader: React.FC<TransactionsHeaderProps> = ({
|
|||||||
totalExpenses,
|
totalExpenses,
|
||||||
isDisabled
|
isDisabled
|
||||||
}) => {
|
}) => {
|
||||||
console.log('TransactionsHeader 렌더링:', {
|
// 월 표시 형식 변환 (2024-04 -> 2024년 04월)
|
||||||
selectedMonth,
|
const displayMonth = useMemo(() =>
|
||||||
totalExpenses
|
formatMonthForDisplay(selectedMonth),
|
||||||
});
|
[selectedMonth]
|
||||||
|
);
|
||||||
|
|
||||||
// 예산 정보가 없는 경우 기본값 사용
|
// 예산 정보가 없는 경우 기본값 사용
|
||||||
const targetAmount = budgetData?.monthly?.targetAmount || 0;
|
const targetAmount = budgetData?.monthly?.targetAmount || 0;
|
||||||
|
|
||||||
|
// 디버깅을 위한 로그
|
||||||
|
React.useEffect(() => {
|
||||||
|
console.log('TransactionsHeader 렌더링:', {
|
||||||
|
selectedMonth,
|
||||||
|
displayMonth,
|
||||||
|
totalExpenses,
|
||||||
|
targetAmount
|
||||||
|
});
|
||||||
|
}, [selectedMonth, displayMonth, totalExpenses, targetAmount]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="py-4">
|
<header className="py-4">
|
||||||
<h1 className="font-bold neuro-text mb-3 text-xl">지출 내역</h1>
|
<h1 className="font-bold neuro-text mb-3 text-xl">지출 내역</h1>
|
||||||
@@ -61,7 +73,7 @@ const TransactionsHeader: React.FC<TransactionsHeaderProps> = ({
|
|||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Calendar size={18} className="text-neuro-income" />
|
<Calendar size={18} className="text-neuro-income" />
|
||||||
<span className="font-medium text-lg">{selectedMonth}</span>
|
<span className="font-medium text-lg">{displayMonth}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { AuthProvider } from './auth/AuthProvider';
|
import { AuthProvider } from './auth/AuthProvider';
|
||||||
import { useAuth } from './auth/useAuth';
|
|
||||||
|
|
||||||
export { AuthProvider, useAuth };
|
export { AuthProvider } from './auth/AuthProvider';
|
||||||
|
export { useAuth } from './auth/useAuth';
|
||||||
|
|
||||||
export default function AuthContextWrapper({ children }: { children: React.ReactNode }) {
|
export default function AuthContextWrapper({ children }: { children: React.ReactNode }) {
|
||||||
return <AuthProvider>{children}</AuthProvider>;
|
return <AuthProvider>{children}</AuthProvider>;
|
||||||
|
|||||||
6
src/contexts/auth/AuthContext.tsx
Normal file
6
src/contexts/auth/AuthContext.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
import React, { createContext } from 'react';
|
||||||
|
import { AuthContextType } from './types';
|
||||||
|
|
||||||
|
// AuthContext 생성
|
||||||
|
export const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||||
@@ -6,7 +6,7 @@ import { toast } from '@/hooks/useToast.wrapper';
|
|||||||
import { AuthContextType } from './types';
|
import { AuthContextType } from './types';
|
||||||
import * as authActions from './authActions';
|
import * as authActions from './authActions';
|
||||||
import { clearAllToasts } from '@/hooks/toast/toastManager';
|
import { clearAllToasts } from '@/hooks/toast/toastManager';
|
||||||
import { AuthContext } from './useAuth';
|
import { AuthContext } from './AuthContext';
|
||||||
|
|
||||||
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
const [session, setSession] = useState<Session | null>(null);
|
const [session, setSession] = useState<Session | null>(null);
|
||||||
@@ -105,5 +105,3 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
|
|
||||||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// useAuth 후크는 useAuth.ts 파일로 이동했습니다
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { useContext, createContext } from 'react';
|
|
||||||
import { AuthContextType } from './types';
|
|
||||||
|
|
||||||
// AuthContext 생성
|
import { useContext } from 'react';
|
||||||
export const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
import { AuthContext } from './AuthContext';
|
||||||
|
import { AuthContextType } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 인증 컨텍스트에 접근하기 위한 커스텀 훅
|
* 인증 컨텍스트에 접근하기 위한 커스텀 훅
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
|
||||||
|
import { format, parse, addMonths, subMonths } from 'date-fns';
|
||||||
|
import { ko } from 'date-fns/locale';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 한글 월 이름 배열
|
* 월 이름 배열 (한국어)
|
||||||
*/
|
*/
|
||||||
export const MONTHS_KR = [
|
export const MONTHS_KR = [
|
||||||
'1월', '2월', '3월', '4월', '5월', '6월',
|
'1월', '2월', '3월', '4월', '5월', '6월',
|
||||||
@@ -8,40 +11,83 @@ export const MONTHS_KR = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 현재 월 가져오기
|
* 월 형식 검증 함수 (YYYY-MM 형식)
|
||||||
|
*/
|
||||||
|
export const isValidMonth = (month: string): boolean => {
|
||||||
|
const regex = /^\d{4}-(0[1-9]|1[0-2])$/;
|
||||||
|
return regex.test(month);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 현재 년월 가져오기
|
||||||
*/
|
*/
|
||||||
export const getCurrentMonth = (): string => {
|
export const getCurrentMonth = (): string => {
|
||||||
const now = new Date();
|
return format(new Date(), 'yyyy-MM');
|
||||||
const month = now.getMonth(); // 0-indexed
|
|
||||||
return `${MONTHS_KR[month]}`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 이전 월 가져오기
|
* 이전 월 가져오기
|
||||||
*/
|
*/
|
||||||
export const getPrevMonth = (currentMonth: string): string => {
|
export const getPrevMonth = (month: string): string => {
|
||||||
const currentMonthIdx = MONTHS_KR.findIndex(m => m === currentMonth);
|
// 입력값 검증
|
||||||
|
if (!isValidMonth(month)) {
|
||||||
|
console.warn('유효하지 않은 월 형식:', month);
|
||||||
|
return getCurrentMonth();
|
||||||
|
}
|
||||||
|
|
||||||
if (currentMonthIdx === 0) {
|
try {
|
||||||
// 1월인 경우 12월로 변경
|
// 월 문자열을 날짜로 파싱
|
||||||
return `${MONTHS_KR[11]}`;
|
const date = parse(month, 'yyyy-MM', new Date());
|
||||||
} else {
|
// 한 달 이전
|
||||||
const prevMonthIdx = currentMonthIdx - 1;
|
const prevMonth = subMonths(date, 1);
|
||||||
return `${MONTHS_KR[prevMonthIdx]}`;
|
// yyyy-MM 형식으로 반환
|
||||||
|
return format(prevMonth, 'yyyy-MM');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('이전 월 계산 중 오류:', error);
|
||||||
|
return getCurrentMonth();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 다음 월 가져오기
|
* 다음 월 가져오기
|
||||||
*/
|
*/
|
||||||
export const getNextMonth = (currentMonth: string): string => {
|
export const getNextMonth = (month: string): string => {
|
||||||
const currentMonthIdx = MONTHS_KR.findIndex(m => m === currentMonth);
|
// 입력값 검증
|
||||||
|
if (!isValidMonth(month)) {
|
||||||
|
console.warn('유효하지 않은 월 형식:', month);
|
||||||
|
return getCurrentMonth();
|
||||||
|
}
|
||||||
|
|
||||||
if (currentMonthIdx === 11) {
|
try {
|
||||||
// 12월인 경우 1월로 변경
|
// 월 문자열을 날짜로 파싱
|
||||||
return `${MONTHS_KR[0]}`;
|
const date = parse(month, 'yyyy-MM', new Date());
|
||||||
} else {
|
// 한 달 이후
|
||||||
const nextMonthIdx = currentMonthIdx + 1;
|
const nextMonth = addMonths(date, 1);
|
||||||
return `${MONTHS_KR[nextMonthIdx]}`;
|
// yyyy-MM 형식으로 반환
|
||||||
|
return format(nextMonth, 'yyyy-MM');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('다음 월 계산 중 오류:', error);
|
||||||
|
return getCurrentMonth();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 표시 형식으로 변환 (yyyy년 MM월)
|
||||||
|
*/
|
||||||
|
export const formatMonthForDisplay = (month: string): string => {
|
||||||
|
try {
|
||||||
|
// 입력값 검증
|
||||||
|
if (!isValidMonth(month)) {
|
||||||
|
console.warn('유효하지 않은 월 형식:', month);
|
||||||
|
return format(new Date(), 'yyyy년 MM월', { locale: ko });
|
||||||
|
}
|
||||||
|
|
||||||
|
// 월 문자열을 날짜로 파싱
|
||||||
|
const date = parse(month, 'yyyy-MM', new Date());
|
||||||
|
// yyyy년 MM월 형식으로 반환 (한국어 로케일)
|
||||||
|
return format(date, 'yyyy년 MM월', { locale: ko });
|
||||||
|
} catch (error) {
|
||||||
|
console.error('월 형식 변환 중 오류:', error);
|
||||||
|
return month;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,126 +2,115 @@
|
|||||||
import { Transaction } from '@/contexts/budget/types';
|
import { Transaction } from '@/contexts/budget/types';
|
||||||
import { parseTransactionDate } from '@/utils/dateParser';
|
import { parseTransactionDate } from '@/utils/dateParser';
|
||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { ko } from 'date-fns/locale';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 월별로 트랜잭션 필터링 - 개선된 버전
|
* 트랜잭션을 월별로 필터링
|
||||||
*/
|
*/
|
||||||
export const filterTransactionsByMonth = (
|
export const filterTransactionsByMonth = (transactions: Transaction[], selectedMonth: string): Transaction[] => {
|
||||||
transactions: Transaction[],
|
if (!transactions || transactions.length === 0) {
|
||||||
selectedMonth: string
|
|
||||||
): Transaction[] => {
|
|
||||||
console.log(`월별 트랜잭션 필터링: ${selectedMonth}, 총 데이터 수: ${transactions.length}`);
|
|
||||||
|
|
||||||
// 필터링 전 샘플 데이터 로그
|
|
||||||
if (transactions.length > 0) {
|
|
||||||
console.log('샘플 트랜잭션 날짜:', transactions.slice(0, 3).map(t => t.date));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 선택된 월의 숫자 추출 (ex: "4월" -> 4)
|
|
||||||
const selectedMonthNumber = parseInt(selectedMonth.replace('월', ''));
|
|
||||||
if (isNaN(selectedMonthNumber) || selectedMonthNumber < 1 || selectedMonthNumber > 12) {
|
|
||||||
console.error('잘못된 월 형식:', selectedMonth);
|
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const filtered = transactions.filter(transaction => {
|
console.log(`월별 필터링 시작: ${selectedMonth}, 트랜잭션 수: ${transactions.length}`);
|
||||||
// 트랜잭션 타입 확인 - 지출 항목만 포함
|
|
||||||
if (transaction.type !== 'expense') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 날짜가 없는 경우 필터링 제외
|
const [year, month] = selectedMonth.split('-').map(Number);
|
||||||
if (!transaction.date) {
|
|
||||||
console.warn('날짜 없는 트랜잭션:', transaction);
|
const filtered = transactions.filter(transaction => {
|
||||||
|
const date = parseTransactionDate(transaction.date);
|
||||||
|
|
||||||
|
if (!date) {
|
||||||
|
console.warn(`날짜를 파싱할 수 없음: ${transaction.date}, 트랜잭션 ID: ${transaction.id}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 날짜 파싱
|
const transactionYear = date.getFullYear();
|
||||||
const parsedDate = parseTransactionDate(transaction.date);
|
const transactionMonth = date.getMonth() + 1; // JavaScript 월은 0부터 시작하므로 +1
|
||||||
if (!parsedDate) {
|
|
||||||
console.warn('날짜 파싱 실패:', transaction.date);
|
const match = transactionYear === year && transactionMonth === month;
|
||||||
return false;
|
|
||||||
|
if (match) {
|
||||||
|
console.log(`트랜잭션 매칭: ${transaction.id}, 제목: ${transaction.title}, 날짜: ${transaction.date}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 월 비교
|
return match;
|
||||||
const transactionMonth = parsedDate.getMonth() + 1; // 0-based -> 1-based
|
});
|
||||||
const isMatchingMonth = transactionMonth === selectedMonthNumber;
|
|
||||||
|
|
||||||
if (isMatchingMonth) {
|
console.log(`월별 필터링 결과: ${filtered.length}개 트랜잭션`);
|
||||||
console.log(`트랜잭션 매칭: ${transaction.title}, 날짜: ${transaction.date}, 월: ${transactionMonth}`);
|
return filtered;
|
||||||
}
|
} catch (error) {
|
||||||
|
console.error('월별 필터링 중 오류:', error);
|
||||||
return isMatchingMonth;
|
return [];
|
||||||
} catch (e) {
|
}
|
||||||
console.error('트랜잭션 필터링 중 오류:', e, transaction);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`월별 필터링 결과: ${filtered.length}개 항목 (${selectedMonth})`);
|
|
||||||
return filtered;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 검색어로 트랜잭션 필터링
|
* 트랜잭션을 검색어로 필터링
|
||||||
*/
|
*/
|
||||||
export const filterTransactionsByQuery = (
|
export const filterTransactionsByQuery = (transactions: Transaction[], searchQuery: string): Transaction[] => {
|
||||||
transactions: Transaction[],
|
if (!searchQuery || searchQuery.trim() === '') {
|
||||||
searchQuery: string
|
return transactions;
|
||||||
): Transaction[] => {
|
}
|
||||||
if (!searchQuery.trim()) return transactions;
|
|
||||||
|
|
||||||
const query = searchQuery.toLowerCase().trim();
|
const normalizedQuery = searchQuery.toLowerCase().trim();
|
||||||
console.log(`검색어 필터링: "${query}"`);
|
|
||||||
|
|
||||||
const filtered = transactions.filter(transaction => {
|
return transactions.filter(transaction => {
|
||||||
try {
|
const titleMatch = transaction.title.toLowerCase().includes(normalizedQuery);
|
||||||
return (
|
const categoryMatch = transaction.category.toLowerCase().includes(normalizedQuery);
|
||||||
(transaction.title?.toLowerCase().includes(query)) ||
|
const amountMatch = transaction.amount.toString().includes(normalizedQuery);
|
||||||
(transaction.category?.toLowerCase().includes(query)) ||
|
|
||||||
(transaction.paymentMethod?.toLowerCase().includes(query))
|
return titleMatch || categoryMatch || amountMatch;
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
console.error('검색어 필터링 중 오류:', e, transaction);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`검색어 필터링 결과: ${filtered.length}개 항목`);
|
|
||||||
return filtered;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 총 지출 금액 계산 - 개선된 버전
|
* 총 지출 계산 (지출 타입만 포함)
|
||||||
*/
|
*/
|
||||||
export const calculateTotalExpenses = (transactions: Transaction[]): number => {
|
export const calculateTotalExpenses = (transactions: Transaction[]): number => {
|
||||||
try {
|
if (!transactions || transactions.length === 0) {
|
||||||
// 유효한 트랜잭션만 필터링 (undefined, null 제외)
|
console.log('계산할 트랜잭션이 없습니다.');
|
||||||
const validTransactions = transactions.filter(t => t && typeof t.amount !== 'undefined');
|
return 0;
|
||||||
console.log(`유효한 트랜잭션 수: ${validTransactions.length}/${transactions.length}`);
|
}
|
||||||
|
|
||||||
// 디버깅용 로그
|
console.log(`총 지출 계산 시작: 트랜잭션 ${transactions.length}개`);
|
||||||
if (validTransactions.length > 0) {
|
|
||||||
console.log('첫 번째 트랜잭션 정보:', {
|
|
||||||
title: validTransactions[0].title,
|
|
||||||
amount: validTransactions[0].amount,
|
|
||||||
type: validTransactions[0].type
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const total = validTransactions.reduce((sum, t) => {
|
// 지출 타입만 필터링하고 합산
|
||||||
// 유효한 숫자인지 확인하고 기본값 처리
|
const expenses = transactions
|
||||||
const amount = typeof t.amount === 'number' ? t.amount :
|
.filter(t => t.type === 'expense')
|
||||||
parseInt(t.amount as any) || 0;
|
.reduce((sum, transaction) => {
|
||||||
|
const amount = Number(transaction.amount);
|
||||||
|
if (isNaN(amount)) {
|
||||||
|
console.warn(`유효하지 않은 금액: ${transaction.amount}, 트랜잭션 ID: ${transaction.id}`);
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
return sum + amount;
|
return sum + amount;
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
console.log(`총 지출 계산: ${total}원 (${validTransactions.length}개 항목)`);
|
console.log(`총 지출 계산 결과: ${expenses}원`);
|
||||||
return total;
|
return expenses;
|
||||||
} catch (e) {
|
};
|
||||||
console.error('총 지출 계산 중 오류:', e);
|
|
||||||
return 0;
|
/**
|
||||||
}
|
* 트랜잭션을 날짜별로 그룹화
|
||||||
|
*/
|
||||||
|
export const groupTransactionsByDate = (transactions: Transaction[]): Record<string, Transaction[]> => {
|
||||||
|
const groups: Record<string, Transaction[]> = {};
|
||||||
|
|
||||||
|
transactions.forEach(transaction => {
|
||||||
|
const date = parseTransactionDate(transaction.date);
|
||||||
|
if (!date) {
|
||||||
|
console.warn(`날짜를 파싱할 수 없음: ${transaction.date}, 트랜잭션 ID: ${transaction.id}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedDate = format(date, 'yyyy-MM-dd');
|
||||||
|
|
||||||
|
if (!groups[formattedDate]) {
|
||||||
|
groups[formattedDate] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
groups[formattedDate].push(transaction);
|
||||||
|
});
|
||||||
|
|
||||||
|
return groups;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
|
|
||||||
// This file is automatically generated. Do not edit it directly.
|
// This file is automatically generated. Do not edit it directly.
|
||||||
import { createClient } from '@supabase/supabase-js';
|
import { createClient } from '@supabase/supabase-js';
|
||||||
import type { Database } from './types';
|
import type { Database } from './types';
|
||||||
|
|
||||||
const SUPABASE_URL = "https://qnerebtvwwfobfzdoftx.supabase.co";
|
const SUPABASE_URL = (() => {
|
||||||
const SUPABASE_PUBLISHABLE_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFuZXJlYnR2d3dmb2JmemRvZnR4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDIwNTE0MzgsImV4cCI6MjA1NzYyNzQzOH0.Wm7h2DUhoQbeANuEM3wm2tz22ITrVEW8FizyLgIVmv8";
|
const url = import.meta.env.VITE_SUPABASE_URL;
|
||||||
|
if (!url) throw new Error("VITE_SUPABASE_URL is not set");
|
||||||
|
return url;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const SUPABASE_PUBLISHABLE_KEY = (() => {
|
||||||
|
const key = import.meta.env.VITE_SUPABASE_ANON_KEY;
|
||||||
|
if (!key) throw new Error("VITE_SUPABASE_ANON_KEY is not set");
|
||||||
|
return key;
|
||||||
|
})();
|
||||||
|
|
||||||
// Import the supabase client like this:
|
// Import the supabase client like this:
|
||||||
// import { supabase } from "@/integrations/supabase/client";
|
// import { supabase } from "@/integrations/supabase/client";
|
||||||
|
|||||||
80
src/lib/fullMigrate.js
Executable file
80
src/lib/fullMigrate.js
Executable file
@@ -0,0 +1,80 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
// 스키마 및 데이터를 Supabase Cloud -> On-Prem(a11)으로 완전 복제
|
||||||
|
import 'dotenv/config';
|
||||||
|
import { execSync } from 'child_process';
|
||||||
|
import { URL, fileURLToPath } from 'url';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
const CLOUD_DATABASE_URL = process.env.CLOUD_DATABASE_URL;
|
||||||
|
const ONPREM_SSH_HOST = process.env.ONPREM_SSH_HOST || 'a11';
|
||||||
|
const ONPREM_REMOTE_TMP_DIR = process.env.ONPREM_REMOTE_TMP_DIR || '/root';
|
||||||
|
|
||||||
|
if (!CLOUD_DATABASE_URL) {
|
||||||
|
console.error('환경 변수 CLOUD_DATABASE_URL이 설정되지 않았습니다.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cloud DB 비밀번호 추출
|
||||||
|
const cloudUrlObj = new URL(CLOUD_DATABASE_URL);
|
||||||
|
const CLOUD_DATABASE_PASSWORD = cloudUrlObj.password;
|
||||||
|
if (!CLOUD_DATABASE_PASSWORD) {
|
||||||
|
console.error('Cloud DB URL에서 비밀번호를 찾을 수 없습니다.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 원격 Postgres 컨테이너 이름 조회
|
||||||
|
console.log('원격 Postgres 컨테이너 조회 (ssh a11)...');
|
||||||
|
let containerName = execSync(
|
||||||
|
`ssh ${ONPREM_SSH_HOST} "docker ps --format '{{.Names}} {{.Image}}' | grep supabase/postgres | awk '{print \\$1}'"`,
|
||||||
|
{ encoding: 'utf8' }
|
||||||
|
).trim();
|
||||||
|
if (!containerName) {
|
||||||
|
console.error('원격 Postgres 컨테이너를 찾을 수 없습니다. docker ps 결과를 확인하세요.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(`발견된 컨테이너: ${containerName}`);
|
||||||
|
|
||||||
|
// 1) 원격 a11에서 Cloud DB 스키마 덤프
|
||||||
|
console.log('원격에서 Cloud DB 스키마 덤프 시작...');
|
||||||
|
execSync(
|
||||||
|
`ssh ${ONPREM_SSH_HOST} "docker run --rm --network host -e PGPASSWORD='${CLOUD_DATABASE_PASSWORD}' postgres:15 ` +
|
||||||
|
`pg_dump --schema-only --no-owner --no-privileges ` +
|
||||||
|
`-h ${cloudUrlObj.hostname} ` +
|
||||||
|
`-p ${cloudUrlObj.port} ` +
|
||||||
|
`-U ${cloudUrlObj.username} ` +
|
||||||
|
`${cloudUrlObj.pathname.slice(1)} > ${ONPREM_REMOTE_TMP_DIR}/supabase_schema.sql"`,
|
||||||
|
{ stdio: 'inherit' }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2) 원격 a11에서 Cloud DB 데이터 덤프
|
||||||
|
console.log('원격에서 Cloud DB 데이터 덤프 시작...');
|
||||||
|
execSync(
|
||||||
|
`ssh ${ONPREM_SSH_HOST} "docker run --rm --network host -e PGPASSWORD='${CLOUD_DATABASE_PASSWORD}' postgres:15 ` +
|
||||||
|
`pg_dump --data-only --column-inserts --no-owner --no-privileges ` +
|
||||||
|
`-h ${cloudUrlObj.hostname} ` +
|
||||||
|
`-p ${cloudUrlObj.port} ` +
|
||||||
|
`-U ${cloudUrlObj.username} ` +
|
||||||
|
`${cloudUrlObj.pathname.slice(1)} > ${ONPREM_REMOTE_TMP_DIR}/supabase_data.sql"`,
|
||||||
|
{ stdio: 'inherit' }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 4) 원격에 복원 (스키마)
|
||||||
|
console.log('원격 스키마 복원...');
|
||||||
|
execSync(
|
||||||
|
`ssh ${ONPREM_SSH_HOST} "docker exec -i ${containerName} ` +
|
||||||
|
`psql -U postgres -d postgres < ${ONPREM_REMOTE_TMP_DIR}/supabase_schema.sql"`,
|
||||||
|
{ stdio: 'inherit' }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 5) 원격에 복원 (데이터)
|
||||||
|
console.log('원격 데이터 복원...');
|
||||||
|
execSync(
|
||||||
|
`ssh ${ONPREM_SSH_HOST} "docker exec -i ${containerName} ` +
|
||||||
|
`psql -U postgres -d postgres < ${ONPREM_REMOTE_TMP_DIR}/supabase_data.sql"`,
|
||||||
|
{ stdio: 'inherit' }
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('Cloud → On-Prem 전체 마이그레이션 완료.');
|
||||||
263
src/lib/migrateData.js
Normal file
263
src/lib/migrateData.js
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
import dotenv from 'dotenv';
|
||||||
|
import { createClient } from '@supabase/supabase-js';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const cloudUrl = process.env.CLOUD_SUPABASE_URL;
|
||||||
|
const cloudKey = process.env.CLOUD_SUPABASE_SERVICE_ROLE_KEY || process.env.CLOUD_SUPABASE_ANON_KEY;
|
||||||
|
const onpremUrl = process.env.ONPREM_SUPABASE_URL;
|
||||||
|
const onpremKey = process.env.ONPREM_SUPABASE_SERVICE_ROLE_KEY || process.env.ONPREM_SUPABASE_ANON_KEY;
|
||||||
|
|
||||||
|
if (!cloudUrl || !cloudKey || !onpremUrl || !onpremKey) {
|
||||||
|
console.error('환경 변수 설정 오류: CLOUD/ONPREM URL 또는 키가 누락되었습니다.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloud = createClient(cloudUrl, cloudKey);
|
||||||
|
const onprem = createClient(onpremUrl, onpremKey);
|
||||||
|
|
||||||
|
// 마이그레이션할 테이블 목록
|
||||||
|
const tables = ['transactions', 'budgets', '_tests'];
|
||||||
|
|
||||||
|
// 테이블 스키마 정의
|
||||||
|
const tableSchemas = {
|
||||||
|
transactions: `
|
||||||
|
CREATE TABLE IF NOT EXISTS transactions (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
user_id UUID REFERENCES auth.users(id) NOT NULL,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
amount NUMERIC NOT NULL,
|
||||||
|
category TEXT NOT NULL,
|
||||||
|
date TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
type TEXT NOT NULL,
|
||||||
|
notes TEXT,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Row Level Security 설정
|
||||||
|
ALTER TABLE transactions ENABLE ROW LEVEL SECURITY;
|
||||||
|
|
||||||
|
-- 사용자 정책 설정 (읽기)
|
||||||
|
DROP POLICY IF EXISTS "사용자는 자신의 트랜잭션만 볼 수 있음" ON transactions;
|
||||||
|
CREATE POLICY "사용자는 자신의 트랜잭션만 볼 수 있음"
|
||||||
|
ON transactions FOR SELECT
|
||||||
|
USING (auth.uid() = user_id);
|
||||||
|
|
||||||
|
-- 사용자 정책 설정 (쓰기)
|
||||||
|
DROP POLICY IF EXISTS "사용자는 자신의 트랜잭션만 추가할 수 있음" ON transactions;
|
||||||
|
CREATE POLICY "사용자는自己的 트랜잭션만 추가할 수 있음"
|
||||||
|
ON transactions FOR INSERT
|
||||||
|
WITH CHECK (auth.uid() = user_id);
|
||||||
|
|
||||||
|
-- 사용자 정책 설정 (업데이트)
|
||||||
|
DROP POLICY IF EXISTS "사용자는 자신의 트랜잭션만 업데이트할 수 있음" ON transactions;
|
||||||
|
CREATE POLICY "사용자는自己的 트랜잭션만 업데이트할 수 있음"
|
||||||
|
ON transactions FOR UPDATE
|
||||||
|
USING (auth.uid() = user_id);
|
||||||
|
|
||||||
|
-- 사용자 정책 설정 (삭제)
|
||||||
|
DROP POLICY IF EXISTS "사용자는 자신의 트랜잭션만 삭제할 수 있음" ON transactions;
|
||||||
|
CREATE POLICY "사용자는自己的 트랜잭션만 삭제할 수 있음"
|
||||||
|
ON transactions FOR DELETE
|
||||||
|
USING (auth.uid() = user_id);
|
||||||
|
`,
|
||||||
|
budgets: `
|
||||||
|
CREATE TABLE IF NOT EXISTS budgets (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
user_id UUID REFERENCES auth.users(id) NOT NULL,
|
||||||
|
month INTEGER NOT NULL,
|
||||||
|
year INTEGER NOT NULL,
|
||||||
|
total_budget NUMERIC NOT NULL DEFAULT 0,
|
||||||
|
categories JSONB NOT NULL DEFAULT '{}',
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||||
|
UNIQUE (user_id, month, year)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Row Level Security 설정
|
||||||
|
ALTER TABLE budgets ENABLE ROW LEVEL SECURITY;
|
||||||
|
|
||||||
|
-- 사용자 정책 설정 (읽기)
|
||||||
|
DROP POLICY IF EXISTS "사용자는自己的 예산만 볼 수 있음" ON budgets;
|
||||||
|
CREATE POLICY "사용자는自己的 예산만 볼 수 있음"
|
||||||
|
ON budgets FOR SELECT
|
||||||
|
USING (auth.uid() = user_id);
|
||||||
|
|
||||||
|
-- 사용자 정책 설정 (쓰기)
|
||||||
|
DROP POLICY IF EXISTS "사용자는自己的 예산만 추가할 수 있음" ON budgets;
|
||||||
|
CREATE POLICY "사용자는自己的 예산만 추가할 수 있음"
|
||||||
|
ON budgets FOR INSERT
|
||||||
|
WITH CHECK (auth.uid() = user_id);
|
||||||
|
|
||||||
|
-- 사용자 정책 설정 (업데이트)
|
||||||
|
DROP POLICY IF EXISTS "사용자는自己的 예산만 업데이트할 수 있음" ON budgets;
|
||||||
|
CREATE POLICY "사용자는自己的 예산만 업데이트할 수 있음"
|
||||||
|
ON budgets FOR UPDATE
|
||||||
|
USING (auth.uid() = user_id);
|
||||||
|
|
||||||
|
-- 사용자 정책 설정 (삭제)
|
||||||
|
DROP POLICY IF EXISTS "사용자는自己的 예산만 삭제할 수 있음" ON budgets;
|
||||||
|
CREATE POLICY "사용자는自己的 예산만 삭제할 수 있음"
|
||||||
|
ON budgets FOR DELETE
|
||||||
|
USING (auth.uid() = user_id);
|
||||||
|
`,
|
||||||
|
_tests: `
|
||||||
|
CREATE TABLE IF NOT EXISTS _tests (
|
||||||
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
test_name TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 모든 사용자가 접근 가능하도록 설정
|
||||||
|
ALTER TABLE _tests ENABLE ROW LEVEL SECURITY;
|
||||||
|
DROP POLICY IF EXISTS "모든 사용자가 테스트 테이블에 접근 가능" ON _tests;
|
||||||
|
CREATE POLICY "모든 사용자가 테스트 테이블에 접근 가능"
|
||||||
|
ON _tests FOR SELECT
|
||||||
|
USING (true);
|
||||||
|
`
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 헬퍼 함수 생성
|
||||||
|
*/
|
||||||
|
async function createHelperFunctions() {
|
||||||
|
console.log('헬퍼 함수 생성 중...');
|
||||||
|
|
||||||
|
// execute_sql 함수 생성
|
||||||
|
const executeSqlSQL = `
|
||||||
|
CREATE OR REPLACE FUNCTION execute_sql(sql_query TEXT)
|
||||||
|
RETURNS VOID AS $$
|
||||||
|
BEGIN
|
||||||
|
EXECUTE sql_query;
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const { error: execFnError } = await onprem.rpc('execute_sql', { sql_query: executeSqlSQL });
|
||||||
|
if (execFnError) {
|
||||||
|
console.error('execute_sql 함수 생성 실패:', execFnError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('헬퍼 함수 생성 완료');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 테이블 생성
|
||||||
|
*/
|
||||||
|
async function createTable(tableName) {
|
||||||
|
console.log(`테이블 생성 중: ${tableName}`);
|
||||||
|
|
||||||
|
if (!tableSchemas[tableName]) {
|
||||||
|
console.warn(`${tableName} 테이블의 스키마 정보가 없습니다.`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 테이블 생성 SQL 실행
|
||||||
|
const { error } = await onprem.rpc('execute_sql', { sql_query: tableSchemas[tableName] });
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error(`${tableName} 테이블 생성 실패:`, error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${tableName} 테이블 생성 완료`);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`${tableName} 테이블 생성 중 오류:`, error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 테이블 데이터 마이그레이션
|
||||||
|
*/
|
||||||
|
async function migrateTableData(tableName) {
|
||||||
|
console.log(`테이블 데이터 마이그레이션 중: ${tableName}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Cloud DB에서 데이터 가져오기
|
||||||
|
const { data, error } = await cloud.from(tableName).select('*');
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
if (error.code === '42P01') {
|
||||||
|
console.warn(`Cloud DB에 ${tableName} 테이블이 없습니다. 건너뜁니다.`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
console.error(`Cloud DB에서 ${tableName} 데이터 가져오기 실패:`, error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
console.log(`${tableName} 테이블에 마이그레이션할 데이터가 없습니다.`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${tableName} 테이블에서 ${data.length}개 행을 가져왔습니다.`);
|
||||||
|
|
||||||
|
// 데이터를 작은 배치로 나누어 삽입 (트랜잭션 삭제 안전성 고려)
|
||||||
|
const batchSize = 100;
|
||||||
|
for (let i = 0; i < data.length; i += batchSize) {
|
||||||
|
const batch = data.slice(i, i + batchSize);
|
||||||
|
const { error: insertError } = await onprem.from(tableName).upsert(batch);
|
||||||
|
|
||||||
|
if (insertError) {
|
||||||
|
console.error(`${tableName} 테이블에 데이터 삽입 실패:`, insertError);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${tableName} 테이블에 ${batch.length}개 행 삽입 완료 (${i + batch.length}/${data.length})`);
|
||||||
|
|
||||||
|
// 비동기 작업 사이에 짧은 지연 추가 (UI 스레드 차단 방지)
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`${tableName} 테이블 데이터 마이그레이션 완료`);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`${tableName} 테이블 데이터 마이그레이션 중 오류:`, error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 메인 마이그레이션 함수
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
try {
|
||||||
|
console.log('Supabase Cloud → On-Prem 마이그레이션 시작');
|
||||||
|
|
||||||
|
// 헬퍼 함수 생성
|
||||||
|
const helperCreated = await createHelperFunctions();
|
||||||
|
if (!helperCreated) {
|
||||||
|
console.warn('헬퍼 함수 생성에 실패했습니다. 계속 진행합니다.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 각 테이블에 대해 스키마 생성 및 데이터 마이그레이션 수행
|
||||||
|
for (const tableName of tables) {
|
||||||
|
// 테이블 생성
|
||||||
|
const tableCreated = await createTable(tableName);
|
||||||
|
if (!tableCreated) {
|
||||||
|
console.warn(`${tableName} 테이블 생성에 실패했습니다. 데이터 마이그레이션을 건너뜁니다.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 데이터 마이그레이션
|
||||||
|
const dataMigrated = await migrateTableData(tableName);
|
||||||
|
if (!dataMigrated) {
|
||||||
|
console.warn(`${tableName} 테이블 데이터 마이그레이션에 실패했습니다.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Supabase Cloud → On-Prem 마이그레이션 완료');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('마이그레이션 중 오류 발생:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 마이그레이션 실행
|
||||||
|
main();
|
||||||
63
src/lib/migrateData.ts
Normal file
63
src/lib/migrateData.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import dotenv from 'dotenv';
|
||||||
|
import { createClient } from '@supabase/supabase-js';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const cloudUrl = process.env.CLOUD_SUPABASE_URL;
|
||||||
|
// 서비스 역할 키가 유효하지 않으면 CLOUD_SUPABASE_ANON_KEY 또는 VITE_SUPABASE_ANON_KEY를 사용합니다.
|
||||||
|
const cloudKey = process.env.CLOUD_SUPABASE_SERVICE_ROLE_KEY || process.env.CLOUD_SUPABASE_ANON_KEY || process.env.VITE_SUPABASE_ANON_KEY;
|
||||||
|
const onpremUrl = process.env.ONPREM_SUPABASE_URL;
|
||||||
|
// 서비스 역할 키가 유효하지 않으면 ONPREM_SUPABASE_ANON_KEY 또는 VITE_SUPABASE_ANON_KEY를 사용합니다.
|
||||||
|
const onpremKey = process.env.ONPREM_SUPABASE_SERVICE_ROLE_KEY || process.env.ONPREM_SUPABASE_ANON_KEY || process.env.VITE_SUPABASE_ANON_KEY;
|
||||||
|
|
||||||
|
if (!cloudUrl || !cloudKey || !onpremUrl || !onpremKey) {
|
||||||
|
console.error('환경 변수가 설정되지 않았습니다. .env 파일을 확인하세요.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cloud = createClient(cloudUrl, cloudKey);
|
||||||
|
const onprem = createClient(onpremUrl, onpremKey);
|
||||||
|
|
||||||
|
// 복사할 테이블 목록을 정의하세요.
|
||||||
|
const tables = [
|
||||||
|
'users',
|
||||||
|
'accounts',
|
||||||
|
'transactions',
|
||||||
|
// 필요에 따라 추가 테이블을 여기에 입력
|
||||||
|
];
|
||||||
|
|
||||||
|
async function migrateTable(table: string) {
|
||||||
|
console.log(`Migrating table: ${table}`);
|
||||||
|
const { data, error } = await cloud.from(table).select('*');
|
||||||
|
if (error) {
|
||||||
|
// 테이블이 없으면 스킵
|
||||||
|
if (error.code === '42P01') {
|
||||||
|
console.warn(`Table ${table} not found in Cloud DB, skipping.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.error(`Error fetching ${table}:`, error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!data || data.length === 0) {
|
||||||
|
console.log(`${table} has no data to migrate.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { error: insertError } = await onprem.from(table).upsert(data);
|
||||||
|
if (insertError) {
|
||||||
|
console.error(`Error inserting into ${table}:`, insertError);
|
||||||
|
} else {
|
||||||
|
console.log(`Migrated ${data.length} rows into ${table}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
for (const table of tables) {
|
||||||
|
await migrateTable(table);
|
||||||
|
}
|
||||||
|
console.log('Migration complete.');
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(err => {
|
||||||
|
console.error('Migration failed:', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -1,16 +1,19 @@
|
|||||||
|
|
||||||
// Supabase Cloud URL과 anon key 설정
|
// Supabase Cloud URL과 anon key 설정
|
||||||
export const getSupabaseUrl = () => {
|
export const getSupabaseUrl = () => {
|
||||||
return "https://qnerebtvwwfobfzdoftx.supabase.co";
|
const url = import.meta.env.VITE_SUPABASE_URL;
|
||||||
|
if (!url) throw new Error("VITE_SUPABASE_URL is not set");
|
||||||
|
return url;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSupabaseKey = () => {
|
export const getSupabaseKey = () => {
|
||||||
return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InFuZXJlYnR2d3dmb2JmemRvZnR4Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDIwNTE0MzgsImV4cCI6MjA1NzYyNzQzOH0.Wm7h2DUhoQbeANuEM3wm2tz22ITrVEW8FizyLgIVmv8";
|
const key = import.meta.env.VITE_SUPABASE_ANON_KEY;
|
||||||
|
if (!key) throw new Error("VITE_SUPABASE_ANON_KEY is not set");
|
||||||
|
return key;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Supabase 키 유효성 검사 - Cloud 환경에서는 항상 유효함
|
// Supabase 키 유효성 검사 - Cloud 환경에서는 항상 유효함
|
||||||
export const isValidSupabaseKey = () => {
|
export const isValidSupabaseKey = () => {
|
||||||
return true;
|
return Boolean(import.meta.env.VITE_SUPABASE_ANON_KEY);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 다음 함수들은 Cloud 환경에서는 필요 없지만 호환성을 위해 유지
|
// 다음 함수들은 Cloud 환경에서는 필요 없지만 호환성을 위해 유지
|
||||||
|
|||||||
@@ -2,35 +2,47 @@
|
|||||||
import { format, parse, isValid, parseISO } from 'date-fns';
|
import { format, parse, isValid, parseISO } from 'date-fns';
|
||||||
import { ko } from 'date-fns/locale';
|
import { ko } from 'date-fns/locale';
|
||||||
|
|
||||||
|
// 날짜 파싱 결과를 캐싱하기 위한 Map
|
||||||
|
const dateParseCache = new Map<string, Date | null>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 다양한 형식의 날짜 문자열을 Date 객체로 변환하는 유틸리티
|
* 다양한 형식의 날짜 문자열을 Date 객체로 변환하는 유틸리티
|
||||||
|
* 성능 최적화를 위해 결과 캐싱 기능 추가
|
||||||
*/
|
*/
|
||||||
export const parseTransactionDate = (dateStr: string): Date | null => {
|
export const parseTransactionDate = (dateStr: string): Date | null => {
|
||||||
// 빈 문자열 체크
|
// 빈 문자열 체크
|
||||||
if (!dateStr || dateStr === '') {
|
if (!dateStr || dateStr === '') {
|
||||||
console.log('빈 날짜 문자열');
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 캐시된 결과가 있으면 반환
|
||||||
|
if (dateParseCache.has(dateStr)) {
|
||||||
|
return dateParseCache.get(dateStr) || null;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let result: Date | null = null;
|
||||||
|
|
||||||
// 특수 키워드 처리
|
// 특수 키워드 처리
|
||||||
if (dateStr.toLowerCase().includes('오늘')) {
|
if (dateStr.toLowerCase().includes('오늘')) {
|
||||||
console.log('오늘 날짜로 변환');
|
result = new Date();
|
||||||
return new Date();
|
dateParseCache.set(dateStr, result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dateStr.toLowerCase().includes('어제')) {
|
if (dateStr.toLowerCase().includes('어제')) {
|
||||||
console.log('어제 날짜로 변환');
|
|
||||||
const yesterday = new Date();
|
const yesterday = new Date();
|
||||||
yesterday.setDate(yesterday.getDate() - 1);
|
yesterday.setDate(yesterday.getDate() - 1);
|
||||||
return yesterday;
|
result = yesterday;
|
||||||
|
dateParseCache.set(dateStr, result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ISO 형식 (yyyy-MM-dd) 시도
|
// ISO 형식 (yyyy-MM-dd) 시도
|
||||||
if (/^\d{4}-\d{2}-\d{2}/.test(dateStr)) {
|
if (/^\d{4}-\d{2}-\d{2}/.test(dateStr)) {
|
||||||
console.log('ISO 형식 날짜 감지');
|
|
||||||
const date = parseISO(dateStr);
|
const date = parseISO(dateStr);
|
||||||
if (isValid(date)) {
|
if (isValid(date)) {
|
||||||
|
dateParseCache.set(dateStr, date);
|
||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,27 +52,30 @@ export const parseTransactionDate = (dateStr: string): Date | null => {
|
|||||||
const koreanMatch = dateStr.match(koreanDatePattern);
|
const koreanMatch = dateStr.match(koreanDatePattern);
|
||||||
|
|
||||||
if (koreanMatch) {
|
if (koreanMatch) {
|
||||||
console.log('한국어 날짜 형식 감지', koreanMatch);
|
|
||||||
const month = parseInt(koreanMatch[1]) - 1; // 0-based month
|
const month = parseInt(koreanMatch[1]) - 1; // 0-based month
|
||||||
const day = parseInt(koreanMatch[2]);
|
const day = parseInt(koreanMatch[2]);
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
date.setMonth(month);
|
date.setMonth(month);
|
||||||
date.setDate(day);
|
date.setDate(day);
|
||||||
|
dateParseCache.set(dateStr, date);
|
||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 쉼표 구분 형식 처리 (예: "오늘, 14:52 PM")
|
// 쉼표 구분 형식 처리 (예: "오늘, 14:52 PM")
|
||||||
const commaSplit = dateStr.split(',');
|
const commaSplit = dateStr.split(',');
|
||||||
if (commaSplit.length > 1) {
|
if (commaSplit.length > 1) {
|
||||||
console.log('쉼표 구분 날짜 감지');
|
|
||||||
// 첫 부분이 오늘/어제 등의 키워드인 경우 처리
|
// 첫 부분이 오늘/어제 등의 키워드인 경우 처리
|
||||||
const firstPart = commaSplit[0].trim().toLowerCase();
|
const firstPart = commaSplit[0].trim().toLowerCase();
|
||||||
if (firstPart === '오늘') {
|
if (firstPart === '오늘') {
|
||||||
return new Date();
|
result = new Date();
|
||||||
|
dateParseCache.set(dateStr, result);
|
||||||
|
return result;
|
||||||
} else if (firstPart === '어제') {
|
} else if (firstPart === '어제') {
|
||||||
const yesterday = new Date();
|
const yesterday = new Date();
|
||||||
yesterday.setDate(yesterday.getDate() - 1);
|
yesterday.setDate(yesterday.getDate() - 1);
|
||||||
return yesterday;
|
result = yesterday;
|
||||||
|
dateParseCache.set(dateStr, result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,13 +93,12 @@ export const parseTransactionDate = (dateStr: string): Date | null => {
|
|||||||
|
|
||||||
for (const formatStr of formats) {
|
for (const formatStr of formats) {
|
||||||
try {
|
try {
|
||||||
console.log(`날짜 형식 시도: ${formatStr}`);
|
|
||||||
const date = parse(dateStr, formatStr, new Date(), { locale: ko });
|
const date = parse(dateStr, formatStr, new Date(), { locale: ko });
|
||||||
if (isValid(date)) {
|
if (isValid(date)) {
|
||||||
console.log('날짜 파싱 성공:', formatStr);
|
dateParseCache.set(dateStr, date);
|
||||||
return date;
|
return date;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch {
|
||||||
// 이 형식이 실패하면 다음 형식 시도
|
// 이 형식이 실패하면 다음 형식 시도
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -93,19 +107,27 @@ export const parseTransactionDate = (dateStr: string): Date | null => {
|
|||||||
// 위 모든 형식이 실패하면 마지막으로 Date 생성자 시도
|
// 위 모든 형식이 실패하면 마지막으로 Date 생성자 시도
|
||||||
const dateFromConstructor = new Date(dateStr);
|
const dateFromConstructor = new Date(dateStr);
|
||||||
if (isValid(dateFromConstructor)) {
|
if (isValid(dateFromConstructor)) {
|
||||||
console.log('Date 생성자로 파싱 성공');
|
dateParseCache.set(dateStr, dateFromConstructor);
|
||||||
return dateFromConstructor;
|
return dateFromConstructor;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 모든 방법이 실패하면 null 반환
|
// 모든 방법이 실패하면 null 반환
|
||||||
console.warn(`날짜 파싱 실패: ${dateStr}`);
|
dateParseCache.set(dateStr, null);
|
||||||
return null;
|
return null;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`날짜 파싱 중 오류 발생: ${dateStr}`, error);
|
console.error(`날짜 파싱 중 오류 발생: ${dateStr}`, error);
|
||||||
|
dateParseCache.set(dateStr, null);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 캐시를 정리하는 함수 (필요시 호출)
|
||||||
|
*/
|
||||||
|
export const clearDateParseCache = () => {
|
||||||
|
dateParseCache.clear();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Date 객체를 yyyy-MM-dd 형식의 문자열로 변환
|
* Date 객체를 yyyy-MM-dd 형식의 문자열로 변환
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
|
||||||
// 월 이름 정의
|
// 이 파일은 레거시 코드로 사용되지만 `MONTHS_KR`가 중복 정의되어 있으므로
|
||||||
export const MONTHS_KR = [
|
// 새로운 구현에서는 src/hooks/transactions/dateUtils.ts에서 가져와 사용합니다.
|
||||||
'1월', '2월', '3월', '4월', '5월', '6월',
|
import { MONTHS_KR } from '@/hooks/transactions/dateUtils';
|
||||||
'7월', '8월', '9월', '10월', '11월', '12월'
|
|
||||||
];
|
|
||||||
|
|
||||||
|
// 다른 파일에서 사용할 수 있도록 재내보내기
|
||||||
|
export { MONTHS_KR };
|
||||||
|
|
||||||
|
// 아래 함수들은 하위 호환성을 위해 유지합니다
|
||||||
// 현재 월 가져오기
|
// 현재 월 가져오기
|
||||||
export const getCurrentMonth = () => {
|
export const getCurrentMonth = () => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|||||||
1
supabase/.temp/cli-latest
Normal file
1
supabase/.temp/cli-latest
Normal file
@@ -0,0 +1 @@
|
|||||||
|
v2.22.6
|
||||||
0
supabase_cloud_data.sql
Normal file
0
supabase_cloud_data.sql
Normal file
0
supabase_schema.sql
Normal file
0
supabase_schema.sql
Normal file
5
version.properties
Normal file
5
version.properties
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Zellyy Finance 앱 버전 정보
|
||||||
|
# 마지막 업데이트: 2025-04-05 19:45:54
|
||||||
|
VERSION_CODE=9
|
||||||
|
VERSION_NAME=1.1.8
|
||||||
|
BUILD_NUMBER=9
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { defineConfig } from "vite";
|
import { defineConfig } from 'vite';
|
||||||
import react from "@vitejs/plugin-react-swc";
|
import react from "@vitejs/plugin-react-swc";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { componentTagger } from "lovable-tagger";
|
import { componentTagger } from "lovable-tagger";
|
||||||
@@ -6,7 +6,7 @@ import { componentTagger } from "lovable-tagger";
|
|||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig(({ mode }) => ({
|
export default defineConfig(({ mode }) => ({
|
||||||
server: {
|
server: {
|
||||||
host: "::",
|
host: "0.0.0.0",
|
||||||
port: 8080,
|
port: 8080,
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|||||||
Reference in New Issue
Block a user