@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
package/src/config.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
|
|
5
|
+
export interface ChConfig {
|
|
6
|
+
apiKey: string;
|
|
7
|
+
projectId: string;
|
|
8
|
+
apiUrl: string;
|
|
9
|
+
defaultFormat: 'table' | 'json' | 'csv';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ChProjectLocal {
|
|
13
|
+
projectId: string;
|
|
14
|
+
projectName?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ProjectContext {
|
|
18
|
+
projectId: string;
|
|
19
|
+
projectName?: string;
|
|
20
|
+
source: 'cli-option' | 'local' | 'global';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const CONFIG_DIR = path.join(os.homedir(), '.ch');
|
|
24
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
25
|
+
const LOCAL_PROJECT_FILE = '.ch-project';
|
|
26
|
+
|
|
27
|
+
export function loadConfig(): ChConfig | null {
|
|
28
|
+
try {
|
|
29
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');
|
|
33
|
+
return JSON.parse(raw) as ChConfig;
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Search for .ch-project file from cwd upward.
|
|
41
|
+
*/
|
|
42
|
+
export function findLocalProject(): ChProjectLocal | null {
|
|
43
|
+
try {
|
|
44
|
+
let dir = process.cwd();
|
|
45
|
+
const root = path.parse(dir).root;
|
|
46
|
+
while (true) {
|
|
47
|
+
const filePath = path.join(dir, LOCAL_PROJECT_FILE);
|
|
48
|
+
if (fs.existsSync(filePath)) {
|
|
49
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
50
|
+
return JSON.parse(raw) as ChProjectLocal;
|
|
51
|
+
}
|
|
52
|
+
const parent = path.dirname(dir);
|
|
53
|
+
if (parent === dir || dir === root) break;
|
|
54
|
+
dir = parent;
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
// ignore parse errors
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Resolve projectId with priority: CLI --project > .ch-project > ~/.ch/config.json
|
|
64
|
+
*/
|
|
65
|
+
export function getProjectContext(cliProjectId?: string): ProjectContext | null {
|
|
66
|
+
if (cliProjectId) {
|
|
67
|
+
return { projectId: cliProjectId, source: 'cli-option' };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const local = findLocalProject();
|
|
71
|
+
if (local?.projectId) {
|
|
72
|
+
return { projectId: local.projectId, projectName: local.projectName, source: 'local' };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const global = loadConfig();
|
|
76
|
+
if (global?.projectId) {
|
|
77
|
+
return { projectId: global.projectId, source: 'global' };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function saveConfig(config: ChConfig): void {
|
|
84
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
85
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
86
|
+
}
|
|
87
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function clearConfig(): void {
|
|
91
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
92
|
+
fs.unlinkSync(CONFIG_FILE);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function getConfigPath(): string {
|
|
97
|
+
return CONFIG_FILE;
|
|
98
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
|
|
7
|
+
import { registerAuthCommand } from './commands/auth';
|
|
8
|
+
import { registerInitCommand } from './commands/init';
|
|
9
|
+
import { registerProjectsCommand } from './commands/projects';
|
|
10
|
+
import { registerSpecsCommand } from './commands/specs';
|
|
11
|
+
import { registerSprintsCommand } from './commands/sprints';
|
|
12
|
+
import { registerQnaCommand } from './commands/qna';
|
|
13
|
+
import { registerSqaCommand } from './commands/sqa';
|
|
14
|
+
import { registerArchivesCommand } from './commands/archives';
|
|
15
|
+
import { registerMembersCommand } from './commands/members';
|
|
16
|
+
import { registerDevStatusCommand } from './commands/dev-status';
|
|
17
|
+
import { registerPrdCommand } from './commands/prd';
|
|
18
|
+
import { registerDbSchemaCommand } from './commands/db-schema';
|
|
19
|
+
|
|
20
|
+
import { registerNotificationsCommand } from './commands/notifications';
|
|
21
|
+
import { registerDashboardCommand } from './commands/dashboard';
|
|
22
|
+
|
|
23
|
+
// Read version from package.json
|
|
24
|
+
function getVersion(): string {
|
|
25
|
+
try {
|
|
26
|
+
const pkgPath = path.join(__dirname, '..', 'package.json');
|
|
27
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
28
|
+
return pkg.version || '1.0.0';
|
|
29
|
+
} catch {
|
|
30
|
+
return '1.0.0';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const program = new Command();
|
|
35
|
+
|
|
36
|
+
program
|
|
37
|
+
.name('ch')
|
|
38
|
+
.description('Decencia Communication Channel CLI')
|
|
39
|
+
.version(getVersion(), '-v, --version', '버전 표시')
|
|
40
|
+
.option('--json', 'JSON 형식으로 출력')
|
|
41
|
+
.option('--table', '테이블 형식으로 출력 (기본값)')
|
|
42
|
+
.option('--csv', 'CSV 형식으로 출력')
|
|
43
|
+
.option('--project <projectId>', '프로젝트 ID 오버라이드')
|
|
44
|
+
.option('--verbose', '상세 로그 출력')
|
|
45
|
+
.option('--quiet', '결과만 출력 (스크립팅용)')
|
|
46
|
+
.option('--dry-run', '쓰기 작업 시뮬레이션');
|
|
47
|
+
|
|
48
|
+
// Register all command modules
|
|
49
|
+
registerInitCommand(program);
|
|
50
|
+
registerAuthCommand(program);
|
|
51
|
+
registerProjectsCommand(program);
|
|
52
|
+
registerSpecsCommand(program);
|
|
53
|
+
registerSprintsCommand(program);
|
|
54
|
+
registerQnaCommand(program);
|
|
55
|
+
registerSqaCommand(program);
|
|
56
|
+
registerArchivesCommand(program);
|
|
57
|
+
registerMembersCommand(program);
|
|
58
|
+
registerDevStatusCommand(program);
|
|
59
|
+
registerPrdCommand(program);
|
|
60
|
+
registerDbSchemaCommand(program);
|
|
61
|
+
|
|
62
|
+
registerNotificationsCommand(program);
|
|
63
|
+
registerDashboardCommand(program);
|
|
64
|
+
|
|
65
|
+
// Parse and execute
|
|
66
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
67
|
+
console.error(err);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
});
|
package/src/output.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import Table from 'cli-table3';
|
|
3
|
+
import { getLastProjectContext } from './client';
|
|
4
|
+
|
|
5
|
+
export interface OutputOptions {
|
|
6
|
+
json?: boolean;
|
|
7
|
+
table?: boolean;
|
|
8
|
+
csv?: boolean;
|
|
9
|
+
quiet?: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function formatOutput(data: any, options: OutputOptions = {}): void {
|
|
13
|
+
if (options.quiet) {
|
|
14
|
+
// In quiet mode, only output raw JSON for piping
|
|
15
|
+
console.log(JSON.stringify(data));
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (options.json) {
|
|
20
|
+
console.log(JSON.stringify(data, null, 2));
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (options.csv) {
|
|
25
|
+
printCsv(data);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Default: table or pretty print
|
|
30
|
+
if (Array.isArray(data)) {
|
|
31
|
+
if (data.length === 0) {
|
|
32
|
+
console.log(chalk.yellow('데이터가 없습니다.'));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
printAutoTable(data);
|
|
36
|
+
} else if (data && typeof data === 'object') {
|
|
37
|
+
// Check if it's a paginated response
|
|
38
|
+
if (data.data && Array.isArray(data.data)) {
|
|
39
|
+
if (data.data.length === 0) {
|
|
40
|
+
console.log(chalk.yellow('데이터가 없습니다.'));
|
|
41
|
+
} else {
|
|
42
|
+
printAutoTable(data.data);
|
|
43
|
+
}
|
|
44
|
+
if (data.pagination) {
|
|
45
|
+
const p = data.pagination;
|
|
46
|
+
console.log(
|
|
47
|
+
chalk.gray(
|
|
48
|
+
`\n페이지 ${p.page}/${p.totalPages} (총 ${p.total}건)`
|
|
49
|
+
)
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
// Single object - print key-value pairs
|
|
54
|
+
printKeyValue(data);
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
console.log(data);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function printAutoTable(rows: any[]): void {
|
|
62
|
+
if (rows.length === 0) return;
|
|
63
|
+
|
|
64
|
+
const keys = Object.keys(rows[0]);
|
|
65
|
+
// Limit columns to avoid overly wide tables
|
|
66
|
+
const displayKeys = keys.slice(0, 8);
|
|
67
|
+
|
|
68
|
+
const table = new Table({
|
|
69
|
+
head: displayKeys.map((k) => chalk.cyan(k)),
|
|
70
|
+
style: { head: [], border: [] },
|
|
71
|
+
wordWrap: true,
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
for (const row of rows) {
|
|
75
|
+
table.push(
|
|
76
|
+
displayKeys.map((k) => {
|
|
77
|
+
const val = row[k];
|
|
78
|
+
if (val === null || val === undefined) return '';
|
|
79
|
+
if (typeof val === 'object') return JSON.stringify(val).substring(0, 50);
|
|
80
|
+
const str = String(val);
|
|
81
|
+
return str.length > 60 ? str.substring(0, 57) + '...' : str;
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
console.log(table.toString());
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function printKeyValue(obj: any): void {
|
|
90
|
+
const table = new Table({
|
|
91
|
+
style: { head: [], border: [] },
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
95
|
+
let display: string;
|
|
96
|
+
if (value === null || value === undefined) {
|
|
97
|
+
display = chalk.gray('(없음)');
|
|
98
|
+
} else if (Array.isArray(value)) {
|
|
99
|
+
display = value.length > 0
|
|
100
|
+
? JSON.stringify(value, null, 2)
|
|
101
|
+
: chalk.gray('(비어있음)');
|
|
102
|
+
} else if (typeof value === 'object') {
|
|
103
|
+
display = JSON.stringify(value, null, 2);
|
|
104
|
+
} else {
|
|
105
|
+
display = String(value);
|
|
106
|
+
}
|
|
107
|
+
table.push({ [chalk.cyan(key)]: display });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log(table.toString());
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function printCsv(data: any): void {
|
|
114
|
+
let rows: any[];
|
|
115
|
+
if (Array.isArray(data)) {
|
|
116
|
+
rows = data;
|
|
117
|
+
} else if (data && data.data && Array.isArray(data.data)) {
|
|
118
|
+
rows = data.data;
|
|
119
|
+
} else {
|
|
120
|
+
// Single object
|
|
121
|
+
rows = [data];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (rows.length === 0) return;
|
|
125
|
+
|
|
126
|
+
const keys = Object.keys(rows[0]);
|
|
127
|
+
// CSV header
|
|
128
|
+
console.log(keys.map(escapeCsvField).join(','));
|
|
129
|
+
// CSV rows
|
|
130
|
+
for (const row of rows) {
|
|
131
|
+
console.log(
|
|
132
|
+
keys
|
|
133
|
+
.map((k) => {
|
|
134
|
+
const val = row[k];
|
|
135
|
+
if (val === null || val === undefined) return '';
|
|
136
|
+
if (typeof val === 'object') return escapeCsvField(JSON.stringify(val));
|
|
137
|
+
return escapeCsvField(String(val));
|
|
138
|
+
})
|
|
139
|
+
.join(',')
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function escapeCsvField(field: string): string {
|
|
145
|
+
if (field.includes(',') || field.includes('"') || field.includes('\n')) {
|
|
146
|
+
return `"${field.replace(/"/g, '""')}"`;
|
|
147
|
+
}
|
|
148
|
+
return field;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function printTable(headers: string[], rows: string[][]): void {
|
|
152
|
+
const table = new Table({
|
|
153
|
+
head: headers.map((h) => chalk.cyan(h)),
|
|
154
|
+
style: { head: [], border: [] },
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
for (const row of rows) {
|
|
158
|
+
table.push(row);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log(table.toString());
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function printError(message: string): void {
|
|
165
|
+
console.error(chalk.red(`오류: ${message}`));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function projectPrefix(): string {
|
|
169
|
+
const ctx = getLastProjectContext();
|
|
170
|
+
if (ctx?.projectName) {
|
|
171
|
+
return chalk.cyan(`[${ctx.projectName}] `);
|
|
172
|
+
}
|
|
173
|
+
return '';
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function printSuccess(message: string): void {
|
|
177
|
+
console.log(projectPrefix() + chalk.green(message));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function printWarning(message: string): void {
|
|
181
|
+
console.log(projectPrefix() + chalk.yellow(message));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function printInfo(message: string): void {
|
|
185
|
+
console.log(projectPrefix() + chalk.blue(message));
|
|
186
|
+
}
|
package/src/utils.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"outDir": "dist",
|
|
6
|
+
"rootDir": "src",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"forceConsistentCasingInFileNames": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"declaration": true,
|
|
13
|
+
"declarationMap": true,
|
|
14
|
+
"sourceMap": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*"],
|
|
17
|
+
"exclude": ["node_modules", "dist"]
|
|
18
|
+
}
|