aiag-cli 2.12.0 → 2.13.0
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/README.md +9 -0
- package/dist/api/client.d.ts +26 -0
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +34 -1
- package/dist/api/client.js.map +1 -1
- package/dist/api/endpoints.d.ts +7 -0
- package/dist/api/endpoints.d.ts.map +1 -1
- package/dist/api/endpoints.js +7 -0
- package/dist/api/endpoints.js.map +1 -1
- package/dist/cli.js +4 -10
- package/dist/cli.js.map +1 -1
- package/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +113 -1
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/sync.d.ts +20 -26
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +281 -545
- package/dist/commands/sync.js.map +1 -1
- package/dist/utils/git.d.ts +9 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +13 -0
- package/dist/utils/git.js.map +1 -1
- package/package.json +1 -1
package/dist/commands/sync.js
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* sync 명령어
|
|
2
|
+
* sync 명령어 (GitHub 중심)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* GitHub를 Single Source of Truth로 두고 자동으로 동기화합니다.
|
|
5
5
|
*
|
|
6
6
|
* 사용법:
|
|
7
|
-
* aiag sync
|
|
8
|
-
* aiag sync --
|
|
9
|
-
* aiag sync --
|
|
10
|
-
* aiag sync --watch
|
|
11
|
-
* aiag sync --status 동기화 상태 확인
|
|
7
|
+
* aiag sync Git 상태 감지 후 자동 pull/push
|
|
8
|
+
* aiag sync --status 동기화 상태 확인
|
|
9
|
+
* aiag sync --reset GitHub 상태로 로컬 초기화 (원격 우선)
|
|
10
|
+
* aiag sync --watch 실시간 이벤트 스트리밍
|
|
12
11
|
*
|
|
13
|
-
*
|
|
14
|
-
* -
|
|
15
|
-
* -
|
|
16
|
-
* -
|
|
12
|
+
* 동작 방식:
|
|
13
|
+
* - up-to-date: "이미 최신 상태"
|
|
14
|
+
* - behind: git pull 자동 실행
|
|
15
|
+
* - ahead: git push 자동 실행
|
|
16
|
+
* - diverged: 경고 메시지 + --reset 권장
|
|
17
17
|
*/
|
|
18
18
|
import chalk from 'chalk';
|
|
19
19
|
import ora from 'ora';
|
|
20
|
-
import { isLoggedIn } from '../auth/credentials.js';
|
|
21
|
-
import { isConnected, loadConnection, updateLastSyncAt, isProjectInitialized,
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { getApiClient, ApiError, NetworkError } from '../api/client.js';
|
|
25
|
-
import { printError, printWarning, printInfo } from '../utils/output.js';
|
|
20
|
+
import { isLoggedIn, loadCredentials } from '../auth/credentials.js';
|
|
21
|
+
import { isConnected, loadConnection, updateLastSyncAt, isProjectInitialized, } from '../utils/connection.js';
|
|
22
|
+
import { getApiClient } from '../api/client.js';
|
|
23
|
+
import { printError, printSuccess, printWarning, printInfo } from '../utils/output.js';
|
|
26
24
|
import { messages } from '../utils/messages.js';
|
|
27
25
|
import { SSEClient, formatSSEEvent } from '../utils/sseClient.js';
|
|
28
|
-
import { getRemoteSyncStatus, gitPull, gitPush } from '../utils/git.js';
|
|
26
|
+
import { getRemoteSyncStatus, gitPull, gitPush, gitFetch, gitResetHard, } from '../utils/git.js';
|
|
27
|
+
import { readProgress } from '../utils/progress.js';
|
|
28
|
+
import { getProgress } from '../utils/featureList.js';
|
|
29
29
|
// ============================================
|
|
30
30
|
// 메인 명령어 함수
|
|
31
31
|
// ============================================
|
|
@@ -33,7 +33,7 @@ import { getRemoteSyncStatus, gitPull, gitPush } from '../utils/git.js';
|
|
|
33
33
|
* sync 명령어 실행
|
|
34
34
|
*/
|
|
35
35
|
export async function sync(options) {
|
|
36
|
-
// 1.
|
|
36
|
+
// 1. 상태 확인
|
|
37
37
|
if (options.status) {
|
|
38
38
|
await handleSyncStatus();
|
|
39
39
|
return;
|
|
@@ -43,240 +43,165 @@ export async function sync(options) {
|
|
|
43
43
|
await handleWatch();
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
|
-
// 3.
|
|
47
|
-
if (
|
|
46
|
+
// 3. GitHub 상태로 초기화
|
|
47
|
+
if (options.reset) {
|
|
48
|
+
await handleReset();
|
|
48
49
|
return;
|
|
49
50
|
}
|
|
50
|
-
// 4.
|
|
51
|
-
|
|
52
|
-
const gitHandled = await handleGitSync(options);
|
|
53
|
-
if (!gitHandled) {
|
|
54
|
-
// Git 동기화 실패 또는 사용자가 중단
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
// 5. 동기화 방향에 따른 처리
|
|
59
|
-
if (options.pull) {
|
|
60
|
-
await handlePullSync(options);
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
await handlePushSync(options);
|
|
64
|
-
}
|
|
51
|
+
// 4. 자동 동기화 (기본 동작)
|
|
52
|
+
await handleAutoSync();
|
|
65
53
|
}
|
|
66
54
|
// ============================================
|
|
67
|
-
//
|
|
55
|
+
// 자동 동기화 (기본 동작)
|
|
68
56
|
// ============================================
|
|
69
57
|
/**
|
|
70
|
-
*
|
|
58
|
+
* Git 상태를 감지하여 자동으로 pull/push 수행
|
|
71
59
|
*/
|
|
72
|
-
function
|
|
60
|
+
async function handleAutoSync() {
|
|
73
61
|
// 프로젝트 초기화 확인
|
|
74
62
|
if (!isProjectInitialized()) {
|
|
75
63
|
printError(messages.common.notInitialized);
|
|
76
64
|
console.log(chalk.gray(messages.common.runInitFirst));
|
|
77
65
|
process.exitCode = 1;
|
|
78
|
-
return
|
|
66
|
+
return;
|
|
79
67
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
68
|
+
const spinner = ora('Git 상태 확인 중...').start();
|
|
69
|
+
try {
|
|
70
|
+
// Git fetch로 원격 상태 동기화
|
|
71
|
+
await gitFetch();
|
|
72
|
+
const status = await getRemoteSyncStatus();
|
|
73
|
+
spinner.stop();
|
|
74
|
+
// 상태 출력
|
|
75
|
+
printGitSyncStatus(status);
|
|
76
|
+
switch (status.status) {
|
|
77
|
+
case 'no-remote':
|
|
78
|
+
printInfo('원격 저장소가 설정되지 않았습니다.');
|
|
79
|
+
console.log(chalk.gray(' git remote add origin <url> 로 원격 저장소를 설정하세요.'));
|
|
80
|
+
break;
|
|
81
|
+
case 'no-upstream':
|
|
82
|
+
printInfo('원격 브랜치 트래킹이 설정되지 않았습니다.');
|
|
83
|
+
console.log(chalk.gray(' git push -u origin ' + (status.branch || 'main') + ' 로 설정하세요.'));
|
|
84
|
+
break;
|
|
85
|
+
case 'up-to-date':
|
|
86
|
+
printSuccess('이미 최신 상태입니다.');
|
|
87
|
+
await notifyServerCache();
|
|
88
|
+
break;
|
|
89
|
+
case 'behind':
|
|
90
|
+
await performGitPull(status);
|
|
91
|
+
break;
|
|
92
|
+
case 'ahead':
|
|
93
|
+
await performGitPush(status);
|
|
94
|
+
break;
|
|
95
|
+
case 'diverged':
|
|
96
|
+
printDivergedWarning(status);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
86
99
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
catch (error) {
|
|
101
|
+
spinner.stop();
|
|
102
|
+
if (error instanceof Error) {
|
|
103
|
+
printError(`Git 동기화 실패: ${error.message}`);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
printError('Git 동기화 중 오류가 발생했습니다.');
|
|
107
|
+
}
|
|
91
108
|
process.exitCode = 1;
|
|
92
|
-
return false;
|
|
93
109
|
}
|
|
94
|
-
return true;
|
|
95
110
|
}
|
|
96
111
|
// ============================================
|
|
97
|
-
//
|
|
112
|
+
// Reset 처리 (GitHub 상태로 초기화)
|
|
98
113
|
// ============================================
|
|
99
114
|
/**
|
|
100
|
-
* 로컬
|
|
115
|
+
* GitHub 상태로 로컬 초기화 (--reset)
|
|
101
116
|
*/
|
|
102
|
-
async function
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
printError('feature_list.json을 읽을 수 없습니다.');
|
|
117
|
+
async function handleReset() {
|
|
118
|
+
if (!isProjectInitialized()) {
|
|
119
|
+
printError(messages.common.notInitialized);
|
|
120
|
+
console.log(chalk.gray(messages.common.runInitFirst));
|
|
107
121
|
process.exitCode = 1;
|
|
108
122
|
return;
|
|
109
123
|
}
|
|
110
|
-
const spinner = ora('
|
|
124
|
+
const spinner = ora('GitHub 상태 확인 중...').start();
|
|
111
125
|
try {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
126
|
+
// Git fetch로 원격 상태 동기화
|
|
127
|
+
await gitFetch();
|
|
128
|
+
const status = await getRemoteSyncStatus();
|
|
129
|
+
if (status.status === 'no-remote') {
|
|
130
|
+
spinner.fail('원격 저장소가 설정되지 않았습니다.');
|
|
116
131
|
process.exitCode = 1;
|
|
117
132
|
return;
|
|
118
133
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
spinner.stop();
|
|
123
|
-
printDryRunResult(syncData);
|
|
134
|
+
if (status.status === 'no-upstream') {
|
|
135
|
+
spinner.fail('원격 브랜치 트래킹이 설정되지 않았습니다.');
|
|
136
|
+
process.exitCode = 1;
|
|
124
137
|
return;
|
|
125
138
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// 결과 출력
|
|
134
|
-
printSyncResult(response, 'push');
|
|
135
|
-
// 충돌 처리
|
|
136
|
-
if (response.conflicts.length > 0 && !options.force) {
|
|
137
|
-
printConflicts(response.conflicts);
|
|
139
|
+
const branch = status.branch || 'main';
|
|
140
|
+
spinner.text = `GitHub 상태로 초기화 중... (origin/${branch})`;
|
|
141
|
+
// git reset --hard origin/{branch}
|
|
142
|
+
const result = await gitResetHard(`origin/${branch}`);
|
|
143
|
+
if (result.success) {
|
|
144
|
+
spinner.succeed(`GitHub 상태로 초기화 완료 (origin/${branch})`);
|
|
145
|
+
await notifyServerCache();
|
|
138
146
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
await syncProgressToServer(client, connection.projectId, spinner);
|
|
145
|
-
// 마지막 동기화 시간 업데이트
|
|
146
|
-
updateLastSyncAt();
|
|
147
|
-
// 미동기화 항목 정리
|
|
148
|
-
clearPendingSync();
|
|
149
|
-
}
|
|
150
|
-
catch (error) {
|
|
151
|
-
spinner.fail();
|
|
152
|
-
handleSyncError(error);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
// ============================================
|
|
156
|
-
// Pull 동기화 (서버 → 로컬)
|
|
157
|
-
// ============================================
|
|
158
|
-
/**
|
|
159
|
-
* 서버 → 로컬 동기화
|
|
160
|
-
*/
|
|
161
|
-
async function handlePullSync(options) {
|
|
162
|
-
const connection = loadConnection();
|
|
163
|
-
const localFeatureList = readFeatureList();
|
|
164
|
-
const spinner = ora('서버에서 데이터 가져오는 중...').start();
|
|
165
|
-
try {
|
|
166
|
-
const client = getApiClient();
|
|
167
|
-
if (!client) {
|
|
168
|
-
spinner.fail();
|
|
169
|
-
printError('API 클라이언트를 초기화할 수 없습니다.');
|
|
147
|
+
else {
|
|
148
|
+
spinner.fail('초기화 실패');
|
|
149
|
+
if (result.stderr) {
|
|
150
|
+
printError(result.stderr);
|
|
151
|
+
}
|
|
170
152
|
process.exitCode = 1;
|
|
171
|
-
return;
|
|
172
153
|
}
|
|
173
|
-
// 서버에서 프로젝트 컨텍스트 조회
|
|
174
|
-
const context = await client.getProjectContext(connection.projectId);
|
|
175
|
-
const serverFeatures = context.features;
|
|
176
|
-
if (options.dryRun) {
|
|
177
|
-
spinner.stop();
|
|
178
|
-
printPullDryRunResult(serverFeatures, localFeatureList);
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
spinner.text = `${serverFeatures.length}개 Feature 동기화 중...`;
|
|
182
|
-
// 서버 Feature를 로컬 형식으로 변환
|
|
183
|
-
const localFeatures = serverFeatures.map(serverToLocalFeature);
|
|
184
|
-
// 로컬 feature_list.json 업데이트
|
|
185
|
-
const updatedFeatureList = {
|
|
186
|
-
project: localFeatureList?.project ?? {
|
|
187
|
-
name: context.project.name,
|
|
188
|
-
version: '1.0.0',
|
|
189
|
-
description: context.project.description ?? '',
|
|
190
|
-
totalFeatures: serverFeatures.length,
|
|
191
|
-
completedFeatures: serverFeatures.filter(f => f.passes).length,
|
|
192
|
-
},
|
|
193
|
-
features: localFeatures,
|
|
194
|
-
};
|
|
195
|
-
// 프로젝트 정보 업데이트
|
|
196
|
-
updatedFeatureList.project.totalFeatures = localFeatures.length;
|
|
197
|
-
updatedFeatureList.project.completedFeatures = localFeatures.filter(f => f.passes).length;
|
|
198
|
-
writeFeatureList(updatedFeatureList);
|
|
199
|
-
spinner.succeed('동기화 완료');
|
|
200
|
-
// 결과 출력
|
|
201
|
-
console.log('');
|
|
202
|
-
console.log(` ${chalk.green('가져옴:')} ${serverFeatures.length}개 Feature`);
|
|
203
|
-
console.log(` ${chalk.blue('완료:')} ${updatedFeatureList.project.completedFeatures}개`);
|
|
204
|
-
console.log(` ${chalk.gray('대기:')} ${updatedFeatureList.project.totalFeatures - updatedFeatureList.project.completedFeatures}개`);
|
|
205
|
-
// 마지막 동기화 시간 업데이트
|
|
206
|
-
updateLastSyncAt();
|
|
207
|
-
clearPendingSync();
|
|
208
154
|
}
|
|
209
155
|
catch (error) {
|
|
210
|
-
spinner.fail();
|
|
211
|
-
|
|
156
|
+
spinner.fail('초기화 중 오류 발생');
|
|
157
|
+
if (error instanceof Error) {
|
|
158
|
+
printError(error.message);
|
|
159
|
+
}
|
|
160
|
+
process.exitCode = 1;
|
|
212
161
|
}
|
|
213
162
|
}
|
|
214
163
|
// ============================================
|
|
215
164
|
// 동기화 상태 확인
|
|
216
165
|
// ============================================
|
|
217
166
|
/**
|
|
218
|
-
* 동기화 상태 표시
|
|
167
|
+
* 동기화 상태 표시 (--status)
|
|
219
168
|
*/
|
|
220
169
|
async function handleSyncStatus() {
|
|
221
170
|
if (!isProjectInitialized()) {
|
|
222
171
|
printInfo('프로젝트가 초기화되지 않았습니다.');
|
|
223
172
|
return;
|
|
224
173
|
}
|
|
225
|
-
const connection = loadConnection();
|
|
226
|
-
const pendingCount = getPendingSyncCount();
|
|
227
|
-
const pendingItems = loadPendingSync();
|
|
228
174
|
console.log('');
|
|
229
|
-
console.log(chalk.bold('동기화 상태'));
|
|
175
|
+
console.log(chalk.bold('📊 동기화 상태'));
|
|
230
176
|
console.log('');
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
177
|
+
// Git 상태 확인
|
|
178
|
+
const spinner = ora('Git 상태 확인 중...').start();
|
|
179
|
+
try {
|
|
180
|
+
await gitFetch();
|
|
181
|
+
const status = await getRemoteSyncStatus();
|
|
182
|
+
spinner.stop();
|
|
183
|
+
printGitSyncStatus(status);
|
|
184
|
+
printStatusSummary(status);
|
|
235
185
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
console.log('');
|
|
242
|
-
if (pendingCount > 0) {
|
|
243
|
-
console.log(` ${chalk.yellow('⚠')} 미동기화 항목: ${chalk.yellow(pendingCount)}개`);
|
|
244
|
-
console.log('');
|
|
245
|
-
for (const item of pendingItems.slice(0, 5)) {
|
|
246
|
-
const actionIcon = item.action === 'create' ? '+' : item.action === 'update' ? '~' : '✓';
|
|
247
|
-
const actionColor = item.action === 'create' ? chalk.green : item.action === 'update' ? chalk.yellow : chalk.blue;
|
|
248
|
-
console.log(` ${actionColor(actionIcon)} ${item.featureId} (${item.action})`);
|
|
249
|
-
}
|
|
250
|
-
if (pendingItems.length > 5) {
|
|
251
|
-
console.log(chalk.gray(` ... 외 ${pendingItems.length - 5}개`));
|
|
186
|
+
catch (error) {
|
|
187
|
+
spinner.stop();
|
|
188
|
+
printWarning('Git 상태 확인 실패');
|
|
189
|
+
if (error instanceof Error) {
|
|
190
|
+
console.log(chalk.gray(` ${error.message}`));
|
|
252
191
|
}
|
|
253
|
-
console.log('');
|
|
254
|
-
console.log(chalk.gray(' 동기화하려면: aiag sync'));
|
|
255
192
|
}
|
|
256
|
-
|
|
257
|
-
|
|
193
|
+
// 서버 연결 상태
|
|
194
|
+
const connection = loadConnection();
|
|
195
|
+
console.log('');
|
|
196
|
+
console.log(chalk.bold('🔗 서버 연결'));
|
|
197
|
+
if (connection && isConnected()) {
|
|
198
|
+
console.log(` 프로젝트: ${chalk.cyan(connection.projectName)}`);
|
|
199
|
+
console.log(` 서버: ${chalk.gray(connection.serverUrl)}`);
|
|
200
|
+
console.log(` 마지막 동기화: ${chalk.gray(formatDate(connection.lastSyncAt))}`);
|
|
258
201
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
try {
|
|
263
|
-
const client = getApiClient();
|
|
264
|
-
if (client) {
|
|
265
|
-
const context = await client.getProjectContext(connection.projectId);
|
|
266
|
-
spinner.stop();
|
|
267
|
-
console.log('');
|
|
268
|
-
console.log(chalk.bold('서버 상태'));
|
|
269
|
-
console.log(` 총 Feature: ${chalk.yellow(context.stats.totalFeatures)}개`);
|
|
270
|
-
console.log(` 완료: ${chalk.green(context.stats.completedFeatures)}개`);
|
|
271
|
-
console.log(` 진행률: ${chalk.cyan(context.stats.completionRate.toFixed(1))}%`);
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
spinner.warn('서버 연결 필요');
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
catch {
|
|
278
|
-
spinner.warn('서버 상태 확인 실패');
|
|
279
|
-
}
|
|
202
|
+
else {
|
|
203
|
+
console.log(chalk.gray(' 연결되지 않음'));
|
|
204
|
+
console.log(chalk.gray(' 연결하려면: aiag connect'));
|
|
280
205
|
}
|
|
281
206
|
console.log('');
|
|
282
207
|
}
|
|
@@ -284,32 +209,40 @@ async function handleSyncStatus() {
|
|
|
284
209
|
// 실시간 이벤트 스트리밍
|
|
285
210
|
// ============================================
|
|
286
211
|
/**
|
|
287
|
-
* 실시간 이벤트 스트리밍 (watch
|
|
212
|
+
* 실시간 이벤트 스트리밍 (--watch)
|
|
288
213
|
*/
|
|
289
214
|
async function handleWatch() {
|
|
290
|
-
|
|
215
|
+
// 사전 조건 확인
|
|
216
|
+
if (!isProjectInitialized()) {
|
|
217
|
+
printError(messages.common.notInitialized);
|
|
218
|
+
process.exitCode = 1;
|
|
291
219
|
return;
|
|
292
220
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
printError('API 클라이언트를 초기화할 수 없습니다.');
|
|
221
|
+
if (!isLoggedIn()) {
|
|
222
|
+
printError(messages.login.serverRequired);
|
|
223
|
+
console.log(chalk.gray('먼저 로그인하세요: aiag login'));
|
|
297
224
|
process.exitCode = 1;
|
|
298
225
|
return;
|
|
299
226
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
const
|
|
227
|
+
if (!isConnected()) {
|
|
228
|
+
printError('웹 플랫폼에 연결되지 않았습니다.');
|
|
229
|
+
console.log(chalk.gray('먼저 연결하세요: aiag connect'));
|
|
230
|
+
process.exitCode = 1;
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const connection = loadConnection();
|
|
307
234
|
const credentials = loadCredentials();
|
|
308
235
|
if (!credentials) {
|
|
309
236
|
printError('인증 정보를 찾을 수 없습니다.');
|
|
310
237
|
process.exitCode = 1;
|
|
311
238
|
return;
|
|
312
239
|
}
|
|
240
|
+
console.log('');
|
|
241
|
+
console.log(chalk.bold('📡 실시간 이벤트 스트리밍'));
|
|
242
|
+
console.log(chalk.gray(`프로젝트: ${connection.projectName}`));
|
|
243
|
+
console.log(chalk.gray('종료하려면 Ctrl+C를 누르세요.'));
|
|
244
|
+
console.log('');
|
|
245
|
+
// SSE 클라이언트 설정
|
|
313
246
|
const sseClient = new SSEClient({
|
|
314
247
|
serverUrl: connection.serverUrl,
|
|
315
248
|
projectId: connection.projectId,
|
|
@@ -330,35 +263,23 @@ async function handleWatch() {
|
|
|
330
263
|
console.log(chalk.red(`✗ 에러: ${error.message}`));
|
|
331
264
|
});
|
|
332
265
|
// Feature 이벤트
|
|
333
|
-
sseClient.on('feature_claimed', (event) =>
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
sseClient.on('
|
|
337
|
-
console.log(formatSSEEvent(event));
|
|
338
|
-
});
|
|
339
|
-
sseClient.on('feature_completed', (event) => {
|
|
340
|
-
console.log(chalk.green(formatSSEEvent(event)));
|
|
341
|
-
});
|
|
342
|
-
sseClient.on('feature_failed', (event) => {
|
|
343
|
-
console.log(chalk.red(formatSSEEvent(event)));
|
|
344
|
-
});
|
|
266
|
+
sseClient.on('feature_claimed', (event) => console.log(formatSSEEvent(event)));
|
|
267
|
+
sseClient.on('feature_progress', (event) => console.log(formatSSEEvent(event)));
|
|
268
|
+
sseClient.on('feature_completed', (event) => console.log(chalk.green(formatSSEEvent(event))));
|
|
269
|
+
sseClient.on('feature_failed', (event) => console.log(chalk.red(formatSSEEvent(event))));
|
|
345
270
|
// 세션 이벤트
|
|
346
|
-
sseClient.on('session_started', (event) =>
|
|
347
|
-
|
|
348
|
-
});
|
|
349
|
-
sseClient.on('session_ended', (event) => {
|
|
350
|
-
console.log(chalk.blue(formatSSEEvent(event)));
|
|
351
|
-
});
|
|
271
|
+
sseClient.on('session_started', (event) => console.log(chalk.blue(formatSSEEvent(event))));
|
|
272
|
+
sseClient.on('session_ended', (event) => console.log(chalk.blue(formatSSEEvent(event))));
|
|
352
273
|
// 로그 이벤트
|
|
274
|
+
const levelColors = {
|
|
275
|
+
info: chalk.blue,
|
|
276
|
+
warn: chalk.yellow,
|
|
277
|
+
error: chalk.red,
|
|
278
|
+
debug: chalk.gray,
|
|
279
|
+
};
|
|
353
280
|
sseClient.on('log', (event) => {
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
warn: chalk.yellow,
|
|
357
|
-
error: chalk.red,
|
|
358
|
-
debug: chalk.gray,
|
|
359
|
-
};
|
|
360
|
-
const levelColor = levelColorMap[event.level] ?? chalk.white;
|
|
361
|
-
console.log(levelColor(formatSSEEvent(event)));
|
|
281
|
+
const color = levelColors[event.level] ?? chalk.white;
|
|
282
|
+
console.log(color(formatSSEEvent(event)));
|
|
362
283
|
});
|
|
363
284
|
// Ctrl+C 핸들링
|
|
364
285
|
process.on('SIGINT', () => {
|
|
@@ -377,177 +298,158 @@ async function handleWatch() {
|
|
|
377
298
|
}
|
|
378
299
|
}
|
|
379
300
|
// ============================================
|
|
380
|
-
//
|
|
301
|
+
// Git 작업 실행
|
|
381
302
|
// ============================================
|
|
382
303
|
/**
|
|
383
|
-
*
|
|
384
|
-
*/
|
|
385
|
-
function localToSyncFeature(feature) {
|
|
386
|
-
return {
|
|
387
|
-
localId: feature.id,
|
|
388
|
-
category: feature.category,
|
|
389
|
-
priority: feature.priority,
|
|
390
|
-
description: feature.description,
|
|
391
|
-
acceptanceCriteria: feature.acceptanceCriteria,
|
|
392
|
-
testCommand: feature.testCommand,
|
|
393
|
-
passes: feature.passes,
|
|
394
|
-
lastTestedAt: feature.lastTestedAt ?? undefined,
|
|
395
|
-
implementedBy: feature.implementedBy ?? undefined,
|
|
396
|
-
notes: feature.notes,
|
|
397
|
-
dependsOn: feature.dependsOn,
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
/**
|
|
401
|
-
* 서버 Feature를 로컬 형식으로 변환
|
|
304
|
+
* Git pull 자동 실행
|
|
402
305
|
*/
|
|
403
|
-
function
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
306
|
+
async function performGitPull(status) {
|
|
307
|
+
console.log(chalk.yellow(` ⬇️ 원격에 ${status.behind}개의 새 커밋이 있습니다.`));
|
|
308
|
+
// 커밋 목록 표시 (최대 5개)
|
|
309
|
+
for (const commit of status.behindCommits.slice(0, 5)) {
|
|
310
|
+
console.log(chalk.gray(` - ${commit}`));
|
|
311
|
+
}
|
|
312
|
+
if (status.behind > 5) {
|
|
313
|
+
console.log(chalk.gray(` ... 외 ${status.behind - 5}개`));
|
|
314
|
+
}
|
|
315
|
+
console.log('');
|
|
316
|
+
const spinner = ora('git pull 실행 중...').start();
|
|
317
|
+
const result = await gitPull();
|
|
318
|
+
if (result.success) {
|
|
319
|
+
spinner.succeed('git pull 완료');
|
|
320
|
+
await notifyServerCache();
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
spinner.fail('git pull 실패');
|
|
324
|
+
if (result.stderr) {
|
|
325
|
+
printError(result.stderr);
|
|
326
|
+
if (result.stderr.includes('conflict')) {
|
|
327
|
+
printWarning('충돌이 발생했습니다. 수동으로 해결해주세요.');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
process.exitCode = 1;
|
|
331
|
+
}
|
|
417
332
|
}
|
|
418
333
|
/**
|
|
419
|
-
*
|
|
334
|
+
* Git push 자동 실행
|
|
420
335
|
*/
|
|
421
|
-
function
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
336
|
+
async function performGitPush(status) {
|
|
337
|
+
console.log(chalk.yellow(` ⬆️ 로컬에 ${status.ahead}개의 커밋이 있습니다.`));
|
|
338
|
+
// 커밋 목록 표시 (최대 5개)
|
|
339
|
+
for (const commit of status.aheadCommits.slice(0, 5)) {
|
|
340
|
+
console.log(chalk.gray(` - ${commit}`));
|
|
341
|
+
}
|
|
342
|
+
if (status.ahead > 5) {
|
|
343
|
+
console.log(chalk.gray(` ... 외 ${status.ahead - 5}개`));
|
|
344
|
+
}
|
|
345
|
+
console.log('');
|
|
346
|
+
const spinner = ora('git push 실행 중...').start();
|
|
347
|
+
const result = await gitPush();
|
|
348
|
+
if (result.success) {
|
|
349
|
+
spinner.succeed('git push 완료');
|
|
350
|
+
await notifyServerCache();
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
spinner.fail('git push 실패');
|
|
354
|
+
if (result.error) {
|
|
355
|
+
printError(result.error);
|
|
431
356
|
}
|
|
357
|
+
process.exitCode = 1;
|
|
432
358
|
}
|
|
433
|
-
writeFeatureList(localList);
|
|
434
359
|
}
|
|
435
360
|
// ============================================
|
|
436
|
-
//
|
|
361
|
+
// 서버 캐시 알림 (선택적)
|
|
437
362
|
// ============================================
|
|
438
363
|
/**
|
|
439
|
-
* 동기화
|
|
364
|
+
* 서버에 동기화 완료 알림 (서버 캐시 갱신 트리거)
|
|
365
|
+
* GitHub Webhook이 처리하므로 필수는 아님
|
|
440
366
|
*/
|
|
441
|
-
function
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
367
|
+
async function notifyServerCache() {
|
|
368
|
+
// 서버 연결이 되어 있으면 마지막 동기화 시간 업데이트
|
|
369
|
+
if (isConnected() && isLoggedIn()) {
|
|
370
|
+
try {
|
|
371
|
+
updateLastSyncAt();
|
|
372
|
+
// 선택적: 서버에 동기화 요청 (GitHub Webhook이 이미 처리하므로 불필요할 수 있음)
|
|
373
|
+
// 하지만 연결 상태 확인 및 프로젝트 존재 확인에 유용
|
|
374
|
+
const connection = loadConnection();
|
|
375
|
+
const client = getApiClient();
|
|
376
|
+
if (connection && client) {
|
|
377
|
+
// 간단한 health check 수준
|
|
378
|
+
await client.getProject(connection.projectId).catch(() => {
|
|
379
|
+
// 무시 (서버 연결 안 되어도 Git 동기화는 성공)
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
// 서버 알림 실패해도 Git 동기화는 성공으로 처리
|
|
385
|
+
}
|
|
455
386
|
}
|
|
456
|
-
console.log('');
|
|
457
387
|
}
|
|
388
|
+
// ============================================
|
|
389
|
+
// 출력 유틸리티
|
|
390
|
+
// ============================================
|
|
458
391
|
/**
|
|
459
|
-
*
|
|
392
|
+
* Git 동기화 상태 출력
|
|
460
393
|
*/
|
|
461
|
-
function
|
|
462
|
-
console.log(chalk.yellow('충돌이 발생했습니다:'));
|
|
394
|
+
function printGitSyncStatus(status) {
|
|
463
395
|
console.log('');
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
console.log(
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
if (conflicts.length > 5) {
|
|
470
|
-
console.log(chalk.gray(` ... 외 ${conflicts.length - 5}개 충돌`));
|
|
396
|
+
console.log(chalk.bold('🔄 Git 상태'));
|
|
397
|
+
if (!status.hasRemote) {
|
|
398
|
+
console.log(chalk.gray(' 원격 저장소 없음'));
|
|
399
|
+
return;
|
|
471
400
|
}
|
|
472
|
-
console.log('');
|
|
473
|
-
console.log(chalk.gray(
|
|
474
|
-
console.log(chalk.gray(
|
|
475
|
-
console.log(chalk.gray(' --pull: 서버 상태로 덮어쓰기'));
|
|
401
|
+
console.log(chalk.gray(` 브랜치: ${status.branch || 'unknown'}`));
|
|
402
|
+
console.log(chalk.gray(` 로컬: ${status.localCommit?.slice(0, 7) || 'N/A'}`));
|
|
403
|
+
console.log(chalk.gray(` 원격: ${status.remoteCommit?.slice(0, 7) || 'N/A'}`));
|
|
476
404
|
console.log('');
|
|
477
405
|
}
|
|
478
406
|
/**
|
|
479
|
-
*
|
|
407
|
+
* 상태 요약 출력
|
|
480
408
|
*/
|
|
481
|
-
function
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
409
|
+
function printStatusSummary(status) {
|
|
410
|
+
switch (status.status) {
|
|
411
|
+
case 'up-to-date':
|
|
412
|
+
console.log(chalk.green(' ✓ 최신 상태'));
|
|
413
|
+
break;
|
|
414
|
+
case 'behind':
|
|
415
|
+
console.log(chalk.yellow(` ⬇️ 원격에 ${status.behind}개 커밋 뒤처짐`));
|
|
416
|
+
console.log(chalk.gray(' aiag sync 로 pull 하세요.'));
|
|
417
|
+
break;
|
|
418
|
+
case 'ahead':
|
|
419
|
+
console.log(chalk.yellow(` ⬆️ 로컬에 ${status.ahead}개 커밋 앞섬`));
|
|
420
|
+
console.log(chalk.gray(' aiag sync 로 push 하세요.'));
|
|
421
|
+
break;
|
|
422
|
+
case 'diverged':
|
|
423
|
+
console.log(chalk.red(` ⚠️ 로컬과 원격이 분기됨`));
|
|
424
|
+
console.log(chalk.gray(` 로컬 +${status.ahead}, 원격 +${status.behind}`));
|
|
425
|
+
console.log(chalk.gray(' aiag sync --reset 으로 GitHub 상태로 초기화하세요.'));
|
|
426
|
+
break;
|
|
427
|
+
case 'no-remote':
|
|
428
|
+
console.log(chalk.gray(' 원격 저장소가 설정되지 않음'));
|
|
429
|
+
break;
|
|
430
|
+
case 'no-upstream':
|
|
431
|
+
console.log(chalk.gray(' 원격 브랜치 트래킹이 설정되지 않음'));
|
|
432
|
+
break;
|
|
496
433
|
}
|
|
497
|
-
console.log('');
|
|
498
|
-
console.log(chalk.gray('실제 동기화하려면 --dry-run 옵션을 제거하세요.'));
|
|
499
|
-
console.log('');
|
|
500
434
|
}
|
|
501
435
|
/**
|
|
502
|
-
*
|
|
436
|
+
* 분기 경고 출력
|
|
503
437
|
*/
|
|
504
|
-
function
|
|
438
|
+
function printDivergedWarning(status) {
|
|
439
|
+
console.log(chalk.red(' ⚠️ 로컬과 원격이 분기되었습니다:'));
|
|
440
|
+
console.log(chalk.yellow(` - 로컬에 ${status.ahead}개 커밋 (푸시 안 됨)`));
|
|
441
|
+
console.log(chalk.yellow(` - 원격에 ${status.behind}개 커밋 (E2B 등에서 생성)`));
|
|
505
442
|
console.log('');
|
|
506
|
-
console.log(chalk.
|
|
443
|
+
console.log(chalk.cyan(' 💡 해결 방법:'));
|
|
507
444
|
console.log('');
|
|
508
|
-
|
|
509
|
-
console.log(
|
|
510
|
-
console.log(` 로컬 Feature: ${localCount}개`);
|
|
511
|
-
if (serverFeatures.length !== localCount) {
|
|
512
|
-
console.log(` ${chalk.yellow('변경:')} ${Math.abs(serverFeatures.length - localCount)}개 차이`);
|
|
513
|
-
}
|
|
445
|
+
console.log(chalk.gray(' 1. GitHub 상태로 초기화 (로컬 변경 삭제):'));
|
|
446
|
+
console.log(chalk.white(' aiag sync --reset'));
|
|
514
447
|
console.log('');
|
|
515
|
-
console.log(chalk.gray('
|
|
448
|
+
console.log(chalk.gray(' 2. 수동으로 rebase 후 push:'));
|
|
449
|
+
console.log(chalk.white(' git pull --rebase origin ' + (status.branch || 'main')));
|
|
450
|
+
console.log(chalk.white(' git push'));
|
|
516
451
|
console.log('');
|
|
517
452
|
}
|
|
518
|
-
// ============================================
|
|
519
|
-
// 에러 처리
|
|
520
|
-
// ============================================
|
|
521
|
-
/**
|
|
522
|
-
* 동기화 에러 처리
|
|
523
|
-
*/
|
|
524
|
-
function handleSyncError(error) {
|
|
525
|
-
if (error instanceof ApiError) {
|
|
526
|
-
if (error.code === 'NOT_FOUND' || error.status === 404) {
|
|
527
|
-
printError('프로젝트를 찾을 수 없습니다. 연결을 다시 확인하세요.');
|
|
528
|
-
}
|
|
529
|
-
else if (error.status === 403) {
|
|
530
|
-
printError('이 프로젝트에 대한 동기화 권한이 없습니다.');
|
|
531
|
-
}
|
|
532
|
-
else if (error.code === 'CONFLICT') {
|
|
533
|
-
printError('동기화 충돌이 발생했습니다. --force 옵션을 사용하거나 수동으로 해결하세요.');
|
|
534
|
-
}
|
|
535
|
-
else {
|
|
536
|
-
printError(`동기화 실패: ${error.message}`);
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
else if (error instanceof NetworkError) {
|
|
540
|
-
printError(`네트워크 오류: ${error.message}`);
|
|
541
|
-
printInfo('오프라인 상태에서 변경한 내용은 나중에 동기화됩니다.');
|
|
542
|
-
}
|
|
543
|
-
else {
|
|
544
|
-
printError(`알 수 없는 오류: ${error instanceof Error ? error.message : String(error)}`);
|
|
545
|
-
}
|
|
546
|
-
process.exitCode = 1;
|
|
547
|
-
}
|
|
548
|
-
// ============================================
|
|
549
|
-
// 유틸리티 함수
|
|
550
|
-
// ============================================
|
|
551
453
|
/**
|
|
552
454
|
* 날짜 포맷팅
|
|
553
455
|
*/
|
|
@@ -562,7 +464,7 @@ function formatDate(isoString) {
|
|
|
562
464
|
});
|
|
563
465
|
}
|
|
564
466
|
// ============================================
|
|
565
|
-
// Progress.md 동기화
|
|
467
|
+
// Progress.md 동기화 (서버 모니터링용)
|
|
566
468
|
// ============================================
|
|
567
469
|
/**
|
|
568
470
|
* Progress.md를 서버에 동기화
|
|
@@ -651,171 +553,5 @@ function truncateProgressContent(content, maxSize) {
|
|
|
651
553
|
}
|
|
652
554
|
return result;
|
|
653
555
|
}
|
|
654
|
-
// ============================================
|
|
655
|
-
// Git 동기화 처리
|
|
656
|
-
// ============================================
|
|
657
|
-
/**
|
|
658
|
-
* Git 원격 저장소 동기화 상태 확인 및 처리
|
|
659
|
-
* @returns true면 계속 진행, false면 중단
|
|
660
|
-
*/
|
|
661
|
-
async function handleGitSync(options) {
|
|
662
|
-
const spinner = ora('Git 상태 확인 중...').start();
|
|
663
|
-
try {
|
|
664
|
-
const status = await getRemoteSyncStatus();
|
|
665
|
-
spinner.stop();
|
|
666
|
-
// 출력
|
|
667
|
-
printGitSyncStatus(status);
|
|
668
|
-
// 상태별 처리
|
|
669
|
-
const shouldAutoPull = options.autoPull || options.autoGit;
|
|
670
|
-
const shouldAutoPush = options.autoPush || options.autoGit;
|
|
671
|
-
switch (status.status) {
|
|
672
|
-
case 'no-remote':
|
|
673
|
-
printInfo('원격 저장소가 설정되지 않았습니다. Feature 동기화만 진행합니다.');
|
|
674
|
-
return true;
|
|
675
|
-
case 'no-upstream':
|
|
676
|
-
printInfo('원격 브랜치 트래킹이 설정되지 않았습니다. Feature 동기화만 진행합니다.');
|
|
677
|
-
return true;
|
|
678
|
-
case 'up-to-date':
|
|
679
|
-
console.log(chalk.green(' ✓ Git 저장소가 최신 상태입니다.\n'));
|
|
680
|
-
return true;
|
|
681
|
-
case 'behind':
|
|
682
|
-
// 원격에 새 커밋이 있음 → pull 권장
|
|
683
|
-
if (shouldAutoPull) {
|
|
684
|
-
return await performGitPull();
|
|
685
|
-
}
|
|
686
|
-
else {
|
|
687
|
-
printGitPullSuggestion(status);
|
|
688
|
-
return true; // Feature 동기화는 계속 진행
|
|
689
|
-
}
|
|
690
|
-
case 'ahead':
|
|
691
|
-
// 로컬에 푸시되지 않은 커밋이 있음 → push 권장
|
|
692
|
-
if (shouldAutoPush) {
|
|
693
|
-
return await performGitPush();
|
|
694
|
-
}
|
|
695
|
-
else {
|
|
696
|
-
printGitPushSuggestion(status);
|
|
697
|
-
return true; // Feature 동기화는 계속 진행
|
|
698
|
-
}
|
|
699
|
-
case 'diverged':
|
|
700
|
-
// 로컬과 원격이 분기됨 → 수동 해결 필요
|
|
701
|
-
printGitDivergedWarning(status);
|
|
702
|
-
return true; // Feature 동기화는 계속 진행 (경고만)
|
|
703
|
-
default:
|
|
704
|
-
return true;
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
catch (error) {
|
|
708
|
-
spinner.stop();
|
|
709
|
-
if (error instanceof Error) {
|
|
710
|
-
printWarning(`Git 상태 확인 실패: ${error.message}`);
|
|
711
|
-
}
|
|
712
|
-
// Git 확인 실패해도 Feature 동기화는 계속
|
|
713
|
-
return true;
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
/**
|
|
717
|
-
* Git 동기화 상태 출력
|
|
718
|
-
*/
|
|
719
|
-
function printGitSyncStatus(status) {
|
|
720
|
-
console.log();
|
|
721
|
-
console.log(chalk.bold('📊 Git 동기화 상태'));
|
|
722
|
-
if (!status.hasRemote) {
|
|
723
|
-
console.log(chalk.gray(' 원격 저장소 없음'));
|
|
724
|
-
return;
|
|
725
|
-
}
|
|
726
|
-
console.log(chalk.gray(` 브랜치: ${status.branch || 'unknown'}`));
|
|
727
|
-
console.log(chalk.gray(` 로컬: ${status.localCommit || 'N/A'}`));
|
|
728
|
-
console.log(chalk.gray(` 원격: ${status.remoteCommit || 'N/A'}`));
|
|
729
|
-
console.log();
|
|
730
|
-
}
|
|
731
|
-
/**
|
|
732
|
-
* Git pull 제안 출력
|
|
733
|
-
*/
|
|
734
|
-
function printGitPullSuggestion(status) {
|
|
735
|
-
console.log(chalk.yellow(` ⬇️ 원격에 ${status.behind}개의 새 커밋이 있습니다:`));
|
|
736
|
-
// 최대 5개만 표시
|
|
737
|
-
const commits = status.behindCommits.slice(0, 5);
|
|
738
|
-
for (const commit of commits) {
|
|
739
|
-
console.log(chalk.gray(` - ${commit}`));
|
|
740
|
-
}
|
|
741
|
-
if (status.behind > 5) {
|
|
742
|
-
console.log(chalk.gray(` ... 외 ${status.behind - 5}개`));
|
|
743
|
-
}
|
|
744
|
-
console.log();
|
|
745
|
-
console.log(chalk.cyan(' 💡 권장: git pull 실행'));
|
|
746
|
-
console.log(chalk.gray(' 또는: aiag sync --auto-pull'));
|
|
747
|
-
console.log();
|
|
748
|
-
}
|
|
749
|
-
/**
|
|
750
|
-
* Git push 제안 출력
|
|
751
|
-
*/
|
|
752
|
-
function printGitPushSuggestion(status) {
|
|
753
|
-
console.log(chalk.yellow(` ⬆️ 로컬에 ${status.ahead}개의 푸시되지 않은 커밋이 있습니다:`));
|
|
754
|
-
// 최대 5개만 표시
|
|
755
|
-
const commits = status.aheadCommits.slice(0, 5);
|
|
756
|
-
for (const commit of commits) {
|
|
757
|
-
console.log(chalk.gray(` - ${commit}`));
|
|
758
|
-
}
|
|
759
|
-
if (status.ahead > 5) {
|
|
760
|
-
console.log(chalk.gray(` ... 외 ${status.ahead - 5}개`));
|
|
761
|
-
}
|
|
762
|
-
console.log();
|
|
763
|
-
console.log(chalk.cyan(' 💡 권장: git push 실행'));
|
|
764
|
-
console.log(chalk.gray(' 또는: aiag sync --auto-push'));
|
|
765
|
-
console.log();
|
|
766
|
-
}
|
|
767
|
-
/**
|
|
768
|
-
* Git 분기 경고 출력
|
|
769
|
-
*/
|
|
770
|
-
function printGitDivergedWarning(status) {
|
|
771
|
-
console.log(chalk.red(' ⚠️ 로컬과 원격이 분기되었습니다:'));
|
|
772
|
-
console.log(chalk.yellow(` - 로컬에 ${status.ahead}개 커밋 (푸시 안 됨)`));
|
|
773
|
-
console.log(chalk.yellow(` - 원격에 ${status.behind}개 커밋 (E2B 등에서 생성)`));
|
|
774
|
-
console.log();
|
|
775
|
-
console.log(chalk.cyan(' 💡 권장 조치:'));
|
|
776
|
-
console.log(chalk.gray(' 1. git pull --rebase origin ' + (status.branch || 'main')));
|
|
777
|
-
console.log(chalk.gray(' 2. git push'));
|
|
778
|
-
console.log();
|
|
779
|
-
console.log(chalk.gray(' 또는 로컬 변경 무시:'));
|
|
780
|
-
console.log(chalk.gray(' git reset --hard origin/' + (status.branch || 'main')));
|
|
781
|
-
console.log();
|
|
782
|
-
}
|
|
783
|
-
/**
|
|
784
|
-
* Git pull 자동 실행
|
|
785
|
-
*/
|
|
786
|
-
async function performGitPull() {
|
|
787
|
-
const spinner = ora('git pull 실행 중...').start();
|
|
788
|
-
const result = await gitPull();
|
|
789
|
-
if (result.success) {
|
|
790
|
-
spinner.succeed('git pull 완료');
|
|
791
|
-
return true;
|
|
792
|
-
}
|
|
793
|
-
else {
|
|
794
|
-
spinner.fail('git pull 실패');
|
|
795
|
-
printError(result.stderr || 'pull 실패');
|
|
796
|
-
if (result.stderr?.includes('conflict')) {
|
|
797
|
-
printWarning('충돌이 발생했습니다. 수동으로 해결해주세요.');
|
|
798
|
-
}
|
|
799
|
-
return true; // Feature 동기화는 계속 진행
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
/**
|
|
803
|
-
* Git push 자동 실행
|
|
804
|
-
*/
|
|
805
|
-
async function performGitPush() {
|
|
806
|
-
const spinner = ora('git push 실행 중...').start();
|
|
807
|
-
const result = await gitPush();
|
|
808
|
-
if (result.success) {
|
|
809
|
-
spinner.succeed('git push 완료');
|
|
810
|
-
return true;
|
|
811
|
-
}
|
|
812
|
-
else {
|
|
813
|
-
spinner.fail('git push 실패');
|
|
814
|
-
if (result.error) {
|
|
815
|
-
printError(result.error);
|
|
816
|
-
}
|
|
817
|
-
return true; // Feature 동기화는 계속 진행
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
556
|
export default sync;
|
|
821
557
|
//# sourceMappingURL=sync.js.map
|