project-auto-wizard 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +139 -0
- package/bin/project-auto-wizard.js +19 -0
- package/package.json +36 -0
- package/payload/coderabbit.yaml +19 -0
- package/payload/config/breaking-changes.json +1 -0
- package/payload/config/wizard-prompts.yml +62 -0
- package/payload/scripts/changelog_manager.py +726 -0
- package/payload/scripts/version_manager.py +617 -0
- package/payload/version.yml.template +48 -0
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +303 -0
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +293 -0
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +289 -0
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +192 -0
- package/payload/workflows/common/secret-backup/PROJECT-COMMON-SECRET-FILE-UPLOAD.yaml +209 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +591 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +700 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +308 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +992 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +689 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +987 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +471 -0
- package/payload/workflows/flutter/PROJECT-FLUTTER-SUH-LAB-APP-BUILD-TRIGGER.yaml +500 -0
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +185 -0
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +271 -0
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +81 -0
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +2191 -0
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +386 -0
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +194 -0
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +255 -0
- package/payload/workflows/spring/PROJECT-SPRING-GITHUB-PACKAGES-PUBLISH.yml +84 -0
- package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-CI.yml +316 -0
- package/payload/workflows/spring/nexus/PROJECT-SPRING-NEXUS-PUBLISH.yml +58 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +535 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +437 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +2277 -0
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +425 -0
- package/src/cli/args.js +95 -0
- package/src/cli/help.js +27 -0
- package/src/commands/full.js +53 -0
- package/src/commands/interactive.js +276 -0
- package/src/commands/revert.js +60 -0
- package/src/commands/version.js +31 -0
- package/src/commands/workflows.js +49 -0
- package/src/context.js +26 -0
- package/src/core/assets.js +47 -0
- package/src/core/branches.js +60 -0
- package/src/core/branding.js +15 -0
- package/src/core/breaking-check.js +65 -0
- package/src/core/breaking.js +26 -0
- package/src/core/copy/coderabbit.js +26 -0
- package/src/core/copy/gitignore.js +55 -0
- package/src/core/copy/readme.js +30 -0
- package/src/core/copy/simple.js +23 -0
- package/src/core/copy/workflows.js +233 -0
- package/src/core/detect-fs.js +106 -0
- package/src/core/detect.js +62 -0
- package/src/core/fsutil.js +46 -0
- package/src/core/options-ask.js +99 -0
- package/src/core/paths-resolve.js +261 -0
- package/src/core/paths.js +16 -0
- package/src/core/version-yml.js +190 -0
- package/src/core/wizard-env.js +101 -0
- package/src/core/wizard-labels.js +107 -0
- package/src/index.js +162 -0
- package/src/ui/ansi.js +26 -0
- package/src/ui/banner.js +29 -0
- package/src/ui/env-plan.js +207 -0
- package/src/ui/prompts.js +99 -0
- package/src/ui/readline-engine.js +257 -0
- package/src/ui/status-cards.js +56 -0
- package/src/ui/summary.js +127 -0
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
# ===================================================================
|
|
2
|
+
# Flutter 테스트 앱 빌드 트리거 워크플로우 (SUH-LAB)
|
|
3
|
+
# ===================================================================
|
|
4
|
+
#
|
|
5
|
+
# 이 워크플로우는 PR 또는 이슈에 댓글로 빌드 명령어를 작성하면
|
|
6
|
+
# Android와 iOS 테스트 앱 빌드를 자동으로 트리거합니다.
|
|
7
|
+
#
|
|
8
|
+
# 주요 특징:
|
|
9
|
+
# - PR/이슈 댓글 감지: "@suh-lab app build / apk build / ios build" 키워드 감지 (어순 무관: build app 도 동작)
|
|
10
|
+
# - repository_dispatch 이벤트로 빌드 워크플로우 트리거
|
|
11
|
+
# - PR/이슈 번호 + 빌드 횟수로 고유한 빌드 번호 생성 (예: 38700, 38701...)
|
|
12
|
+
# - 브랜치명에서 이슈 번호 자동 추출 (YYYYMMDD_#이슈번호_내용 형식)
|
|
13
|
+
# - 테스트 버전은 version.yml에서 동적으로 읽어 사용 (빌드 번호로 운영/테스트 구분)
|
|
14
|
+
#
|
|
15
|
+
# 사용 방법:
|
|
16
|
+
# - PR에 댓글 작성: "@suh-lab app build" (Android + iOS 모두 빌드)
|
|
17
|
+
# - PR에 댓글 작성: "@suh-lab apk build" (Android만 빌드)
|
|
18
|
+
# - PR에 댓글 작성: "@suh-lab ios build" (iOS만 빌드)
|
|
19
|
+
# - 이슈에 댓글 작성: 위 명령어 동일 (Guide by SUH-LAB 댓글 필요)
|
|
20
|
+
# - 어순은 무관: "build app" / "build apk" / "build ios"로 적어도 동일하게 동작
|
|
21
|
+
# - 특정 브랜치 지정: "@suh-lab app build <브랜치명>" (명령어 뒤에 브랜치명을 적으면 해당 브랜치를 빌드)
|
|
22
|
+
# 예: "@suh-lab apk build 20260609_#349_feature" → 해당 브랜치로 Android 빌드
|
|
23
|
+
# 브랜치명을 생략하면 PR head 또는 이슈의 Guide by SUH-LAB 댓글 브랜치를 사용
|
|
24
|
+
#
|
|
25
|
+
# 트리거되는 워크플로우:
|
|
26
|
+
# - PROJECT-FLUTTER-ANDROID-TEST-APK.yaml (event_type: build-android-app)
|
|
27
|
+
# - PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml (event_type: build-ios-app)
|
|
28
|
+
#
|
|
29
|
+
# ===================================================================
|
|
30
|
+
|
|
31
|
+
name: PROJECT-Flutter-SUH-LAB-App-Build-Trigger
|
|
32
|
+
|
|
33
|
+
on:
|
|
34
|
+
issue_comment:
|
|
35
|
+
types: [created]
|
|
36
|
+
|
|
37
|
+
permissions:
|
|
38
|
+
contents: write
|
|
39
|
+
pull-requests: write
|
|
40
|
+
issues: write
|
|
41
|
+
|
|
42
|
+
jobs:
|
|
43
|
+
trigger-builds:
|
|
44
|
+
name: 빌드 워크플로우 트리거
|
|
45
|
+
# @suh-lab app build / @suh-lab apk build / @suh-lab ios build 키워드 감지 (어순 무관)
|
|
46
|
+
if: |
|
|
47
|
+
contains(github.event.comment.body, '@suh-lab') &&
|
|
48
|
+
(
|
|
49
|
+
(contains(github.event.comment.body, 'build') && contains(github.event.comment.body, 'app')) ||
|
|
50
|
+
(contains(github.event.comment.body, 'apk') && contains(github.event.comment.body, 'build')) ||
|
|
51
|
+
(contains(github.event.comment.body, 'ios') && contains(github.event.comment.body, 'build'))
|
|
52
|
+
)
|
|
53
|
+
runs-on: ubuntu-latest
|
|
54
|
+
|
|
55
|
+
steps:
|
|
56
|
+
- name: 댓글에 👀 리액션 추가
|
|
57
|
+
uses: actions/github-script@v7
|
|
58
|
+
with:
|
|
59
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
60
|
+
script: |
|
|
61
|
+
const commentId = context.payload.comment.id;
|
|
62
|
+
|
|
63
|
+
await github.rest.reactions.createForIssueComment({
|
|
64
|
+
owner: context.repo.owner,
|
|
65
|
+
repo: context.repo.repo,
|
|
66
|
+
comment_id: commentId,
|
|
67
|
+
content: 'eyes'
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
console.log('👀 댓글에 확인 리액션 추가 완료');
|
|
71
|
+
|
|
72
|
+
- name: 빌드 타입 판별
|
|
73
|
+
id: build_type
|
|
74
|
+
uses: actions/github-script@v7
|
|
75
|
+
with:
|
|
76
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
77
|
+
script: |
|
|
78
|
+
// 키워드 판별은 소문자로(대소문자 무관 인식), 브랜치명 캡처는 원본 본문에서(대소문자 보존)
|
|
79
|
+
const comment = context.payload.comment.body.toLowerCase();
|
|
80
|
+
const rawComment = context.payload.comment.body;
|
|
81
|
+
|
|
82
|
+
let buildAndroid = false;
|
|
83
|
+
let buildIos = false;
|
|
84
|
+
let buildType = '';
|
|
85
|
+
let customBranch = '';
|
|
86
|
+
let m;
|
|
87
|
+
|
|
88
|
+
// 명령어 키워드 뒤의 같은 줄 토큰을 옵셔널 브랜치 인자로 캡처
|
|
89
|
+
// (줄바꿈 너머 단어가 브랜치로 오인되지 않도록 [^\S\r\n]+ 로 같은 줄 공백만 허용)
|
|
90
|
+
// 브랜치명은 rawComment에서 i 플래그로 다시 추출 → Feature/MyBranch 같은 대문자 브랜치 보존
|
|
91
|
+
//
|
|
92
|
+
// 어순은 양방향 지원: "{타입} build"(app/apk/ios build)와 "build {타입}"(build app 등) 모두 허용.
|
|
93
|
+
// apk가 app으로 오인되지 않도록 app 토큰은 \b 단어경계로 정확히 끊는다(apk·application 제외).
|
|
94
|
+
// apk/ios를 app보다 먼저 검사해, "apk build"가 app 분기로 새지 않게 한다.
|
|
95
|
+
if (comment.match(/@suh-lab[^\S\r\n]+apk[^\S\r\n]+build/) || comment.match(/@suh-lab[^\S\r\n]+build[^\S\r\n]+apk/)) {
|
|
96
|
+
// @suh-lab apk build [브랜치] (또는 build apk) → Android만
|
|
97
|
+
buildAndroid = true;
|
|
98
|
+
buildType = 'apk';
|
|
99
|
+
m = rawComment.match(/@suh-lab[^\S\r\n]+apk[^\S\r\n]+build(?:[^\S\r\n]+(\S+))?/i)
|
|
100
|
+
|| rawComment.match(/@suh-lab[^\S\r\n]+build[^\S\r\n]+apk(?:[^\S\r\n]+(\S+))?/i);
|
|
101
|
+
customBranch = (m && m[1]) || '';
|
|
102
|
+
} else if (comment.match(/@suh-lab[^\S\r\n]+ios[^\S\r\n]+build/) || comment.match(/@suh-lab[^\S\r\n]+build[^\S\r\n]+ios/)) {
|
|
103
|
+
// @suh-lab ios build [브랜치] (또는 build ios) → iOS만
|
|
104
|
+
buildIos = true;
|
|
105
|
+
buildType = 'ios';
|
|
106
|
+
m = rawComment.match(/@suh-lab[^\S\r\n]+ios[^\S\r\n]+build(?:[^\S\r\n]+(\S+))?/i)
|
|
107
|
+
|| rawComment.match(/@suh-lab[^\S\r\n]+build[^\S\r\n]+ios(?:[^\S\r\n]+(\S+))?/i);
|
|
108
|
+
customBranch = (m && m[1]) || '';
|
|
109
|
+
} else if (comment.match(/@suh-lab[^\S\r\n]+app\b[^\S\r\n]+build/) || comment.match(/@suh-lab[^\S\r\n]+build[^\S\r\n]+app\b/)) {
|
|
110
|
+
// @suh-lab app build [브랜치] (또는 build app) → Android + iOS 모두
|
|
111
|
+
buildAndroid = true;
|
|
112
|
+
buildIos = true;
|
|
113
|
+
buildType = 'app';
|
|
114
|
+
m = rawComment.match(/@suh-lab[^\S\r\n]+app\b[^\S\r\n]+build(?:[^\S\r\n]+(\S+))?/i)
|
|
115
|
+
|| rawComment.match(/@suh-lab[^\S\r\n]+build[^\S\r\n]+app\b(?:[^\S\r\n]+(\S+))?/i);
|
|
116
|
+
customBranch = (m && m[1]) || '';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
core.setOutput('buildAndroid', buildAndroid.toString());
|
|
120
|
+
core.setOutput('buildIos', buildIos.toString());
|
|
121
|
+
core.setOutput('buildType', buildType);
|
|
122
|
+
core.setOutput('customBranch', customBranch);
|
|
123
|
+
|
|
124
|
+
console.log(`📱 빌드 타입: ${buildType} (Android=${buildAndroid}, iOS=${buildIos})`);
|
|
125
|
+
if (customBranch) {
|
|
126
|
+
console.log(`🌿 댓글에서 지정된 브랜치: ${customBranch}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
- name: PR/이슈 정보 확인 및 추출
|
|
130
|
+
id: source_info
|
|
131
|
+
uses: actions/github-script@v7
|
|
132
|
+
env:
|
|
133
|
+
CUSTOM_BRANCH: ${{ steps.build_type.outputs.customBranch }}
|
|
134
|
+
with:
|
|
135
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
136
|
+
script: |
|
|
137
|
+
const customBranch = (process.env.CUSTOM_BRANCH || '').trim();
|
|
138
|
+
const issueNumber = context.payload.issue.number;
|
|
139
|
+
let sourceType = '';
|
|
140
|
+
let sourceNumber = '';
|
|
141
|
+
let branchName = '';
|
|
142
|
+
let headSha = '';
|
|
143
|
+
let relatedIssueNumber = '';
|
|
144
|
+
|
|
145
|
+
// 1. 먼저 PR인지 확인
|
|
146
|
+
try {
|
|
147
|
+
const pr = await github.rest.pulls.get({
|
|
148
|
+
owner: context.repo.owner,
|
|
149
|
+
repo: context.repo.repo,
|
|
150
|
+
pull_number: issueNumber
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
sourceType = 'PR';
|
|
154
|
+
sourceNumber = pr.data.number.toString();
|
|
155
|
+
branchName = customBranch || pr.data.head.ref;
|
|
156
|
+
headSha = pr.data.head.sha;
|
|
157
|
+
|
|
158
|
+
if (customBranch) {
|
|
159
|
+
console.log(`🌿 댓글 지정 브랜치 우선 적용: ${customBranch} (PR head=${pr.data.head.ref})`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
console.log(`✅ PR 확인: #${sourceNumber}`);
|
|
163
|
+
console.log(`🌿 브랜치명: ${branchName}`);
|
|
164
|
+
console.log(`📝 커밋 해시: ${headSha}`);
|
|
165
|
+
|
|
166
|
+
// 브랜치명에서 이슈 번호 추출 (#280 형식)
|
|
167
|
+
const issueMatch = branchName.match(/#(\d+)/);
|
|
168
|
+
relatedIssueNumber = issueMatch ? issueMatch[1] : '';
|
|
169
|
+
|
|
170
|
+
if (relatedIssueNumber) {
|
|
171
|
+
console.log(`📌 추출된 이슈 번호: #${relatedIssueNumber}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.log('ℹ️ PR이 아닙니다. 이슈에서 브랜치 정보를 찾습니다...');
|
|
176
|
+
|
|
177
|
+
// 2. PR이 아니면 이슈에서 "Guide by SUH-LAB" 댓글 찾기
|
|
178
|
+
sourceType = 'ISSUE';
|
|
179
|
+
sourceNumber = issueNumber.toString();
|
|
180
|
+
relatedIssueNumber = issueNumber.toString();
|
|
181
|
+
|
|
182
|
+
// 댓글에서 브랜치를 직접 지정한 경우 Guide 댓글 탐색을 건너뛴다
|
|
183
|
+
if (customBranch) {
|
|
184
|
+
branchName = customBranch;
|
|
185
|
+
console.log(`🌿 댓글 지정 브랜치 사용 (Guide 댓글 건너뜀): ${branchName}`);
|
|
186
|
+
console.log(`📌 관련 이슈 번호: #${relatedIssueNumber}`);
|
|
187
|
+
|
|
188
|
+
core.setOutput('found', 'true');
|
|
189
|
+
core.setOutput('sourceType', sourceType);
|
|
190
|
+
core.setOutput('sourceNumber', sourceNumber);
|
|
191
|
+
core.setOutput('branchName', branchName);
|
|
192
|
+
core.setOutput('headSha', headSha);
|
|
193
|
+
core.setOutput('relatedIssueNumber', relatedIssueNumber);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
const comments = await github.rest.issues.listComments({
|
|
199
|
+
owner: context.repo.owner,
|
|
200
|
+
repo: context.repo.repo,
|
|
201
|
+
issue_number: issueNumber,
|
|
202
|
+
per_page: 100
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// "Guide by SUH-LAB" 댓글 찾기
|
|
206
|
+
const guideComment = comments.data.find(c =>
|
|
207
|
+
c.body.includes('Guide by SUH-LAB')
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
if (!guideComment) {
|
|
211
|
+
console.log('❌ "Guide by SUH-LAB" 댓글을 찾을 수 없습니다.');
|
|
212
|
+
core.setOutput('found', 'false');
|
|
213
|
+
core.setOutput('errorMessage', '이슈에서 "Guide by SUH-LAB" 댓글을 찾을 수 없습니다. 브랜치 정보가 포함된 댓글이 필요합니다.');
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log('✅ "Guide by SUH-LAB" 댓글 발견');
|
|
218
|
+
|
|
219
|
+
// 브랜치명 추출 (### 브랜치 다음 ``` 블록 내용)
|
|
220
|
+
const branchMatch = guideComment.body.match(/### 브랜치\s*```\s*([\s\S]*?)\s*```/);
|
|
221
|
+
|
|
222
|
+
if (!branchMatch) {
|
|
223
|
+
console.log('❌ 브랜치 정보를 파싱할 수 없습니다.');
|
|
224
|
+
core.setOutput('found', 'false');
|
|
225
|
+
core.setOutput('errorMessage', '"Guide by SUH-LAB" 댓글에서 브랜치 정보를 파싱할 수 없습니다. 댓글 형식을 확인해주세요.');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
branchName = branchMatch[1].trim();
|
|
230
|
+
console.log(`🌿 추출된 브랜치명: ${branchName}`);
|
|
231
|
+
console.log(`✅ 이슈 #${sourceNumber}에서 빌드 정보 추출 완료`);
|
|
232
|
+
|
|
233
|
+
} catch (commentError) {
|
|
234
|
+
console.log('❌ 이슈 댓글 조회 중 오류 발생:', commentError.message);
|
|
235
|
+
core.setOutput('found', 'false');
|
|
236
|
+
core.setOutput('errorMessage', `이슈 댓글 조회 중 오류가 발생했습니다: ${commentError.message}`);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 결과 출력
|
|
242
|
+
core.setOutput('found', 'true');
|
|
243
|
+
core.setOutput('sourceType', sourceType);
|
|
244
|
+
core.setOutput('sourceNumber', sourceNumber);
|
|
245
|
+
core.setOutput('branchName', branchName);
|
|
246
|
+
core.setOutput('headSha', headSha);
|
|
247
|
+
core.setOutput('relatedIssueNumber', relatedIssueNumber);
|
|
248
|
+
|
|
249
|
+
- name: 브랜치 정보를 찾을 수 없는 경우 에러 댓글 작성
|
|
250
|
+
if: steps.source_info.outputs.found != 'true'
|
|
251
|
+
uses: actions/github-script@v7
|
|
252
|
+
with:
|
|
253
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
254
|
+
script: |
|
|
255
|
+
const issueNumber = context.payload.issue.number;
|
|
256
|
+
const errorMessage = '${{ steps.source_info.outputs.errorMessage }}';
|
|
257
|
+
|
|
258
|
+
const body = `❌ **앱 빌드 트리거 실패**
|
|
259
|
+
|
|
260
|
+
${errorMessage}
|
|
261
|
+
|
|
262
|
+
**해결 방법:**
|
|
263
|
+
- PR에서 빌드하는 경우: PR 페이지에서 댓글을 작성해주세요.
|
|
264
|
+
- 이슈에서 빌드하는 경우: "Guide by SUH-LAB" 댓글이 있어야 합니다.
|
|
265
|
+
- 댓글에 \`### 브랜치\` 섹션과 브랜치명이 포함되어 있어야 합니다.
|
|
266
|
+
- 또는 댓글에 브랜치명을 직접 지정할 수 있습니다: \`@suh-lab app build <브랜치명>\``;
|
|
267
|
+
|
|
268
|
+
await github.rest.issues.createComment({
|
|
269
|
+
owner: context.repo.owner,
|
|
270
|
+
repo: context.repo.repo,
|
|
271
|
+
issue_number: issueNumber,
|
|
272
|
+
body: body
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
core.setFailed(errorMessage);
|
|
276
|
+
|
|
277
|
+
- name: 브랜치 존재 여부 확인
|
|
278
|
+
id: check_branch
|
|
279
|
+
if: steps.source_info.outputs.found == 'true'
|
|
280
|
+
uses: actions/github-script@v7
|
|
281
|
+
with:
|
|
282
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
283
|
+
script: |
|
|
284
|
+
const branchName = '${{ steps.source_info.outputs.branchName }}';
|
|
285
|
+
const runId = '${{ github.run_id }}';
|
|
286
|
+
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`;
|
|
287
|
+
|
|
288
|
+
console.log(`🔍 브랜치 존재 여부 확인: ${branchName}`);
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
await github.rest.repos.getBranch({
|
|
292
|
+
owner: context.repo.owner,
|
|
293
|
+
repo: context.repo.repo,
|
|
294
|
+
branch: branchName
|
|
295
|
+
});
|
|
296
|
+
console.log(`✅ 브랜치 존재 확인됨: ${branchName}`);
|
|
297
|
+
core.setOutput('exists', 'true');
|
|
298
|
+
} catch (error) {
|
|
299
|
+
if (error.status === 404) {
|
|
300
|
+
console.log(`❌ 브랜치를 찾을 수 없음: ${branchName}`);
|
|
301
|
+
core.setOutput('exists', 'false');
|
|
302
|
+
core.setOutput('errorMessage', `브랜치 \`${branchName}\`를 찾을 수 없습니다.`);
|
|
303
|
+
} else {
|
|
304
|
+
console.log(`❌ 브랜치 확인 중 오류: ${error.message}`);
|
|
305
|
+
core.setOutput('exists', 'false');
|
|
306
|
+
core.setOutput('errorMessage', `브랜치 확인 중 오류가 발생했습니다: ${error.message}`);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
- name: 브랜치가 존재하지 않는 경우 에러 댓글 작성
|
|
311
|
+
if: steps.source_info.outputs.found == 'true' && steps.check_branch.outputs.exists == 'false'
|
|
312
|
+
uses: actions/github-script@v7
|
|
313
|
+
with:
|
|
314
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
315
|
+
script: |
|
|
316
|
+
const issueNumber = context.payload.issue.number;
|
|
317
|
+
const branchName = '${{ steps.source_info.outputs.branchName }}';
|
|
318
|
+
const runId = '${{ github.run_id }}';
|
|
319
|
+
const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}`;
|
|
320
|
+
|
|
321
|
+
const body = [
|
|
322
|
+
'❌ **앱 빌드 트리거 실패 - 브랜치를 찾을 수 없습니다**',
|
|
323
|
+
'',
|
|
324
|
+
'| 항목 | 값 |',
|
|
325
|
+
'|------|-----|',
|
|
326
|
+
`| **요청된 브랜치** | \`${branchName}\` |`,
|
|
327
|
+
`| **이슈/PR** | #${issueNumber} |`,
|
|
328
|
+
'',
|
|
329
|
+
'### 💡 확인 사항',
|
|
330
|
+
'1. 브랜치가 원격 저장소에 push되었는지 확인하세요',
|
|
331
|
+
'2. "Guide by SUH-LAB" 댓글의 브랜치명이 올바른지 확인하세요',
|
|
332
|
+
'3. 브랜치명에 오타가 없는지 확인하세요',
|
|
333
|
+
'',
|
|
334
|
+
`🔗 [워크플로우 로그](${runUrl})`
|
|
335
|
+
].join('\n');
|
|
336
|
+
|
|
337
|
+
await github.rest.issues.createComment({
|
|
338
|
+
owner: context.repo.owner,
|
|
339
|
+
repo: context.repo.repo,
|
|
340
|
+
issue_number: issueNumber,
|
|
341
|
+
body: body
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
core.setFailed(`브랜치를 찾을 수 없습니다: ${branchName}`);
|
|
345
|
+
|
|
346
|
+
- name: 빌드 횟수 조회 및 빌드 번호 생성
|
|
347
|
+
id: build_number_calc
|
|
348
|
+
if: steps.source_info.outputs.found == 'true' && steps.check_branch.outputs.exists == 'true'
|
|
349
|
+
uses: actions/github-script@v7
|
|
350
|
+
with:
|
|
351
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
352
|
+
script: |
|
|
353
|
+
const sourceNumber = parseInt('${{ steps.source_info.outputs.sourceNumber }}');
|
|
354
|
+
const sourceType = '${{ steps.source_info.outputs.sourceType }}';
|
|
355
|
+
const buildType = '${{ steps.build_type.outputs.buildType }}';
|
|
356
|
+
|
|
357
|
+
console.log(`📊 ${sourceType} #${sourceNumber}의 빌드 횟수 조회 중...`);
|
|
358
|
+
console.log(`📱 빌드 타입: ${buildType}`);
|
|
359
|
+
|
|
360
|
+
// PR/이슈의 모든 댓글 조회
|
|
361
|
+
const comments = await github.rest.issues.listComments({
|
|
362
|
+
owner: context.repo.owner,
|
|
363
|
+
repo: context.repo.repo,
|
|
364
|
+
issue_number: sourceNumber,
|
|
365
|
+
per_page: 100
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// 빌드 타입에 따라 해당 플랫폼의 빌드 횟수 카운트
|
|
369
|
+
// 어순 무관 카운트: includes('build') && includes('{타입}') 로 "ios build"·"build ios" 모두 잡는다.
|
|
370
|
+
// iOS: ios 빌드 + app(공용) 빌드 통합 (App Store Connect는 앱 전체에서 빌드 번호 unique)
|
|
371
|
+
// Android: apk 빌드 + app(공용) 빌드 통합 (Play Store도 동일)
|
|
372
|
+
// App: 모든 빌드 명령어 통합 카운트
|
|
373
|
+
let buildCount = 0;
|
|
374
|
+
|
|
375
|
+
if (buildType === 'ios') {
|
|
376
|
+
// iOS 빌드: ios 빌드 + app 공용 빌드 둘 다 카운트 (어순 무관)
|
|
377
|
+
buildCount = comments.data.filter(c => {
|
|
378
|
+
const body = c.body.toLowerCase();
|
|
379
|
+
return body.includes('@suh-lab') && body.includes('build') && (
|
|
380
|
+
body.includes('ios') || body.includes('app')
|
|
381
|
+
);
|
|
382
|
+
}).length;
|
|
383
|
+
console.log(`📊 iOS 빌드 카운트: ios + app(공용) 통합`);
|
|
384
|
+
|
|
385
|
+
} else if (buildType === 'apk') {
|
|
386
|
+
// Android 빌드: apk 빌드 + app 공용 빌드 둘 다 카운트 (어순 무관)
|
|
387
|
+
buildCount = comments.data.filter(c => {
|
|
388
|
+
const body = c.body.toLowerCase();
|
|
389
|
+
return body.includes('@suh-lab') && body.includes('build') && (
|
|
390
|
+
body.includes('apk') || body.includes('app')
|
|
391
|
+
);
|
|
392
|
+
}).length;
|
|
393
|
+
console.log(`📊 Android 빌드 카운트: apk + app(공용) 통합`);
|
|
394
|
+
|
|
395
|
+
} else if (buildType === 'app') {
|
|
396
|
+
// 전체 앱 빌드: 모든 빌드 명령어 카운트 (어순 무관)
|
|
397
|
+
buildCount = comments.data.filter(c => {
|
|
398
|
+
const body = c.body.toLowerCase();
|
|
399
|
+
return body.includes('@suh-lab') && body.includes('build') && (
|
|
400
|
+
body.includes('ios') ||
|
|
401
|
+
body.includes('apk') ||
|
|
402
|
+
body.includes('app')
|
|
403
|
+
);
|
|
404
|
+
}).length;
|
|
405
|
+
console.log(`📊 전체 앱 빌드 카운트: 모든 빌드 명령어 통합`);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// 빌드 번호: 소스번호 + 2자리 카운트 (38800, 38801, 38802...)
|
|
409
|
+
const buildNumber = `${sourceNumber}${String(buildCount).padStart(2, '0')}`;
|
|
410
|
+
|
|
411
|
+
console.log(`📊 ${sourceType} #${sourceNumber}: ${buildType} ${buildCount}번째 빌드`);
|
|
412
|
+
console.log(`📦 생성된 빌드 번호: ${buildNumber}`);
|
|
413
|
+
|
|
414
|
+
core.setOutput('buildCount', buildCount.toString());
|
|
415
|
+
core.setOutput('buildNumber', buildNumber);
|
|
416
|
+
|
|
417
|
+
- name: Android 빌드 워크플로우 트리거
|
|
418
|
+
if: |
|
|
419
|
+
steps.source_info.outputs.found == 'true' &&
|
|
420
|
+
steps.check_branch.outputs.exists == 'true' &&
|
|
421
|
+
steps.build_type.outputs.buildAndroid == 'true'
|
|
422
|
+
uses: actions/github-script@v7
|
|
423
|
+
with:
|
|
424
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
425
|
+
script: |
|
|
426
|
+
const sourceType = '${{ steps.source_info.outputs.sourceType }}';
|
|
427
|
+
const sourceNumber = '${{ steps.source_info.outputs.sourceNumber }}';
|
|
428
|
+
const branchName = '${{ steps.source_info.outputs.branchName }}';
|
|
429
|
+
const relatedIssueNumber = '${{ steps.source_info.outputs.relatedIssueNumber }}';
|
|
430
|
+
const buildNumber = '${{ steps.build_number_calc.outputs.buildNumber }}';
|
|
431
|
+
|
|
432
|
+
console.log(`🚀 Android 빌드 워크플로우 트리거 시작...`);
|
|
433
|
+
console.log(` ${sourceType} 번호: #${sourceNumber}`);
|
|
434
|
+
console.log(` 빌드 번호: ${buildNumber}`);
|
|
435
|
+
console.log(` 브랜치: ${branchName}`);
|
|
436
|
+
console.log(` 관련 이슈 번호: ${relatedIssueNumber || '없음'}`);
|
|
437
|
+
|
|
438
|
+
await github.rest.repos.createDispatchEvent({
|
|
439
|
+
owner: context.repo.owner,
|
|
440
|
+
repo: context.repo.repo,
|
|
441
|
+
event_type: 'build-android-app',
|
|
442
|
+
client_payload: {
|
|
443
|
+
pr_number: sourceNumber,
|
|
444
|
+
build_number: buildNumber,
|
|
445
|
+
branch_name: branchName,
|
|
446
|
+
issue_number: relatedIssueNumber || '',
|
|
447
|
+
triggered_by: `${sourceType.toLowerCase()}-comment`,
|
|
448
|
+
comment_id: context.payload.comment.id.toString(),
|
|
449
|
+
source_type: sourceType
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
console.log('✅ Android 빌드 워크플로우 트리거 완료');
|
|
454
|
+
|
|
455
|
+
- name: iOS 빌드 워크플로우 트리거
|
|
456
|
+
if: |
|
|
457
|
+
steps.source_info.outputs.found == 'true' &&
|
|
458
|
+
steps.check_branch.outputs.exists == 'true' &&
|
|
459
|
+
steps.build_type.outputs.buildIos == 'true'
|
|
460
|
+
uses: actions/github-script@v7
|
|
461
|
+
with:
|
|
462
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
463
|
+
script: |
|
|
464
|
+
const sourceType = '${{ steps.source_info.outputs.sourceType }}';
|
|
465
|
+
const sourceNumber = '${{ steps.source_info.outputs.sourceNumber }}';
|
|
466
|
+
const branchName = '${{ steps.source_info.outputs.branchName }}';
|
|
467
|
+
const relatedIssueNumber = '${{ steps.source_info.outputs.relatedIssueNumber }}';
|
|
468
|
+
const buildNumber = '${{ steps.build_number_calc.outputs.buildNumber }}';
|
|
469
|
+
|
|
470
|
+
console.log(`🚀 iOS 빌드 워크플로우 트리거 시작...`);
|
|
471
|
+
console.log(` ${sourceType} 번호: #${sourceNumber}`);
|
|
472
|
+
console.log(` 빌드 번호: ${buildNumber}`);
|
|
473
|
+
console.log(` 브랜치: ${branchName}`);
|
|
474
|
+
console.log(` 관련 이슈 번호: ${relatedIssueNumber || '없음'}`);
|
|
475
|
+
|
|
476
|
+
await github.rest.repos.createDispatchEvent({
|
|
477
|
+
owner: context.repo.owner,
|
|
478
|
+
repo: context.repo.repo,
|
|
479
|
+
event_type: 'build-ios-app',
|
|
480
|
+
client_payload: {
|
|
481
|
+
pr_number: sourceNumber,
|
|
482
|
+
build_number: buildNumber,
|
|
483
|
+
branch_name: branchName,
|
|
484
|
+
issue_number: relatedIssueNumber || '',
|
|
485
|
+
triggered_by: `${sourceType.toLowerCase()}-comment`,
|
|
486
|
+
comment_id: context.payload.comment.id.toString(),
|
|
487
|
+
source_type: sourceType
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
console.log('✅ iOS 빌드 워크플로우 트리거 완료');
|
|
492
|
+
|
|
493
|
+
# 트리거 완료 댓글은 제거됨 - Android/iOS 각각의 진행상황 댓글이 이 역할을 대체
|
|
494
|
+
- name: 트리거 완료 로그
|
|
495
|
+
if: steps.source_info.outputs.found == 'true' && steps.check_branch.outputs.exists == 'true'
|
|
496
|
+
run: |
|
|
497
|
+
echo "✅ Android 및 iOS 빌드 워크플로우 트리거 완료"
|
|
498
|
+
echo "📦 빌드 번호: ${{ steps.build_number_calc.outputs.buildNumber }}"
|
|
499
|
+
echo "🌿 브랜치: ${{ steps.source_info.outputs.branchName }}"
|
|
500
|
+
echo "📋 각 플랫폼별 진행상황 댓글이 자동으로 생성됩니다."
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# ===================================================================
|
|
2
|
+
# Next.js CI (코드 분석 및 빌드 검증)
|
|
3
|
+
# ===================================================================
|
|
4
|
+
#
|
|
5
|
+
# {{MAIN_BRANCH}} 브랜치 푸시 시 Next.js 애플리케이션의 빌드를 검증합니다.
|
|
6
|
+
#
|
|
7
|
+
# 주요 특징:
|
|
8
|
+
# - npm ci로 의존성 설치
|
|
9
|
+
# - npm run build로 빌드 검증
|
|
10
|
+
# - Next.js 증분 빌드 캐싱 (.next/cache)
|
|
11
|
+
# - node_modules 캐싱 지원
|
|
12
|
+
#
|
|
13
|
+
# ===================================================================
|
|
14
|
+
# 🔑 필수 GitHub Secrets
|
|
15
|
+
# ===================================================================
|
|
16
|
+
# ENV_FILE (선택): .env 파일 내용
|
|
17
|
+
#
|
|
18
|
+
# ※ CI는 빌드 검증 목적이므로 배포 관련 Secrets 불필요
|
|
19
|
+
# ===================================================================
|
|
20
|
+
#
|
|
21
|
+
# 🔧 프로젝트별 설정 (env 섹션)
|
|
22
|
+
# ===================================================================
|
|
23
|
+
# PROJECT_NAME : 프로젝트 이름
|
|
24
|
+
# NODE_VERSION : Node.js 버전 (기본: 20.15.0)
|
|
25
|
+
# ENV_FILE : 환경변수 파일명 (기본: .env)
|
|
26
|
+
# ===================================================================
|
|
27
|
+
|
|
28
|
+
name: PROJECT-NEXT-CI
|
|
29
|
+
|
|
30
|
+
on:
|
|
31
|
+
push:
|
|
32
|
+
branches: ["{{DEVELOP_BRANCH}}"]
|
|
33
|
+
workflow_dispatch:
|
|
34
|
+
|
|
35
|
+
env:
|
|
36
|
+
PROJECT_NAME: "__PROJECT_NAME__" # @wizard ask:@repo
|
|
37
|
+
NODE_VERSION: "20.15.0"
|
|
38
|
+
ENV_FILE: ".env"
|
|
39
|
+
|
|
40
|
+
permissions:
|
|
41
|
+
contents: read
|
|
42
|
+
pull-requests: write
|
|
43
|
+
issues: write
|
|
44
|
+
|
|
45
|
+
jobs:
|
|
46
|
+
build:
|
|
47
|
+
name: Next.js 애플리케이션 빌드
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
|
|
50
|
+
steps:
|
|
51
|
+
# 1. 소스코드 체크아웃
|
|
52
|
+
- name: 코드 체크아웃
|
|
53
|
+
uses: actions/checkout@v5
|
|
54
|
+
|
|
55
|
+
# 2. 환경변수 파일 생성
|
|
56
|
+
- name: 환경변수 파일 생성 (${{ env.ENV_FILE }})
|
|
57
|
+
run: |
|
|
58
|
+
echo -e "${{ secrets.ENV_FILE || secrets.ENV }}" > ${{ env.ENV_FILE }}
|
|
59
|
+
echo "✅ ${{ env.ENV_FILE }} 파일 생성 완료"
|
|
60
|
+
|
|
61
|
+
# 3. Node.js 설정
|
|
62
|
+
- name: Node.js 설정
|
|
63
|
+
uses: actions/setup-node@v4
|
|
64
|
+
with:
|
|
65
|
+
node-version: ${{ env.NODE_VERSION }}
|
|
66
|
+
cache: "npm"
|
|
67
|
+
|
|
68
|
+
# 4. npm 의존성 캐싱
|
|
69
|
+
- name: npm 의존성 캐싱
|
|
70
|
+
uses: actions/cache@v4
|
|
71
|
+
with:
|
|
72
|
+
path: ~/.npm
|
|
73
|
+
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
74
|
+
restore-keys: |
|
|
75
|
+
${{ runner.os }}-npm-
|
|
76
|
+
|
|
77
|
+
# 5. node_modules 캐싱
|
|
78
|
+
- name: node_modules 캐싱
|
|
79
|
+
id: node-modules-cache
|
|
80
|
+
uses: actions/cache@v4
|
|
81
|
+
with:
|
|
82
|
+
path: "**/node_modules"
|
|
83
|
+
key: ${{ runner.os }}-node-modules-${{ hashFiles('**/package-lock.json') }}
|
|
84
|
+
restore-keys: |
|
|
85
|
+
${{ runner.os }}-node-modules-
|
|
86
|
+
|
|
87
|
+
# 6. 의존성 설치
|
|
88
|
+
- name: 의존성 설치
|
|
89
|
+
if: steps.node-modules-cache.outputs.cache-hit != 'true'
|
|
90
|
+
run: |
|
|
91
|
+
echo "📦 의존성 설치 중..."
|
|
92
|
+
npm ci
|
|
93
|
+
echo "✅ 의존성 설치 완료"
|
|
94
|
+
|
|
95
|
+
# 7. Next.js 빌드 캐싱 (.next/cache)
|
|
96
|
+
# 증분 빌드를 위해 .next/cache 폴더를 캐싱합니다
|
|
97
|
+
- name: Next.js 빌드 캐싱
|
|
98
|
+
uses: actions/cache@v4
|
|
99
|
+
with:
|
|
100
|
+
path: .next/cache
|
|
101
|
+
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
|
|
102
|
+
restore-keys: |
|
|
103
|
+
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
|
|
104
|
+
${{ runner.os }}-nextjs-
|
|
105
|
+
|
|
106
|
+
# 8. 프로젝트 빌드 (SSR/SSG)
|
|
107
|
+
- name: 프로젝트 빌드
|
|
108
|
+
id: build
|
|
109
|
+
run: |
|
|
110
|
+
echo "🔨 Next.js 빌드 시작..."
|
|
111
|
+
echo "프로젝트: ${{ env.PROJECT_NAME }}"
|
|
112
|
+
echo "Node.js: ${{ env.NODE_VERSION }}"
|
|
113
|
+
echo ""
|
|
114
|
+
|
|
115
|
+
# 빌드 실행 및 로그 저장
|
|
116
|
+
npm run build > build_log.txt 2>&1 || {
|
|
117
|
+
echo "build_failed=true" >> $GITHUB_OUTPUT
|
|
118
|
+
echo "status=failed" >> $GITHUB_OUTPUT
|
|
119
|
+
echo ""
|
|
120
|
+
echo "❌ 빌드 실패"
|
|
121
|
+
exit 0
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
echo "build_failed=false" >> $GITHUB_OUTPUT
|
|
125
|
+
echo "status=success" >> $GITHUB_OUTPUT
|
|
126
|
+
echo "✅ 빌드 완료"
|
|
127
|
+
|
|
128
|
+
# 빌드 결과 확인
|
|
129
|
+
if [ -d ".next" ]; then
|
|
130
|
+
echo ""
|
|
131
|
+
echo "📁 빌드 결과물:"
|
|
132
|
+
echo " - .next/ 폴더 생성됨"
|
|
133
|
+
du -sh .next/ || true
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
# 9. 빌드 실패 시 에러 로그 출력
|
|
137
|
+
- name: 빌드 에러 로그 출력
|
|
138
|
+
if: steps.build.outputs.build_failed == 'true'
|
|
139
|
+
run: |
|
|
140
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
141
|
+
echo "❌ 빌드 실패 - 에러 로그"
|
|
142
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
143
|
+
echo ""
|
|
144
|
+
|
|
145
|
+
if [ -f "build_log.txt" ]; then
|
|
146
|
+
tail -n 50 build_log.txt
|
|
147
|
+
else
|
|
148
|
+
echo "에러 로그 파일을 찾을 수 없습니다."
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
echo ""
|
|
152
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
153
|
+
exit 1
|
|
154
|
+
|
|
155
|
+
# 10. 빌드 로그 아티팩트 업로드
|
|
156
|
+
- name: 빌드 로그 업로드
|
|
157
|
+
if: always()
|
|
158
|
+
uses: actions/upload-artifact@v4
|
|
159
|
+
with:
|
|
160
|
+
name: build-log
|
|
161
|
+
path: build_log.txt
|
|
162
|
+
retention-days: 7
|
|
163
|
+
|
|
164
|
+
# 11. 빌드 결과 요약
|
|
165
|
+
- name: 빌드 결과 요약
|
|
166
|
+
if: always()
|
|
167
|
+
run: |
|
|
168
|
+
echo ""
|
|
169
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
170
|
+
if [ "${{ steps.build.outputs.status }}" = "success" ]; then
|
|
171
|
+
echo "🎉 Next.js 빌드 완료!"
|
|
172
|
+
else
|
|
173
|
+
echo "❌ Next.js 빌드 실패"
|
|
174
|
+
fi
|
|
175
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
176
|
+
echo ""
|
|
177
|
+
echo "📊 빌드 결과:"
|
|
178
|
+
echo " 📦 프로젝트: ${{ env.PROJECT_NAME }}"
|
|
179
|
+
echo " 📌 Node.js 버전: ${{ env.NODE_VERSION }}"
|
|
180
|
+
echo " 🆙 빌드 상태: ${{ steps.build.outputs.status }}"
|
|
181
|
+
echo " 🌿 브랜치: ${{ github.ref_name }}"
|
|
182
|
+
echo " 📝 커밋: ${{ github.sha }}"
|
|
183
|
+
echo " 🕐 시간: $(date '+%Y-%m-%d %H:%M:%S UTC')"
|
|
184
|
+
echo ""
|
|
185
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|