@coderpp/yunxiao-cli 0.1.11 → 0.2.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.
@@ -5,8 +5,16 @@ import MarkdownIt from "markdown-it";
5
5
  export function parseWorkitemUrl(link) {
6
6
  const u = new URL(link);
7
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]) {
8
+ if (u.protocol !== "https:" ||
9
+ u.host !== "devops.aliyun.com" ||
10
+ u.username ||
11
+ u.password ||
12
+ parts.length !== 6 ||
13
+ parts[1] !== "projex" ||
14
+ parts[2] !== "project" ||
15
+ !parts[3] ||
16
+ !parts[4] ||
17
+ !parts[5]) {
10
18
  throw new Error("Expected a Yunxiao workitem URL: https://devops.aliyun.com/projex/project/<projectId>/<type>/<workitemId>");
11
19
  }
12
20
  return { projectId: parts[3], workitemId: parts[5] };
@@ -21,7 +29,9 @@ function records(value) {
21
29
  throw new Error("Expected an API array.");
22
30
  return value.map(record);
23
31
  }
24
- function str(value) { return typeof value === "string" ? value : ""; }
32
+ function str(value) {
33
+ return typeof value === "string" ? value : "";
34
+ }
25
35
  function markup(content, format) {
26
36
  const text = str(content);
27
37
  if (format === "MARKDOWN")
@@ -41,7 +51,8 @@ function imageExtension(head) {
41
51
  return ".jpg";
42
52
  if (/^GIF8[79]a/.test(head.toString("ascii", 0, 6)))
43
53
  return ".gif";
44
- if (head.toString("ascii", 0, 4) === "RIFF" && head.toString("ascii", 8, 12) === "WEBP")
54
+ if (head.toString("ascii", 0, 4) === "RIFF" &&
55
+ head.toString("ascii", 8, 12) === "WEBP")
45
56
  return ".webp";
46
57
  if (head.toString("ascii", 0, 2) === "BM")
47
58
  return ".bmp";
@@ -49,10 +60,10 @@ function imageExtension(head) {
49
60
  return ".ico";
50
61
  throw new Error("Download is not a supported image (PNG/JPEG/GIF/WebP/BMP/ICO).");
51
62
  }
52
- async function download(url, target, image, fetcher) {
63
+ async function download(url, target, image, fetcher, timeoutMs = 30000) {
53
64
  let response;
54
65
  try {
55
- response = await fetcher(url, { signal: AbortSignal.timeout(120000) });
66
+ response = await fetcher(url, { signal: AbortSignal.timeout(timeoutMs) });
56
67
  }
57
68
  catch {
58
69
  throw new Error("File download failed (network error or timeout).");
@@ -105,12 +116,13 @@ async function download(url, target, image, fetcher) {
105
116
  }
106
117
  }
107
118
  }
108
- async function localize(client, id, content, format, source, dir, cache, images, fetcher) {
119
+ async function localize(client, id, content, format, source, dir, cache, images, fetcher, timeoutMs) {
109
120
  const tree = parseFragment(markup(content, format));
110
121
  async function visit(node) {
111
122
  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");
123
+ const src = node.attrs.find((a) => a.name === "src");
124
+ const fileId = src &&
125
+ new URL(src.value, "https://devops.aliyun.com").searchParams.get("fileIdentifier");
114
126
  if (!src || !fileId)
115
127
  throw new Error(`${source}: only Yunxiao images with fileIdentifier are supported.`);
116
128
  let local = cache.get(fileId);
@@ -118,11 +130,11 @@ async function localize(client, id, content, format, source, dir, cache, images,
118
130
  const file = record(await client.getWorkitemFile(id, fileId));
119
131
  if (!str(file.url))
120
132
  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);
133
+ local = await download(str(file.url), path.resolve(dir, String(cache.size + 1).padStart(2, "0")), true, fetcher, timeoutMs);
122
134
  cache.set(fileId, local);
123
135
  }
124
136
  src.value = local;
125
- if (!images.some(i => i.fileId === fileId && i.source === source))
137
+ if (!images.some((i) => i.fileId === fileId && i.source === source))
126
138
  images.push({ fileId, path: local, source });
127
139
  }
128
140
  if ("childNodes" in node)
@@ -144,7 +156,7 @@ async function attachments(client, id, options) {
144
156
  let name = path.posix.basename(str(file.fileName).replaceAll("\\", "/"));
145
157
  if (!name || name === "." || name === "..")
146
158
  name = "attachment";
147
- file.path = await download(url, path.resolve(options.dir, `${String(index + 1).padStart(2, "0")}-${name}`), false, options.fetcher ?? fetch);
159
+ file.path = await download(url, path.resolve(options.dir, `${String(index + 1).padStart(2, "0")}-${name}`), false, options.fetcher ?? fetch, options.timeoutMs);
148
160
  }
149
161
  return files;
150
162
  }
@@ -161,11 +173,20 @@ export async function readWorkitemContent(client, link, options) {
161
173
  const cache = new Map();
162
174
  const dir = path.join(options.dir, "images");
163
175
  const fetcher = options.fetcher ?? fetch;
164
- const descriptionHtml = await localize(client, id, workitem.description, workitem.formatType, "description", dir, cache, images, fetcher);
176
+ const descriptionHtml = await localize(client, id, workitem.description, workitem.formatType, "description", dir, cache, images, fetcher, options.timeoutMs);
165
177
  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") }) };
178
+ c.contentHtml = await localize(client, id, c.content, c.contentFormat, `comment:${c.id}`, dir, cache, images, fetcher, options.timeoutMs);
179
+ return {
180
+ url: link,
181
+ workitem,
182
+ descriptionHtml,
183
+ comments,
184
+ images,
185
+ attachments: await attachments(client, id, {
186
+ ...options,
187
+ dir: path.join(options.dir, "attachments"),
188
+ }),
189
+ };
169
190
  }
170
191
  export async function downloadWorkitemImages(client, id, options) {
171
192
  const workitem = record(await client.getWorkitem(id));
@@ -173,8 +194,8 @@ export async function downloadWorkitemImages(client, id, options) {
173
194
  const images = [];
174
195
  const cache = new Map();
175
196
  const fetcher = options.fetcher ?? fetch;
176
- await localize(client, id, workitem.description, workitem.formatType, "description", options.dir, cache, images, fetcher);
197
+ await localize(client, id, workitem.description, workitem.formatType, "description", options.dir, cache, images, fetcher, options.timeoutMs);
177
198
  for (const c of comments)
178
- await localize(client, id, c.content, c.contentFormat, `comment:${c.id}`, options.dir, cache, images, fetcher);
199
+ await localize(client, id, c.content, c.contentFormat, `comment:${c.id}`, options.dir, cache, images, fetcher, options.timeoutMs);
179
200
  return images;
180
201
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@coderpp/yunxiao-cli",
3
- "version": "0.1.11",
4
- "description": "A CLI for Yunxiao merge request, pipeline retry, project member, and workitem workflows.",
3
+ "version": "0.2.0",
4
+ "description": "A CLI for Yunxiao merge requests, reviews, releases, pipelines, project members, and workitems.",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",