@decencia/ch-cli 1.0.0 → 1.2.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,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
- }
@@ -1,422 +0,0 @@
1
- import { Command } from 'commander';
2
- import * as fs from 'fs';
3
- import * as path from 'path';
4
- import FormData from 'form-data';
5
- import { createClient } from '../client';
6
- import { formatOutput, printSuccess, printError, printInfo } from '../output';
7
-
8
- export function registerSqaCommand(program: Command): void {
9
- const sqa = program
10
- .command('sqa')
11
- .description('SQA 관리 (시트 + 수행)');
12
-
13
- // ══════════════════════════════════════════════════
14
- // Sheet (시트 템플릿) commands
15
- // ══════════════════════════════════════════════════
16
-
17
- // ch sqa list — 시트 목록
18
- sqa
19
- .command('list')
20
- .description('SQA 시트 목록 조회')
21
- .option('--sort <field>', '정렬 필드')
22
- .option('--desc', '내림차순 정렬')
23
- .option('--page <number>', '페이지 번호', '1')
24
- .action(async (options) => {
25
- const globalOpts = program.opts();
26
- try {
27
- const client = createClient({ projectId: globalOpts.project });
28
- const params: any = {};
29
- if (options.sort) params.sortBy = options.sort;
30
- if (options.desc) params.sortOrder = 'desc';
31
- params.page = parseInt(options.page, 10);
32
-
33
- const { data } = await client.get('/sqa', { params });
34
- formatOutput(data, globalOpts);
35
- } catch (error) {
36
- // Error already handled
37
- }
38
- });
39
-
40
- // ch sqa get <sheetId> — 시트 상세
41
- sqa
42
- .command('get <sheetId>')
43
- .description('SQA 시트 상세 조회')
44
- .action(async (sheetId: string) => {
45
- const globalOpts = program.opts();
46
- try {
47
- const client = createClient({ projectId: globalOpts.project });
48
- const { data } = await client.get(`/sqa/${sheetId}`);
49
- formatOutput(data, globalOpts);
50
- } catch (error) {
51
- // Error already handled
52
- }
53
- });
54
-
55
- // ch sqa create — 시트 생성
56
- sqa
57
- .command('create')
58
- .description('SQA 시트 생성')
59
- .requiredOption('--name <name>', '시트명')
60
- .action(async (options) => {
61
- const globalOpts = program.opts();
62
- try {
63
- if (globalOpts.dryRun) {
64
- printInfo(`[DRY-RUN] SQA 시트 "${options.name}" 생성 요청`);
65
- return;
66
- }
67
- const client = createClient({ projectId: globalOpts.project });
68
- const { data } = await client.post('/sqa', {
69
- name: options.name,
70
- });
71
- formatOutput(data, globalOpts);
72
- printSuccess('SQA 시트가 생성되었습니다.');
73
- } catch (error) {
74
- // Error already handled
75
- }
76
- });
77
-
78
- // ch sqa update <sheetId> — 시트 수정
79
- sqa
80
- .command('update <sheetId>')
81
- .description('SQA 시트 수정')
82
- .option('--name <name>', '시트명')
83
- .action(async (sheetId: string, options) => {
84
- const globalOpts = program.opts();
85
- try {
86
- if (globalOpts.dryRun) {
87
- printInfo(`[DRY-RUN] SQA 시트 "${sheetId}" 수정 요청`);
88
- return;
89
- }
90
- const body: any = {};
91
- if (options.name) body.name = options.name;
92
-
93
- if (Object.keys(body).length === 0) {
94
- printError('수정할 필드를 지정하세요 (--name).');
95
- return;
96
- }
97
-
98
- const client = createClient({ projectId: globalOpts.project });
99
- const { data } = await client.patch(`/sqa/${sheetId}`, body);
100
- formatOutput(data, globalOpts);
101
- printSuccess('SQA 시트가 수정되었습니다.');
102
- } catch (error) {
103
- // Error already handled
104
- }
105
- });
106
-
107
- // ch sqa delete <sheetId> — 시트 삭제
108
- sqa
109
- .command('delete <sheetId>')
110
- .description('SQA 시트 삭제')
111
- .action(async (sheetId: string) => {
112
- const globalOpts = program.opts();
113
- try {
114
- if (globalOpts.dryRun) {
115
- printInfo(`[DRY-RUN] SQA 시트 "${sheetId}" 삭제 요청`);
116
- return;
117
- }
118
- const client = createClient({ projectId: globalOpts.project });
119
- await client.delete(`/sqa/${sheetId}`);
120
- printSuccess('SQA 시트가 삭제되었습니다.');
121
- } catch (error) {
122
- // Error already handled
123
- }
124
- });
125
-
126
- // ch sqa add-item <sheetId> — 시트에 테스트 항목 추가
127
- sqa
128
- .command('add-item <sheetId>')
129
- .description('SQA 시트에 테스트 항목 추가')
130
- .requiredOption('--test <testItem>', '테스트 항목명')
131
- .option('--spec <specId>', '관련 기능명세 ID')
132
- .action(async (sheetId: string, options) => {
133
- const globalOpts = program.opts();
134
- try {
135
- if (globalOpts.dryRun) {
136
- printInfo(`[DRY-RUN] SQA 시트 "${sheetId}"에 항목 추가 요청`);
137
- return;
138
- }
139
- const client = createClient({ projectId: globalOpts.project });
140
- const { data } = await client.post(`/sqa/${sheetId}/items`, {
141
- testItem: options.test,
142
- relatedSpec: options.spec || '',
143
- });
144
- formatOutput(data, globalOpts);
145
- printSuccess('테스트 항목이 추가되었습니다.');
146
- } catch (error) {
147
- // Error already handled
148
- }
149
- });
150
-
151
- // ══════════════════════════════════════════════════
152
- // Run (수행) commands
153
- // ══════════════════════════════════════════════════
154
-
155
- // ch sqa start-run <sheetId> — 시트 기반 수행 시작
156
- sqa
157
- .command('start-run <sheetId>')
158
- .description('시트 기반 SQA 수행 시작 (항목을 스냅샷하여 Run 생성)')
159
- .requiredOption('--date <date>', '수행일 (YYYY-MM-DD)')
160
- .action(async (sheetId: string, options) => {
161
- const globalOpts = program.opts();
162
- try {
163
- if (globalOpts.dryRun) {
164
- printInfo(`[DRY-RUN] SQA 시트 "${sheetId}" 기반 수행 시작 요청`);
165
- return;
166
- }
167
- const client = createClient({ projectId: globalOpts.project });
168
- const { data } = await client.post(`/sqa/${sheetId}/start-run`, {
169
- performDate: options.date,
170
- });
171
- formatOutput(data, globalOpts);
172
- printSuccess('SQA 수행이 시작되었습니다.');
173
- } catch (error) {
174
- // Error already handled
175
- }
176
- });
177
-
178
- // ch sqa runs — 수행 목록
179
- sqa
180
- .command('runs')
181
- .description('SQA 수행 목록 조회')
182
- .option('--sort <field>', '정렬 필드')
183
- .option('--desc', '내림차순 정렬')
184
- .option('--page <number>', '페이지 번호', '1')
185
- .action(async (options) => {
186
- const globalOpts = program.opts();
187
- try {
188
- const client = createClient({ projectId: globalOpts.project });
189
- const params: any = {};
190
- if (options.sort) params.sortBy = options.sort;
191
- if (options.desc) params.sortOrder = 'desc';
192
- params.page = parseInt(options.page, 10);
193
-
194
- const { data } = await client.get('/sqa/runs/list', { params });
195
- formatOutput(data, globalOpts);
196
- } catch (error) {
197
- // Error already handled
198
- }
199
- });
200
-
201
- // ch sqa run <runId> — 수행 상세
202
- sqa
203
- .command('run <runId>')
204
- .description('SQA 수행 상세 조회')
205
- .action(async (runId: string) => {
206
- const globalOpts = program.opts();
207
- try {
208
- const client = createClient({ projectId: globalOpts.project });
209
- const { data } = await client.get(`/sqa/runs/${runId}`);
210
- formatOutput(data, globalOpts);
211
- } catch (error) {
212
- // Error already handled
213
- }
214
- });
215
-
216
- // ch sqa check <runId> <itemId> — 수행 항목 체크
217
- sqa
218
- .command('check <runId> <itemId>')
219
- .description('SQA 수행 항목 검토결과 체크')
220
- .requiredOption('--result <result>', '결과 (yes|no)')
221
- .option('--note <note>', '비고')
222
- .action(async (runId: string, itemId: string, options) => {
223
- const globalOpts = program.opts();
224
- try {
225
- if (globalOpts.dryRun) {
226
- printInfo(`[DRY-RUN] SQA Run "${runId}" 항목 "${itemId}" 체크 요청`);
227
- return;
228
- }
229
- const client = createClient({ projectId: globalOpts.project });
230
- const body: any = { result: options.result };
231
- if (options.note) body.note = options.note;
232
-
233
- const { data } = await client.patch(`/sqa/runs/${runId}/items/${itemId}/check`, body);
234
- formatOutput(data, globalOpts);
235
- printSuccess('검토결과가 체크되었습니다.');
236
- } catch (error) {
237
- // Error already handled
238
- }
239
- });
240
-
241
- // ch sqa check-bulk <runId> — 수행 항목 일괄 체크
242
- sqa
243
- .command('check-bulk <runId>')
244
- .description('SQA 수행 여러 항목 일괄 체크')
245
- .requiredOption('--file <filePath>', '결과 JSON 파일 경로')
246
- .action(async (runId: string, options) => {
247
- const globalOpts = program.opts();
248
- try {
249
- if (globalOpts.dryRun) {
250
- printInfo(`[DRY-RUN] SQA Run "${runId}" 일괄 체크 요청`);
251
- return;
252
- }
253
-
254
- const filePath = path.resolve(options.file);
255
- if (!fs.existsSync(filePath)) {
256
- printError(`파일을 찾을 수 없습니다: ${filePath}`);
257
- return;
258
- }
259
-
260
- const fileContent = fs.readFileSync(filePath, 'utf-8');
261
- let results: any;
262
- try {
263
- results = JSON.parse(fileContent);
264
- } catch {
265
- printError('파일이 올바른 JSON 형식이 아닙니다.');
266
- return;
267
- }
268
-
269
- const client = createClient({ projectId: globalOpts.project });
270
- const { data } = await client.post(`/sqa/runs/${runId}/items/check-bulk`, results);
271
- formatOutput(data, globalOpts);
272
- printSuccess('일괄 체크가 완료되었습니다.');
273
- } catch (error) {
274
- // Error already handled
275
- }
276
- });
277
-
278
- // ch sqa complete <runId> — 수행 완료
279
- sqa
280
- .command('complete <runId>')
281
- .description('SQA 수행 완료')
282
- .action(async (runId: string) => {
283
- const globalOpts = program.opts();
284
- try {
285
- if (globalOpts.dryRun) {
286
- printInfo(`[DRY-RUN] SQA Run "${runId}" 수행 완료 요청`);
287
- return;
288
- }
289
- const client = createClient({ projectId: globalOpts.project });
290
- const { data } = await client.post(`/sqa/runs/${runId}/complete`);
291
- formatOutput(data, globalOpts);
292
- printSuccess('SQA 수행이 완료되었습니다.');
293
- } catch (error) {
294
- // Error already handled
295
- }
296
- });
297
-
298
- // ch sqa summary <runId> — 수행 집계
299
- sqa
300
- .command('summary <runId>')
301
- .description('SQA 수행 Pass/Fail 집계 조회')
302
- .action(async (runId: string) => {
303
- const globalOpts = program.opts();
304
- try {
305
- const client = createClient({ projectId: globalOpts.project });
306
- const { data } = await client.get(`/sqa/runs/${runId}/summary`);
307
- formatOutput(data, globalOpts);
308
- } catch (error) {
309
- // Error already handled
310
- }
311
- });
312
-
313
- // ch sqa export <runId> — 수행 엑셀 Export
314
- sqa
315
- .command('export <runId>')
316
- .description('SQA 수행 엑셀 Export')
317
- .requiredOption('--output <filePath>', '출력 파일 경로')
318
- .action(async (runId: string, options) => {
319
- const globalOpts = program.opts();
320
- try {
321
- const client = createClient({ projectId: globalOpts.project });
322
- const response = await client.get(`/sqa/runs/${runId}/export`, {
323
- responseType: 'arraybuffer',
324
- });
325
-
326
- const outputPath = path.resolve(options.output);
327
- fs.writeFileSync(outputPath, Buffer.from(response.data));
328
- printSuccess(`엑셀 파일이 저장되었습니다: ${outputPath}`);
329
- } catch (error) {
330
- // Error already handled
331
- }
332
- });
333
-
334
- // ch sqa delete-run <runId> — 수행 삭제
335
- sqa
336
- .command('delete-run <runId>')
337
- .description('SQA 수행 삭제')
338
- .action(async (runId: string) => {
339
- const globalOpts = program.opts();
340
- try {
341
- if (globalOpts.dryRun) {
342
- printInfo(`[DRY-RUN] SQA Run "${runId}" 삭제 요청`);
343
- return;
344
- }
345
- const client = createClient({ projectId: globalOpts.project });
346
- await client.delete(`/sqa/runs/${runId}`);
347
- printSuccess('SQA 수행이 삭제되었습니다.');
348
- } catch (error) {
349
- // Error already handled
350
- }
351
- });
352
-
353
- // ══════════════════════════════════════════════════
354
- // Import (엑셀 → 시트)
355
- // ══════════════════════════════════════════════════
356
-
357
- sqa
358
- .command('import')
359
- .description('SQA 엑셀 Import (시트 생성)')
360
- .requiredOption('--file <filePath>', '엑셀 파일 경로')
361
- .option('--name <name>', '시트명')
362
- .option('--date <date>', '수행일')
363
- .action(async (options) => {
364
- const globalOpts = program.opts();
365
- try {
366
- if (globalOpts.dryRun) {
367
- printInfo('[DRY-RUN] SQA 엑셀 Import 요청');
368
- return;
369
- }
370
-
371
- const filePath = path.resolve(options.file);
372
- if (!fs.existsSync(filePath)) {
373
- printError(`파일을 찾을 수 없습니다: ${filePath}`);
374
- return;
375
- }
376
-
377
- const form = new FormData();
378
- form.append('file', fs.createReadStream(filePath));
379
- if (options.name) form.append('name', options.name);
380
- if (options.date) form.append('performDate', options.date);
381
-
382
- const client = createClient({ projectId: globalOpts.project });
383
- const { data } = await client.post('/sqa/import', form, {
384
- headers: {
385
- ...form.getHeaders(),
386
- },
387
- });
388
- formatOutput(data, globalOpts);
389
- printSuccess('SQA 엑셀 Import가 완료되었습니다.');
390
- } catch (error) {
391
- // Error already handled
392
- }
393
- });
394
-
395
- sqa
396
- .command('import-preview')
397
- .description('SQA 엑셀 Import 미리보기')
398
- .requiredOption('--file <filePath>', '엑셀 파일 경로')
399
- .action(async (options) => {
400
- const globalOpts = program.opts();
401
- try {
402
- const filePath = path.resolve(options.file);
403
- if (!fs.existsSync(filePath)) {
404
- printError(`파일을 찾을 수 없습니다: ${filePath}`);
405
- return;
406
- }
407
-
408
- const form = new FormData();
409
- form.append('file', fs.createReadStream(filePath));
410
-
411
- const client = createClient({ projectId: globalOpts.project });
412
- const { data } = await client.post('/sqa/import-preview', form, {
413
- headers: {
414
- ...form.getHeaders(),
415
- },
416
- });
417
- formatOutput(data, globalOpts);
418
- } catch (error) {
419
- // Error already handled
420
- }
421
- });
422
- }