@decencia/ch-cli 1.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/dist/client.d.ts +15 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +117 -0
- package/dist/client.js.map +1 -0
- package/dist/commands/archives.d.ts +3 -0
- package/dist/commands/archives.d.ts.map +1 -0
- package/dist/commands/archives.js +371 -0
- package/dist/commands/archives.js.map +1 -0
- package/dist/commands/auth.d.ts +3 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +149 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/dashboard.d.ts +3 -0
- package/dist/commands/dashboard.d.ts.map +1 -0
- package/dist/commands/dashboard.js +115 -0
- package/dist/commands/dashboard.js.map +1 -0
- package/dist/commands/db-schema.d.ts +3 -0
- package/dist/commands/db-schema.d.ts.map +1 -0
- package/dist/commands/db-schema.js +76 -0
- package/dist/commands/db-schema.js.map +1 -0
- package/dist/commands/dev-status.d.ts +3 -0
- package/dist/commands/dev-status.d.ts.map +1 -0
- package/dist/commands/dev-status.js +175 -0
- package/dist/commands/dev-status.js.map +1 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +124 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/invitations.d.ts +3 -0
- package/dist/commands/invitations.d.ts.map +1 -0
- package/dist/commands/invitations.js +48 -0
- package/dist/commands/invitations.js.map +1 -0
- package/dist/commands/members.d.ts +3 -0
- package/dist/commands/members.d.ts.map +1 -0
- package/dist/commands/members.js +93 -0
- package/dist/commands/members.js.map +1 -0
- package/dist/commands/notifications.d.ts +3 -0
- package/dist/commands/notifications.d.ts.map +1 -0
- package/dist/commands/notifications.js +163 -0
- package/dist/commands/notifications.js.map +1 -0
- package/dist/commands/prd.d.ts +3 -0
- package/dist/commands/prd.d.ts.map +1 -0
- package/dist/commands/prd.js +74 -0
- package/dist/commands/prd.js.map +1 -0
- package/dist/commands/projects.d.ts +3 -0
- package/dist/commands/projects.d.ts.map +1 -0
- package/dist/commands/projects.js +215 -0
- package/dist/commands/projects.js.map +1 -0
- package/dist/commands/qna.d.ts +3 -0
- package/dist/commands/qna.d.ts.map +1 -0
- package/dist/commands/qna.js +211 -0
- package/dist/commands/qna.js.map +1 -0
- package/dist/commands/specs.d.ts +3 -0
- package/dist/commands/specs.d.ts.map +1 -0
- package/dist/commands/specs.js +277 -0
- package/dist/commands/specs.js.map +1 -0
- package/dist/commands/sprints.d.ts +3 -0
- package/dist/commands/sprints.d.ts.map +1 -0
- package/dist/commands/sprints.js +262 -0
- package/dist/commands/sprints.js.map +1 -0
- package/dist/commands/sqa.d.ts +3 -0
- package/dist/commands/sqa.d.ts.map +1 -0
- package/dist/commands/sqa.js +452 -0
- package/dist/commands/sqa.js.map +1 -0
- package/dist/config.d.ts +28 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +115 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +97 -0
- package/dist/index.js.map +1 -0
- package/dist/output.d.ts +13 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +174 -0
- package/dist/output.js.map +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +7 -0
- package/dist/utils.js.map +1 -0
- package/package.json +33 -0
- package/src/client.ts +137 -0
- package/src/commands/archives.ts +334 -0
- package/src/commands/auth.ts +154 -0
- package/src/commands/dashboard.ts +112 -0
- package/src/commands/db-schema.ts +74 -0
- package/src/commands/dev-status.ts +166 -0
- package/src/commands/init.ts +92 -0
- package/src/commands/members.ts +92 -0
- package/src/commands/notifications.ts +169 -0
- package/src/commands/prd.ts +72 -0
- package/src/commands/projects.ts +207 -0
- package/src/commands/qna.ts +205 -0
- package/src/commands/specs.ts +257 -0
- package/src/commands/sprints.ts +255 -0
- package/src/commands/sqa.ts +422 -0
- package/src/config.ts +98 -0
- package/src/index.ts +69 -0
- package/src/output.ts +186 -0
- package/src/utils.ts +3 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { createClient } from '../client';
|
|
3
|
+
import { formatOutput, printSuccess, printError, printInfo } from '../output';
|
|
4
|
+
import { parseCommaSeparated } from '../utils';
|
|
5
|
+
|
|
6
|
+
export function registerDevStatusCommand(program: Command): void {
|
|
7
|
+
const devStatus = program
|
|
8
|
+
.command('dev-status')
|
|
9
|
+
.description('개발현황 문서 관리');
|
|
10
|
+
|
|
11
|
+
// ch dev-status list
|
|
12
|
+
devStatus
|
|
13
|
+
.command('list')
|
|
14
|
+
.description('개발현황 문서 목록 조회')
|
|
15
|
+
.option('--sprint <sprintId>', '관련 스프린트 ID 필터')
|
|
16
|
+
.option('--spec <specId>', '관련 기능명세 ID 필터')
|
|
17
|
+
.option('--search <keyword>', '제목 검색')
|
|
18
|
+
.option('--sort <field>', '정렬 필드')
|
|
19
|
+
.option('--desc', '내림차순 정렬')
|
|
20
|
+
.option('--page <number>', '페이지 번호', '1')
|
|
21
|
+
.option('--per-page <number>', '페이지당 건수', '20')
|
|
22
|
+
.action(async (options) => {
|
|
23
|
+
const globalOpts = program.opts();
|
|
24
|
+
try {
|
|
25
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
26
|
+
const params: any = {};
|
|
27
|
+
if (options.sprint) params.relatedSprintId = options.sprint;
|
|
28
|
+
if (options.spec) params.relatedSpecId = options.spec;
|
|
29
|
+
if (options.search) params.search = options.search;
|
|
30
|
+
if (options.sort) params.sortBy = options.sort;
|
|
31
|
+
if (options.desc) params.sortOrder = 'desc';
|
|
32
|
+
params.page = parseInt(options.page, 10);
|
|
33
|
+
params.perPage = parseInt(options.perPage, 10);
|
|
34
|
+
|
|
35
|
+
const { data } = await client.get('/dev-status', { params });
|
|
36
|
+
formatOutput(data, globalOpts);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
// Error already handled
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// ch dev-status get
|
|
43
|
+
devStatus
|
|
44
|
+
.command('get <docId>')
|
|
45
|
+
.description('개발현황 문서 상세 조회')
|
|
46
|
+
.action(async (docId: string) => {
|
|
47
|
+
const globalOpts = program.opts();
|
|
48
|
+
try {
|
|
49
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
50
|
+
const { data } = await client.get(`/dev-status/${docId}`);
|
|
51
|
+
formatOutput(data, globalOpts);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
// Error already handled
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ch dev-status create
|
|
58
|
+
devStatus
|
|
59
|
+
.command('create')
|
|
60
|
+
.description('개발현황 문서 생성')
|
|
61
|
+
.requiredOption('--title <title>', '문서 제목')
|
|
62
|
+
.option('--content <content>', '본문 (HTML)')
|
|
63
|
+
.option('--sprints <sprintIds>', '관련 스프린트 ID (쉼표 구분)')
|
|
64
|
+
.option('--specs <specIds>', '관련 기능명세 ID (쉼표 구분)')
|
|
65
|
+
.action(async (options) => {
|
|
66
|
+
const globalOpts = program.opts();
|
|
67
|
+
try {
|
|
68
|
+
if (globalOpts.dryRun) {
|
|
69
|
+
printInfo(`[DRY-RUN] 개발현황 문서 "${options.title}" 생성 요청`);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
73
|
+
const body: any = {
|
|
74
|
+
title: options.title,
|
|
75
|
+
};
|
|
76
|
+
if (options.content) body.content = options.content;
|
|
77
|
+
if (options.sprints) body.relatedSprintIds = parseCommaSeparated(options.sprints);
|
|
78
|
+
if (options.specs) body.relatedSpecIds = parseCommaSeparated(options.specs);
|
|
79
|
+
|
|
80
|
+
const { data } = await client.post('/dev-status', body);
|
|
81
|
+
formatOutput(data, globalOpts);
|
|
82
|
+
printSuccess('개발현황 문서가 생성되었습니다.');
|
|
83
|
+
} catch (error) {
|
|
84
|
+
// Error already handled
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// ch dev-status update
|
|
89
|
+
devStatus
|
|
90
|
+
.command('update <docId>')
|
|
91
|
+
.description('개발현황 문서 수정')
|
|
92
|
+
.option('--title <title>', '문서 제목')
|
|
93
|
+
.option('--content <content>', '본문 (HTML)')
|
|
94
|
+
.option('--sprints <sprintIds>', '관련 스프린트 ID (쉼표 구분)')
|
|
95
|
+
.option('--specs <specIds>', '관련 기능명세 ID (쉼표 구분)')
|
|
96
|
+
.option('--change-note <note>', '변경 사유')
|
|
97
|
+
.option('--new-version', '새 버전으로 수정')
|
|
98
|
+
.action(async (docId: string, options) => {
|
|
99
|
+
const globalOpts = program.opts();
|
|
100
|
+
try {
|
|
101
|
+
if (globalOpts.dryRun) {
|
|
102
|
+
printInfo(`[DRY-RUN] 개발현황 문서 "${docId}" 수정 요청`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
106
|
+
const body: any = {};
|
|
107
|
+
if (options.title) body.title = options.title;
|
|
108
|
+
if (options.content) body.content = options.content;
|
|
109
|
+
if (options.sprints) body.relatedSprintIds = parseCommaSeparated(options.sprints);
|
|
110
|
+
if (options.specs) body.relatedSpecIds = parseCommaSeparated(options.specs);
|
|
111
|
+
if (options.changeNote) body.changeNote = options.changeNote;
|
|
112
|
+
|
|
113
|
+
if (Object.keys(body).length === 0) {
|
|
114
|
+
printError('수정할 필드를 지정하세요.');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const params: any = {};
|
|
119
|
+
if (options.newVersion) params.newVersion = 'true';
|
|
120
|
+
|
|
121
|
+
const { data } = await client.patch(`/dev-status/${docId}`, body, { params });
|
|
122
|
+
formatOutput(data, globalOpts);
|
|
123
|
+
printSuccess(
|
|
124
|
+
options.newVersion
|
|
125
|
+
? '개발현황 문서가 새 버전으로 수정되었습니다.'
|
|
126
|
+
: '개발현황 문서가 수정되었습니다.',
|
|
127
|
+
);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
// Error already handled
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// ch dev-status delete
|
|
134
|
+
devStatus
|
|
135
|
+
.command('delete <docId>')
|
|
136
|
+
.description('개발현황 문서 삭제')
|
|
137
|
+
.action(async (docId: string) => {
|
|
138
|
+
const globalOpts = program.opts();
|
|
139
|
+
try {
|
|
140
|
+
if (globalOpts.dryRun) {
|
|
141
|
+
printInfo(`[DRY-RUN] 개발현황 문서 "${docId}" 삭제 요청`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
145
|
+
await client.delete(`/dev-status/${docId}`);
|
|
146
|
+
printSuccess('개발현황 문서가 삭제되었습니다.');
|
|
147
|
+
} catch (error) {
|
|
148
|
+
// Error already handled
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ch dev-status versions
|
|
153
|
+
devStatus
|
|
154
|
+
.command('versions <docId>')
|
|
155
|
+
.description('개발현황 문서 버전 이력 조회')
|
|
156
|
+
.action(async (docId: string) => {
|
|
157
|
+
const globalOpts = program.opts();
|
|
158
|
+
try {
|
|
159
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
160
|
+
const { data } = await client.get(`/dev-status/${docId}/versions`);
|
|
161
|
+
formatOutput(data, globalOpts);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
// Error already handled
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { createClient } from '../client';
|
|
5
|
+
import { printSuccess, printError, printInfo } from '../output';
|
|
6
|
+
import { loadConfig, findLocalProject } from '../config';
|
|
7
|
+
|
|
8
|
+
export function registerInitCommand(program: Command): void {
|
|
9
|
+
program
|
|
10
|
+
.command('init')
|
|
11
|
+
.description('현재 디렉토리에 .ch-project 파일 생성 (로컬 프로젝트 설정)')
|
|
12
|
+
.requiredOption('--project-id <projectId>', '프로젝트 ID')
|
|
13
|
+
.option('--project-name <projectName>', '프로젝트명 (표시용)')
|
|
14
|
+
.action(async (options) => {
|
|
15
|
+
const targetPath = path.join(process.cwd(), '.ch-project');
|
|
16
|
+
|
|
17
|
+
if (fs.existsSync(targetPath)) {
|
|
18
|
+
try {
|
|
19
|
+
const existing = JSON.parse(fs.readFileSync(targetPath, 'utf-8'));
|
|
20
|
+
printError(
|
|
21
|
+
`.ch-project 파일이 이미 존재합니다 (projectId: ${existing.projectId}). 덮어쓰려면 파일을 삭제 후 다시 실행하세요.`
|
|
22
|
+
);
|
|
23
|
+
} catch {
|
|
24
|
+
printError('.ch-project 파일이 이미 존재합니다. 덮어쓰려면 파일을 삭제 후 다시 실행하세요.');
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// If projectName not given, try to fetch from API (only if API key is available)
|
|
30
|
+
let projectName = options.projectName;
|
|
31
|
+
if (!projectName) {
|
|
32
|
+
const config = loadConfig();
|
|
33
|
+
if (config?.apiKey) {
|
|
34
|
+
try {
|
|
35
|
+
const client = createClient({ projectId: options.projectId });
|
|
36
|
+
const { data } = await client.get('/projects/current');
|
|
37
|
+
projectName = data.name;
|
|
38
|
+
} catch {
|
|
39
|
+
// API call failed, proceed without name
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const content: any = { projectId: options.projectId };
|
|
45
|
+
if (projectName) content.projectName = projectName;
|
|
46
|
+
|
|
47
|
+
fs.writeFileSync(targetPath, JSON.stringify(content, null, 2) + '\n', 'utf-8');
|
|
48
|
+
printSuccess(`✅ .ch-project 파일이 생성되었습니다.`);
|
|
49
|
+
printInfo(` projectId: ${options.projectId}`);
|
|
50
|
+
if (projectName) {
|
|
51
|
+
printInfo(` projectName: ${projectName}`);
|
|
52
|
+
}
|
|
53
|
+
printInfo(` 경로: ${targetPath}`);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// ch status - show current project context
|
|
57
|
+
program
|
|
58
|
+
.command('status')
|
|
59
|
+
.description('현재 프로젝트 설정 상태 확인')
|
|
60
|
+
.action(() => {
|
|
61
|
+
const config = loadConfig();
|
|
62
|
+
const local = findLocalProject();
|
|
63
|
+
|
|
64
|
+
console.log('');
|
|
65
|
+
if (local) {
|
|
66
|
+
printSuccess(`로컬 설정 (.ch-project):`);
|
|
67
|
+
printInfo(` projectId: ${local.projectId}`);
|
|
68
|
+
if (local.projectName) printInfo(` projectName: ${local.projectName}`);
|
|
69
|
+
} else {
|
|
70
|
+
printInfo('로컬 설정 (.ch-project): 없음');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log('');
|
|
74
|
+
if (config) {
|
|
75
|
+
printSuccess('글로벌 설정 (~/.ch/config.json):');
|
|
76
|
+
printInfo(` apiUrl: ${config.apiUrl}`);
|
|
77
|
+
printInfo(` projectId: ${config.projectId || '(없음)'}`);
|
|
78
|
+
printInfo(` apiKey: ${config.apiKey ? '***설정됨***' : '(없음)'}`);
|
|
79
|
+
} else {
|
|
80
|
+
printInfo('글로벌 설정 (~/.ch/config.json): 없음');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
console.log('');
|
|
84
|
+
const effectiveProjectId = local?.projectId || config?.projectId;
|
|
85
|
+
const source = local ? '.ch-project (로컬)' : config?.projectId ? '~/.ch/config.json (글로벌)' : '없음';
|
|
86
|
+
if (effectiveProjectId) {
|
|
87
|
+
printSuccess(`→ 현재 사용할 projectId: ${effectiveProjectId} (출처: ${source})`);
|
|
88
|
+
} else {
|
|
89
|
+
printError('프로젝트가 설정되지 않았습니다. "ch init --project-id <id>" 또는 "ch auth login"을 실행하세요.');
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { createClient } from '../client';
|
|
3
|
+
import { formatOutput, printSuccess, printError, printInfo } from '../output';
|
|
4
|
+
|
|
5
|
+
export function registerMembersCommand(program: Command): void {
|
|
6
|
+
const members = program
|
|
7
|
+
.command('members')
|
|
8
|
+
.description('멤버 관리');
|
|
9
|
+
|
|
10
|
+
// ch members list
|
|
11
|
+
members
|
|
12
|
+
.command('list')
|
|
13
|
+
.description('멤버 목록 조회')
|
|
14
|
+
.action(async () => {
|
|
15
|
+
const globalOpts = program.opts();
|
|
16
|
+
try {
|
|
17
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
18
|
+
const { data } = await client.get('/members');
|
|
19
|
+
formatOutput(data, globalOpts);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
// Error already handled
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// ch members invite
|
|
26
|
+
members
|
|
27
|
+
.command('invite')
|
|
28
|
+
.description('멤버 추가 (이메일로 사용자를 찾아 바로 권한 부여)')
|
|
29
|
+
.requiredOption('--email <email>', '추가할 사용자 이메일')
|
|
30
|
+
.requiredOption('--role <role>', '역할 (developer|client)')
|
|
31
|
+
.action(async (options) => {
|
|
32
|
+
const globalOpts = program.opts();
|
|
33
|
+
try {
|
|
34
|
+
if (globalOpts.dryRun) {
|
|
35
|
+
printInfo(`[DRY-RUN] "${options.email}" 멤버 추가 요청`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
39
|
+
const { data } = await client.post('/members/invite', {
|
|
40
|
+
email: options.email,
|
|
41
|
+
role: options.role,
|
|
42
|
+
});
|
|
43
|
+
formatOutput(data, globalOpts);
|
|
44
|
+
printSuccess(data.message || '멤버가 추가되었습니다.');
|
|
45
|
+
} catch (error) {
|
|
46
|
+
// Error already handled
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// ch members set-role
|
|
51
|
+
members
|
|
52
|
+
.command('set-role <userId>')
|
|
53
|
+
.description('멤버 역할 변경')
|
|
54
|
+
.requiredOption('--role <role>', '새 역할 (admin|developer|client)')
|
|
55
|
+
.action(async (userId: string, options) => {
|
|
56
|
+
const globalOpts = program.opts();
|
|
57
|
+
try {
|
|
58
|
+
if (globalOpts.dryRun) {
|
|
59
|
+
printInfo(`[DRY-RUN] 멤버 "${userId}" 역할을 "${options.role}"로 변경 요청`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
63
|
+
const { data } = await client.patch(`/members/${userId}/role`, {
|
|
64
|
+
role: options.role,
|
|
65
|
+
});
|
|
66
|
+
formatOutput(data, globalOpts);
|
|
67
|
+
printSuccess(`멤버 역할이 "${options.role}"로 변경되었습니다.`);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
// Error already handled
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// ch members remove
|
|
74
|
+
members
|
|
75
|
+
.command('remove <userId>')
|
|
76
|
+
.description('멤버 제거')
|
|
77
|
+
.action(async (userId: string) => {
|
|
78
|
+
const globalOpts = program.opts();
|
|
79
|
+
try {
|
|
80
|
+
if (globalOpts.dryRun) {
|
|
81
|
+
printInfo(`[DRY-RUN] 멤버 "${userId}" 제거 요청`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
85
|
+
await client.delete(`/members/${userId}`);
|
|
86
|
+
printSuccess('멤버가 제거되었습니다.');
|
|
87
|
+
} catch (error) {
|
|
88
|
+
// Error already handled
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { createClient } from '../client';
|
|
3
|
+
import { formatOutput, printSuccess, printError, printInfo } from '../output';
|
|
4
|
+
|
|
5
|
+
export function registerNotificationsCommand(program: Command): void {
|
|
6
|
+
const notifications = program
|
|
7
|
+
.command('notifications')
|
|
8
|
+
.description('알림 설정 관리');
|
|
9
|
+
|
|
10
|
+
// ch notifications get
|
|
11
|
+
notifications
|
|
12
|
+
.command('get')
|
|
13
|
+
.description('현재 알림 설정 조회')
|
|
14
|
+
.action(async () => {
|
|
15
|
+
const globalOpts = program.opts();
|
|
16
|
+
try {
|
|
17
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
18
|
+
const { data } = await client.get('/notifications');
|
|
19
|
+
formatOutput(data, globalOpts);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
// Error already handled
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// ch notifications set-webhook
|
|
26
|
+
notifications
|
|
27
|
+
.command('set-webhook')
|
|
28
|
+
.description('Webhook URL 설정')
|
|
29
|
+
.option('--kakao <url>', '카카오워크 Webhook URL')
|
|
30
|
+
.option('--slack <url>', 'Slack Webhook URL')
|
|
31
|
+
.action(async (options) => {
|
|
32
|
+
const globalOpts = program.opts();
|
|
33
|
+
try {
|
|
34
|
+
if (!options.kakao && !options.slack) {
|
|
35
|
+
printError('--kakao 또는 --slack 옵션을 지정하세요.');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (globalOpts.dryRun) {
|
|
40
|
+
printInfo('[DRY-RUN] Webhook URL 설정 요청');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const body: any = {};
|
|
45
|
+
if (options.kakao) body.kakaoworkWebhookUrl = options.kakao;
|
|
46
|
+
if (options.slack) body.slackWebhookUrl = options.slack;
|
|
47
|
+
|
|
48
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
49
|
+
const { data } = await client.patch('/notifications/webhook', body);
|
|
50
|
+
formatOutput(data, globalOpts);
|
|
51
|
+
printSuccess('Webhook URL이 설정되었습니다.');
|
|
52
|
+
} catch (error) {
|
|
53
|
+
// Error already handled
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ch notifications remove-webhook
|
|
58
|
+
notifications
|
|
59
|
+
.command('remove-webhook')
|
|
60
|
+
.description('Webhook URL 제거')
|
|
61
|
+
.option('--kakao', '카카오워크 Webhook 제거')
|
|
62
|
+
.option('--slack', 'Slack Webhook 제거')
|
|
63
|
+
.action(async (options) => {
|
|
64
|
+
const globalOpts = program.opts();
|
|
65
|
+
try {
|
|
66
|
+
if (!options.kakao && !options.slack) {
|
|
67
|
+
printError('--kakao 또는 --slack 옵션을 지정하세요.');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (globalOpts.dryRun) {
|
|
72
|
+
printInfo('[DRY-RUN] Webhook URL 제거 요청');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
77
|
+
|
|
78
|
+
if (options.kakao) {
|
|
79
|
+
await client.delete('/notifications/webhook/kakao');
|
|
80
|
+
printSuccess('카카오워크 Webhook URL이 제거되었습니다.');
|
|
81
|
+
}
|
|
82
|
+
if (options.slack) {
|
|
83
|
+
await client.delete('/notifications/webhook/slack');
|
|
84
|
+
printSuccess('Slack Webhook URL이 제거되었습니다.');
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
// Error already handled
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// ch notifications test
|
|
92
|
+
notifications
|
|
93
|
+
.command('test')
|
|
94
|
+
.description('테스트 알림 발송')
|
|
95
|
+
.option('--kakao', '카카오워크로 테스트')
|
|
96
|
+
.option('--slack', 'Slack으로 테스트')
|
|
97
|
+
.action(async (options) => {
|
|
98
|
+
const globalOpts = program.opts();
|
|
99
|
+
try {
|
|
100
|
+
if (!options.kakao && !options.slack) {
|
|
101
|
+
printError('--kakao 또는 --slack 옵션을 지정하세요.');
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
106
|
+
|
|
107
|
+
if (options.kakao) {
|
|
108
|
+
const { data } = await client.post('/notifications/test/kakao');
|
|
109
|
+
formatOutput(data, globalOpts);
|
|
110
|
+
printSuccess('카카오워크 테스트 알림이 발송되었습니다.');
|
|
111
|
+
}
|
|
112
|
+
if (options.slack) {
|
|
113
|
+
const { data } = await client.post('/notifications/test/slack');
|
|
114
|
+
formatOutput(data, globalOpts);
|
|
115
|
+
printSuccess('Slack 테스트 알림이 발송되었습니다.');
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
// Error already handled
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// ch notifications toggle
|
|
123
|
+
notifications
|
|
124
|
+
.command('toggle <eventName>')
|
|
125
|
+
.description('이벤트 알림 ON/OFF 토글')
|
|
126
|
+
.option('--on', '알림 활성화')
|
|
127
|
+
.option('--off', '알림 비활성화')
|
|
128
|
+
.action(async (eventName: string, options) => {
|
|
129
|
+
const globalOpts = program.opts();
|
|
130
|
+
try {
|
|
131
|
+
if (!options.on && !options.off) {
|
|
132
|
+
printError('--on 또는 --off 옵션을 지정하세요.');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (globalOpts.dryRun) {
|
|
137
|
+
const state = options.on ? 'ON' : 'OFF';
|
|
138
|
+
printInfo(`[DRY-RUN] 이벤트 "${eventName}" ${state} 요청`);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const enabled = !!options.on;
|
|
143
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
144
|
+
const { data } = await client.patch(`/notifications/events/${eventName}`, { enabled });
|
|
145
|
+
formatOutput(data, globalOpts);
|
|
146
|
+
printSuccess(`이벤트 "${eventName}"이 ${enabled ? '활성화' : '비활성화'}되었습니다.`);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
// Error already handled
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// ch notifications logs
|
|
153
|
+
notifications
|
|
154
|
+
.command('logs')
|
|
155
|
+
.description('알림 발송 로그 조회')
|
|
156
|
+
.option('--page <number>', '페이지 번호', '1')
|
|
157
|
+
.action(async (options) => {
|
|
158
|
+
const globalOpts = program.opts();
|
|
159
|
+
try {
|
|
160
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
161
|
+
const { data } = await client.get('/notifications/logs', {
|
|
162
|
+
params: { page: parseInt(options.page, 10) },
|
|
163
|
+
});
|
|
164
|
+
formatOutput(data, globalOpts);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
// Error already handled
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { createClient } from '../client';
|
|
3
|
+
import { formatOutput, printSuccess, printError, printInfo } from '../output';
|
|
4
|
+
|
|
5
|
+
export function registerPrdCommand(program: Command): void {
|
|
6
|
+
const prd = program
|
|
7
|
+
.command('prd')
|
|
8
|
+
.description('PRD(Product Requirements Document) 관리');
|
|
9
|
+
|
|
10
|
+
// ch prd get
|
|
11
|
+
prd
|
|
12
|
+
.command('get')
|
|
13
|
+
.description('PRD 조회')
|
|
14
|
+
.action(async () => {
|
|
15
|
+
const globalOpts = program.opts();
|
|
16
|
+
try {
|
|
17
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
18
|
+
const { data } = await client.get('/prd');
|
|
19
|
+
if (!data) {
|
|
20
|
+
printInfo('PRD가 아직 작성되지 않았습니다.');
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
formatOutput(data, globalOpts);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
// Error already handled
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// ch prd set
|
|
30
|
+
prd
|
|
31
|
+
.command('set')
|
|
32
|
+
.description('PRD 생성/수정')
|
|
33
|
+
.requiredOption('--title <title>', 'PRD 제목')
|
|
34
|
+
.requiredOption('--content <content>', 'PRD 본문 (마크다운)')
|
|
35
|
+
.option('--new-version', '새 버전으로 기록')
|
|
36
|
+
.action(async (options) => {
|
|
37
|
+
const globalOpts = program.opts();
|
|
38
|
+
try {
|
|
39
|
+
if (globalOpts.dryRun) {
|
|
40
|
+
printInfo(`[DRY-RUN] PRD "${options.title}" 생성/수정 요청`);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
44
|
+
const params: any = {};
|
|
45
|
+
if (options.newVersion) params.newVersion = 'true';
|
|
46
|
+
|
|
47
|
+
const { data } = await client.put('/prd', {
|
|
48
|
+
title: options.title,
|
|
49
|
+
content: options.content,
|
|
50
|
+
}, { params });
|
|
51
|
+
formatOutput(data, globalOpts);
|
|
52
|
+
printSuccess(data.message || 'PRD가 저장되었습니다.');
|
|
53
|
+
} catch (error) {
|
|
54
|
+
// Error already handled
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// ch prd versions
|
|
59
|
+
prd
|
|
60
|
+
.command('versions')
|
|
61
|
+
.description('PRD 버전 이력 조회')
|
|
62
|
+
.action(async () => {
|
|
63
|
+
const globalOpts = program.opts();
|
|
64
|
+
try {
|
|
65
|
+
const client = createClient({ projectId: globalOpts.project });
|
|
66
|
+
const { data } = await client.get('/prd/versions');
|
|
67
|
+
formatOutput(data, globalOpts);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
// Error already handled
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|