commit-ai-agent 1.0.8 → 2.0.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/.env.example +0 -2
- package/README.md +134 -18
- package/bin/cli.js +97 -1
- package/package.json +1 -1
- package/public/app.js +191 -121
- package/public/index.html +71 -24
- package/public/style.css +161 -0
- package/src/config.js +4 -20
- package/src/git.js +0 -19
- package/src/hooks/installer.js +191 -0
- package/src/hooks/post-commit.js +90 -0
- package/src/hooks/pre-push.js +298 -0
- package/src/server.js +178 -45
package/public/style.css
CHANGED
|
@@ -922,3 +922,164 @@ body {
|
|
|
922
922
|
width: 100%;
|
|
923
923
|
}
|
|
924
924
|
}
|
|
925
|
+
|
|
926
|
+
/* ── Hooks Tab ── */
|
|
927
|
+
.hook-desc {
|
|
928
|
+
font-size: 14px;
|
|
929
|
+
color: var(--text2);
|
|
930
|
+
margin-bottom: 20px;
|
|
931
|
+
line-height: 1.6;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
.hook-section {
|
|
935
|
+
background: var(--bg3);
|
|
936
|
+
border: 1px solid var(--border);
|
|
937
|
+
border-radius: 10px;
|
|
938
|
+
padding: 14px 16px;
|
|
939
|
+
margin-bottom: 12px;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
.hook-section-title {
|
|
943
|
+
font-size: 14px;
|
|
944
|
+
font-weight: 600;
|
|
945
|
+
color: var(--text);
|
|
946
|
+
margin-bottom: 4px;
|
|
947
|
+
display: flex;
|
|
948
|
+
align-items: center;
|
|
949
|
+
gap: 8px;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
.hook-icon {
|
|
953
|
+
font-size: 16px;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
.hook-section-desc {
|
|
957
|
+
font-size: 13px;
|
|
958
|
+
color: var(--text2);
|
|
959
|
+
line-height: 1.5;
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
.hook-projects-list {
|
|
963
|
+
margin-top: 20px;
|
|
964
|
+
display: flex;
|
|
965
|
+
flex-direction: column;
|
|
966
|
+
gap: 10px;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
.hook-project-row {
|
|
970
|
+
display: flex;
|
|
971
|
+
align-items: center;
|
|
972
|
+
justify-content: space-between;
|
|
973
|
+
padding: 12px 14px;
|
|
974
|
+
background: var(--bg3);
|
|
975
|
+
border: 1px solid var(--border);
|
|
976
|
+
border-radius: 10px;
|
|
977
|
+
gap: 12px;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
.hook-project-info {
|
|
981
|
+
display: flex;
|
|
982
|
+
align-items: center;
|
|
983
|
+
gap: 10px;
|
|
984
|
+
flex-wrap: wrap;
|
|
985
|
+
flex: 1;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
.hook-project-name {
|
|
989
|
+
font-size: 14px;
|
|
990
|
+
font-weight: 600;
|
|
991
|
+
color: var(--text);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
.hook-detail {
|
|
995
|
+
font-size: 12px;
|
|
996
|
+
color: var(--text3);
|
|
997
|
+
font-family: "JetBrains Mono", monospace;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
.hook-badge {
|
|
1001
|
+
font-size: 11px;
|
|
1002
|
+
font-weight: 600;
|
|
1003
|
+
padding: 2px 8px;
|
|
1004
|
+
border-radius: 20px;
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
.hook-badge.installed {
|
|
1008
|
+
background: rgba(52, 211, 153, 0.15);
|
|
1009
|
+
color: var(--success);
|
|
1010
|
+
border: 1px solid rgba(52, 211, 153, 0.3);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
.hook-badge.not-installed {
|
|
1014
|
+
background: rgba(248, 113, 113, 0.12);
|
|
1015
|
+
color: var(--danger);
|
|
1016
|
+
border: 1px solid rgba(248, 113, 113, 0.25);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
.hook-badge.partial {
|
|
1020
|
+
background: rgba(251, 146, 60, 0.12);
|
|
1021
|
+
color: var(--warn);
|
|
1022
|
+
border: 1px solid rgba(251, 146, 60, 0.25);
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
.hook-footer {
|
|
1026
|
+
margin-top: 20px;
|
|
1027
|
+
border-top: 1px solid var(--border);
|
|
1028
|
+
padding-top: 16px;
|
|
1029
|
+
justify-content: flex-start;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
.btn-secondary {
|
|
1033
|
+
background: rgba(99, 102, 241, 0.12);
|
|
1034
|
+
color: var(--primary-h);
|
|
1035
|
+
border: 1px solid rgba(99, 102, 241, 0.3);
|
|
1036
|
+
border-radius: 8px;
|
|
1037
|
+
padding: 8px 16px;
|
|
1038
|
+
font-size: 13px;
|
|
1039
|
+
font-weight: 500;
|
|
1040
|
+
cursor: pointer;
|
|
1041
|
+
transition: background 0.15s;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
.btn-secondary:hover {
|
|
1045
|
+
background: rgba(99, 102, 241, 0.22);
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
.btn-secondary:disabled {
|
|
1049
|
+
opacity: 0.5;
|
|
1050
|
+
cursor: not-allowed;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
.hook-cli-card {
|
|
1054
|
+
margin-top: 16px;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
.hook-cli-block {
|
|
1058
|
+
display: flex;
|
|
1059
|
+
flex-direction: column;
|
|
1060
|
+
gap: 10px;
|
|
1061
|
+
margin-top: 12px;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
.hook-cli-item {
|
|
1065
|
+
display: flex;
|
|
1066
|
+
align-items: center;
|
|
1067
|
+
gap: 16px;
|
|
1068
|
+
padding: 10px 14px;
|
|
1069
|
+
background: var(--bg3);
|
|
1070
|
+
border-radius: 8px;
|
|
1071
|
+
border: 1px solid var(--border);
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
.hook-cli-item code {
|
|
1075
|
+
font-family: "JetBrains Mono", monospace;
|
|
1076
|
+
font-size: 12px;
|
|
1077
|
+
color: var(--accent);
|
|
1078
|
+
white-space: nowrap;
|
|
1079
|
+
min-width: 260px;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
.hook-cli-item span {
|
|
1083
|
+
font-size: 13px;
|
|
1084
|
+
color: var(--text2);
|
|
1085
|
+
}
|
package/src/config.js
CHANGED
|
@@ -2,31 +2,15 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
|
|
4
4
|
export function resolveDevRoot() {
|
|
5
|
-
const fromEnv = process.env.DEV_ROOT?.trim();
|
|
6
|
-
if (fromEnv) {
|
|
7
|
-
const devRoot = path.resolve(fromEnv);
|
|
8
|
-
validateDirectory(devRoot, "DEV_ROOT");
|
|
9
|
-
return { devRoot, source: "env" };
|
|
10
|
-
}
|
|
11
|
-
|
|
12
5
|
const devRoot = path.resolve(process.cwd());
|
|
13
|
-
validateDirectory(devRoot, "cwd");
|
|
14
|
-
return { devRoot, source: "cwd" };
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function validateDirectory(targetPath, sourceLabel) {
|
|
18
|
-
if (!targetPath) {
|
|
19
|
-
throw new Error(`${sourceLabel} 경로가 비어 있습니다.`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
6
|
let stat;
|
|
23
7
|
try {
|
|
24
|
-
stat = fs.statSync(
|
|
8
|
+
stat = fs.statSync(devRoot);
|
|
25
9
|
} catch {
|
|
26
|
-
throw new Error(
|
|
10
|
+
throw new Error(`경로를 찾을 수 없습니다: ${devRoot}`);
|
|
27
11
|
}
|
|
28
|
-
|
|
29
12
|
if (!stat.isDirectory()) {
|
|
30
|
-
throw new Error(
|
|
13
|
+
throw new Error(`경로가 디렉토리가 아닙니다: ${devRoot}`);
|
|
31
14
|
}
|
|
15
|
+
return devRoot;
|
|
32
16
|
}
|
package/src/git.js
CHANGED
|
@@ -2,25 +2,6 @@ import simpleGit from "simple-git";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* DEV_ROOT 하위의 git 프로젝트 목록을 반환합니다.
|
|
7
|
-
*/
|
|
8
|
-
export async function listGitProjects(devRoot) {
|
|
9
|
-
const entries = fs.readdirSync(devRoot, { withFileTypes: true });
|
|
10
|
-
const projects = [];
|
|
11
|
-
|
|
12
|
-
for (const entry of entries) {
|
|
13
|
-
if (!entry.isDirectory()) continue;
|
|
14
|
-
const fullPath = path.join(devRoot, entry.name);
|
|
15
|
-
const gitDir = path.join(fullPath, ".git");
|
|
16
|
-
if (fs.existsSync(gitDir)) {
|
|
17
|
-
projects.push({ name: entry.name, path: fullPath });
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return projects;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
5
|
/**
|
|
25
6
|
* 특정 프로젝트의 최신 커밋 정보와 diff를 가져옵니다.
|
|
26
7
|
*/
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
|
|
7
|
+
// 이 파일의 위치: src/hooks/installer.js → 패키지 루트는 2단계 상위
|
|
8
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
|
|
9
|
+
|
|
10
|
+
const HOOK_MARKER_START = '# === commit-ai-agent START ===';
|
|
11
|
+
const HOOK_MARKER_END = '# === commit-ai-agent END ===';
|
|
12
|
+
|
|
13
|
+
function getHooksDir(projectPath) {
|
|
14
|
+
return path.join(projectPath, '.git', 'hooks');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Git Bash / sh 호환 경로로 변환 (Windows 역슬래시 → 포워드슬래시)
|
|
19
|
+
*/
|
|
20
|
+
function toPosixPath(p) {
|
|
21
|
+
return p.replace(/\\/g, '/');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* .git/hooks/ 에 삽입될 셸 스크립트 블록을 생성합니다.
|
|
26
|
+
*/
|
|
27
|
+
function buildHookBlock(hookType) {
|
|
28
|
+
const scriptPath = toPosixPath(
|
|
29
|
+
path.resolve(PACKAGE_ROOT, 'src', 'hooks', `${hookType}.js`)
|
|
30
|
+
);
|
|
31
|
+
const nodeExec = toPosixPath(process.execPath);
|
|
32
|
+
|
|
33
|
+
if (hookType === 'post-commit') {
|
|
34
|
+
// 백그라운드(&) 실행 — 커밋을 블록하지 않음
|
|
35
|
+
return [
|
|
36
|
+
HOOK_MARKER_START,
|
|
37
|
+
`"${nodeExec}" "${scriptPath}" "$(pwd)" &`,
|
|
38
|
+
HOOK_MARKER_END,
|
|
39
|
+
'',
|
|
40
|
+
].join('\n');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (hookType === 'pre-push') {
|
|
44
|
+
// 동기 실행 — exit code 로 push 차단 가능
|
|
45
|
+
return [
|
|
46
|
+
HOOK_MARKER_START,
|
|
47
|
+
`"${nodeExec}" "${scriptPath}" "$(pwd)"`,
|
|
48
|
+
`_cai_result=$?`,
|
|
49
|
+
`if [ $_cai_result -ne 0 ]; then exit $_cai_result; fi`,
|
|
50
|
+
HOOK_MARKER_END,
|
|
51
|
+
'',
|
|
52
|
+
].join('\n');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
throw new Error(`알 수 없는 hook 유형: ${hookType}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 기존 훅 파일에서 commit-ai-agent 섹션만 제거합니다.
|
|
60
|
+
*/
|
|
61
|
+
function removeHookSection(content) {
|
|
62
|
+
const startIdx = content.indexOf(HOOK_MARKER_START);
|
|
63
|
+
const endIdx = content.indexOf(HOOK_MARKER_END);
|
|
64
|
+
if (startIdx === -1 || endIdx === -1) return content;
|
|
65
|
+
|
|
66
|
+
const before = content.slice(0, startIdx).trimEnd();
|
|
67
|
+
const after = content
|
|
68
|
+
.slice(endIdx + HOOK_MARKER_END.length)
|
|
69
|
+
.replace(/^\n+/, ''); // 마커 뒤 빈 줄 제거
|
|
70
|
+
|
|
71
|
+
return before ? before + '\n\n' + after : after;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 단일 훅 파일에 commit-ai-agent 블록을 설치합니다.
|
|
76
|
+
* 기존 훅 내용은 보존됩니다.
|
|
77
|
+
*/
|
|
78
|
+
function installSingleHook(hooksDir, hookName) {
|
|
79
|
+
const hookPath = path.join(hooksDir, hookName);
|
|
80
|
+
const newBlock = buildHookBlock(hookName);
|
|
81
|
+
|
|
82
|
+
let existing = '';
|
|
83
|
+
if (fs.existsSync(hookPath)) {
|
|
84
|
+
existing = fs.readFileSync(hookPath, 'utf-8');
|
|
85
|
+
// 이미 설치된 경우 기존 블록 제거 후 재설치
|
|
86
|
+
if (existing.includes(HOOK_MARKER_START)) {
|
|
87
|
+
existing = removeHookSection(existing);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const shebang = '#!/bin/sh';
|
|
92
|
+
let final;
|
|
93
|
+
|
|
94
|
+
if (!existing.trim()) {
|
|
95
|
+
final = shebang + '\n' + newBlock;
|
|
96
|
+
} else if (!existing.startsWith('#!')) {
|
|
97
|
+
final = shebang + '\n' + existing.trimEnd() + '\n\n' + newBlock;
|
|
98
|
+
} else {
|
|
99
|
+
final = existing.trimEnd() + '\n\n' + newBlock;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
fs.writeFileSync(hookPath, final, { mode: 0o755 });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 프로젝트의 git hook을 설치합니다.
|
|
107
|
+
* @param {string} projectPath - git 저장소 루트 경로
|
|
108
|
+
* @param {{ postCommit?: boolean, prePush?: boolean }} options
|
|
109
|
+
* @returns {string[]} 설치된 훅 이름 목록
|
|
110
|
+
*/
|
|
111
|
+
export async function installHooks(projectPath, options = {}) {
|
|
112
|
+
const { postCommit = true, prePush = true } = options;
|
|
113
|
+
const hooksDir = getHooksDir(projectPath);
|
|
114
|
+
|
|
115
|
+
if (!fs.existsSync(hooksDir)) {
|
|
116
|
+
throw new Error(`Git hooks 디렉토리를 찾을 수 없습니다: ${hooksDir}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const installed = [];
|
|
120
|
+
|
|
121
|
+
if (postCommit) {
|
|
122
|
+
installSingleHook(hooksDir, 'post-commit');
|
|
123
|
+
installed.push('post-commit');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (prePush) {
|
|
127
|
+
installSingleHook(hooksDir, 'pre-push');
|
|
128
|
+
installed.push('pre-push');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return installed;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* 프로젝트의 commit-ai-agent git hook을 제거합니다.
|
|
136
|
+
* 기존 커스텀 훅 내용은 보존됩니다.
|
|
137
|
+
* @param {string} projectPath
|
|
138
|
+
* @returns {string[]} 제거된 훅 이름 목록
|
|
139
|
+
*/
|
|
140
|
+
export async function removeHooks(projectPath) {
|
|
141
|
+
const hooksDir = getHooksDir(projectPath);
|
|
142
|
+
const removed = [];
|
|
143
|
+
|
|
144
|
+
for (const hookName of ['post-commit', 'pre-push']) {
|
|
145
|
+
const hookPath = path.join(hooksDir, hookName);
|
|
146
|
+
if (!fs.existsSync(hookPath)) continue;
|
|
147
|
+
|
|
148
|
+
const content = fs.readFileSync(hookPath, 'utf-8');
|
|
149
|
+
if (!content.includes(HOOK_MARKER_START)) continue;
|
|
150
|
+
|
|
151
|
+
const newContent = removeHookSection(content);
|
|
152
|
+
const trimmed = newContent.trim();
|
|
153
|
+
|
|
154
|
+
if (!trimmed || trimmed === '#!/bin/sh') {
|
|
155
|
+
fs.unlinkSync(hookPath);
|
|
156
|
+
} else {
|
|
157
|
+
fs.writeFileSync(hookPath, newContent, { mode: 0o755 });
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
removed.push(hookName);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return removed;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* 프로젝트의 훅 설치 상태를 반환합니다.
|
|
168
|
+
* @param {string} projectPath
|
|
169
|
+
* @returns {{ postCommit: { installed: boolean }, prePush: { installed: boolean } }}
|
|
170
|
+
*/
|
|
171
|
+
export async function getHookStatus(projectPath) {
|
|
172
|
+
const hooksDir = getHooksDir(projectPath);
|
|
173
|
+
|
|
174
|
+
const status = {
|
|
175
|
+
postCommit: { installed: false },
|
|
176
|
+
prePush: { installed: false },
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
for (const [hookName, key] of [
|
|
180
|
+
['post-commit', 'postCommit'],
|
|
181
|
+
['pre-push', 'prePush'],
|
|
182
|
+
]) {
|
|
183
|
+
const hookPath = path.join(hooksDir, hookName);
|
|
184
|
+
if (fs.existsSync(hookPath)) {
|
|
185
|
+
const content = fs.readFileSync(hookPath, 'utf-8');
|
|
186
|
+
status[key] = { installed: content.includes(HOOK_MARKER_START) };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return status;
|
|
191
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* post-commit 훅 스크립트
|
|
4
|
+
* .git/hooks/post-commit 에서 실행됩니다.
|
|
5
|
+
*
|
|
6
|
+
* 사용: node post-commit.js <projectPath>
|
|
7
|
+
*/
|
|
8
|
+
import http from 'http';
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
const rawPath = process.argv[2] || process.cwd();
|
|
13
|
+
const projectPath = rawPath.replace(/^\/([a-z])\//i, '$1:/');
|
|
14
|
+
|
|
15
|
+
// 프로젝트 .env 로드 (PORT 등)
|
|
16
|
+
try {
|
|
17
|
+
const envPath = path.join(projectPath, '.env');
|
|
18
|
+
if (fs.existsSync(envPath)) {
|
|
19
|
+
const lines = fs.readFileSync(envPath, 'utf-8').split('\n');
|
|
20
|
+
for (const line of lines) {
|
|
21
|
+
const match = line.match(/^([A-Z0-9_]+)=(.*)$/);
|
|
22
|
+
if (match && !process.env[match[1]]) {
|
|
23
|
+
process.env[match[1]] = match[2].replace(/^['"]|['"]$/g, '');
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
} catch {}
|
|
28
|
+
|
|
29
|
+
const PORT = process.env.PORT || 50324;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 서버 실행 여부 확인 (500ms 타임아웃)
|
|
33
|
+
*/
|
|
34
|
+
function checkServerRunning() {
|
|
35
|
+
return new Promise((resolve) => {
|
|
36
|
+
const req = http.get(
|
|
37
|
+
`http://localhost:${PORT}/api/health`,
|
|
38
|
+
{ timeout: 500 },
|
|
39
|
+
(res) => resolve(res.statusCode === 200)
|
|
40
|
+
);
|
|
41
|
+
req.on('error', () => resolve(false));
|
|
42
|
+
req.on('timeout', () => {
|
|
43
|
+
req.destroy();
|
|
44
|
+
resolve(false);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 서버에 post-commit 분석 요청 (비동기, 2초 타임아웃)
|
|
51
|
+
*/
|
|
52
|
+
function notifyServer(projectPath) {
|
|
53
|
+
return new Promise((resolve) => {
|
|
54
|
+
const body = JSON.stringify({ projectPath });
|
|
55
|
+
const options = {
|
|
56
|
+
hostname: 'localhost',
|
|
57
|
+
port: PORT,
|
|
58
|
+
path: '/api/hooks/post-commit-notify',
|
|
59
|
+
method: 'POST',
|
|
60
|
+
headers: {
|
|
61
|
+
'Content-Type': 'application/json',
|
|
62
|
+
'Content-Length': Buffer.byteLength(body),
|
|
63
|
+
},
|
|
64
|
+
timeout: 2000,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const req = http.request(options, (res) => resolve(res.statusCode === 200));
|
|
68
|
+
req.on('error', () => resolve(false));
|
|
69
|
+
req.on('timeout', () => {
|
|
70
|
+
req.destroy();
|
|
71
|
+
resolve(false);
|
|
72
|
+
});
|
|
73
|
+
req.write(body);
|
|
74
|
+
req.end();
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function main() {
|
|
79
|
+
try {
|
|
80
|
+
const running = await checkServerRunning();
|
|
81
|
+
if (running) {
|
|
82
|
+
await notifyServer(projectPath);
|
|
83
|
+
}
|
|
84
|
+
// 서버가 꺼져 있으면 조용히 종료 (git 커밋을 방해하지 않음)
|
|
85
|
+
} catch {
|
|
86
|
+
// Git 커밋을 절대 방해하지 않음
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
main();
|