@levinariy.fedorov/youtrack-mcp 0.2.0 → 0.3.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 +19 -1
- package/dist/bin.js +23 -7
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/issues.d.ts +35 -0
- package/dist/issues.js +45 -2
- package/dist/server.js +75 -4
- package/dist/write.d.ts +9 -2
- package/dist/write.js +14 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,7 +28,9 @@ MCP-сервер прямо в продукте, и если он у вас ес
|
|
|
28
28
|
## Вход
|
|
29
29
|
|
|
30
30
|
Токен — постоянный токен YouTrack из профиля пользователя. Принимается только
|
|
31
|
-
потоком: в аргументе он осел бы в истории оболочки и был бы виден в списке
|
|
31
|
+
потоком: в аргументе он осел бы в истории оболочки и был бы виден в списке
|
|
32
|
+
процессов. Флаг `--token-stdin` нужен лишь в живом терминале, где команда иначе
|
|
33
|
+
молча ждала бы ввод; при передаче через конвейер он не обязателен.
|
|
32
34
|
|
|
33
35
|
```powershell
|
|
34
36
|
"<токен>" | npx @levinariy.fedorov/youtrack-mcp login --base-url https://youtrack.example.com --token-stdin
|
|
@@ -51,6 +53,12 @@ npx @levinariy.fedorov/youtrack-mcp profiles
|
|
|
51
53
|
| `read_issue` | Задача целиком: поля, комментарии, вложения, связи — одним вызовом |
|
|
52
54
|
| `search_issues` | Поиск запросом YouTrack; только идентификатор, тема и поля |
|
|
53
55
|
| `add_comment` | Комментарий: заказчику или ограниченного круга |
|
|
56
|
+
| `edit_comment` | Переписать уже опубликованный комментарий |
|
|
57
|
+
| `find_users` | Логины по фамилии, имени или домену |
|
|
58
|
+
| `list_work_items` | Что уже списано за период, с суммой |
|
|
59
|
+
| `edit_comment` | Переписать уже опубликованный комментарий |
|
|
60
|
+
| `find_users` | Логины по фамилии, имени или домену |
|
|
61
|
+
| `list_work_items` | Что уже списано за период, с суммой |
|
|
54
62
|
| `update_issue` | Поля, статус, тема, описание |
|
|
55
63
|
| `create_issue` | Новая задача в проекте |
|
|
56
64
|
| `log_work` | Списание времени |
|
|
@@ -114,6 +122,16 @@ npm run typecheck
|
|
|
114
122
|
Собранный `dist` в репозитории не хранится: его делает `prepare` — при публикации
|
|
115
123
|
и при установке пакета из git.
|
|
116
124
|
|
|
125
|
+
## Выпуск версии
|
|
126
|
+
|
|
127
|
+
Публикует GitLab CI по тегу: поднимите версию в `package.json`, закоммитьте,
|
|
128
|
+
повесьте тег `vX.Y.Z` и запушьте его. Пайплайн сверит тег с версией, прогонит
|
|
129
|
+
проверки и опубликует пакет. Пуш в ветку ничего не публикует.
|
|
130
|
+
|
|
131
|
+
Токен для публикации лежит в переменной `NPM_TOKEN`: это granular access token
|
|
132
|
+
npm с правом publish и разрешённым обходом двухфакторки — ввести одноразовый код
|
|
133
|
+
в пайплайне некому.
|
|
134
|
+
|
|
117
135
|
## Лицензия
|
|
118
136
|
|
|
119
137
|
MIT.
|
package/dist/bin.js
CHANGED
|
@@ -14,7 +14,7 @@ const USAGE = `youtrack-mcp — MCP-сервер для YouTrack.
|
|
|
14
14
|
|
|
15
15
|
Ключи login:
|
|
16
16
|
--base-url <URL> адрес YouTrack
|
|
17
|
-
--token-stdin
|
|
17
|
+
--token-stdin ждать токен из stdin в интерактивном терминале
|
|
18
18
|
--profile <имя> профиль (по умолчанию default)
|
|
19
19
|
--set-default сделать профиль основным
|
|
20
20
|
`;
|
|
@@ -38,18 +38,34 @@ async function login(argv) {
|
|
|
38
38
|
if (!baseUrl)
|
|
39
39
|
throw new AuthError("Нужен --base-url: адрес YouTrack.");
|
|
40
40
|
// Токен принимаем только потоком: в аргументе он осядет в истории оболочки и
|
|
41
|
-
// будет виден в списке процессов.
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
// будет виден в списке процессов. Требовать при этом ещё и --token-stdin
|
|
42
|
+
// незачем — другого способа всё равно нет. Флаг остаётся ради явности, но
|
|
43
|
+
// сам по себе решает только одно: ждать ли ввода в живом терминале, где
|
|
44
|
+
// молчаливое ожидание выглядит зависанием.
|
|
45
|
+
if (process.stdin.isTTY && !values["token-stdin"]) {
|
|
46
|
+
throw new AuthError("Токен передаётся потоком. Либо направьте его в stdin, либо добавьте --token-stdin и введите вручную.");
|
|
47
|
+
}
|
|
44
48
|
const token = await readStdin();
|
|
45
49
|
if (token === "")
|
|
46
|
-
throw new AuthError("Пустой
|
|
50
|
+
throw new AuthError("Пустой токен: в stdin ничего не пришло.");
|
|
51
|
+
// Токен уходит в заголовок HTTP, а туда пролезают только однобайтовые символы.
|
|
52
|
+
// Без этой проверки кириллица в токене роняет запрос сообщением про ByteString,
|
|
53
|
+
// по которому не догадаться, что дело в самом токене.
|
|
54
|
+
if (!/^[\x21-\x7e]+$/.test(token)) {
|
|
55
|
+
throw new AuthError("Токен содержит недопустимые символы: постоянный токен YouTrack состоит из латиницы, цифр и знаков препинания.");
|
|
56
|
+
}
|
|
57
|
+
// Сначала проверяем, потом сохраняем: профиль с негодным токеном в файле хуже,
|
|
58
|
+
// чем его отсутствие — он выглядит настроенным и ломается позже и не там.
|
|
47
59
|
const profile = values.profile ?? "default";
|
|
60
|
+
const user = await me(new YouTrackClient({ baseUrl: baseUrl.replace(/\/+$/, ""), token }));
|
|
61
|
+
// Негодный токен YouTrack не отвергает: он отвечает от лица гостя, и логин
|
|
62
|
+
// выглядит удавшимся ровно до первой попытки что-нибудь записать.
|
|
63
|
+
if (user.guest) {
|
|
64
|
+
throw new AuthError("YouTrack не принял токен: ответ пришёл от гостевой учётной записи. Проверьте, что токен скопирован целиком и не отозван.");
|
|
65
|
+
}
|
|
48
66
|
await saveProfile(profile, { baseUrl, token });
|
|
49
67
|
if (values["set-default"])
|
|
50
68
|
await setDefaultProfile(profile);
|
|
51
|
-
const { credentials } = await resolveCredentials(profile);
|
|
52
|
-
const user = await me(new YouTrackClient(credentials));
|
|
53
69
|
process.stdout.write(`Профиль «${profile}» сохранён: ${user.name} (${user.login}) на ${baseUrl}\n`);
|
|
54
70
|
}
|
|
55
71
|
async function status() {
|
package/dist/index.d.ts
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
export { YouTrackClient, YouTrackError } from "./client.js";
|
|
8
8
|
export { AuthError, configPath, listProfiles, readConfig, removeProfile, resolveCredentials, saveProfile, setDefaultProfile } from "./config.js";
|
|
9
9
|
export type { Config, Credentials, ProfileInfo } from "./config.js";
|
|
10
|
-
export { me, readIssue, searchIssues } from "./issues.js";
|
|
11
|
-
export type { Attachment, Comment, Found, Issue, Link, Person, ReadOptions } from "./issues.js";
|
|
12
|
-
export { addComment, createIssue, logWork, updateIssue } from "./write.js";
|
|
13
|
-
export type { Audience, NewIssue, PostedComment, UpdateRequest
|
|
10
|
+
export { findUsers, listWorkItems, me, readIssue, searchIssues } from "./issues.js";
|
|
11
|
+
export type { Attachment, Comment, Found, FoundPerson, Issue, Link, Person, ReadOptions, WorkItem } from "./issues.js";
|
|
12
|
+
export { addComment, createIssue, editComment, logWork, updateIssue } from "./write.js";
|
|
13
|
+
export type { Audience, LoggedWork, NewIssue, PostedComment, UpdateRequest } from "./write.js";
|
|
14
14
|
export { createServer } from "./server.js";
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export { YouTrackClient, YouTrackError } from "./client.js";
|
|
8
8
|
export { AuthError, configPath, listProfiles, readConfig, removeProfile, resolveCredentials, saveProfile, setDefaultProfile } from "./config.js";
|
|
9
|
-
export { me, readIssue, searchIssues } from "./issues.js";
|
|
10
|
-
export { addComment, createIssue, logWork, updateIssue } from "./write.js";
|
|
9
|
+
export { findUsers, listWorkItems, me, readIssue, searchIssues } from "./issues.js";
|
|
10
|
+
export { addComment, createIssue, editComment, logWork, updateIssue } from "./write.js";
|
|
11
11
|
export { createServer } from "./server.js";
|
package/dist/issues.d.ts
CHANGED
|
@@ -59,6 +59,41 @@ export type Found = {
|
|
|
59
59
|
fields: Record<string, string>;
|
|
60
60
|
};
|
|
61
61
|
export declare function searchIssues(client: YouTrackClient, query: string, limit?: number): Promise<Found[]>;
|
|
62
|
+
export type FoundPerson = Person & {
|
|
63
|
+
email: string;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Ищет людей по имени, фамилии или части адреса.
|
|
67
|
+
*
|
|
68
|
+
* Нужен там, где известен человек, а требуется логин: в упоминаниях и в поле
|
|
69
|
+
* исполнителя YouTrack принимает только логин, и угадывать его по фамилии
|
|
70
|
+
* нельзя.
|
|
71
|
+
*/
|
|
72
|
+
export declare function findUsers(client: YouTrackClient, query: string, limit?: number): Promise<FoundPerson[]>;
|
|
73
|
+
export type WorkItem = {
|
|
74
|
+
id: string;
|
|
75
|
+
issue: {
|
|
76
|
+
id: string;
|
|
77
|
+
summary: string;
|
|
78
|
+
};
|
|
79
|
+
author: Person;
|
|
80
|
+
date: string;
|
|
81
|
+
minutes: number;
|
|
82
|
+
text: string;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Записи о работе за период.
|
|
86
|
+
*
|
|
87
|
+
* Без автора берутся свои: чужие списания нужны редко, а «все за неделю» на
|
|
88
|
+
* большом трекере — это тысячи записей, которые никто не просил.
|
|
89
|
+
*/
|
|
90
|
+
export declare function listWorkItems(client: YouTrackClient, options?: {
|
|
91
|
+
from: string;
|
|
92
|
+
to: string;
|
|
93
|
+
author?: string;
|
|
94
|
+
limit?: number;
|
|
95
|
+
}): Promise<WorkItem[]>;
|
|
62
96
|
export declare function me(client: YouTrackClient): Promise<Person & {
|
|
63
97
|
email: string;
|
|
98
|
+
guest: boolean;
|
|
64
99
|
}>;
|
package/dist/issues.js
CHANGED
|
@@ -138,7 +138,50 @@ export async function searchIssues(client, query, limit = 50) {
|
|
|
138
138
|
return { id: issue.idReadable ?? "", summary: issue.summary ?? "", fields: collectFields(issue) };
|
|
139
139
|
});
|
|
140
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Ищет людей по имени, фамилии или части адреса.
|
|
143
|
+
*
|
|
144
|
+
* Нужен там, где известен человек, а требуется логин: в упоминаниях и в поле
|
|
145
|
+
* исполнителя YouTrack принимает только логин, и угадывать его по фамилии
|
|
146
|
+
* нельзя.
|
|
147
|
+
*/
|
|
148
|
+
export async function findUsers(client, query, limit = 20) {
|
|
149
|
+
const raw = await client.get("/api/users", {
|
|
150
|
+
fields: "login,fullName,email",
|
|
151
|
+
query,
|
|
152
|
+
$top: limit
|
|
153
|
+
});
|
|
154
|
+
return raw.map((item) => ({ ...person(item), email: item.email ?? "" }));
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Записи о работе за период.
|
|
158
|
+
*
|
|
159
|
+
* Без автора берутся свои: чужие списания нужны редко, а «все за неделю» на
|
|
160
|
+
* большом трекере — это тысячи записей, которые никто не просил.
|
|
161
|
+
*/
|
|
162
|
+
export async function listWorkItems(client, options = { from: "", to: "" }) {
|
|
163
|
+
const author = options.author ?? (await me(client)).login;
|
|
164
|
+
const raw = await client.get("/api/workItems", {
|
|
165
|
+
fields: "id,issue(idReadable,summary),author(login,fullName),date,duration(minutes),text",
|
|
166
|
+
author,
|
|
167
|
+
startDate: options.from,
|
|
168
|
+
endDate: options.to,
|
|
169
|
+
$top: options.limit ?? 200
|
|
170
|
+
});
|
|
171
|
+
return raw.map((item) => ({
|
|
172
|
+
id: item.id ?? "",
|
|
173
|
+
issue: { id: item.issue?.idReadable ?? "", summary: item.issue?.summary ?? "" },
|
|
174
|
+
author: person(item.author),
|
|
175
|
+
date: item.date ? new Date(item.date).toISOString().slice(0, 10) : "",
|
|
176
|
+
minutes: item.duration?.minutes ?? 0,
|
|
177
|
+
text: item.text ?? ""
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
141
180
|
export async function me(client) {
|
|
142
|
-
|
|
143
|
-
|
|
181
|
+
// Поле guest спрашивается не из любопытства: с негодным токеном YouTrack не
|
|
182
|
+
// отвечает ошибкой, а представляется гостем — и вызов выглядит удавшимся.
|
|
183
|
+
const raw = await client.get("/api/users/me", {
|
|
184
|
+
fields: "login,fullName,email,guest"
|
|
185
|
+
});
|
|
186
|
+
return { ...person(raw), email: raw.email ?? "", guest: raw.guest === true };
|
|
144
187
|
}
|
package/dist/server.js
CHANGED
|
@@ -2,8 +2,8 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { YouTrackClient } from "./client.js";
|
|
4
4
|
import { resolveCredentials } from "./config.js";
|
|
5
|
-
import { me, readIssue, searchIssues } from "./issues.js";
|
|
6
|
-
import { addComment, createIssue, logWork, updateIssue } from "./write.js";
|
|
5
|
+
import { findUsers, listWorkItems, me, readIssue, searchIssues } from "./issues.js";
|
|
6
|
+
import { addComment, createIssue, editComment, logWork, updateIssue } from "./write.js";
|
|
7
7
|
/**
|
|
8
8
|
* Клиенты живут по имени профиля: конфигурацию незачем перечитывать на каждый
|
|
9
9
|
* вызов, а профилей всё равно единицы.
|
|
@@ -114,6 +114,76 @@ export function createServer() {
|
|
|
114
114
|
structuredContent: comment
|
|
115
115
|
};
|
|
116
116
|
});
|
|
117
|
+
server.registerTool("edit_comment", {
|
|
118
|
+
title: "Переписать комментарий",
|
|
119
|
+
description: "Заменяет текст уже опубликованного комментария целиком — дополнить его нечем, YouTrack хранит его одной строкой, " +
|
|
120
|
+
"так что прежний текст возьмите из `read_issue` и пришлите полный новый. " +
|
|
121
|
+
"Идентификатор комментария — оттуда же. Правьте только свои: чужой комментарий переписывать не следует.",
|
|
122
|
+
inputSchema: {
|
|
123
|
+
id: z.string().describe("Идентификатор задачи, например PROJ-1234"),
|
|
124
|
+
commentId: z.string().describe("Идентификатор комментария из read_issue"),
|
|
125
|
+
text: z.string().min(1).describe("Полный новый текст"),
|
|
126
|
+
profile: profileArg
|
|
127
|
+
},
|
|
128
|
+
outputSchema: { id: z.string(), created: z.string(), public: z.boolean() },
|
|
129
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true }
|
|
130
|
+
}, async ({ id, commentId, text, profile }) => {
|
|
131
|
+
const comment = await editComment(await clientFor(profile), id, commentId, text);
|
|
132
|
+
return { content: [{ type: "text", text: "Комментарий переписан." }], structuredContent: comment };
|
|
133
|
+
});
|
|
134
|
+
server.registerTool("find_users", {
|
|
135
|
+
title: "Найти людей",
|
|
136
|
+
description: "Ищет по имени, фамилии или части адреса и возвращает логины. Нужен там, где человек известен, а требуется логин: " +
|
|
137
|
+
"в упоминаниях и в поле исполнителя YouTrack принимает только его.",
|
|
138
|
+
inputSchema: {
|
|
139
|
+
query: z.string().min(1).describe("Фамилия, имя, часть адреса или домен компании"),
|
|
140
|
+
limit: z.number().int().min(1).max(100).default(20).describe("Сколько человек вернуть"),
|
|
141
|
+
profile: profileArg
|
|
142
|
+
},
|
|
143
|
+
outputSchema: {
|
|
144
|
+
users: z.array(z.object({ login: z.string(), name: z.string(), email: z.string() }))
|
|
145
|
+
},
|
|
146
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
147
|
+
}, async ({ query, limit, profile }) => {
|
|
148
|
+
const users = await findUsers(await clientFor(profile), query, limit);
|
|
149
|
+
return { content: [{ type: "text", text: `Найдено людей: ${users.length}` }], structuredContent: { users } };
|
|
150
|
+
});
|
|
151
|
+
server.registerTool("list_work_items", {
|
|
152
|
+
title: "Записи о работе за период",
|
|
153
|
+
description: "Что уже списано за интервал дат — по задачам, с датами и минутами. Без `author` берутся ваши записи. " +
|
|
154
|
+
"Отвечает на «сколько уже списано» до того, как списывать ещё; `totalMinutes` — сумма по выдаче.",
|
|
155
|
+
inputSchema: {
|
|
156
|
+
from: z.string().describe("Начало периода, 2026-09-01"),
|
|
157
|
+
to: z.string().describe("Конец периода включительно, 2026-09-07"),
|
|
158
|
+
author: z.string().optional().describe("Логин автора записей; по умолчанию вы"),
|
|
159
|
+
limit: z.number().int().min(1).max(1000).default(200).describe("Сколько записей вернуть"),
|
|
160
|
+
profile: profileArg
|
|
161
|
+
},
|
|
162
|
+
outputSchema: {
|
|
163
|
+
items: z.array(z.object({
|
|
164
|
+
id: z.string(),
|
|
165
|
+
issue: z.object({ id: z.string(), summary: z.string() }),
|
|
166
|
+
author: PersonSchema,
|
|
167
|
+
date: z.string(),
|
|
168
|
+
minutes: z.number(),
|
|
169
|
+
text: z.string()
|
|
170
|
+
})),
|
|
171
|
+
totalMinutes: z.number()
|
|
172
|
+
},
|
|
173
|
+
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
174
|
+
}, async ({ from, to, author, limit, profile }) => {
|
|
175
|
+
const items = await listWorkItems(await clientFor(profile), {
|
|
176
|
+
from,
|
|
177
|
+
to,
|
|
178
|
+
limit,
|
|
179
|
+
...(author === undefined ? {} : { author })
|
|
180
|
+
});
|
|
181
|
+
const totalMinutes = items.reduce((sum, item) => sum + item.minutes, 0);
|
|
182
|
+
return {
|
|
183
|
+
content: [{ type: "text", text: `Записей: ${items.length}, всего ${totalMinutes} мин.` }],
|
|
184
|
+
structuredContent: { items, totalMinutes }
|
|
185
|
+
};
|
|
186
|
+
});
|
|
117
187
|
server.registerTool("update_issue", {
|
|
118
188
|
title: "Изменить поля задачи",
|
|
119
189
|
description: "Меняет поля задачи по их именам в YouTrack: `{\"State\": \"Analysis\", \"Assignee\": \"ivan.petrov\"}`. " +
|
|
@@ -183,9 +253,10 @@ export function createServer() {
|
|
|
183
253
|
});
|
|
184
254
|
server.registerTool("whoami", {
|
|
185
255
|
title: "Кто я в YouTrack",
|
|
186
|
-
description: "Учётная запись, от имени которой идут вызовы. Нужна там, где скилл ставит Assignee на себя."
|
|
256
|
+
description: "Учётная запись, от имени которой идут вызовы. Нужна там, где скилл ставит Assignee на себя. " +
|
|
257
|
+
"`guest: true` означает, что токен не принят и YouTrack отвечает анонимно: писать от такого имени нельзя.",
|
|
187
258
|
inputSchema: { profile: profileArg },
|
|
188
|
-
outputSchema: { login: z.string(), name: z.string(), email: z.string() },
|
|
259
|
+
outputSchema: { login: z.string(), name: z.string(), email: z.string(), guest: z.boolean() },
|
|
189
260
|
annotations: { readOnlyHint: true, openWorldHint: true }
|
|
190
261
|
}, async ({ profile }) => {
|
|
191
262
|
const user = await me(await clientFor(profile));
|
package/dist/write.d.ts
CHANGED
|
@@ -17,6 +17,13 @@ export type PostedComment = {
|
|
|
17
17
|
public: boolean;
|
|
18
18
|
};
|
|
19
19
|
export declare function addComment(client: YouTrackClient, issueId: string, text: string, audience?: Audience): Promise<PostedComment>;
|
|
20
|
+
/**
|
|
21
|
+
* Переписывает уже опубликованный комментарий.
|
|
22
|
+
*
|
|
23
|
+
* Текст заменяется целиком — дополнить его нечем: YouTrack хранит комментарий
|
|
24
|
+
* одной строкой. Прежний текст, если он нужен, читается из `read_issue`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function editComment(client: YouTrackClient, issueId: string, commentId: string, text: string): Promise<PostedComment>;
|
|
20
27
|
export type UpdateRequest = {
|
|
21
28
|
/** Поля задачи по именам, как они называются в YouTrack. Пустая строка очищает поле. */
|
|
22
29
|
fields?: Record<string, string>;
|
|
@@ -31,7 +38,7 @@ export type NewIssue = {
|
|
|
31
38
|
fields?: Record<string, string>;
|
|
32
39
|
};
|
|
33
40
|
export declare function createIssue(client: YouTrackClient, request: NewIssue): Promise<Issue>;
|
|
34
|
-
export type
|
|
41
|
+
export type LoggedWork = {
|
|
35
42
|
id: string;
|
|
36
43
|
minutes: number;
|
|
37
44
|
date: string;
|
|
@@ -47,4 +54,4 @@ export declare function logWork(client: YouTrackClient, issueId: string, minutes
|
|
|
47
54
|
date?: string;
|
|
48
55
|
text?: string;
|
|
49
56
|
type?: string;
|
|
50
|
-
}): Promise<
|
|
57
|
+
}): Promise<LoggedWork>;
|
package/dist/write.js
CHANGED
|
@@ -42,6 +42,20 @@ export async function addComment(client, issueId, text, audience = []) {
|
|
|
42
42
|
public: created.visibility?.$type !== "LimitedVisibility"
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Переписывает уже опубликованный комментарий.
|
|
47
|
+
*
|
|
48
|
+
* Текст заменяется целиком — дополнить его нечем: YouTrack хранит комментарий
|
|
49
|
+
* одной строкой. Прежний текст, если он нужен, читается из `read_issue`.
|
|
50
|
+
*/
|
|
51
|
+
export async function editComment(client, issueId, commentId, text) {
|
|
52
|
+
const updated = await client.post(`/api/issues/${encodeURIComponent(issueId)}/comments/${encodeURIComponent(commentId)}`, { text }, { fields: "id,created,visibility($type)" });
|
|
53
|
+
return {
|
|
54
|
+
id: updated.id ?? commentId,
|
|
55
|
+
created: updated.created ? new Date(updated.created).toISOString() : "",
|
|
56
|
+
public: updated.visibility?.$type !== "LimitedVisibility"
|
|
57
|
+
};
|
|
58
|
+
}
|
|
45
59
|
/**
|
|
46
60
|
* Готовит значение поля к записи.
|
|
47
61
|
*
|
package/package.json
CHANGED