trello-cli-unofficial 0.7.6 → 0.8.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/README.md +27 -0
- package/bun.lock +225 -2
- package/dist/main.js +26490 -25401
- package/main.ts +6 -3
- package/package.json +15 -3
- package/src/application/use-cases/AuthenticateUserUseCase.ts +7 -6
- package/src/application/use-cases/CreateBoardUseCase.ts +19 -0
- package/src/application/use-cases/CreateCardUseCase.ts +2 -1
- package/src/application/use-cases/CreateListUseCase.ts +19 -0
- package/src/application/use-cases/GetBoardDetailsUseCase.ts +41 -0
- package/src/application/use-cases/UpdateCardUseCase.ts +2 -1
- package/src/application/use-cases/index.ts +3 -0
- package/src/domain/entities/Board.ts +10 -2
- package/src/domain/entities/Card.ts +12 -1
- package/src/domain/entities/Config.ts +3 -1
- package/src/domain/entities/List.ts +14 -2
- package/src/domain/repositories/TrelloRepository.ts +4 -0
- package/src/i18n/index.ts +62 -5
- package/src/i18n/locales/en.json +154 -17
- package/src/i18n/locales/pt-BR.json +154 -17
- package/src/infrastructure/repositories/FileConfigRepository.ts +6 -3
- package/src/infrastructure/repositories/TrelloApiRepository.ts +155 -10
- package/src/presentation/cli/AuthController.ts +2 -1
- package/src/presentation/cli/BoardController.ts +160 -17
- package/src/presentation/cli/CardController.ts +169 -45
- package/src/presentation/cli/CommandController.ts +293 -27
- package/src/presentation/cli/ConfigController.ts +4 -3
- package/src/presentation/cli/TrelloCliController.ts +10 -2
- package/src/shared/ErrorHandler.ts +233 -0
- package/src/shared/OutputFormatter.ts +210 -0
- package/src/shared/index.ts +2 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for Trello CLI application
|
|
3
|
+
*/
|
|
4
|
+
import { t } from '@/i18n';
|
|
5
|
+
|
|
6
|
+
export class TrelloCliError extends Error {
|
|
7
|
+
constructor(
|
|
8
|
+
message: string,
|
|
9
|
+
public readonly code: string,
|
|
10
|
+
public readonly statusCode?: number,
|
|
11
|
+
) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = this.constructor.name;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Authentication related errors
|
|
19
|
+
*/
|
|
20
|
+
export class AuthenticationError extends TrelloCliError {
|
|
21
|
+
constructor(message: string = 'Authentication failed') {
|
|
22
|
+
super(message, 'AUTH_ERROR', 401);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* API related errors
|
|
28
|
+
*/
|
|
29
|
+
export class ApiError extends TrelloCliError {
|
|
30
|
+
constructor(
|
|
31
|
+
message: string,
|
|
32
|
+
override readonly statusCode: number,
|
|
33
|
+
public readonly endpoint?: string,
|
|
34
|
+
) {
|
|
35
|
+
super(message, 'API_ERROR', statusCode);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Validation errors
|
|
41
|
+
*/
|
|
42
|
+
export class ValidationError extends TrelloCliError {
|
|
43
|
+
constructor(
|
|
44
|
+
message: string,
|
|
45
|
+
public readonly field?: string,
|
|
46
|
+
) {
|
|
47
|
+
super(message, 'VALIDATION_ERROR', 400);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resource not found errors
|
|
53
|
+
*/
|
|
54
|
+
export class NotFoundError extends TrelloCliError {
|
|
55
|
+
constructor(
|
|
56
|
+
message: string,
|
|
57
|
+
public readonly resourceType: string,
|
|
58
|
+
public readonly resourceId?: string,
|
|
59
|
+
) {
|
|
60
|
+
super(message, 'NOT_FOUND_ERROR', 404);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Configuration errors
|
|
66
|
+
*/
|
|
67
|
+
export class ConfigurationError extends TrelloCliError {
|
|
68
|
+
constructor(message: string) {
|
|
69
|
+
super(message, 'CONFIG_ERROR', 500);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Network connectivity errors
|
|
75
|
+
*/
|
|
76
|
+
export class NetworkError extends TrelloCliError {
|
|
77
|
+
constructor(message: string = t('api.networkConnectionFailed')) {
|
|
78
|
+
super(message, 'NETWORK_ERROR', 0);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Error handler class for consistent error handling and user feedback
|
|
84
|
+
*/
|
|
85
|
+
export class ErrorHandler {
|
|
86
|
+
/**
|
|
87
|
+
* Handle an error and provide appropriate user feedback
|
|
88
|
+
*/
|
|
89
|
+
static handle(error: unknown, context?: string): void {
|
|
90
|
+
if (error instanceof TrelloCliError) {
|
|
91
|
+
this.handleTrelloError(error, context);
|
|
92
|
+
} else if (error instanceof Error) {
|
|
93
|
+
this.handleGenericError(error, context);
|
|
94
|
+
} else {
|
|
95
|
+
this.handleUnknownError(error, context);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Handle Trello CLI specific errors
|
|
101
|
+
*/
|
|
102
|
+
private static handleTrelloError(
|
|
103
|
+
error: TrelloCliError,
|
|
104
|
+
context?: string,
|
|
105
|
+
): void {
|
|
106
|
+
const prefix = context ? `[${context}] ` : '';
|
|
107
|
+
|
|
108
|
+
switch (error.code) {
|
|
109
|
+
case 'AUTH_ERROR':
|
|
110
|
+
console.error(t('errors.authFailed', { message: error.message }));
|
|
111
|
+
console.error(t('errors.trySetup'));
|
|
112
|
+
break;
|
|
113
|
+
|
|
114
|
+
case 'API_ERROR':
|
|
115
|
+
console.error(
|
|
116
|
+
t('errors.apiError', { statusCode: error.statusCode, message: error.message }),
|
|
117
|
+
);
|
|
118
|
+
if (error instanceof ApiError && error.endpoint) {
|
|
119
|
+
console.error(t('errors.endpoint', { endpoint: error.endpoint }));
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
|
|
123
|
+
case 'VALIDATION_ERROR':
|
|
124
|
+
console.error(t('errors.validationError', { message: error.message }));
|
|
125
|
+
if (error instanceof ValidationError && error.field) {
|
|
126
|
+
console.error(t('errors.field', { field: error.field }));
|
|
127
|
+
}
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
case 'NOT_FOUND_ERROR':
|
|
131
|
+
console.error(t('errors.notFound', { message: error.message }));
|
|
132
|
+
if (error instanceof NotFoundError) {
|
|
133
|
+
if (error.resourceType) {
|
|
134
|
+
console.error(
|
|
135
|
+
t('errors.resourceType', { resourceType: error.resourceType }),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
if (error.resourceId) {
|
|
139
|
+
console.error(
|
|
140
|
+
t('errors.resourceId', { resourceId: error.resourceId }),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
|
|
146
|
+
case 'CONFIG_ERROR':
|
|
147
|
+
console.error(t('errors.configError', { message: error.message }));
|
|
148
|
+
console.error(t('errors.checkConfig'));
|
|
149
|
+
break;
|
|
150
|
+
|
|
151
|
+
case 'NETWORK_ERROR':
|
|
152
|
+
console.error(t('errors.networkError', { message: error.message }));
|
|
153
|
+
console.error(t('errors.checkConnection'));
|
|
154
|
+
break;
|
|
155
|
+
|
|
156
|
+
default:
|
|
157
|
+
console.error(`❌ ${prefix}${error.message}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Exit with appropriate code for automation
|
|
161
|
+
process.exit(error.statusCode || 1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Handle generic JavaScript errors
|
|
166
|
+
*/
|
|
167
|
+
private static handleGenericError(error: Error, _context?: string): void {
|
|
168
|
+
console.error(t('errors.unexpectedError', { message: error.message }));
|
|
169
|
+
|
|
170
|
+
// In development, show stack trace
|
|
171
|
+
if (process.env.NODE_ENV === 'development') {
|
|
172
|
+
console.error(t('errors.stackTrace'), error.stack);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Handle unknown errors
|
|
180
|
+
*/
|
|
181
|
+
private static handleUnknownError(error: unknown, _context?: string): void {
|
|
182
|
+
console.error(t('errors.unknownError'), error);
|
|
183
|
+
process.exit(1);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Wrap async operations with error handling
|
|
188
|
+
*/
|
|
189
|
+
static async withErrorHandling<T>(
|
|
190
|
+
operation: () => Promise<T>,
|
|
191
|
+
context?: string,
|
|
192
|
+
): Promise<T> {
|
|
193
|
+
try {
|
|
194
|
+
return await operation();
|
|
195
|
+
} catch (error) {
|
|
196
|
+
this.handle(error, context);
|
|
197
|
+
// This line won't be reached due to process.exit, but TypeScript needs it
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Create user-friendly error messages from API responses
|
|
204
|
+
*/
|
|
205
|
+
static fromApiResponse(
|
|
206
|
+
response: Record<string, unknown>,
|
|
207
|
+
endpoint?: string,
|
|
208
|
+
): TrelloCliError {
|
|
209
|
+
const statusCode
|
|
210
|
+
= (response.status as number) || (response.statusCode as number) || 500;
|
|
211
|
+
const message
|
|
212
|
+
= (response.message as string)
|
|
213
|
+
|| (response.error as string)
|
|
214
|
+
|| t('api.unknownApiError');
|
|
215
|
+
|
|
216
|
+
switch (statusCode) {
|
|
217
|
+
case 401:
|
|
218
|
+
return new AuthenticationError(t('api.invalidToken'));
|
|
219
|
+
case 403:
|
|
220
|
+
return new AuthenticationError('Access denied');
|
|
221
|
+
case 404:
|
|
222
|
+
return new NotFoundError(t('api.resourceNotFound'), 'unknown');
|
|
223
|
+
case 400:
|
|
224
|
+
return new ValidationError(message);
|
|
225
|
+
case 429:
|
|
226
|
+
return new ApiError(t('api.rateLimitExceeded'), statusCode, endpoint);
|
|
227
|
+
case 500:
|
|
228
|
+
return new ApiError(t('api.internalServerError'), statusCode, endpoint);
|
|
229
|
+
default:
|
|
230
|
+
return new ApiError(message, statusCode, endpoint);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { t } from '@/i18n';
|
|
2
|
+
|
|
3
|
+
export type OutputFormat = 'table' | 'json' | 'csv';
|
|
4
|
+
|
|
5
|
+
export interface OutputFormatterOptions {
|
|
6
|
+
format: OutputFormat;
|
|
7
|
+
headers?: string[];
|
|
8
|
+
fields?: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class OutputFormatter {
|
|
12
|
+
private format: OutputFormat;
|
|
13
|
+
|
|
14
|
+
constructor(format: OutputFormat = 'table') {
|
|
15
|
+
this.format = format;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
setFormat(format: OutputFormat): void {
|
|
19
|
+
this.format = format;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Format and output data based on the current format
|
|
24
|
+
*/
|
|
25
|
+
output<T>(data: T[] | T, options?: Partial<OutputFormatterOptions>): void {
|
|
26
|
+
const format = options?.format || this.format;
|
|
27
|
+
|
|
28
|
+
switch (format) {
|
|
29
|
+
case 'json':
|
|
30
|
+
this.outputJson(data);
|
|
31
|
+
break;
|
|
32
|
+
case 'csv':
|
|
33
|
+
this.outputCsv(data as T[], options);
|
|
34
|
+
break;
|
|
35
|
+
case 'table':
|
|
36
|
+
default:
|
|
37
|
+
this.outputTable(data as T[], options);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Output data in JSON format
|
|
44
|
+
*/
|
|
45
|
+
private outputJson<T>(data: T[] | T): void {
|
|
46
|
+
console.log(JSON.stringify(data, null, 2));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Output data in CSV format
|
|
51
|
+
*/
|
|
52
|
+
private outputCsv<T>(
|
|
53
|
+
data: T[],
|
|
54
|
+
options?: Partial<OutputFormatterOptions>,
|
|
55
|
+
): void {
|
|
56
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
57
|
+
console.log(t('common.noData'));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const firstItem = data[0];
|
|
62
|
+
if (!firstItem) {
|
|
63
|
+
console.log(t('common.noData'));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Convert to plain object if it's a class instance
|
|
68
|
+
const plainItem = this.toPlainObject(firstItem);
|
|
69
|
+
const fields = options?.fields || Object.keys(plainItem);
|
|
70
|
+
const headers = options?.headers || fields;
|
|
71
|
+
|
|
72
|
+
// Output headers
|
|
73
|
+
console.log(headers.join(','));
|
|
74
|
+
|
|
75
|
+
// Output data rows
|
|
76
|
+
for (const item of data) {
|
|
77
|
+
const plainObject = this.toPlainObject(item);
|
|
78
|
+
const row = fields.map((field) => {
|
|
79
|
+
const value = plainObject[field];
|
|
80
|
+
// Escape commas and quotes in CSV
|
|
81
|
+
const stringValue = String(value || '');
|
|
82
|
+
if (
|
|
83
|
+
stringValue.includes(',')
|
|
84
|
+
|| stringValue.includes('"')
|
|
85
|
+
|| stringValue.includes('\n')
|
|
86
|
+
) {
|
|
87
|
+
return `"${stringValue.replace(/"/g, '""')}"`;
|
|
88
|
+
}
|
|
89
|
+
return stringValue;
|
|
90
|
+
});
|
|
91
|
+
console.log(row.join(','));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Output data in table format (default)
|
|
97
|
+
*/
|
|
98
|
+
private outputTable<T>(
|
|
99
|
+
data: T[],
|
|
100
|
+
options?: Partial<OutputFormatterOptions>,
|
|
101
|
+
): void {
|
|
102
|
+
if (!Array.isArray(data) || data.length === 0) {
|
|
103
|
+
console.log(t('common.noData'));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const firstItem = data[0];
|
|
108
|
+
if (!firstItem) {
|
|
109
|
+
console.log(t('common.noData'));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Convert to plain object if it's a class instance
|
|
114
|
+
const plainItem = this.toPlainObject(firstItem);
|
|
115
|
+
const fields = options?.fields || Object.keys(plainItem);
|
|
116
|
+
const headers = options?.headers || fields;
|
|
117
|
+
|
|
118
|
+
// Calculate column widths
|
|
119
|
+
const columnWidths = headers.map((header, index) => {
|
|
120
|
+
const field = fields[index]!;
|
|
121
|
+
const headerWidth = header.length;
|
|
122
|
+
const maxDataWidth = Math.max(
|
|
123
|
+
...data.map((item) => {
|
|
124
|
+
const plainObject = this.toPlainObject(item);
|
|
125
|
+
return String(plainObject[field] || '').length;
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
128
|
+
return Math.max(headerWidth, maxDataWidth);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Output header
|
|
132
|
+
const headerRow = headers
|
|
133
|
+
.map((header, index) => header.padEnd(columnWidths[index]!))
|
|
134
|
+
.join(' | ');
|
|
135
|
+
console.log(headerRow);
|
|
136
|
+
|
|
137
|
+
// Output separator
|
|
138
|
+
const separator = columnWidths
|
|
139
|
+
.map(width => '-'.repeat(width))
|
|
140
|
+
.join('-+-');
|
|
141
|
+
console.log(separator);
|
|
142
|
+
|
|
143
|
+
// Output data rows
|
|
144
|
+
for (const item of data) {
|
|
145
|
+
const plainObject = this.toPlainObject(item);
|
|
146
|
+
const row = fields
|
|
147
|
+
.map((field, index) => {
|
|
148
|
+
const value = String(plainObject[field!] || '');
|
|
149
|
+
return value.padEnd(columnWidths[index]!);
|
|
150
|
+
})
|
|
151
|
+
.join(' | ');
|
|
152
|
+
console.log(row);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Convert an object to a plain object for serialization
|
|
158
|
+
*/
|
|
159
|
+
private toPlainObject<T>(obj: T): Record<string, unknown> {
|
|
160
|
+
if (obj === null || obj === undefined) {
|
|
161
|
+
return {};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (typeof obj === 'object' && obj.constructor !== Object) {
|
|
165
|
+
// It's a class instance, convert to plain object
|
|
166
|
+
const plain: Record<string, unknown> = {};
|
|
167
|
+
for (const key of Object.keys(obj as object)) {
|
|
168
|
+
plain[key] = (obj as Record<string, unknown>)[key];
|
|
169
|
+
}
|
|
170
|
+
return plain;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return obj as Record<string, unknown>;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Output a simple message
|
|
178
|
+
*/
|
|
179
|
+
message(message: string): void {
|
|
180
|
+
console.log(message);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Output an error message
|
|
185
|
+
*/
|
|
186
|
+
error(message: string): void {
|
|
187
|
+
console.error(`❌ ${message}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Output a success message
|
|
192
|
+
*/
|
|
193
|
+
success(message: string): void {
|
|
194
|
+
console.log(`✅ ${message}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Output a warning message
|
|
199
|
+
*/
|
|
200
|
+
warning(message: string): void {
|
|
201
|
+
console.log(`⚠️ ${message}`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Output an info message
|
|
206
|
+
*/
|
|
207
|
+
info(message: string): void {
|
|
208
|
+
console.log(`ℹ️ ${message}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
package/src/shared/index.ts
CHANGED