@urugus/slack-cli 0.2.7 → 0.2.9
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/.claude/settings.local.json +13 -55
- package/.github/workflows/ci.yml +2 -2
- package/dist/commands/history-display.d.ts +1 -1
- package/dist/commands/history-display.d.ts.map +1 -1
- package/dist/commands/history-display.js +8 -28
- package/dist/commands/history-display.js.map +1 -1
- package/dist/commands/history.d.ts.map +1 -1
- package/dist/commands/history.js +9 -2
- package/dist/commands/history.js.map +1 -1
- package/dist/commands/unread.d.ts.map +1 -1
- package/dist/commands/unread.js +32 -16
- package/dist/commands/unread.js.map +1 -1
- package/dist/index.js +0 -0
- package/dist/types/commands.d.ts +1 -0
- package/dist/types/commands.d.ts.map +1 -1
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +1 -5
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/formatters/history-formatters.d.ts +8 -0
- package/dist/utils/formatters/history-formatters.d.ts.map +1 -0
- package/dist/utils/formatters/history-formatters.js +105 -0
- package/dist/utils/formatters/history-formatters.js.map +1 -0
- package/dist/utils/validators.d.ts +4 -0
- package/dist/utils/validators.d.ts.map +1 -1
- package/dist/utils/validators.js +12 -0
- package/dist/utils/validators.js.map +1 -1
- package/eslint.config.js +38 -0
- package/package.json +12 -14
- package/src/commands/history-display.ts +9 -28
- package/src/commands/history.ts +9 -2
- package/src/commands/unread.ts +52 -22
- package/src/types/commands.ts +1 -0
- package/src/utils/errors.ts +1 -5
- package/src/utils/formatters/history-formatters.ts +123 -0
- package/src/utils/validators.ts +13 -0
- package/tests/commands/history.test.ts +115 -0
- package/tests/index.test.ts +2 -2
- package/.eslintrc.json +0 -25
- package/dist/utils/config.d.ts +0 -10
- package/dist/utils/config.d.ts.map +0 -1
- package/dist/utils/config.js +0 -94
- package/dist/utils/config.js.map +0 -1
- package/dist/utils/formatters/output-formatter.d.ts +0 -7
- package/dist/utils/formatters/output-formatter.d.ts.map +0 -1
- package/dist/utils/formatters/output-formatter.js +0 -7
- package/dist/utils/formatters/output-formatter.js.map +0 -1
- package/dist/utils/profile-config-refactored.d.ts +0 -20
- package/dist/utils/profile-config-refactored.d.ts.map +0 -1
- package/dist/utils/profile-config-refactored.js +0 -174
- package/dist/utils/profile-config-refactored.js.map +0 -1
- package/src/utils/formatters/output-formatter.ts +0 -7
- package/tests/utils/slack-operations/channel-operations-refactored.test.ts +0 -179
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@urugus/slack-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "A command-line tool for sending messages to Slack",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -31,27 +31,25 @@
|
|
|
31
31
|
},
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@slack/web-api": "^
|
|
35
|
-
"chalk": "^4.1
|
|
36
|
-
"commander": "^
|
|
37
|
-
"dotenv": "^16.3.1",
|
|
38
|
-
"inquirer": "^8.2.6",
|
|
34
|
+
"@slack/web-api": "^7.9.3",
|
|
35
|
+
"chalk": "^5.4.1",
|
|
36
|
+
"commander": "^14.0.0",
|
|
39
37
|
"p-limit": "^3.1.0"
|
|
40
38
|
},
|
|
41
39
|
"devDependencies": {
|
|
42
|
-
"@types/inquirer": "^8.2.10",
|
|
43
40
|
"@types/node": "^20.10.0",
|
|
44
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
45
|
-
"@typescript-eslint/parser": "^
|
|
46
|
-
"@vitest/coverage-v8": "^
|
|
47
|
-
"eslint": "^
|
|
48
|
-
"eslint-config-prettier": "^
|
|
41
|
+
"@typescript-eslint/eslint-plugin": "^8.34.1",
|
|
42
|
+
"@typescript-eslint/parser": "^8.34.1",
|
|
43
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
44
|
+
"eslint": "^9.30.0",
|
|
45
|
+
"eslint-config-prettier": "^10.1.5",
|
|
49
46
|
"prettier": "^3.1.1",
|
|
50
47
|
"ts-node": "^10.9.1",
|
|
51
48
|
"typescript": "^5.3.2",
|
|
52
|
-
"
|
|
49
|
+
"typescript-eslint": "^8.35.0",
|
|
50
|
+
"vitest": "^3.2.4"
|
|
53
51
|
},
|
|
54
52
|
"engines": {
|
|
55
|
-
"node": ">=
|
|
53
|
+
"node": ">=20.0.0"
|
|
56
54
|
}
|
|
57
55
|
}
|
|
@@ -1,38 +1,19 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
1
|
import { Message } from '../utils/slack-api-client';
|
|
3
|
-
import {
|
|
4
|
-
import { formatMessageWithMentions } from '../utils/format-utils';
|
|
2
|
+
import { createHistoryFormatter } from '../utils/formatters/history-formatters';
|
|
5
3
|
|
|
6
4
|
export function displayHistoryResults(
|
|
7
5
|
messages: Message[],
|
|
8
6
|
users: Map<string, string>,
|
|
9
|
-
channelName: string
|
|
7
|
+
channelName: string,
|
|
8
|
+
format = 'table'
|
|
10
9
|
): void {
|
|
11
|
-
if (messages.length === 0) {
|
|
12
|
-
console.log(chalk.yellow('No messages found in the specified channel.'));
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
console.log(chalk.bold(`\nMessage History for #${channelName}:\n`));
|
|
17
|
-
|
|
18
10
|
// Display messages in reverse order (oldest first)
|
|
19
|
-
messages.reverse()
|
|
20
|
-
const timestamp = formatSlackTimestamp(message.ts);
|
|
21
|
-
let author = 'Unknown';
|
|
11
|
+
const orderedMessages = [...messages].reverse();
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
console.log(chalk.gray(`[${timestamp}]`) + ' ' + chalk.cyan(author));
|
|
30
|
-
if (message.text) {
|
|
31
|
-
const formattedText = formatMessageWithMentions(message.text, users);
|
|
32
|
-
console.log(formattedText);
|
|
33
|
-
}
|
|
34
|
-
console.log(''); // Empty line between messages
|
|
13
|
+
const formatter = createHistoryFormatter(format);
|
|
14
|
+
formatter.format({
|
|
15
|
+
channelName,
|
|
16
|
+
messages: orderedMessages,
|
|
17
|
+
users,
|
|
35
18
|
});
|
|
36
|
-
|
|
37
|
-
console.log(chalk.green(`✓ Displayed ${messages.length} message(s)`));
|
|
38
19
|
}
|
package/src/commands/history.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { HistoryOptions } from '../types/commands';
|
|
|
6
6
|
import { API_LIMITS } from '../utils/constants';
|
|
7
7
|
import { parseCount, parseProfile } from '../utils/option-parsers';
|
|
8
8
|
import { createValidationHook, optionValidators } from '../utils/validators';
|
|
9
|
+
import { parseFormat } from '../utils/option-parsers';
|
|
9
10
|
import { prepareSinceTimestamp } from './history-validators';
|
|
10
11
|
import { displayHistoryResults } from './history-display';
|
|
11
12
|
|
|
@@ -19,10 +20,15 @@ export function setupHistoryCommand(): Command {
|
|
|
19
20
|
API_LIMITS.DEFAULT_MESSAGE_COUNT.toString()
|
|
20
21
|
)
|
|
21
22
|
.option('--since <date>', 'Get messages since specific date (YYYY-MM-DD HH:MM:SS)')
|
|
23
|
+
.option('--format <format>', 'Output format: table, simple, json', 'table')
|
|
22
24
|
.option('--profile <profile>', 'Use specific workspace profile')
|
|
23
25
|
.hook(
|
|
24
26
|
'preAction',
|
|
25
|
-
createValidationHook([
|
|
27
|
+
createValidationHook([
|
|
28
|
+
optionValidators.messageCount,
|
|
29
|
+
optionValidators.sinceDate,
|
|
30
|
+
optionValidators.format,
|
|
31
|
+
])
|
|
26
32
|
)
|
|
27
33
|
.action(
|
|
28
34
|
wrapCommand(async (options: HistoryOptions) => {
|
|
@@ -46,7 +52,8 @@ export function setupHistoryCommand(): Command {
|
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
const { messages, users } = await client.getHistory(options.channel, historyOptions);
|
|
49
|
-
|
|
55
|
+
const format = parseFormat(options.format);
|
|
56
|
+
displayHistoryResults(messages, users, options.channel, format);
|
|
50
57
|
})
|
|
51
58
|
);
|
|
52
59
|
|
package/src/commands/unread.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { wrapCommand } from '../utils/command-wrapper';
|
|
3
3
|
import { createSlackClient } from '../utils/client-factory';
|
|
4
|
-
import { SlackApiClient } from '../utils/slack-api-client';
|
|
4
|
+
import { SlackApiClient, ChannelUnreadResult, Channel } from '../utils/slack-api-client';
|
|
5
5
|
import { UnreadOptions } from '../types/commands';
|
|
6
6
|
import chalk from 'chalk';
|
|
7
7
|
import { createChannelFormatter } from '../utils/formatters/channel-formatters';
|
|
@@ -9,15 +9,15 @@ import { createMessageFormatter } from '../utils/formatters/message-formatters';
|
|
|
9
9
|
import { DEFAULTS } from '../utils/constants';
|
|
10
10
|
import { parseLimit, parseFormat, parseBoolean } from '../utils/option-parsers';
|
|
11
11
|
|
|
12
|
-
async function
|
|
13
|
-
client
|
|
14
|
-
|
|
15
|
-
): Promise<void> {
|
|
16
|
-
const result = await client.getChannelUnread(options.channel!);
|
|
17
|
-
|
|
18
|
-
const format = parseFormat(options.format);
|
|
19
|
-
const countOnly = parseBoolean(options.countOnly);
|
|
12
|
+
async function fetchChannelUnreadData(client: SlackApiClient, channelName: string) {
|
|
13
|
+
return await client.getChannelUnread(channelName);
|
|
14
|
+
}
|
|
20
15
|
|
|
16
|
+
function formatChannelUnreadOutput(
|
|
17
|
+
result: ChannelUnreadResult,
|
|
18
|
+
format: string,
|
|
19
|
+
countOnly: boolean
|
|
20
|
+
): void {
|
|
21
21
|
const formatter = createMessageFormatter(format);
|
|
22
22
|
formatter.format({
|
|
23
23
|
channel: result.channel,
|
|
@@ -26,40 +26,70 @@ async function handleSpecificChannelUnread(
|
|
|
26
26
|
countOnly: countOnly,
|
|
27
27
|
format: format,
|
|
28
28
|
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function markChannelAsRead(client: SlackApiClient, channel: Channel): Promise<void> {
|
|
32
|
+
await client.markAsRead(channel.id);
|
|
33
|
+
console.log(chalk.green(`✓ Marked messages in #${channel.name} as read`));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function handleSpecificChannelUnread(
|
|
37
|
+
client: SlackApiClient,
|
|
38
|
+
options: UnreadOptions
|
|
39
|
+
): Promise<void> {
|
|
40
|
+
const result = await fetchChannelUnreadData(client, options.channel!);
|
|
41
|
+
|
|
42
|
+
const format = parseFormat(options.format);
|
|
43
|
+
const countOnly = parseBoolean(options.countOnly);
|
|
44
|
+
|
|
45
|
+
formatChannelUnreadOutput(result, format, countOnly);
|
|
29
46
|
|
|
30
47
|
if (parseBoolean(options.markRead)) {
|
|
31
|
-
await client
|
|
32
|
-
console.log(chalk.green(`✓ Marked messages in #${result.channel.name} as read`));
|
|
48
|
+
await markChannelAsRead(client, result.channel);
|
|
33
49
|
}
|
|
34
50
|
}
|
|
35
51
|
|
|
52
|
+
async function fetchAllUnreadChannels(client: SlackApiClient) {
|
|
53
|
+
return await client.listUnreadChannels();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function formatAllChannelsOutput(
|
|
57
|
+
channels: Channel[],
|
|
58
|
+
format: string,
|
|
59
|
+
countOnly: boolean,
|
|
60
|
+
limit: number
|
|
61
|
+
): void {
|
|
62
|
+
const displayChannels = channels.slice(0, limit);
|
|
63
|
+
const formatter = createChannelFormatter(format, countOnly);
|
|
64
|
+
formatter.format({ channels: displayChannels, countOnly: countOnly });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function markAllChannelsAsRead(client: SlackApiClient, channels: Channel[]): Promise<void> {
|
|
68
|
+
for (const channel of channels) {
|
|
69
|
+
await client.markAsRead(channel.id);
|
|
70
|
+
}
|
|
71
|
+
console.log(chalk.green('✓ Marked all messages as read'));
|
|
72
|
+
}
|
|
73
|
+
|
|
36
74
|
async function handleAllChannelsUnread(
|
|
37
75
|
client: SlackApiClient,
|
|
38
76
|
options: UnreadOptions
|
|
39
77
|
): Promise<void> {
|
|
40
|
-
const channels = await client
|
|
78
|
+
const channels = await fetchAllUnreadChannels(client);
|
|
41
79
|
|
|
42
80
|
if (channels.length === 0) {
|
|
43
81
|
console.log(chalk.green('✓ No unread messages'));
|
|
44
82
|
return;
|
|
45
83
|
}
|
|
46
84
|
|
|
47
|
-
// Apply limit
|
|
48
85
|
const limit = parseLimit(options.limit, DEFAULTS.UNREAD_DISPLAY_LIMIT);
|
|
49
|
-
const displayChannels = channels.slice(0, limit);
|
|
50
|
-
|
|
51
86
|
const format = parseFormat(options.format);
|
|
52
87
|
const countOnly = parseBoolean(options.countOnly);
|
|
53
88
|
|
|
54
|
-
|
|
55
|
-
formatter.format({ channels: displayChannels, countOnly: countOnly });
|
|
89
|
+
formatAllChannelsOutput(channels, format, countOnly, limit);
|
|
56
90
|
|
|
57
91
|
if (parseBoolean(options.markRead)) {
|
|
58
|
-
|
|
59
|
-
for (const channel of channels) {
|
|
60
|
-
await client.markAsRead(channel.id);
|
|
61
|
-
}
|
|
62
|
-
console.log(chalk.green('✓ Marked all messages as read'));
|
|
92
|
+
await markAllChannelsAsRead(client, channels);
|
|
63
93
|
}
|
|
64
94
|
}
|
|
65
95
|
|
package/src/types/commands.ts
CHANGED
package/src/utils/errors.ts
CHANGED
|
@@ -4,34 +4,30 @@ export class SlackCliError extends Error {
|
|
|
4
4
|
public code?: string
|
|
5
5
|
) {
|
|
6
6
|
super(message);
|
|
7
|
-
this.name =
|
|
7
|
+
this.name = this.constructor.name;
|
|
8
8
|
}
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
export class ConfigurationError extends SlackCliError {
|
|
12
12
|
constructor(message: string) {
|
|
13
13
|
super(message, 'CONFIGURATION_ERROR');
|
|
14
|
-
this.name = 'ConfigurationError';
|
|
15
14
|
}
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
export class ValidationError extends SlackCliError {
|
|
19
18
|
constructor(message: string) {
|
|
20
19
|
super(message, 'VALIDATION_ERROR');
|
|
21
|
-
this.name = 'ValidationError';
|
|
22
20
|
}
|
|
23
21
|
}
|
|
24
22
|
|
|
25
23
|
export class ApiError extends SlackCliError {
|
|
26
24
|
constructor(message: string) {
|
|
27
25
|
super(message, 'API_ERROR');
|
|
28
|
-
this.name = 'ApiError';
|
|
29
26
|
}
|
|
30
27
|
}
|
|
31
28
|
|
|
32
29
|
export class FileError extends SlackCliError {
|
|
33
30
|
constructor(message: string) {
|
|
34
31
|
super(message, 'FILE_ERROR');
|
|
35
|
-
this.name = 'FileError';
|
|
36
32
|
}
|
|
37
33
|
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { AbstractFormatter, JsonFormatter, createFormatterFactory } from './base-formatter';
|
|
3
|
+
import { formatSlackTimestamp } from '../date-utils';
|
|
4
|
+
|
|
5
|
+
function formatTimestampFixed(slackTimestamp: string): string {
|
|
6
|
+
const timestamp = parseFloat(slackTimestamp);
|
|
7
|
+
const date = new Date(timestamp * 1000);
|
|
8
|
+
const year = date.getUTCFullYear();
|
|
9
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
|
|
10
|
+
const day = String(date.getUTCDate()).padStart(2, '0');
|
|
11
|
+
const hours = String(date.getUTCHours()).padStart(2, '0');
|
|
12
|
+
const minutes = String(date.getUTCMinutes()).padStart(2, '0');
|
|
13
|
+
const seconds = String(date.getUTCSeconds()).padStart(2, '0');
|
|
14
|
+
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
15
|
+
}
|
|
16
|
+
import { formatMessageWithMentions } from '../format-utils';
|
|
17
|
+
import { Message as SlackMessage } from '../slack-api-client';
|
|
18
|
+
|
|
19
|
+
export interface HistoryFormatterOptions {
|
|
20
|
+
channelName: string;
|
|
21
|
+
messages: SlackMessage[];
|
|
22
|
+
users: Map<string, string>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class TableHistoryFormatter extends AbstractFormatter<HistoryFormatterOptions> {
|
|
26
|
+
format(options: HistoryFormatterOptions): void {
|
|
27
|
+
const { channelName, messages, users } = options;
|
|
28
|
+
|
|
29
|
+
console.log(chalk.bold(`\nMessage History for #${channelName}:`));
|
|
30
|
+
|
|
31
|
+
if (messages.length === 0) {
|
|
32
|
+
console.log(chalk.yellow('No messages found'));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log('');
|
|
37
|
+
messages.forEach((message) => {
|
|
38
|
+
const timestamp = formatTimestampFixed(message.ts);
|
|
39
|
+
const username = this.getUsername(message, users);
|
|
40
|
+
|
|
41
|
+
console.log(`${chalk.gray(`[${timestamp}]`)} ${chalk.cyan(username)}`);
|
|
42
|
+
const text = message.text ? formatMessageWithMentions(message.text, users) : '(no text)';
|
|
43
|
+
console.log(text);
|
|
44
|
+
console.log('');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
console.log(chalk.green(`✓ Displayed ${messages.length} message(s)`));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private getUsername(message: SlackMessage, users: Map<string, string>): string {
|
|
51
|
+
if (message.user) {
|
|
52
|
+
return users.get(message.user) || 'Unknown User';
|
|
53
|
+
}
|
|
54
|
+
if (message.bot_id) {
|
|
55
|
+
return 'Bot';
|
|
56
|
+
}
|
|
57
|
+
return 'Unknown';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
class SimpleHistoryFormatter extends AbstractFormatter<HistoryFormatterOptions> {
|
|
62
|
+
format(options: HistoryFormatterOptions): void {
|
|
63
|
+
const { messages, users } = options;
|
|
64
|
+
|
|
65
|
+
if (messages.length === 0) {
|
|
66
|
+
console.log('No messages found');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
messages.forEach((message) => {
|
|
71
|
+
const timestamp = formatTimestampFixed(message.ts);
|
|
72
|
+
const username = this.getUsername(message, users);
|
|
73
|
+
const text = message.text ? formatMessageWithMentions(message.text, users) : '(no text)';
|
|
74
|
+
console.log(`[${timestamp}] ${username}: ${text}`);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private getUsername(message: SlackMessage, users: Map<string, string>): string {
|
|
79
|
+
if (message.user) {
|
|
80
|
+
return users.get(message.user) || 'Unknown User';
|
|
81
|
+
}
|
|
82
|
+
if (message.bot_id) {
|
|
83
|
+
return 'Bot';
|
|
84
|
+
}
|
|
85
|
+
return 'Unknown';
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
class JsonHistoryFormatter extends JsonFormatter<HistoryFormatterOptions> {
|
|
90
|
+
protected transform(options: HistoryFormatterOptions) {
|
|
91
|
+
const { channelName, messages, users } = options;
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
channel: channelName,
|
|
95
|
+
messages: messages.map((message) => ({
|
|
96
|
+
timestamp: formatTimestampFixed(message.ts),
|
|
97
|
+
user: this.getUsername(message, users),
|
|
98
|
+
text: message.text || '(no text)',
|
|
99
|
+
})),
|
|
100
|
+
total: messages.length,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private getUsername(message: SlackMessage, users: Map<string, string>): string {
|
|
105
|
+
if (message.user) {
|
|
106
|
+
return users.get(message.user) || 'Unknown User';
|
|
107
|
+
}
|
|
108
|
+
if (message.bot_id) {
|
|
109
|
+
return 'Bot';
|
|
110
|
+
}
|
|
111
|
+
return 'Unknown';
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const historyFormatterFactory = createFormatterFactory<HistoryFormatterOptions>({
|
|
116
|
+
table: new TableHistoryFormatter(),
|
|
117
|
+
simple: new SimpleHistoryFormatter(),
|
|
118
|
+
json: new JsonHistoryFormatter(),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
export function createHistoryFormatter(format: string) {
|
|
122
|
+
return historyFormatterFactory.create(format);
|
|
123
|
+
}
|
package/src/utils/validators.ts
CHANGED
|
@@ -190,6 +190,19 @@ export const optionValidators = {
|
|
|
190
190
|
}
|
|
191
191
|
return null;
|
|
192
192
|
},
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Validates format option
|
|
196
|
+
*/
|
|
197
|
+
format: (options: Record<string, unknown>): string | null => {
|
|
198
|
+
if (options.format) {
|
|
199
|
+
const validFormats = ['table', 'simple', 'json'];
|
|
200
|
+
if (!validFormats.includes(options.format as string)) {
|
|
201
|
+
return `Invalid format '${options.format}'. Must be one of: ${validFormats.join(', ')}`;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return null;
|
|
205
|
+
},
|
|
193
206
|
};
|
|
194
207
|
|
|
195
208
|
/**
|
|
@@ -217,6 +217,121 @@ describe('history command', () => {
|
|
|
217
217
|
});
|
|
218
218
|
|
|
219
219
|
describe('output formatting', () => {
|
|
220
|
+
describe('format options', () => {
|
|
221
|
+
it('should display messages in JSON format when --format json is specified', async () => {
|
|
222
|
+
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
223
|
+
token: 'test-token',
|
|
224
|
+
updatedAt: new Date().toISOString()
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const mockMessages = [
|
|
228
|
+
{
|
|
229
|
+
type: 'message',
|
|
230
|
+
text: 'Hello world',
|
|
231
|
+
user: 'U123456',
|
|
232
|
+
ts: '1609459200.000100',
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
type: 'message',
|
|
236
|
+
text: 'Another message',
|
|
237
|
+
user: 'U789012',
|
|
238
|
+
ts: '1609459300.000200',
|
|
239
|
+
},
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
vi.mocked(mockSlackClient.getHistory).mockResolvedValue({
|
|
243
|
+
messages: mockMessages,
|
|
244
|
+
users: new Map([
|
|
245
|
+
['U123456', 'john.doe'],
|
|
246
|
+
['U789012', 'jane.smith']
|
|
247
|
+
])
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
await program.parseAsync(['node', 'slack-cli', 'history', '-c', 'general', '--format', 'json']);
|
|
251
|
+
|
|
252
|
+
const expectedOutput = {
|
|
253
|
+
channel: 'general',
|
|
254
|
+
messages: [
|
|
255
|
+
{
|
|
256
|
+
timestamp: '2021-01-01 00:01:40',
|
|
257
|
+
user: 'jane.smith',
|
|
258
|
+
text: 'Another message'
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
timestamp: '2021-01-01 00:00:00',
|
|
262
|
+
user: 'john.doe',
|
|
263
|
+
text: 'Hello world'
|
|
264
|
+
}
|
|
265
|
+
],
|
|
266
|
+
total: 2
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
expect(mockConsole.logSpy).toHaveBeenCalledWith(JSON.stringify(expectedOutput, null, 2));
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('should display messages in simple format when --format simple is specified', async () => {
|
|
273
|
+
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
274
|
+
token: 'test-token',
|
|
275
|
+
updatedAt: new Date().toISOString()
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const mockMessages = [
|
|
279
|
+
{
|
|
280
|
+
type: 'message',
|
|
281
|
+
text: 'Hello world',
|
|
282
|
+
user: 'U123456',
|
|
283
|
+
ts: '1609459200.000100',
|
|
284
|
+
},
|
|
285
|
+
];
|
|
286
|
+
|
|
287
|
+
vi.mocked(mockSlackClient.getHistory).mockResolvedValue({
|
|
288
|
+
messages: mockMessages,
|
|
289
|
+
users: new Map([['U123456', 'john.doe']])
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
await program.parseAsync(['node', 'slack-cli', 'history', '-c', 'general', '--format', 'simple']);
|
|
293
|
+
|
|
294
|
+
expect(mockConsole.logSpy).toHaveBeenCalledWith('[2021-01-01 00:00:00] john.doe: Hello world');
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('should display messages in table format by default', async () => {
|
|
298
|
+
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
299
|
+
token: 'test-token',
|
|
300
|
+
updatedAt: new Date().toISOString()
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const mockMessages = [
|
|
304
|
+
{
|
|
305
|
+
type: 'message',
|
|
306
|
+
text: 'Hello world',
|
|
307
|
+
user: 'U123456',
|
|
308
|
+
ts: '1609459200.000100',
|
|
309
|
+
},
|
|
310
|
+
];
|
|
311
|
+
|
|
312
|
+
vi.mocked(mockSlackClient.getHistory).mockResolvedValue({
|
|
313
|
+
messages: mockMessages,
|
|
314
|
+
users: new Map([['U123456', 'john.doe']])
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
await program.parseAsync(['node', 'slack-cli', 'history', '-c', 'general']);
|
|
318
|
+
|
|
319
|
+
// Should display in table format (current format)
|
|
320
|
+
expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('Message History for #general'));
|
|
321
|
+
expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('john.doe'));
|
|
322
|
+
expect(mockConsole.logSpy).toHaveBeenCalledWith(expect.stringContaining('Hello world'));
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it('should handle invalid format option', async () => {
|
|
326
|
+
const historyCommand = setupHistoryCommand();
|
|
327
|
+
historyCommand.exitOverride();
|
|
328
|
+
|
|
329
|
+
await expect(
|
|
330
|
+
historyCommand.parseAsync(['-c', 'general', '--format', 'invalid'], { from: 'user' })
|
|
331
|
+
).rejects.toThrow();
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
220
335
|
it('should format messages with user names and timestamps', async () => {
|
|
221
336
|
vi.mocked(mockConfigManager.getConfig).mockResolvedValue({
|
|
222
337
|
token: 'test-token',
|
package/tests/index.test.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { readFileSync } from 'fs';
|
|
|
4
4
|
import { join } from 'path';
|
|
5
5
|
|
|
6
6
|
describe('slack-cli version', () => {
|
|
7
|
-
it('should display the correct version from package.json', () => {
|
|
7
|
+
it('should display the correct version from package.json', { timeout: 20000 }, () => {
|
|
8
8
|
// Read the expected version from package.json
|
|
9
9
|
const packageJson = JSON.parse(
|
|
10
10
|
readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')
|
|
@@ -21,7 +21,7 @@ describe('slack-cli version', () => {
|
|
|
21
21
|
expect(output).toBe(expectedVersion);
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
it('should display version with -V flag', () => {
|
|
24
|
+
it('should display version with -V flag', { timeout: 20000 }, () => {
|
|
25
25
|
// Read the expected version from package.json
|
|
26
26
|
const packageJson = JSON.parse(
|
|
27
27
|
readFileSync(join(__dirname, '..', 'package.json'), 'utf-8')
|
package/.eslintrc.json
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"parser": "@typescript-eslint/parser",
|
|
3
|
-
"extends": [
|
|
4
|
-
"eslint:recommended",
|
|
5
|
-
"plugin:@typescript-eslint/recommended",
|
|
6
|
-
"prettier"
|
|
7
|
-
],
|
|
8
|
-
"plugins": ["@typescript-eslint"],
|
|
9
|
-
"parserOptions": {
|
|
10
|
-
"ecmaVersion": 2020,
|
|
11
|
-
"sourceType": "module",
|
|
12
|
-
"project": "./tsconfig.json"
|
|
13
|
-
},
|
|
14
|
-
"env": {
|
|
15
|
-
"node": true,
|
|
16
|
-
"es2020": true
|
|
17
|
-
},
|
|
18
|
-
"rules": {
|
|
19
|
-
"@typescript-eslint/explicit-function-return-type": "off",
|
|
20
|
-
"@typescript-eslint/no-explicit-any": "warn",
|
|
21
|
-
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
|
|
22
|
-
"no-console": "off"
|
|
23
|
-
},
|
|
24
|
-
"ignorePatterns": ["dist", "node_modules", "coverage", "vitest.config.ts"]
|
|
25
|
-
}
|
package/dist/utils/config.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { Config, ConfigOptions } from '../types/config';
|
|
2
|
-
export declare class ConfigManager {
|
|
3
|
-
private configPath;
|
|
4
|
-
constructor(options?: ConfigOptions);
|
|
5
|
-
setToken(token: string): Promise<void>;
|
|
6
|
-
getConfig(): Promise<Config | null>;
|
|
7
|
-
clearConfig(): Promise<void>;
|
|
8
|
-
maskToken(token: string): string;
|
|
9
|
-
}
|
|
10
|
-
//# sourceMappingURL=config.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAE7D,qBAAa,aAAa;IACxB,OAAO,CAAC,UAAU,CAAS;gBAEf,OAAO,GAAE,aAAkB;IAKjC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAatC,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAqBnC,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAUlC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;CAUjC"}
|