@coderpp/yunxiao-cli 0.1.10 → 0.1.11
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 +25 -9
- package/dist/src/cli.js +24 -2
- package/dist/src/client.d.ts +2 -0
- package/dist/src/client.js +6 -0
- package/dist/src/workitem-content.d.ts +27 -0
- package/dist/src/workitem-content.js +180 -0
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -169,7 +169,7 @@ mr release <repositoryName> <sourceBranch> <targetBranch>
|
|
|
169
169
|
例如,把 `release` 合并到 `master`:
|
|
170
170
|
|
|
171
171
|
```bash
|
|
172
|
-
node dist/src/index.js mr release
|
|
172
|
+
node dist/src/index.js mr release example-web release master --yes
|
|
173
173
|
```
|
|
174
174
|
|
|
175
175
|
这个命令会先按仓库名称查询代码库 ID,再用当前 token 对应的用户作为评审人创建合并请求,随后评审通过并合并。合并时固定保留源分支,不会删除源分支。
|
|
@@ -177,7 +177,7 @@ node dist/src/index.js mr release syjc-web release master --yes
|
|
|
177
177
|
如果只想创建合并请求,不自动评审和合并,使用 `--create-only`:
|
|
178
178
|
|
|
179
179
|
```bash
|
|
180
|
-
node dist/src/index.js mr release
|
|
180
|
+
node dist/src/index.js mr release example-web release master --yes --create-only
|
|
181
181
|
```
|
|
182
182
|
|
|
183
183
|
`--create-only` 仍会在云效创建合并请求,所以仍然需要 `--yes`。
|
|
@@ -186,13 +186,7 @@ node dist/src/index.js mr release syjc-web release master --yes --create-only
|
|
|
186
186
|
|
|
187
187
|
发布版本时,如果云效返回“源分支相对目标分支没有改动,不能新建代码评审”,可视为该仓库无改动并跳过。
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
1. 商混版:`master` -> `gx-concrete/dev`
|
|
192
|
-
2. 蜀道版:`master` -> `sdjt/dev`
|
|
193
|
-
3. 标准版:`master` -> `release`
|
|
194
|
-
|
|
195
|
-
标准版应最后发布。
|
|
189
|
+
目标分支和多个版本的发布顺序由使用方按自己的项目约定指定。
|
|
196
190
|
|
|
197
191
|
查询变更文件树:
|
|
198
192
|
|
|
@@ -437,3 +431,25 @@ node dist/src/index.js workitem comment <workitemId> --content "处理完成,
|
|
|
437
431
|
- `pipeline retry-job`
|
|
438
432
|
- `workitem update`
|
|
439
433
|
- `workitem comment`
|
|
434
|
+
|
|
435
|
+
## 根据链接读取完整工作项及下载文件
|
|
436
|
+
|
|
437
|
+
```bash
|
|
438
|
+
npx --yes --package @coderpp/yunxiao-cli yunxiao workitem content "<云效工作项链接>" --dir ./content
|
|
439
|
+
npx --yes --package @coderpp/yunxiao-cli yunxiao workitem content "<云效工作项链接>" --dir ./content --download-attachments
|
|
440
|
+
|
|
441
|
+
# 单独下载正文及评论图片,或普通附件
|
|
442
|
+
npx --yes --package @coderpp/yunxiao-cli yunxiao workitem images <workitemId> --dir ./images
|
|
443
|
+
npx --yes --package @coderpp/yunxiao-cli yunxiao workitem attachments <workitemId> --dir ./attachments
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
`content`、`images`、`attachments` 始终输出 JSON。沿用现有 Token、组织、中心版/Region 版配置,
|
|
447
|
+
`content` 校验链接项目位于 `YUNXIAO_PROJECT_IDS` 或 `--project-ids` 配置的范围内。
|
|
448
|
+
`content` 保留全部详情字段和评论原文,`descriptionHtml`、评论 `contentHtml` 的图片引用替换为本地绝对路径;
|
|
449
|
+
`images` 返回 `fileId`、`path`、`source`,支持 HTML、富文本 JSON、Markdown,跨来源重复图片只下载一次。
|
|
450
|
+
普通附件默认仅返回元信息;开启 `--download-attachments` 后返回附件 `path`。
|
|
451
|
+
不返回附件临时签名 URL,下载时不向存储站点传递 Token。
|
|
452
|
+
图片位于 `images/`、普通附件位于 `attachments/`,默认根目录为 `workitem-<ID>-content`。
|
|
453
|
+
已有文件不覆盖,重复读取请选择新目录;失败返回非零退出码,清理未完成文件并保留已完成文件。
|
|
454
|
+
仅支持带 `fileIdentifier` 的云效图片(PNG/JPEG/GIF/WebP/BMP/ICO);外部图片和 data URL 会报错。
|
|
455
|
+
需要工作项、评论、附件只读权限。完整内容不递归包含关联工作项或历史动态。
|
package/dist/src/cli.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { readWorkitemContent, downloadWorkitemImages, downloadWorkitemAttachments, parseWorkitemUrl } from "./workitem-content.js";
|
|
1
2
|
import { YunxiaoClient } from "./client.js";
|
|
2
3
|
const mergeTypes = new Set(["ff-only", "no-fast-forward", "squash", "rebase"]);
|
|
3
4
|
const outputFormats = new Set(["table", "json"]);
|
|
4
|
-
const booleanFlags = new Set(["all-pages", "create-only", "help", "json", "remove-source-branch", "yes"]);
|
|
5
|
+
const booleanFlags = new Set(["download-attachments", "all-pages", "create-only", "help", "json", "remove-source-branch", "yes"]);
|
|
5
6
|
const workitemStateNames = new Map([
|
|
6
7
|
["todo", ["TODO", "待处理", "待办"]],
|
|
7
8
|
["opened", ["TODO", "待处理", "待办"]],
|
|
@@ -26,7 +27,7 @@ export async function runCli(args = process.argv.slice(2), env = process.env, io
|
|
|
26
27
|
const outputFormat = outputFormatFromFlags(parsed.flags);
|
|
27
28
|
const client = createClient(parsed.flags, env, io.fetcher);
|
|
28
29
|
const result = await runCommand(scope, command, parsed, client, env);
|
|
29
|
-
printResult(result, outputFormat, stdout);
|
|
30
|
+
printResult(result, scope === "workitem" && ["content", "images", "attachments"].includes(command) ? "json" : outputFormat, stdout);
|
|
30
31
|
return 0;
|
|
31
32
|
}
|
|
32
33
|
catch (error) {
|
|
@@ -177,6 +178,24 @@ async function runWorkitemCommand(command, parsed, client, env) {
|
|
|
177
178
|
const currentUser = await client.getCurrentUser();
|
|
178
179
|
return searchWorkitems(client, { ...parsed.flags, "assigned-to": currentUser.id }, env);
|
|
179
180
|
}
|
|
181
|
+
case "content": {
|
|
182
|
+
const link = requiredPositional(workitemId, "workitemUrl");
|
|
183
|
+
const { projectId, workitemId: id } = parseWorkitemUrl(link);
|
|
184
|
+
if (!workitemProjectIds(parsed.flags, env).includes(projectId))
|
|
185
|
+
throw new Error("Link project is outside the configured project scope.");
|
|
186
|
+
return readWorkitemContent(client, link, {
|
|
187
|
+
dir: optionalString(parsed.flags.dir) ?? `workitem-${id}-content`,
|
|
188
|
+
downloadAttachments: hasFlag(parsed.flags, "download-attachments")
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
case "images":
|
|
192
|
+
return downloadWorkitemImages(client, requiredPositional(workitemId, "workitemId"), {
|
|
193
|
+
dir: optionalString(parsed.flags.dir) ?? `workitem-${workitemId}-images`
|
|
194
|
+
});
|
|
195
|
+
case "attachments":
|
|
196
|
+
return downloadWorkitemAttachments(client, requiredPositional(workitemId, "workitemId"), {
|
|
197
|
+
dir: optionalString(parsed.flags.dir) ?? `workitem-${workitemId}-attachments`
|
|
198
|
+
});
|
|
180
199
|
case "get":
|
|
181
200
|
return client.getWorkitem(requiredPositional(workitemId, "workitemId"));
|
|
182
201
|
case "update":
|
|
@@ -712,6 +731,9 @@ Usage:
|
|
|
712
731
|
yunxiao workitem list --project-ids p1,p2 [--assigned-to userId] [--created-by userId] [--state todo|doing|done] [--from "2026-04-01 00:00:00"] [--to "2026-04-30 23:59:59"]
|
|
713
732
|
yunxiao workitem mine --project-ids p1,p2 [--state todo|doing|done] [--output table|json]
|
|
714
733
|
yunxiao workitem get <workitemId> [--output table|json]
|
|
734
|
+
yunxiao workitem content <workitemUrl> [--dir directory] [--download-attachments]
|
|
735
|
+
yunxiao workitem images <workitemId> [--dir directory]
|
|
736
|
+
yunxiao workitem attachments <workitemId> [--dir directory]
|
|
715
737
|
yunxiao workitem update <workitemId> --yes [--assigned-to userId] [--participants user1,user2] [--due-date "2026-05-01 18:00:00"] [--field key=value]
|
|
716
738
|
yunxiao workitem activities <workitemId> [--output table|json]
|
|
717
739
|
yunxiao workitem comments <workitemId> [--output table|json]
|
package/dist/src/client.d.ts
CHANGED
|
@@ -189,6 +189,8 @@ export declare class YunxiaoClient {
|
|
|
189
189
|
getMember(memberId: string): Promise<unknown>;
|
|
190
190
|
searchWorkitems(options: SearchWorkitemsOptions): Promise<unknown>;
|
|
191
191
|
getWorkitem(workitemId: string): Promise<unknown>;
|
|
192
|
+
getWorkitemFile(workitemId: string, fileId: string): Promise<unknown>;
|
|
193
|
+
listWorkitemAttachments(workitemId: string): Promise<unknown>;
|
|
192
194
|
updateWorkitem(workitemId: string, options: UpdateWorkitemOptions): Promise<unknown>;
|
|
193
195
|
listWorkitemActivities(workitemId: string): Promise<unknown>;
|
|
194
196
|
listWorkitemComments(workitemId: string): Promise<unknown>;
|
package/dist/src/client.js
CHANGED
|
@@ -85,6 +85,12 @@ export class YunxiaoClient {
|
|
|
85
85
|
getWorkitem(workitemId) {
|
|
86
86
|
return this.request("GET", `${this.projexPath("/workitems")}/${encodePathSegment(workitemId)}`);
|
|
87
87
|
}
|
|
88
|
+
getWorkitemFile(workitemId, fileId) {
|
|
89
|
+
return this.request("GET", `${this.projexPath("/workitems")}/${encodePathSegment(workitemId)}/files/${encodePathSegment(fileId)}`);
|
|
90
|
+
}
|
|
91
|
+
listWorkitemAttachments(workitemId) {
|
|
92
|
+
return this.request("GET", `${this.projexPath("/workitems")}/${encodePathSegment(workitemId)}/attachments`);
|
|
93
|
+
}
|
|
88
94
|
updateWorkitem(workitemId, options) {
|
|
89
95
|
return this.request("PUT", `${this.projexPath("/workitems")}/${encodePathSegment(workitemId)}`, undefined, options);
|
|
90
96
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { YunxiaoClient, type Fetcher } from "./client.js";
|
|
2
|
+
type RecordData = Record<string, unknown>;
|
|
3
|
+
type Image = {
|
|
4
|
+
fileId: string;
|
|
5
|
+
path: string;
|
|
6
|
+
source: string;
|
|
7
|
+
};
|
|
8
|
+
export type ContentOptions = {
|
|
9
|
+
dir: string;
|
|
10
|
+
downloadAttachments?: boolean;
|
|
11
|
+
fetcher?: Fetcher;
|
|
12
|
+
};
|
|
13
|
+
export declare function parseWorkitemUrl(link: string): {
|
|
14
|
+
projectId: string;
|
|
15
|
+
workitemId: string;
|
|
16
|
+
};
|
|
17
|
+
export declare function downloadWorkitemAttachments(client: YunxiaoClient, id: string, options: ContentOptions): Promise<RecordData[]>;
|
|
18
|
+
export declare function readWorkitemContent(client: YunxiaoClient, link: string, options: ContentOptions): Promise<{
|
|
19
|
+
url: string;
|
|
20
|
+
workitem: RecordData;
|
|
21
|
+
descriptionHtml: string;
|
|
22
|
+
comments: RecordData[];
|
|
23
|
+
images: Image[];
|
|
24
|
+
attachments: RecordData[];
|
|
25
|
+
}>;
|
|
26
|
+
export declare function downloadWorkitemImages(client: YunxiaoClient, id: string, options: ContentOptions): Promise<Image[]>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { mkdir, open, rm } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { parseFragment, serialize } from "parse5";
|
|
4
|
+
import MarkdownIt from "markdown-it";
|
|
5
|
+
export function parseWorkitemUrl(link) {
|
|
6
|
+
const u = new URL(link);
|
|
7
|
+
const parts = u.pathname.replace(/\/$/, "").split("/");
|
|
8
|
+
if (u.protocol !== "https:" || u.host !== "devops.aliyun.com" || u.username || u.password ||
|
|
9
|
+
parts.length !== 6 || parts[1] !== "projex" || parts[2] !== "project" || !parts[3] || !parts[4] || !parts[5]) {
|
|
10
|
+
throw new Error("Expected a Yunxiao workitem URL: https://devops.aliyun.com/projex/project/<projectId>/<type>/<workitemId>");
|
|
11
|
+
}
|
|
12
|
+
return { projectId: parts[3], workitemId: parts[5] };
|
|
13
|
+
}
|
|
14
|
+
function record(value) {
|
|
15
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
16
|
+
throw new Error("Unexpected workitem API response.");
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
function records(value) {
|
|
20
|
+
if (!Array.isArray(value))
|
|
21
|
+
throw new Error("Expected an API array.");
|
|
22
|
+
return value.map(record);
|
|
23
|
+
}
|
|
24
|
+
function str(value) { return typeof value === "string" ? value : ""; }
|
|
25
|
+
function markup(content, format) {
|
|
26
|
+
const text = str(content);
|
|
27
|
+
if (format === "MARKDOWN")
|
|
28
|
+
return new MarkdownIt({ html: true }).render(text);
|
|
29
|
+
if (text.trimStart().startsWith("{")) {
|
|
30
|
+
const rich = record(JSON.parse(text));
|
|
31
|
+
if (typeof rich.htmlValue !== "string")
|
|
32
|
+
throw new Error("Rich text is missing htmlValue.");
|
|
33
|
+
return rich.htmlValue;
|
|
34
|
+
}
|
|
35
|
+
return text;
|
|
36
|
+
}
|
|
37
|
+
function imageExtension(head) {
|
|
38
|
+
if (head.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])))
|
|
39
|
+
return ".png";
|
|
40
|
+
if (head[0] === 255 && head[1] === 216 && head[2] === 255)
|
|
41
|
+
return ".jpg";
|
|
42
|
+
if (/^GIF8[79]a/.test(head.toString("ascii", 0, 6)))
|
|
43
|
+
return ".gif";
|
|
44
|
+
if (head.toString("ascii", 0, 4) === "RIFF" && head.toString("ascii", 8, 12) === "WEBP")
|
|
45
|
+
return ".webp";
|
|
46
|
+
if (head.toString("ascii", 0, 2) === "BM")
|
|
47
|
+
return ".bmp";
|
|
48
|
+
if (head.subarray(0, 4).equals(Buffer.from([0, 0, 1, 0])))
|
|
49
|
+
return ".ico";
|
|
50
|
+
throw new Error("Download is not a supported image (PNG/JPEG/GIF/WebP/BMP/ICO).");
|
|
51
|
+
}
|
|
52
|
+
async function download(url, target, image, fetcher) {
|
|
53
|
+
let response;
|
|
54
|
+
try {
|
|
55
|
+
response = await fetcher(url, { signal: AbortSignal.timeout(120000) });
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
throw new Error("File download failed (network error or timeout).");
|
|
59
|
+
}
|
|
60
|
+
if (!response.ok || !response.body) {
|
|
61
|
+
await response.body?.cancel();
|
|
62
|
+
throw new Error(`File download failed: HTTP ${response.status}.`);
|
|
63
|
+
}
|
|
64
|
+
const reader = response.body.getReader();
|
|
65
|
+
let file;
|
|
66
|
+
let destination = target;
|
|
67
|
+
let complete = false;
|
|
68
|
+
try {
|
|
69
|
+
const chunks = [];
|
|
70
|
+
let size = 0;
|
|
71
|
+
while (size < 16) {
|
|
72
|
+
const chunk = await reader.read();
|
|
73
|
+
if (chunk.done)
|
|
74
|
+
break;
|
|
75
|
+
chunks.push(Buffer.from(chunk.value));
|
|
76
|
+
size += chunk.value.length;
|
|
77
|
+
}
|
|
78
|
+
const head = Buffer.concat(chunks);
|
|
79
|
+
if (image)
|
|
80
|
+
destination += imageExtension(head);
|
|
81
|
+
await mkdir(path.dirname(destination), { recursive: true, mode: 0o700 });
|
|
82
|
+
file = await open(destination, "wx", 0o600);
|
|
83
|
+
await file.writeFile(head);
|
|
84
|
+
for (;;) {
|
|
85
|
+
const chunk = await reader.read();
|
|
86
|
+
if (chunk.done)
|
|
87
|
+
break;
|
|
88
|
+
await file.writeFile(chunk.value);
|
|
89
|
+
}
|
|
90
|
+
await file.close();
|
|
91
|
+
complete = true;
|
|
92
|
+
return destination;
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (error.code === "EEXIST")
|
|
96
|
+
throw new Error(`File already exists; choose a new directory: ${destination}`);
|
|
97
|
+
// Do not leak signed storage URLs from fetch errors.
|
|
98
|
+
throw new Error(`Unable to save file ${destination}: download or filesystem error.`);
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
await reader.cancel().catch(() => undefined);
|
|
102
|
+
if (file && !complete) {
|
|
103
|
+
await file.close();
|
|
104
|
+
await rm(destination, { force: true });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
async function localize(client, id, content, format, source, dir, cache, images, fetcher) {
|
|
109
|
+
const tree = parseFragment(markup(content, format));
|
|
110
|
+
async function visit(node) {
|
|
111
|
+
if ("tagName" in node && node.tagName === "img") {
|
|
112
|
+
const src = node.attrs.find(a => a.name === "src");
|
|
113
|
+
const fileId = src && new URL(src.value, "https://devops.aliyun.com").searchParams.get("fileIdentifier");
|
|
114
|
+
if (!src || !fileId)
|
|
115
|
+
throw new Error(`${source}: only Yunxiao images with fileIdentifier are supported.`);
|
|
116
|
+
let local = cache.get(fileId);
|
|
117
|
+
if (!local) {
|
|
118
|
+
const file = record(await client.getWorkitemFile(id, fileId));
|
|
119
|
+
if (!str(file.url))
|
|
120
|
+
throw new Error(`Missing download URL for image ${fileId}.`);
|
|
121
|
+
local = await download(str(file.url), path.resolve(dir, String(cache.size + 1).padStart(2, "0")), true, fetcher);
|
|
122
|
+
cache.set(fileId, local);
|
|
123
|
+
}
|
|
124
|
+
src.value = local;
|
|
125
|
+
if (!images.some(i => i.fileId === fileId && i.source === source))
|
|
126
|
+
images.push({ fileId, path: local, source });
|
|
127
|
+
}
|
|
128
|
+
if ("childNodes" in node)
|
|
129
|
+
for (const child of node.childNodes)
|
|
130
|
+
await visit(child);
|
|
131
|
+
}
|
|
132
|
+
await visit(tree);
|
|
133
|
+
return serialize(tree);
|
|
134
|
+
}
|
|
135
|
+
async function attachments(client, id, options) {
|
|
136
|
+
const files = records(await client.listWorkitemAttachments(id));
|
|
137
|
+
for (const [index, file] of files.entries()) {
|
|
138
|
+
const url = str(file.url);
|
|
139
|
+
delete file.url;
|
|
140
|
+
if (!options.downloadAttachments)
|
|
141
|
+
continue;
|
|
142
|
+
if (!url)
|
|
143
|
+
throw new Error(`Missing download URL for attachment ${index + 1}.`);
|
|
144
|
+
let name = path.posix.basename(str(file.fileName).replaceAll("\\", "/"));
|
|
145
|
+
if (!name || name === "." || name === "..")
|
|
146
|
+
name = "attachment";
|
|
147
|
+
file.path = await download(url, path.resolve(options.dir, `${String(index + 1).padStart(2, "0")}-${name}`), false, options.fetcher ?? fetch);
|
|
148
|
+
}
|
|
149
|
+
return files;
|
|
150
|
+
}
|
|
151
|
+
export async function downloadWorkitemAttachments(client, id, options) {
|
|
152
|
+
return attachments(client, id, { ...options, downloadAttachments: true });
|
|
153
|
+
}
|
|
154
|
+
export async function readWorkitemContent(client, link, options) {
|
|
155
|
+
const { projectId, workitemId: id } = parseWorkitemUrl(link);
|
|
156
|
+
const workitem = record(await client.getWorkitem(id));
|
|
157
|
+
if (workitem.id !== id || record(workitem.space).id !== projectId)
|
|
158
|
+
throw new Error("Workitem/project does not match the link.");
|
|
159
|
+
const comments = records(await client.listWorkitemComments(id));
|
|
160
|
+
const images = [];
|
|
161
|
+
const cache = new Map();
|
|
162
|
+
const dir = path.join(options.dir, "images");
|
|
163
|
+
const fetcher = options.fetcher ?? fetch;
|
|
164
|
+
const descriptionHtml = await localize(client, id, workitem.description, workitem.formatType, "description", dir, cache, images, fetcher);
|
|
165
|
+
for (const c of comments)
|
|
166
|
+
c.contentHtml = await localize(client, id, c.content, c.contentFormat, `comment:${c.id}`, dir, cache, images, fetcher);
|
|
167
|
+
return { url: link, workitem, descriptionHtml, comments, images,
|
|
168
|
+
attachments: await attachments(client, id, { ...options, dir: path.join(options.dir, "attachments") }) };
|
|
169
|
+
}
|
|
170
|
+
export async function downloadWorkitemImages(client, id, options) {
|
|
171
|
+
const workitem = record(await client.getWorkitem(id));
|
|
172
|
+
const comments = records(await client.listWorkitemComments(id));
|
|
173
|
+
const images = [];
|
|
174
|
+
const cache = new Map();
|
|
175
|
+
const fetcher = options.fetcher ?? fetch;
|
|
176
|
+
await localize(client, id, workitem.description, workitem.formatType, "description", options.dir, cache, images, fetcher);
|
|
177
|
+
for (const c of comments)
|
|
178
|
+
await localize(client, id, c.content, c.contentFormat, `comment:${c.id}`, options.dir, cache, images, fetcher);
|
|
179
|
+
return images;
|
|
180
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coderpp/yunxiao-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "A CLI for Yunxiao merge request, pipeline retry, project member, and workitem workflows.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"access": "public"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
+
"@types/markdown-it": "^14.2.0",
|
|
32
33
|
"@types/node": "^20.12.12",
|
|
33
34
|
"typescript": "^5.4.5"
|
|
34
35
|
},
|
|
@@ -38,5 +39,9 @@
|
|
|
38
39
|
"files": [
|
|
39
40
|
"dist/src",
|
|
40
41
|
"README.md"
|
|
41
|
-
]
|
|
42
|
+
],
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"markdown-it": "^14.3.3",
|
|
45
|
+
"parse5": "^7.3.0"
|
|
46
|
+
}
|
|
42
47
|
}
|