@decencia/ch-cli 1.0.0 → 1.1.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.
@@ -1,257 +0,0 @@
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 registerSpecsCommand(program: Command): void {
7
- const specs = program
8
- .command('specs')
9
- .description('기능명세 관리');
10
-
11
- // ch specs meta
12
- specs
13
- .command('meta')
14
- .description('기능명세 메타데이터 조회 (프로젝트 스키마 + 사용 현황)')
15
- .action(async () => {
16
- const globalOpts = program.opts();
17
- try {
18
- const client = createClient({ projectId: globalOpts.project });
19
- const { data } = await client.get('/specs/meta');
20
- formatOutput(data, globalOpts);
21
- } catch (error) {
22
- // Error already handled
23
- }
24
- });
25
-
26
- // ch specs list
27
- specs
28
- .command('list')
29
- .description('기능명세 목록 조회')
30
- .option('--device <device>', '디바이스 필터 (해당 값을 포함하는 기능명세)')
31
- .option('--domain <domain>', '도메인 필터')
32
- .option('--type <featureType>', '기능유형 필터 (해당 값을 포함하는 기능명세)')
33
- .option('--status <status>', '진행상황 필터')
34
- .option('--group-by <field>', '그룹핑 (devices|domain|featureTypes|status)')
35
- .option('--search <keyword>', '이름 검색')
36
- .option('--sort <field>', '정렬 필드')
37
- .option('--desc', '내림차순 정렬')
38
- .option('--page <number>', '페이지 번호', '1')
39
- .option('--per-page <number>', '페이지당 건수', '20')
40
- .action(async (options) => {
41
- const globalOpts = program.opts();
42
- try {
43
- const client = createClient({ projectId: globalOpts.project });
44
- const params: any = {};
45
- if (options.device) params.device = options.device;
46
- if (options.domain) params.domain = options.domain;
47
- if (options.type) params.featureType = options.type;
48
- if (options.status) params.status = options.status;
49
- if (options.groupBy) params.groupBy = options.groupBy;
50
- if (options.search) params.search = options.search;
51
- if (options.sort) params.sortBy = options.sort;
52
- if (options.desc) params.sortOrder = 'desc';
53
- params.page = parseInt(options.page, 10);
54
- params.perPage = parseInt(options.perPage, 10);
55
-
56
- const { data } = await client.get('/specs', { params });
57
- formatOutput(data, globalOpts);
58
- } catch (error) {
59
- // Error already handled
60
- }
61
- });
62
-
63
- // ch specs get
64
- specs
65
- .command('get <specId>')
66
- .description('기능명세 상세 조회')
67
- .action(async (specId: string) => {
68
- const globalOpts = program.opts();
69
- try {
70
- const client = createClient({ projectId: globalOpts.project });
71
- const { data } = await client.get(`/specs/${specId}`);
72
- formatOutput(data, globalOpts);
73
- } catch (error) {
74
- // Error already handled
75
- }
76
- });
77
-
78
- // ch specs create
79
- specs
80
- .command('create')
81
- .description('기능명세 생성')
82
- .requiredOption('--name <name>', '기능명')
83
- .requiredOption('--device <devices>', '디바이스 (쉼표 구분, 예: web,app)')
84
- .requiredOption('--domain <domain>', '도메인')
85
- .requiredOption('--type <featureTypes>', '기능유형 (쉼표 구분, 예: 인증,마이페이지)')
86
- .option('--permission <permissions>', '권한 (쉼표 구분, 예: 비회원,회원)')
87
- .option('--content <content>', '내용')
88
- .option('--dto <dto>', 'DTO')
89
- .option('--status <status>', '진행상황')
90
- .option('--note <note>', '비고')
91
- .action(async (options) => {
92
- const globalOpts = program.opts();
93
- try {
94
- if (globalOpts.dryRun) {
95
- printInfo(`[DRY-RUN] 기능명세 "${options.name}" 생성 요청`);
96
- return;
97
- }
98
- const client = createClient({ projectId: globalOpts.project });
99
- const body: any = {
100
- name: options.name,
101
- devices: parseCommaSeparated(options.device),
102
- domain: options.domain,
103
- featureTypes: parseCommaSeparated(options.type),
104
- };
105
- if (options.permission) body.permissions = parseCommaSeparated(options.permission);
106
- if (options.content) body.content = options.content;
107
- if (options.dto) body.dto = options.dto;
108
- if (options.status) body.status = options.status;
109
- if (options.note) body.note = options.note;
110
-
111
- const { data } = await client.post('/specs', body);
112
- formatOutput(data, globalOpts);
113
- printSuccess('기능명세가 생성되었습니다.');
114
- } catch (error) {
115
- // Error already handled
116
- }
117
- });
118
-
119
- // ch specs update
120
- specs
121
- .command('update <specId>')
122
- .description('기능명세 수정')
123
- .option('--name <name>', '기능명')
124
- .option('--device <devices>', '디바이스 (쉼표 구분)')
125
- .option('--domain <domain>', '도메인')
126
- .option('--type <featureTypes>', '기능유형 (쉼표 구분)')
127
- .option('--permission <permissions>', '권한 (쉼표 구분)')
128
- .option('--content <content>', '내용')
129
- .option('--dto <dto>', 'DTO')
130
- .option('--status <status>', '진행상황')
131
- .option('--note <note>', '비고')
132
- .option('--new-version', '새 버전으로 수정')
133
- .action(async (specId: string, options) => {
134
- const globalOpts = program.opts();
135
- try {
136
- if (globalOpts.dryRun) {
137
- printInfo(`[DRY-RUN] 기능명세 "${specId}" 수정 요청`);
138
- return;
139
- }
140
- const client = createClient({ projectId: globalOpts.project });
141
- const body: any = {};
142
- if (options.name) body.name = options.name;
143
- if (options.device) body.devices = parseCommaSeparated(options.device);
144
- if (options.domain) body.domain = options.domain;
145
- if (options.type) body.featureTypes = parseCommaSeparated(options.type);
146
- if (options.permission) body.permissions = parseCommaSeparated(options.permission);
147
- if (options.content) body.content = options.content;
148
- if (options.dto) body.dto = options.dto;
149
- if (options.status) body.status = options.status;
150
- if (options.note) body.note = options.note;
151
-
152
- if (Object.keys(body).length === 0) {
153
- printError('수정할 필드를 지정하세요.');
154
- return;
155
- }
156
-
157
- const params: any = {};
158
- if (options.newVersion) params.newVersion = 'true';
159
-
160
- const { data } = await client.patch(`/specs/${specId}`, body, { params });
161
- formatOutput(data, globalOpts);
162
- printSuccess(options.newVersion ? '기능명세가 새 버전으로 수정되었습니다.' : '기능명세가 수정되었습니다.');
163
- } catch (error) {
164
- // Error already handled
165
- }
166
- });
167
-
168
- // ch specs set-status
169
- specs
170
- .command('set-status <specId> <status>')
171
- .description('기능명세 진행상황 변경')
172
- .action(async (specId: string, status: string) => {
173
- const globalOpts = program.opts();
174
- try {
175
- if (globalOpts.dryRun) {
176
- printInfo(`[DRY-RUN] 기능명세 "${specId}" 상태를 "${status}"로 변경 요청`);
177
- return;
178
- }
179
- const client = createClient({ projectId: globalOpts.project });
180
- const { data } = await client.patch(`/specs/${specId}/status`, { status });
181
- formatOutput(data, globalOpts);
182
- printSuccess(`기능명세 상태가 "${status}"로 변경되었습니다.`);
183
- } catch (error) {
184
- // Error already handled
185
- }
186
- });
187
-
188
- // ch specs delete
189
- specs
190
- .command('delete <specId>')
191
- .description('기능명세 삭제')
192
- .action(async (specId: string) => {
193
- const globalOpts = program.opts();
194
- try {
195
- if (globalOpts.dryRun) {
196
- printInfo(`[DRY-RUN] 기능명세 "${specId}" 삭제 요청`);
197
- return;
198
- }
199
- const client = createClient({ projectId: globalOpts.project });
200
- await client.delete(`/specs/${specId}`);
201
- printSuccess('기능명세가 삭제되었습니다.');
202
- } catch (error) {
203
- // Error already handled
204
- }
205
- });
206
-
207
- // ch specs versions
208
- specs
209
- .command('versions <specId>')
210
- .description('기능명세 버전 이력 조회')
211
- .action(async (specId: string) => {
212
- const globalOpts = program.opts();
213
- try {
214
- const client = createClient({ projectId: globalOpts.project });
215
- const { data } = await client.get(`/specs/${specId}/versions`);
216
- formatOutput(data, globalOpts);
217
- } catch (error) {
218
- // Error already handled
219
- }
220
- });
221
-
222
- // ch specs related
223
- specs
224
- .command('related <specId>')
225
- .description('기능명세 관련 데이터 조회')
226
- .requiredOption('--type <type>', '관련 데이터 유형 (sprints|qna|sqa)')
227
- .action(async (specId: string, options) => {
228
- const globalOpts = program.opts();
229
- try {
230
- const client = createClient({ projectId: globalOpts.project });
231
- const { data } = await client.get(`/specs/${specId}/related`, {
232
- params: { type: options.type },
233
- });
234
- formatOutput(data, globalOpts);
235
- } catch (error) {
236
- // Error already handled
237
- }
238
- });
239
-
240
- // ch specs export
241
- specs
242
- .command('export')
243
- .description('기능명세 전체 내보내기')
244
- .option('--format <format>', '출력 형식 (json|csv)', 'json')
245
- .action(async (options) => {
246
- const globalOpts = program.opts();
247
- try {
248
- const client = createClient({ projectId: globalOpts.project });
249
- const { data } = await client.get('/specs/export', {
250
- params: { format: options.format },
251
- });
252
- formatOutput(data, globalOpts);
253
- } catch (error) {
254
- // Error already handled
255
- }
256
- });
257
- }
@@ -1,255 +0,0 @@
1
- import { Command } from 'commander';
2
- import { createClient } from '../client';
3
- import { formatOutput, printSuccess, printError, printInfo } from '../output';
4
-
5
- export function registerSprintsCommand(program: Command): void {
6
- const sprints = program
7
- .command('sprints')
8
- .description('스프린트 관리');
9
-
10
- // ch sprints list
11
- sprints
12
- .command('list')
13
- .description('스프린트 목록 조회')
14
- .option('--status <status>', '상태 필터 (scheduled|inProgress|completed)')
15
- .option('--sort <field>', '정렬 필드')
16
- .option('--desc', '내림차순 정렬')
17
- .option('--page <number>', '페이지 번호', '1')
18
- .action(async (options) => {
19
- const globalOpts = program.opts();
20
- try {
21
- const client = createClient({ projectId: globalOpts.project });
22
- const params: any = {};
23
- if (options.status) params.status = options.status;
24
- if (options.sort) params.sortBy = options.sort;
25
- if (options.desc) params.sortOrder = 'desc';
26
- params.page = parseInt(options.page, 10);
27
-
28
- const { data } = await client.get('/sprints', { params });
29
- formatOutput(data, globalOpts);
30
- } catch (error) {
31
- // Error already handled
32
- }
33
- });
34
-
35
- // ch sprints get
36
- sprints
37
- .command('get <sprintId>')
38
- .description('스프린트 상세 조회')
39
- .action(async (sprintId: string) => {
40
- const globalOpts = program.opts();
41
- try {
42
- const client = createClient({ projectId: globalOpts.project });
43
- const { data } = await client.get(`/sprints/${sprintId}`);
44
- formatOutput(data, globalOpts);
45
- } catch (error) {
46
- // Error already handled
47
- }
48
- });
49
-
50
- // ch sprints create
51
- sprints
52
- .command('create')
53
- .description('스프린트 생성')
54
- .requiredOption('--name <name>', 'Sprint명')
55
- .requiredOption('--start <date>', '시작일 (YYYY-MM-DD)')
56
- .requiredOption('--end <date>', '종료일 (YYYY-MM-DD)')
57
- .option('--content <content>', '내용')
58
- .option('--status <status>', '상태')
59
- .option('--specs <specIds>', '연결할 기능명세 ID (쉼표 구분)')
60
- .action(async (options) => {
61
- const globalOpts = program.opts();
62
- try {
63
- if (globalOpts.dryRun) {
64
- printInfo(`[DRY-RUN] 스프린트 "${options.name}" 생성 요청`);
65
- return;
66
- }
67
- const client = createClient({ projectId: globalOpts.project });
68
- const body: any = {
69
- name: options.name,
70
- startDate: options.start,
71
- endDate: options.end,
72
- };
73
- if (options.content) body.content = options.content;
74
- if (options.status) body.status = options.status;
75
- if (options.specs) body.relatedSpecs = options.specs.split(',').map((s: string) => s.trim());
76
-
77
- const { data } = await client.post('/sprints', body);
78
- formatOutput(data, globalOpts);
79
- printSuccess('스프린트가 생성되었습니다.');
80
- } catch (error) {
81
- // Error already handled
82
- }
83
- });
84
-
85
- // ch sprints update
86
- sprints
87
- .command('update <sprintId>')
88
- .description('스프린트 수정')
89
- .option('--name <name>', 'Sprint명')
90
- .option('--content <content>', '내용')
91
- .option('--status <status>', '상태')
92
- .action(async (sprintId: string, options) => {
93
- const globalOpts = program.opts();
94
- try {
95
- if (globalOpts.dryRun) {
96
- printInfo(`[DRY-RUN] 스프린트 "${sprintId}" 수정 요청`);
97
- return;
98
- }
99
- const body: any = {};
100
- if (options.name) body.name = options.name;
101
- if (options.content) body.content = options.content;
102
- if (options.status) body.status = options.status;
103
-
104
- if (Object.keys(body).length === 0) {
105
- printError('수정할 필드를 지정해주세요.');
106
- return;
107
- }
108
-
109
- const client = createClient({ projectId: globalOpts.project });
110
- const { data } = await client.patch(`/sprints/${sprintId}`, body);
111
- formatOutput(data, globalOpts);
112
- printSuccess('스프린트가 수정되었습니다.');
113
- } catch (error) {
114
- // Error already handled
115
- }
116
- });
117
-
118
- // ch sprints set-status
119
- sprints
120
- .command('set-status <sprintId> <status>')
121
- .description('스프린트 상태 변경')
122
- .action(async (sprintId: string, status: string) => {
123
- const globalOpts = program.opts();
124
- try {
125
- if (globalOpts.dryRun) {
126
- printInfo(`[DRY-RUN] 스프린트 "${sprintId}" 상태를 "${status}"로 변경 요청`);
127
- return;
128
- }
129
- const client = createClient({ projectId: globalOpts.project });
130
- const { data } = await client.patch(`/sprints/${sprintId}/status`, { status });
131
- formatOutput(data, globalOpts);
132
- printSuccess(`스프린트 상태가 "${status}"로 변경되었습니다.`);
133
- } catch (error) {
134
- // Error already handled
135
- }
136
- });
137
-
138
- // ch sprints set-dates
139
- sprints
140
- .command('set-dates <sprintId>')
141
- .description('스프린트 기간 변경')
142
- .requiredOption('--start <date>', '시작일 (YYYY-MM-DD)')
143
- .requiredOption('--end <date>', '종료일 (YYYY-MM-DD)')
144
- .action(async (sprintId: string, options) => {
145
- const globalOpts = program.opts();
146
- try {
147
- if (globalOpts.dryRun) {
148
- printInfo(`[DRY-RUN] 스프린트 "${sprintId}" 기간 변경 요청`);
149
- return;
150
- }
151
- const client = createClient({ projectId: globalOpts.project });
152
- const { data } = await client.patch(`/sprints/${sprintId}/dates`, {
153
- startDate: options.start,
154
- endDate: options.end,
155
- });
156
- formatOutput(data, globalOpts);
157
- printSuccess('스프린트 기간이 변경되었습니다.');
158
- } catch (error) {
159
- // Error already handled
160
- }
161
- });
162
-
163
- // ch sprints add-specs
164
- sprints
165
- .command('add-specs <sprintId> <specIds>')
166
- .description('스프린트에 기능명세 연결 (쉼표 구분)')
167
- .action(async (sprintId: string, specIds: string) => {
168
- const globalOpts = program.opts();
169
- try {
170
- if (globalOpts.dryRun) {
171
- printInfo(`[DRY-RUN] 스프린트 "${sprintId}"에 기능명세 연결 요청`);
172
- return;
173
- }
174
- const client = createClient({ projectId: globalOpts.project });
175
- const ids = specIds.split(',').map((s) => s.trim());
176
- const { data } = await client.post(`/sprints/${sprintId}/specs`, { specIds: ids });
177
- formatOutput(data, globalOpts);
178
- printSuccess('기능명세가 스프린트에 연결되었습니다.');
179
- } catch (error) {
180
- // Error already handled
181
- }
182
- });
183
-
184
- // ch sprints remove-specs
185
- sprints
186
- .command('remove-specs <sprintId> <specIds>')
187
- .description('스프린트에서 기능명세 연결 해제 (쉼표 구분)')
188
- .action(async (sprintId: string, specIds: string) => {
189
- const globalOpts = program.opts();
190
- try {
191
- if (globalOpts.dryRun) {
192
- printInfo(`[DRY-RUN] 스프린트 "${sprintId}" 기능명세 해제 요청`);
193
- return;
194
- }
195
- const client = createClient({ projectId: globalOpts.project });
196
- const ids = specIds.split(',').map((s) => s.trim());
197
- await client.delete(`/sprints/${sprintId}/specs`, { data: { specIds: ids } });
198
- printSuccess('기능명세 연결이 해제되었습니다.');
199
- } catch (error) {
200
- // Error already handled
201
- }
202
- });
203
-
204
- // ch sprints delete
205
- sprints
206
- .command('delete <sprintId>')
207
- .description('스프린트 삭제')
208
- .action(async (sprintId: string) => {
209
- const globalOpts = program.opts();
210
- try {
211
- if (globalOpts.dryRun) {
212
- printInfo(`[DRY-RUN] 스프린트 "${sprintId}" 삭제 요청`);
213
- return;
214
- }
215
- const client = createClient({ projectId: globalOpts.project });
216
- await client.delete(`/sprints/${sprintId}`);
217
- printSuccess('스프린트가 삭제되었습니다.');
218
- } catch (error) {
219
- // Error already handled
220
- }
221
- });
222
-
223
- // ch sprints progress
224
- sprints
225
- .command('progress <sprintId>')
226
- .description('스프린트 진행률 조회')
227
- .action(async (sprintId: string) => {
228
- const globalOpts = program.opts();
229
- try {
230
- const client = createClient({ projectId: globalOpts.project });
231
- const { data } = await client.get(`/sprints/${sprintId}/progress`);
232
- formatOutput(data, globalOpts);
233
- } catch (error) {
234
- // Error already handled
235
- }
236
- });
237
-
238
- // ch sprints timeline
239
- sprints
240
- .command('timeline')
241
- .description('스프린트 타임라인 조회')
242
- .option('--weeks <number>', '주 수', '8')
243
- .action(async (options) => {
244
- const globalOpts = program.opts();
245
- try {
246
- const client = createClient({ projectId: globalOpts.project });
247
- const { data } = await client.get('/sprints/timeline', {
248
- params: { weeks: parseInt(options.weeks, 10) },
249
- });
250
- formatOutput(data, globalOpts);
251
- } catch (error) {
252
- // Error already handled
253
- }
254
- });
255
- }