Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions .github/workflows/deploy-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: CI/CD - Searchweb (version.txt 기반)

on:
push:
branches: [ "main", "dev" ]
pull_request:
branches: [ "main", "dev" ]

Comment on lines +3 to +8
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

YAML 형식 오류를 수정하세요: 'on' 키와 배열 괄호 간격.

YAMLlint에서 다음 오류를 보고했습니다:

  • Line 3: on: 값이 boolean처럼 보임
  • Lines 5, 7: 배열 괄호 간격 오류

다음과 같이 수정하세요:

-on:
+on:
-    branches: [ "main", "dev" ]
+    branches: ["main", "dev"]

또는 더 명확하게:

-on:
+on: &defaults

권장 YAML 형식:

-on:
+on:
   push:
-    branches: [ "main", "dev" ]
+    branches: ["main", "dev"]
   pull_request:
-    branches: [ "main", "dev" ]
+    branches: ["main", "dev"]
🧰 Tools
🪛 YAMLlint (1.37.1)

[warning] 3-3: truthy value should be one of [false, true]

(truthy)


[error] 5-5: too many spaces inside brackets

(brackets)


[error] 5-5: too many spaces inside brackets

(brackets)


[error] 7-7: too many spaces inside brackets

(brackets)


[error] 7-7: too many spaces inside brackets

(brackets)

🤖 Prompt for AI Agents
.github/workflows/deploy-test.yml around lines 3 to 8: the 'on' mapping and
branch arrays are incorrectly formatted causing YAMLlint errors; fix by ensuring
'push' and 'pull_request' are indented under 'on' and replace the inline array
with a proper block list (branches: - main - dev) or, if you prefer inline, use
correct flow style with no extra spaces inside brackets (branches:
["main","dev"]) so the file parses as a mapping with properly formatted arrays.

jobs:
build-and-push:
name: Build & Push Docker Image
runs-on: ubuntu-latest

outputs:
image_tag: ${{ steps.meta.outputs.IMAGE_TAG }}

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Read version.txt
id: meta
run: |
VERSION=$(cat version.txt | tr -d ' \n')
echo "IMAGE_TAG=${VERSION}" >> $GITHUB_OUTPUT
Comment on lines +21 to +25
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

version.txt 읽기 방식을 개선하세요. 현재 방식도 작동하나 더 명확한 방법이 있습니다.

현재 tr -d ' \n' 사용도 작동하지만, 더 robust한 방식을 추천합니다:

       - name: Read version.txt
         id: meta
         run: |
-          VERSION=$(cat version.txt | tr -d ' \n')
-          echo "IMAGE_TAG=${VERSION}" >> $GITHUB_OUTPUT
+          VERSION=$(head -1 version.txt | xargs)
+          if [ -z "$VERSION" ]; then
+            echo "❌ version.txt is empty"
+            exit 1
+          fi
+          echo "IMAGE_TAG=${VERSION}" >> $GITHUB_OUTPUT
+          echo "✅ Image tag: ${VERSION}"

또는 더 간단하게:

VERSION=$(grep . version.txt | head -1 | xargs)

장점:

  • xargs: 공백 및 줄바꿈 자동 제거
  • 빈 파일 검증 추가
  • 로그에 버전 출력으로 디버깅 용이
🤖 Prompt for AI Agents
.github/workflows/deploy-test.yml around lines 21 to 25: the current command
reads version.txt with cat and strips whitespace via tr which works but is less
robust; update the step to read the first non-empty line, trim surrounding
whitespace, fail early if the file is empty, and echo the resolved version to
the workflow logs and to GITHUB_OUTPUT; implement this by reading the first
non-empty line (e.g., using grep/head or a shell read loop), trim with xargs or
parameter expansion, test for empty string and exit non-zero with a clear
message if empty, then set IMAGE_TAG and echo the version for debugging.


- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'

- name: Grant execute permission
run: chmod +x ./gradlew

- name: Build JAR
run: ./gradlew clean bootJar

- name: Docker Login
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Docker Build (version + latest)
run: |
docker build \
-t ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:${{ steps.meta.outputs.IMAGE_TAG }} \
-t ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:latest \
.

- name: Docker Push
run: |
docker push ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:${{ steps.meta.outputs.IMAGE_TAG }}
docker push ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:latest
Comment on lines +45 to +55
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

빌드 및 푸시 단계의 에러 핸들링을 강화하세요. 이미지 푸시 실패 시에도 배포가 진행됩니다.

현재는 docker build/push 실패 시에도 배포 job이 실행될 수 있습니다. needs 의존성이 있으나, build/push 단계 내 에러 핸들링이 부족합니다.

다음과 같이 개선하세요:

       - name: Docker Build (version + latest)
         run: |
           docker build \
             -t ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:${{ steps.meta.outputs.IMAGE_TAG }} \
             -t ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:latest \
             .
+          if [ $? -ne 0 ]; then
+            echo "❌ Docker build failed"
+            exit 1
+          fi
 
       - name: Docker Push
         run: |
           docker push ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:${{ steps.meta.outputs.IMAGE_TAG }}
-          docker push ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:latest
+          if [ $? -ne 0 ]; then
+            echo "❌ Failed to push versioned image"
+            exit 1
+          fi
+          
+          docker push ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:latest
+          if [ $? -ne 0 ]; then
+            echo "❌ Failed to push latest image"
+            exit 1
+          fi

또는 set -e 사용:

       - name: Docker Build (version + latest)
+        shell: bash
         run: |
+          set -e
           docker build \
             -t ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:${{ steps.meta.outputs.IMAGE_TAG }} \
             -t ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:latest \
             .
 
       - name: Docker Push
+        shell: bash
         run: |
+          set -e
           docker push ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:${{ steps.meta.outputs.IMAGE_TAG }}
           docker push ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:latest
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Docker Build (version + latest)
run: |
docker build \
-t ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:${{ steps.meta.outputs.IMAGE_TAG }} \
-t ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:latest \
.
- name: Docker Push
run: |
docker push ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:${{ steps.meta.outputs.IMAGE_TAG }}
docker push ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:latest
- name: Docker Build (version + latest)
run: |
docker build \
-t ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:${{ steps.meta.outputs.IMAGE_TAG }} \
-t ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:latest \
.
if [ $? -ne 0 ]; then
echo "❌ Docker build failed"
exit 1
fi
- name: Docker Push
run: |
docker push ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:${{ steps.meta.outputs.IMAGE_TAG }}
if [ $? -ne 0 ]; then
echo "❌ Failed to push versioned image"
exit 1
fi
docker push ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:latest
if [ $? -ne 0 ]; then
echo "❌ Failed to push latest image"
exit 1
fi
Suggested change
- name: Docker Build (version + latest)
run: |
docker build \
-t ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:${{ steps.meta.outputs.IMAGE_TAG }} \
-t ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:latest \
.
- name: Docker Push
run: |
docker push ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:${{ steps.meta.outputs.IMAGE_TAG }}
docker push ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:latest
- name: Docker Build (version + latest)
shell: bash
run: |
set -e
docker build \
-t ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:${{ steps.meta.outputs.IMAGE_TAG }} \
-t ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:latest \
.
- name: Docker Push
shell: bash
run: |
set -e
docker push ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:${{ steps.meta.outputs.IMAGE_TAG }}
docker push ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:latest
🤖 Prompt for AI Agents
.github/workflows/deploy-test.yml around lines 45 to 55: the Docker build/push
steps lack robust error handling so failures can be ignored and downstream
deploy can still run; modify the steps so the shell fails fast (e.g., enable
"set -e" at the top of the run block or explicitly check each command's exit
status and exit non‑zero on failure) or configure the job/step to not
continue-on-error and to fail the workflow when build or push fails, ensuring
the deploy job does not run if image creation or push fails.


deploy:
name: Deploy to OCI
runs-on: ubuntu-latest
needs: build-and-push

env:
IMAGE_TAG: ${{ needs.build-and-push.outputs.image_tag }}

steps:
- name: Prepare SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa

- name: Add Known Hosts
run: ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
Comment on lines +66 to +73
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🔒 SSH 키 보안: ssh-agent를 사용하세요. 현재 방식은 로그 노출 위험이 있습니다.

Line 69에서 SSH 키를 직접 파일에 작성하는 것은 다음 위험이 있습니다:

  • 워크플로우 디버그 로그에서 키가 노출될 수 있음
  • 임시 파일이 안전하지 않을 수 있음

ssh-agent를 사용하여 개선하세요:

-      - name: Prepare SSH key
-        run: |
-          mkdir -p ~/.ssh
-          echo "${{ secrets.SSH_KEY }}" > ~/.ssh/id_rsa
-          chmod 600 ~/.ssh/id_rsa
+      - name: Setup SSH
+        uses: webfactory/ssh-agent@v0.8.0
+        with:
+          ssh-private-key: ${{ secrets.SSH_KEY }}

그리고 다음 단계를 수정하세요:

-      - name: Add Known Hosts
-        run: ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
+      - name: Add SSH Host Key
+        run: |
+          ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
+          chmod 644 ~/.ssh/known_hosts
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Prepare SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
- name: Add Known Hosts
run: ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
- name: Setup SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.SSH_KEY }}
- name: Add SSH Host Key
run: |
ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
🤖 Prompt for AI Agents
.github/workflows/deploy-test.yml lines 66-73: the workflow writes the SSH
private key directly to ~/.ssh/id_rsa (exposing it in logs and as a temporary
file); replace that block to use ssh-agent instead — invoke the official
actions/ssh-agent with the private key from secrets (so it loads the key into
the agent rather than echoing to disk), ensure PUBLIC/known_hosts is populated
using ssh-keyscan (or use the ssh-agent action's known_hosts input if
available), and remove the echo/chmod steps; keep the Known Hosts step but run
it after the agent is started if you still need to append to ~/.ssh/known_hosts.


- name: Deploy via SSH
run: |
ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << 'EOF'
docker login -u '${{ secrets.DOCKERHUB_USERNAME }}' -p '${{ secrets.DOCKERHUB_TOKEN }}'
docker pull ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:${IMAGE_TAG}
docker pull ${{ secrets.DOCKERHUB_USERNAME }}/searchweb:latest

# 기존 컨테이너 강제 삭제 (있으면 지우고, 없으면 에러 무시)
docker rm -f searchweb 2>/dev/null || true

# 새 컨테이너 실행
docker run -d \
--env SPRING_PROFILES_ACTIVE=dev \
--env-file /home/ubuntu/Searchweb/.env \
-p 8080:8080 \
--name searchweb \
${{ secrets.DOCKERHUB_USERNAME }}/searchweb:latest
EOF
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ bin/


### Ignore application.properties ###
application.properties
application-*.properties
.env


Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
FROM openjdk:17-jdk-slim
FROM eclipse-temurin:17-jre-alpine
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

베이스 이미지 변경은 좋은 선택입니다. 그러나 보안 및 운영성을 위해 USER와 HEALTHCHECK을 추가하세요.

eclipse-temurin:17-jre-alpine은 보안 업데이트가 정기적이고 이미지 크기가 작아 배포 성능이 개선됩니다. 그러나 컨테이너 보안과 운영성을 위해 다음 개선사항을 추천합니다:

  1. USER 지정: root 사용자로 실행하는 것은 보안 위험입니다.
  2. HEALTHCHECK 추가: 쿠버네티스나 컨테이너 오케스트레이션 환경에서 헬스 체크가 필요합니다.

다음 diff를 적용하세요:

-FROM eclipse-temurin:17-jre-alpine
+FROM eclipse-temurin:17-jre-alpine
+
+# Non-root user 생성
+RUN addgroup -S appgroup && adduser -S appuser -G appgroup
+
 COPY build/libs/SearchWeb-0.0.1-SNAPSHOT.jar app.jar
-
+
+# 헬스 체크 추가
+HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+  CMD java -jar app.jar || exit 1
+
+USER appuser
 ENTRYPOINT ["java", "-Dspring.profiles.active=docker", "-jar", "app.jar"]

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Checkov (3.2.334)

[low] 1-3: Ensure that HEALTHCHECK instructions have been added to container images

(CKV_DOCKER_2)


[low] 1-3: Ensure that a user for the container has been created

(CKV_DOCKER_3)

🤖 Prompt for AI Agents
In Dockerfile around lines 1 to 1, the base image was changed but the container
runs as root and lacks a healthcheck; update the Dockerfile to (1) create and
switch to a non-root user and set USER to that account (e.g., add a group/user
with non-zero UID, chown necessary app dirs, and set USER), and (2) add a
HEALTHCHECK instruction that uses a lightweight command to verify the
application is responsive (adjust interval, timeout, retries) so orchestration
platforms can detect unhealthy containers; ensure the healthcheck command exits
with proper status codes and does not run as root-only.

COPY build/libs/SearchWeb-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java", "-Dspring.profiles.active=docker", "-jar", "app.jar"]
ENTRYPOINT ["java", "-jar", "app.jar"]
15 changes: 15 additions & 0 deletions src/main/resources/application-dev.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Dev mysql settings
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=${DEV_DB_URL}
spring.datasource.username=${DEV_DB_USERNAME}
spring.datasource.password=${DEV_DB_PASSWORD}

## Dev OAUTH2
spring.security.oauth2.client.registration.naver.redirect-uri=${CLOUD_NAVER_OAUTH_REDIRECT_URI}
spring.security.oauth2.client.registration.google.redirect-uri=${CLOUD_GOOGLE_OAUTH_REDIRECT_URI}
spring.security.oauth2.client.registration.kakao.redirect-uri=${CLOUD_KAKAO_OAUTH_REDIRECT_URI}

## openai settings
#ai.openai.api-key=${OPENAI_API_KEY}
#ai.openai.chat.options.model=${OPENAI_API_MODEL}
#ai.openai.chat.options.temperature=${OPENAI_API_tmp}
14 changes: 14 additions & 0 deletions src/main/resources/application-local.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## Local mysql settings
spring.datasource.url=${LOCAL_DB_URL}
spring.datasource.username=${LOCAL_DB_USERNAME}
spring.datasource.password=${LOCAL_DB_PASSWORD}

## LOCAL OAUTH2
spring.security.oauth2.client.registration.naver.redirect-uri=${LOCAL_NAVER_OAUTH_REDIRECT_URI}
spring.security.oauth2.client.registration.google.redirect-uri=${LOCAL_GOOGLE_OAUTH_REDIRECT_URI}
spring.security.oauth2.client.registration.kakao.redirect-uri=${LOCAL_KAKAO_OAUTH_REDIRECT_URI}

## openai settings
#ai.openai.api-key=${OPENAI_API_KEY}
#ai.openai.chat.options.model=${OPENAI_API_MODEL}
#ai.openai.chat.options.temperature=${OPENAI_API_tmp}
15 changes: 15 additions & 0 deletions src/main/resources/application-prod.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Prod mysql settings
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=${PROD_DB_URL}
spring.datasource.username=${PROD_DB_USERNAME}
spring.datasource.password=${PROD_DB_PASSWORD}

## Prod OAUTH2
spring.security.oauth2.client.registration.naver.redirect-uri=${CLOUD_NAVER_OAUTH_REDIRECT_URI}
spring.security.oauth2.client.registration.google.redirect-uri=${CLOUD_GOOGLE_OAUTH_REDIRECT_URI}
spring.security.oauth2.client.registration.kakao.redirect-uri=${CLOUD_KAKAO_OAUTH_REDIRECT_URI}

## openai settings
#ai.openai.api-key=${OPENAI_API_KEY}
#ai.openai.chat.options.model=${OPENAI_API_MODEL}
#ai.openai.chat.options.temperature=${OPENAI_API_tmp}
50 changes: 50 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
spring.application.name=SearchWeb

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# mybatis settings
mybatis.mapper-locations=classpath:mapper/*.xml
logging.level.org.mybatis=DEBUG

#logging.level.org.springframework.security=TRACE
logging.level.org.springframework.security=DEBUG


##### Oauth2 session login settings #####
#naver settings
#registration
spring.security.oauth2.client.registration.naver.client-name=naver
spring.security.oauth2.client.registration.naver.client-id=${NAVER_CLIENT_ID}
spring.security.oauth2.client.registration.naver.client-secret=${NAVER_CLIENT_SECRET}
spring.security.oauth2.client.registration.naver.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.naver.scope=name,email
#provider
spring.security.oauth2.client.provider.naver.authorization-uri=https://nid.naver.com/oauth2.0/authorize
spring.security.oauth2.client.provider.naver.token-uri=https://nid.naver.com/oauth2.0/token
spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me
spring.security.oauth2.client.provider.naver.user-name-attribute=response


#google settings
#registration
spring.security.oauth2.client.registration.google.client-name=google
spring.security.oauth2.client.registration.google.client-id=${GOOGLE_CLIENT_ID}
spring.security.oauth2.client.registration.google.client-secret=${GOOGLE_CLIENT_SECRET}
spring.security.oauth2.client.registration.google.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.google.scope=profile,email


#kakao settings
#registration
spring.security.oauth2.client.registration.kakao.client-name=kakao
spring.security.oauth2.client.registration.kakao.client-id=${KAKAO_CLIENT_ID}
spring.security.oauth2.client.registration.kakao.client-secret=${KAKAO_CLIENT_SECRET}
spring.security.oauth2.client.registration.kakao.client-authentication-method=client_secret_post
spring.security.oauth2.client.registration.kakao.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.kakao.scope=profile_nickname, account_email
#provider
spring.security.oauth2.client.provider.kakao.authorization-uri=https://kauth.kakao.com/oauth/authorize
spring.security.oauth2.client.provider.kakao.token-uri=https://kauth.kakao.com/oauth/token
spring.security.oauth2.client.provider.kakao.user-info-uri=https://kapi.kakao.com/v2/user/me
spring.security.oauth2.client.provider.kakao.user-name-attribute=id

#spring.config.import=optional:dotenv:.env
1 change: 1 addition & 0 deletions version.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.1.0
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

파일 끝에 줄바꿈을 추가하세요.

버전 형식(Semantic Versioning)은 좋으나, 파일 끝에 줄바꿈이 없으면 Git에서 경고가 발생합니다. 이는 Unix/POSIX 파일 표준입니다.

다음 diff를 적용하세요:

-1.1.0
\ No newline at end of file
+1.1.0

또는 명령어로 수정:

echo "" >> version.txt
🤖 Prompt for AI Agents
version.txt lines 1-1: 파일 끝에 개행(newline)이 누락되어 Git에서 경고가 발생합니다; 파일의 마지막 줄 뒤에 빈
줄(줄바꿈)을 추가하여 파일을 종료하도록 수정하세요 (직접 파일에 개행을 추가하거나 파일 끝에 한 줄 개행을 추가하는 명령을 실행하여 저장).