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.
Files changed (31) hide show
  1. package/README.md +27 -0
  2. package/bun.lock +225 -2
  3. package/dist/main.js +26490 -25401
  4. package/main.ts +6 -3
  5. package/package.json +15 -3
  6. package/src/application/use-cases/AuthenticateUserUseCase.ts +7 -6
  7. package/src/application/use-cases/CreateBoardUseCase.ts +19 -0
  8. package/src/application/use-cases/CreateCardUseCase.ts +2 -1
  9. package/src/application/use-cases/CreateListUseCase.ts +19 -0
  10. package/src/application/use-cases/GetBoardDetailsUseCase.ts +41 -0
  11. package/src/application/use-cases/UpdateCardUseCase.ts +2 -1
  12. package/src/application/use-cases/index.ts +3 -0
  13. package/src/domain/entities/Board.ts +10 -2
  14. package/src/domain/entities/Card.ts +12 -1
  15. package/src/domain/entities/Config.ts +3 -1
  16. package/src/domain/entities/List.ts +14 -2
  17. package/src/domain/repositories/TrelloRepository.ts +4 -0
  18. package/src/i18n/index.ts +62 -5
  19. package/src/i18n/locales/en.json +154 -17
  20. package/src/i18n/locales/pt-BR.json +154 -17
  21. package/src/infrastructure/repositories/FileConfigRepository.ts +6 -3
  22. package/src/infrastructure/repositories/TrelloApiRepository.ts +155 -10
  23. package/src/presentation/cli/AuthController.ts +2 -1
  24. package/src/presentation/cli/BoardController.ts +160 -17
  25. package/src/presentation/cli/CardController.ts +169 -45
  26. package/src/presentation/cli/CommandController.ts +293 -27
  27. package/src/presentation/cli/ConfigController.ts +4 -3
  28. package/src/presentation/cli/TrelloCliController.ts +10 -2
  29. package/src/shared/ErrorHandler.ts +233 -0
  30. package/src/shared/OutputFormatter.ts +210 -0
  31. 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
+ }
@@ -1 +1,3 @@
1
+ export * from './ErrorHandler';
2
+ export * from './OutputFormatter';
1
3
  export * from './types';