@kud/jira 0.1.0 → 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.
package/dist/index.d.ts CHANGED
@@ -203,7 +203,7 @@ declare const createJiraClient: (options: JiraClientOptions) => {
203
203
  getTransitions: (key: string) => Promise<{
204
204
  transitions: JiraTransition[];
205
205
  }>;
206
- transitionIssue: (key: string, transitionId: string) => Promise<void>;
206
+ transitionIssue: (key: string, transitionId: string, fields?: Record<string, unknown>) => Promise<void>;
207
207
  addComment: (key: string, body: unknown) => Promise<void>;
208
208
  getMe: () => Promise<JiraUser>;
209
209
  getFields: () => Promise<JiraField[]>;
package/dist/index.js CHANGED
@@ -104,9 +104,16 @@ var createJiraClient = (options) => {
104
104
  ]).join(",")}`
105
105
  ),
106
106
  getTransitions: (key) => request(`/rest/api/3/issue/${encodeURIComponent(key)}/transitions`),
107
- transitionIssue: (key, transitionId) => request(`/rest/api/3/issue/${encodeURIComponent(key)}/transitions`, {
107
+ // `fields` rides along with the transition rather than going through a
108
+ // separate edit: Jira rejects a resolution set on an already-closed issue, so
109
+ // the only moment a screen-required field like `resolution` can be written is
110
+ // the transition itself.
111
+ transitionIssue: (key, transitionId, fields) => request(`/rest/api/3/issue/${encodeURIComponent(key)}/transitions`, {
108
112
  method: "POST",
109
- body: JSON.stringify({ transition: { id: transitionId } })
113
+ body: JSON.stringify({
114
+ transition: { id: transitionId },
115
+ ...fields ? { fields } : {}
116
+ })
110
117
  }),
111
118
  addComment: (key, body) => request(`/rest/api/3/issue/${encodeURIComponent(key)}/comment`, {
112
119
  method: "POST",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/adf.ts","../src/attachments.ts"],"sourcesContent":["import type {\n JiraBoard,\n JiraChangelogEntry,\n JiraComment,\n JiraComponent,\n JiraCreated,\n JiraEpic,\n JiraFilter,\n JiraIssueLinkType,\n JiraIssueType,\n JiraNamed,\n JiraProjectStatuses,\n JiraVersion,\n JiraWorklog,\n JiraField,\n JiraIssue,\n JiraProject,\n JiraSearchPage,\n JiraSprint,\n JiraTransition,\n JiraUser,\n SearchOptions,\n} from \"./types.js\"\n\nexport type JiraCredentials = {\n baseUrl: string\n email: string\n token: string\n}\n\nexport type JiraClientOptions = JiraCredentials & {\n /** Custom fields to request and label, discovered per instance via `jira fields`. */\n customFields?: { id: string; label: string }[]\n /** The instance's sprint field id, e.g. customfield_10020. */\n sprintField?: string\n fetch?: typeof globalThis.fetch\n}\n\nexport type JiraApiError = Error & {\n name: \"JiraApiError\"\n status: number\n method: string\n url: string\n body: string\n}\n\nexport const jiraApiError = (\n status: number,\n method: string,\n url: string,\n body: string,\n): JiraApiError =>\n Object.assign(\n new Error(`Jira API ${status} ${method} ${url}: ${truncate(body)}`),\n { name: \"JiraApiError\" as const, status, method, url, body },\n )\n\nexport const isJiraApiError = (e: unknown): e is JiraApiError =>\n e instanceof Error && e.name === \"JiraApiError\"\n\nconst truncate = (s: string, max = 400): string =>\n s.length > max ? `${s.slice(0, max)}…` : s\n\n/** Accepts `myorg.atlassian.net` as readily as a full URL; a bare host is the\n * common shape of the env var and produces an opaque ERR_INVALID_URL if left. */\nexport const normalizeBaseUrl = (raw: string): string => {\n const trimmed = raw.trim().replace(/\\/+$/, \"\")\n return /^https?:\\/\\//.test(trimmed) ? trimmed : `https://${trimmed}`\n}\n\nconst DEFAULT_FIELDS = [\n \"summary\",\n \"status\",\n \"assignee\",\n \"issuetype\",\n \"priority\",\n \"project\",\n \"labels\",\n \"updated\",\n]\n\nexport const createJiraClient = (options: JiraClientOptions) => {\n const doFetch = options.fetch ?? globalThis.fetch\n const baseUrl = normalizeBaseUrl(options.baseUrl)\n const customFields = options.customFields ?? []\n const authHeader = `Basic ${Buffer.from(`${options.email}:${options.token}`).toString(\"base64\")}`\n\n const request = async <T>(\n path: string,\n init: RequestInit & { raw?: boolean } = {},\n ): Promise<T> => {\n const url = path.startsWith(\"http\")\n ? path\n : `${baseUrl}${path.startsWith(\"/\") ? \"\" : \"/\"}${path}`\n\n const res = await doFetch(url, {\n ...init,\n headers: {\n Authorization: authHeader,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n // Jira localises error bodies from the account's language. Errors here\n // are read by scripts and pasted into issues, so pin them to English.\n \"Accept-Language\": \"en\",\n ...init.headers,\n },\n })\n\n // `raw` hands back the Response untouched — binary downloads and manual\n // redirect handling both need the headers, not a parsed body.\n if (init.raw) return res as T\n\n if (!res.ok) {\n throw jiraApiError(\n res.status,\n init.method ?? \"GET\",\n url,\n await res.text(),\n )\n }\n if (res.status === 204) return undefined as T\n return (await res.json()) as T\n }\n\n /** Every field the caller cares about, including this instance's custom ones. */\n const fieldList = (extra?: string[]): string[] => [\n ...new Set([\n ...(extra ?? DEFAULT_FIELDS),\n ...customFields.map((f) => f.id),\n ...(options.sprintField ? [options.sprintField] : []),\n ]),\n ]\n\n const searchPage = (\n jql: string,\n opts: SearchOptions = {},\n ): Promise<JiraSearchPage> =>\n request<JiraSearchPage>(\"/rest/api/3/search/jql\", {\n method: \"POST\",\n body: JSON.stringify({\n jql,\n fields: fieldList(opts.fields),\n maxResults: opts.maxResults ?? 50,\n ...(opts.nextPageToken ? { nextPageToken: opts.nextPageToken } : {}),\n ...(opts.expand ? { expand: opts.expand } : {}),\n }),\n })\n\n /**\n * Walks the cursor to `limit` issues. Three separate stop conditions, because\n * the cursor is opaque and has been reported to loop: no token, an empty\n * page, or a token we have already followed. Trusting `isLast` alone would\n * page forever against an instance exhibiting that bug.\n */\n const searchIssues = async (\n jql: string,\n opts: SearchOptions & { limit?: number } = {},\n ): Promise<JiraIssue[]> => {\n const limit = opts.limit ?? 50\n const issues: JiraIssue[] = []\n const seenTokens = new Set<string>()\n let token = opts.nextPageToken\n\n while (issues.length < limit) {\n const page = await searchPage(jql, {\n ...opts,\n nextPageToken: token,\n maxResults: Math.min(100, limit - issues.length),\n })\n if (page.issues.length === 0) break\n issues.push(...page.issues)\n\n const next = page.nextPageToken\n if (!next || page.isLast || seenTokens.has(next)) break\n seenTokens.add(next)\n token = next\n }\n\n return issues.slice(0, limit)\n }\n\n return {\n request,\n customFields,\n sprintField: options.sprintField,\n\n searchPage,\n searchIssues,\n\n getIssue: (key: string): Promise<JiraIssue> =>\n request(\n `/rest/api/3/issue/${encodeURIComponent(key)}?fields=${fieldList([\n ...DEFAULT_FIELDS,\n \"description\",\n \"comment\",\n \"reporter\",\n \"created\",\n \"attachment\",\n ]).join(\",\")}`,\n ),\n\n getTransitions: (key: string): Promise<{ transitions: JiraTransition[] }> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/transitions`),\n\n transitionIssue: (key: string, transitionId: string): Promise<void> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/transitions`, {\n method: \"POST\",\n body: JSON.stringify({ transition: { id: transitionId } }),\n }),\n\n addComment: (key: string, body: unknown): Promise<void> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/comment`, {\n method: \"POST\",\n body: JSON.stringify({ body }),\n }),\n\n getMe: (): Promise<JiraUser> => request(\"/rest/api/3/myself\"),\n\n getFields: (): Promise<JiraField[]> => request(\"/rest/api/3/field\"),\n\n getProjects: (): Promise<JiraProject[]> =>\n request(\"/rest/api/3/project?expand=description\"),\n\n getBoards: (): Promise<{ values: JiraBoard[] }> =>\n request(\"/rest/agile/1.0/board\"),\n\n getSprints: (\n boardId: number,\n state?: string,\n ): Promise<{ values: JiraSprint[] }> =>\n request(\n `/rest/agile/1.0/board/${boardId}/sprint${state ? `?state=${state}` : \"\"}`,\n ),\n\n // ── issues ────────────────────────────────────────────────────────────\n createIssue: (fields: Record<string, unknown>): Promise<JiraCreated> =>\n request(\"/rest/api/3/issue\", {\n method: \"POST\",\n body: JSON.stringify({ fields }),\n }),\n\n updateIssue: (\n key: string,\n fields: Record<string, unknown>,\n ): Promise<void> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}`, {\n method: \"PUT\",\n body: JSON.stringify({ fields }),\n }),\n\n deleteIssue: (key: string, deleteSubtasks = false): Promise<void> =>\n request(\n `/rest/api/3/issue/${encodeURIComponent(key)}?deleteSubtasks=${deleteSubtasks}`,\n { method: \"DELETE\" },\n ),\n\n assignIssue: (key: string, accountId: string | null): Promise<void> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/assignee`, {\n method: \"PUT\",\n body: JSON.stringify({ accountId }),\n }),\n\n getComments: (key: string): Promise<{ comments: JiraComment[] }> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/comment`),\n\n deleteComment: (key: string, commentId: string): Promise<void> =>\n request(\n `/rest/api/3/issue/${encodeURIComponent(key)}/comment/${encodeURIComponent(commentId)}`,\n { method: \"DELETE\" },\n ),\n\n getWatchers: (key: string): Promise<{ watchers: JiraUser[] }> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/watchers`),\n\n addWatcher: (key: string, accountId: string): Promise<void> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/watchers`, {\n method: \"POST\",\n body: JSON.stringify(accountId),\n }),\n\n removeWatcher: (key: string, accountId: string): Promise<void> =>\n request(\n `/rest/api/3/issue/${encodeURIComponent(key)}/watchers?accountId=${encodeURIComponent(accountId)}`,\n { method: \"DELETE\" },\n ),\n\n getWorklogs: (key: string): Promise<{ worklogs: JiraWorklog[] }> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/worklog`),\n\n addWorklog: (\n key: string,\n body: { timeSpent: string; comment?: unknown; started?: string },\n ): Promise<JiraWorklog> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/worklog`, {\n method: \"POST\",\n body: JSON.stringify(body),\n }),\n\n getChangelog: (key: string): Promise<{ values: JiraChangelogEntry[] }> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/changelog`),\n\n getIssueLinkTypes: (): Promise<{ issueLinkTypes: JiraIssueLinkType[] }> =>\n request(\"/rest/api/3/issueLinkType\"),\n\n linkIssues: (\n type: string,\n inwardKey: string,\n outwardKey: string,\n ): Promise<void> =>\n request(\"/rest/api/3/issueLink\", {\n method: \"POST\",\n body: JSON.stringify({\n type: { name: type },\n inwardIssue: { key: inwardKey },\n outwardIssue: { key: outwardKey },\n }),\n }),\n\n // ── projects ──────────────────────────────────────────────────────────\n getProject: (key: string): Promise<JiraProject> =>\n request(\n `/rest/api/3/project/${encodeURIComponent(key)}?expand=description,lead,url`,\n ),\n\n getProjectVersions: (key: string): Promise<JiraVersion[]> =>\n request(`/rest/api/3/project/${encodeURIComponent(key)}/versions`),\n\n getProjectComponents: (key: string): Promise<JiraComponent[]> =>\n request(`/rest/api/3/project/${encodeURIComponent(key)}/components`),\n\n getProjectStatuses: (key: string): Promise<JiraProjectStatuses[]> =>\n request(`/rest/api/3/project/${encodeURIComponent(key)}/statuses`),\n\n // ── agile ─────────────────────────────────────────────────────────────\n getBoard: (id: number): Promise<JiraBoard> =>\n request(`/rest/agile/1.0/board/${id}`),\n\n getBoardIssues: (\n id: number,\n jql?: string,\n ): Promise<{ issues: JiraIssue[] }> =>\n request(\n `/rest/agile/1.0/board/${id}/issue${jql ? `?jql=${encodeURIComponent(jql)}` : \"\"}`,\n ),\n\n getBacklog: (id: number): Promise<{ issues: JiraIssue[] }> =>\n request(`/rest/agile/1.0/board/${id}/backlog`),\n\n getSprint: (id: number): Promise<JiraSprint> =>\n request(`/rest/agile/1.0/sprint/${id}`),\n\n getSprintIssues: (id: number): Promise<{ issues: JiraIssue[] }> =>\n request(`/rest/agile/1.0/sprint/${id}/issue`),\n\n getBoardEpics: (id: number): Promise<{ values: JiraEpic[] }> =>\n request(`/rest/agile/1.0/board/${id}/epic`),\n\n getEpicIssues: (id: string): Promise<{ issues: JiraIssue[] }> =>\n request(`/rest/agile/1.0/epic/${encodeURIComponent(id)}/issue`),\n\n // ── people ────────────────────────────────────────────────────────────\n searchUsers: (query: string, maxResults = 20): Promise<JiraUser[]> =>\n request(\n `/rest/api/3/user/search?query=${encodeURIComponent(query)}&maxResults=${maxResults}`,\n ),\n\n searchAssignableUsers: (\n query: string,\n projectKey: string,\n maxResults = 20,\n ): Promise<JiraUser[]> =>\n request(\n `/rest/api/3/user/assignable/search?query=${encodeURIComponent(query)}&project=${encodeURIComponent(projectKey)}&maxResults=${maxResults}`,\n ),\n\n // ── instance metadata ─────────────────────────────────────────────────\n getIssueTypes: (): Promise<JiraIssueType[]> =>\n request(\"/rest/api/3/issuetype\"),\n\n getPriorities: (): Promise<JiraNamed[]> => request(\"/rest/api/3/priority\"),\n\n getResolutions: (): Promise<JiraNamed[]> =>\n request(\"/rest/api/3/resolution\"),\n\n getStatuses: (): Promise<JiraNamed[]> => request(\"/rest/api/3/status\"),\n\n getLabels: (): Promise<{ values: string[] }> =>\n request(\"/rest/api/3/label?maxResults=1000\"),\n\n getFilters: (): Promise<{ values: JiraFilter[] }> =>\n request(\"/rest/api/3/filter/search?expand=jql&maxResults=50\"),\n\n getDashboards: (): Promise<{ dashboards: JiraNamed[] }> =>\n request(\"/rest/api/3/dashboard\"),\n\n getServerInfo: (): Promise<Record<string, unknown>> =>\n request(\"/rest/api/3/serverInfo\"),\n\n getMyPermissions: (projectKey?: string): Promise<Record<string, unknown>> =>\n request(\n `/rest/api/3/mypermissions${projectKey ? `?projectKey=${encodeURIComponent(projectKey)}` : \"\"}`,\n ),\n\n /**\n * The count the removed `total` field used to give. Deliberately named\n * approximate because that is what Atlassian guarantees — it is an index\n * estimate, not a scan, and will disagree with a full page walk.\n */\n approximateCount: (jql: string): Promise<{ count: number }> =>\n request(\"/rest/api/3/search/approximate-count\", {\n method: \"POST\",\n body: JSON.stringify({ jql }),\n }),\n }\n}\n\nexport type JiraClient = ReturnType<typeof createJiraClient>\n","type AdfNode = {\n type?: string\n text?: string\n content?: AdfNode[]\n attrs?: Record<string, unknown>\n marks?: { type: string; attrs?: Record<string, unknown> }[]\n}\n\nconst markUp = (text: string, marks: AdfNode[\"marks\"]): string =>\n (marks ?? []).reduce((acc, mark) => {\n if (mark.type === \"code\") return `\\`${acc}\\``\n if (mark.type === \"strong\") return `**${acc}**`\n if (mark.type === \"em\") return `_${acc}_`\n if (mark.type === \"strike\") return `~~${acc}~~`\n if (mark.type === \"link\") return `[${acc}](${mark.attrs?.[\"href\"] ?? \"\"})`\n return acc\n }, text)\n\nexport type MediaResolver = (id: string) => string | undefined\n\nlet resolveMedia: MediaResolver = () => undefined\n\nconst children = (node: AdfNode, sep = \"\"): string =>\n (node.content ?? []).map(render).join(sep)\n\nconst listItems = (node: AdfNode, bullet: (i: number) => string): string =>\n (node.content ?? [])\n .map((item, i) => {\n const body = children(item, \"\\n\\n\").trim()\n const [first = \"\", ...rest] = body.split(\"\\n\")\n const marker = bullet(i)\n const indent = \" \".repeat(marker.length)\n return [\n `${marker}${first}`,\n ...rest.map((l) => (l ? `${indent}${l}` : l)),\n ].join(\"\\n\")\n })\n .join(\"\\n\")\n\n/** A table row rendered as a pipe row; the header separator is added by the caller. */\nconst row = (node: AdfNode): string =>\n `| ${(node.content ?? []).map((cell) => children(cell, \" \").trim().replace(/\\n+/g, \" \")).join(\" | \")} |`\n\nconst renderTable = (node: AdfNode): string => {\n const rows = node.content ?? []\n if (rows.length === 0) return \"\"\n const isHeader = (r: AdfNode): boolean =>\n (r.content ?? []).some((c) => c.type === \"tableHeader\")\n const [first] = rows\n const rendered = rows.map(row)\n if (first && isHeader(first)) {\n const columns = (first.content ?? []).length\n rendered.splice(1, 0, `|${\" --- |\".repeat(columns)}`)\n }\n return rendered.join(\"\\n\")\n}\n\nconst render = (node: AdfNode): string => {\n switch (node.type) {\n case \"doc\":\n return children(node, \"\\n\\n\")\n case \"paragraph\":\n return children(node)\n case \"text\":\n return markUp(node.text ?? \"\", node.marks)\n case \"hardBreak\":\n return \"\\n\"\n case \"heading\":\n return `${\"#\".repeat(Number(node.attrs?.[\"level\"] ?? 1))} ${children(node)}`\n case \"bulletList\":\n return listItems(node, () => \"- \")\n case \"orderedList\":\n return listItems(\n node,\n (i) => `${Number(node.attrs?.[\"order\"] ?? 1) + i}. `,\n )\n case \"codeBlock\":\n return `\\`\\`\\`${node.attrs?.[\"language\"] ?? \"\"}\\n${children(node)}\\n\\`\\`\\``\n case \"blockquote\":\n return children(node, \"\\n\\n\")\n .split(\"\\n\")\n .map((l) => `> ${l}`)\n .join(\"\\n\")\n case \"panel\":\n return `> [!${String(node.attrs?.[\"panelType\"] ?? \"note\").toUpperCase()}]\\n${children(\n node,\n \"\\n\\n\",\n )\n .split(\"\\n\")\n .map((l) => `> ${l}`)\n .join(\"\\n\")}`\n case \"rule\":\n return \"---\"\n case \"table\":\n return renderTable(node)\n case \"mediaSingle\":\n case \"mediaGroup\":\n return children(node, \"\\n\")\n case \"media\": {\n // `alt` is the filename Jira stored; the id is a media-platform UUID that\n // means nothing to a reader and does not match the attachment id either.\n const alt = node.attrs?.[\"alt\"]\n if (typeof alt === \"string\" && alt) return `[attachment: ${alt}]`\n const id = String(node.attrs?.[\"id\"] ?? \"\")\n const name = resolveMedia(id)\n return name ? `[attachment: ${name}]` : `[attachment: ${id || \"unknown\"}]`\n }\n case \"mention\": {\n // Jira stores the display text with its own leading @ most of the time,\n // but not always, so normalise rather than assume either way.\n const label = String(node.attrs?.[\"text\"] ?? node.attrs?.[\"id\"] ?? \"\")\n return label.startsWith(\"@\") ? label : `@${label}`\n }\n case \"emoji\":\n return String(node.attrs?.[\"text\"] ?? node.attrs?.[\"shortName\"] ?? \"\")\n case \"date\":\n return String(node.attrs?.[\"timestamp\"] ?? \"\")\n case \"status\":\n return `[${String(node.attrs?.[\"text\"] ?? \"\").toUpperCase()}]`\n case \"inlineCard\":\n return String(node.attrs?.[\"url\"] ?? \"\")\n default:\n return children(node, \"\\n\\n\")\n }\n}\n\n/**\n * Atlassian Document Format to Markdown. Deliberately stops at Markdown rather\n * than emitting ANSI: rendering is the surface's job, so a TUI, a pager and a\n * `--json` consumer all get the same text and only one of them styles it.\n */\nexport const adfToMarkdown = (\n doc: unknown,\n media?: MediaResolver,\n): string => {\n if (typeof doc === \"string\") return doc\n if (!doc || typeof doc !== \"object\") return \"\"\n const previous = resolveMedia\n resolveMedia = media ?? (() => undefined)\n try {\n return render(doc as AdfNode)\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim()\n } finally {\n resolveMedia = previous\n }\n}\n\ntype AdfDoc = { type: \"doc\"; version: 1; content: unknown[] }\n\nconst inline = (text: string): unknown[] => {\n // Only code spans and links are worth parsing: they are the two marks whose\n // absence changes meaning rather than appearance.\n const pattern = /(`[^`]+`)|(\\[[^\\]]+\\]\\([^)]+\\))/g\n const nodes: unknown[] = []\n let cursor = 0\n\n for (const match of text.matchAll(pattern)) {\n const at = match.index\n if (at > cursor)\n nodes.push({ type: \"text\", text: text.slice(cursor, at) })\n\n const token = match[0]\n if (token.startsWith(\"`\")) {\n nodes.push({\n type: \"text\",\n text: token.slice(1, -1),\n marks: [{ type: \"code\" }],\n })\n } else {\n const [, label = \"\", href = \"\"] =\n token.match(/\\[([^\\]]+)\\]\\(([^)]+)\\)/) ?? []\n nodes.push({\n type: \"text\",\n text: label,\n marks: [{ type: \"link\", attrs: { href } }],\n })\n }\n cursor = at + token.length\n }\n\n if (cursor < text.length) nodes.push({ type: \"text\", text: text.slice(cursor) })\n return nodes.length > 0 ? nodes : [{ type: \"text\", text }]\n}\n\nconst blockToAdf = (block: string): unknown => {\n const fence = block.match(/^```(\\w*)\\n([\\s\\S]*?)\\n?```$/)\n if (fence)\n return {\n type: \"codeBlock\",\n ...(fence[1] ? { attrs: { language: fence[1] } } : {}),\n content: [{ type: \"text\", text: fence[2] ?? \"\" }],\n }\n\n const heading = block.match(/^(#{1,6})\\s+(.*)$/)\n if (heading)\n return {\n type: \"heading\",\n attrs: { level: heading[1]?.length ?? 1 },\n content: inline(heading[2] ?? \"\"),\n }\n\n const lines = block.split(\"\\n\")\n if (lines.every((l) => /^\\s*[-*]\\s+/.test(l)))\n return {\n type: \"bulletList\",\n content: lines.map((l) => ({\n type: \"listItem\",\n content: [\n { type: \"paragraph\", content: inline(l.replace(/^\\s*[-*]\\s+/, \"\")) },\n ],\n })),\n }\n\n if (lines.every((l) => /^\\s*\\d+[.)]\\s+/.test(l)))\n return {\n type: \"orderedList\",\n content: lines.map((l) => ({\n type: \"listItem\",\n content: [\n { type: \"paragraph\", content: inline(l.replace(/^\\s*\\d+[.)]\\s+/, \"\")) },\n ],\n })),\n }\n\n return { type: \"paragraph\", content: inline(block) }\n}\n\n/**\n * Markdown to ADF, covering what someone actually types into a comment from a\n * terminal: paragraphs, fenced code, headings, lists, links and code spans.\n * Deliberately partial — anything richer is better authored in Jira, and a\n * half-supported table would corrupt more often than it would help.\n */\nexport const markdownToAdf = (text: string): AdfDoc => ({\n type: \"doc\",\n version: 1,\n content: text\n .replace(/\\r\\n/g, \"\\n\")\n .split(/\\n{2,}/)\n .map((b) => b.trim())\n .filter(Boolean)\n .map(blockToAdf),\n})\n","import type { JiraClient } from \"./client.js\"\nimport type { JiraIssue } from \"./types.js\"\n\nexport type JiraAttachment = {\n id: string\n filename: string\n mimeType: string\n size: number\n created?: string\n author?: { displayName: string }\n content?: string\n}\n\n/** Where an attachment was referenced from, so `issue view` can say so. */\nexport type AttachmentOrigin =\n | { kind: \"issue\" }\n | { kind: \"description\" }\n | { kind: \"comment\"; commentId: string; author?: string }\n\nexport type LocatedAttachment = JiraAttachment & { origins: AttachmentOrigin[] }\n\ntype AdfNode = {\n type?: string\n attrs?: Record<string, unknown>\n content?: AdfNode[]\n}\n\nexport type MediaRef = { id?: string; filename?: string }\n\n/**\n * Media nodes carry a media-platform UUID in `attrs.id`, which is a different\n * namespace from the numeric attachment id — joining on it matches nothing,\n * ever. `attrs.alt` holds the original filename and is the only field the two\n * representations share, so it is the real key and the id is the fallback.\n */\nconst mediaRefs = (node: unknown): MediaRef[] => {\n if (!node || typeof node !== \"object\") return []\n const n = node as AdfNode\n const here: MediaRef[] =\n n.type === \"media\"\n ? [\n {\n ...(typeof n.attrs?.[\"id\"] === \"string\"\n ? { id: n.attrs[\"id\"] as string }\n : {}),\n ...(typeof n.attrs?.[\"alt\"] === \"string\"\n ? { filename: n.attrs[\"alt\"] as string }\n : {}),\n },\n ]\n : []\n return [...here, ...(n.content ?? []).flatMap(mediaRefs)]\n}\n\n/**\n * Jira reports attachments once, on the issue, and never says where they were\n * embedded. Walking the description and each comment for media nodes and\n * joining them back is the only way to answer \"which comment did this come\n * from\" — a question the API cannot be asked directly.\n */\nexport const locateAttachments = (issue: JiraIssue): LocatedAttachment[] => {\n const attachments = (issue.fields[\"attachment\"] as JiraAttachment[]) ?? []\n const origins = new Map<string, AttachmentOrigin[]>()\n\n const note = (ref: MediaRef, origin: AttachmentOrigin): void => {\n const match = attachments.find(\n (a) =>\n (ref.filename !== undefined && a.filename === ref.filename) ||\n (ref.id !== undefined && a.id === ref.id),\n )\n if (!match) return\n origins.set(match.id, [...(origins.get(match.id) ?? []), origin])\n }\n\n for (const ref of mediaRefs(issue.fields.description))\n note(ref, { kind: \"description\" })\n\n for (const comment of issue.fields.comment?.comments ?? [])\n for (const ref of mediaRefs(comment.body))\n note(ref, {\n kind: \"comment\",\n commentId: comment.id,\n author: comment.author?.displayName,\n })\n\n return attachments.map((a) => ({\n ...a,\n origins: origins.get(a.id) ?? [{ kind: \"issue\" as const }],\n }))\n}\n\nconst TEXTUAL =\n /^(text\\/|application\\/(json|xml|x-yaml|yaml|javascript|sql|x-sh))/\n\n/** Extensions Jira commonly mislabels as application/octet-stream. */\nconst TEXTUAL_EXTENSIONS =\n /\\.(txt|md|markdown|log|json|ya?ml|csv|tsv|xml|html?|css|jsx?|tsx?|py|rb|go|rs|java|kt|sh|zsh|bash|sql|ini|toml|conf|env|diff|patch)$/i\n\nexport const isTextual = (attachment: JiraAttachment): boolean =>\n TEXTUAL.test(attachment.mimeType) ||\n TEXTUAL_EXTENSIONS.test(attachment.filename)\n\n/**\n * Fetches attachment bytes. The documented content endpoint 302s to a\n * short-lived media host, and the auth header must NOT follow: it is a Jira\n * credential and the redirect target is a different origin that neither needs\n * nor should see it. Hence manual redirect handling rather than fetch's default.\n */\nexport const downloadAttachment = async (\n client: JiraClient,\n id: string,\n fetchImpl: typeof globalThis.fetch = globalThis.fetch,\n): Promise<{ bytes: Uint8Array; mimeType: string | null }> => {\n const res = await client.request<Response>(\n `/rest/api/3/attachment/content/${encodeURIComponent(id)}`,\n { redirect: \"manual\", raw: true },\n )\n\n const location = res.headers.get(\"location\")\n const final =\n res.status >= 300 && res.status < 400 && location\n ? await fetchImpl(location)\n : res\n\n if (!final.ok) {\n throw new Error(\n `could not download attachment ${id}: ${final.status} ${final.statusText}`,\n )\n }\n\n return {\n bytes: new Uint8Array(await final.arrayBuffer()),\n mimeType: final.headers.get(\"content-type\"),\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/client.ts","../src/adf.ts","../src/attachments.ts"],"sourcesContent":["import type {\n JiraBoard,\n JiraChangelogEntry,\n JiraComment,\n JiraComponent,\n JiraCreated,\n JiraEpic,\n JiraFilter,\n JiraIssueLinkType,\n JiraIssueType,\n JiraNamed,\n JiraProjectStatuses,\n JiraVersion,\n JiraWorklog,\n JiraField,\n JiraIssue,\n JiraProject,\n JiraSearchPage,\n JiraSprint,\n JiraTransition,\n JiraUser,\n SearchOptions,\n} from \"./types.js\"\n\nexport type JiraCredentials = {\n baseUrl: string\n email: string\n token: string\n}\n\nexport type JiraClientOptions = JiraCredentials & {\n /** Custom fields to request and label, discovered per instance via `jira fields`. */\n customFields?: { id: string; label: string }[]\n /** The instance's sprint field id, e.g. customfield_10020. */\n sprintField?: string\n fetch?: typeof globalThis.fetch\n}\n\nexport type JiraApiError = Error & {\n name: \"JiraApiError\"\n status: number\n method: string\n url: string\n body: string\n}\n\nexport const jiraApiError = (\n status: number,\n method: string,\n url: string,\n body: string,\n): JiraApiError =>\n Object.assign(\n new Error(`Jira API ${status} ${method} ${url}: ${truncate(body)}`),\n { name: \"JiraApiError\" as const, status, method, url, body },\n )\n\nexport const isJiraApiError = (e: unknown): e is JiraApiError =>\n e instanceof Error && e.name === \"JiraApiError\"\n\nconst truncate = (s: string, max = 400): string =>\n s.length > max ? `${s.slice(0, max)}…` : s\n\n/** Accepts `myorg.atlassian.net` as readily as a full URL; a bare host is the\n * common shape of the env var and produces an opaque ERR_INVALID_URL if left. */\nexport const normalizeBaseUrl = (raw: string): string => {\n const trimmed = raw.trim().replace(/\\/+$/, \"\")\n return /^https?:\\/\\//.test(trimmed) ? trimmed : `https://${trimmed}`\n}\n\nconst DEFAULT_FIELDS = [\n \"summary\",\n \"status\",\n \"assignee\",\n \"issuetype\",\n \"priority\",\n \"project\",\n \"labels\",\n \"updated\",\n]\n\nexport const createJiraClient = (options: JiraClientOptions) => {\n const doFetch = options.fetch ?? globalThis.fetch\n const baseUrl = normalizeBaseUrl(options.baseUrl)\n const customFields = options.customFields ?? []\n const authHeader = `Basic ${Buffer.from(`${options.email}:${options.token}`).toString(\"base64\")}`\n\n const request = async <T>(\n path: string,\n init: RequestInit & { raw?: boolean } = {},\n ): Promise<T> => {\n const url = path.startsWith(\"http\")\n ? path\n : `${baseUrl}${path.startsWith(\"/\") ? \"\" : \"/\"}${path}`\n\n const res = await doFetch(url, {\n ...init,\n headers: {\n Authorization: authHeader,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n // Jira localises error bodies from the account's language. Errors here\n // are read by scripts and pasted into issues, so pin them to English.\n \"Accept-Language\": \"en\",\n ...init.headers,\n },\n })\n\n // `raw` hands back the Response untouched — binary downloads and manual\n // redirect handling both need the headers, not a parsed body.\n if (init.raw) return res as T\n\n if (!res.ok) {\n throw jiraApiError(\n res.status,\n init.method ?? \"GET\",\n url,\n await res.text(),\n )\n }\n if (res.status === 204) return undefined as T\n return (await res.json()) as T\n }\n\n /** Every field the caller cares about, including this instance's custom ones. */\n const fieldList = (extra?: string[]): string[] => [\n ...new Set([\n ...(extra ?? DEFAULT_FIELDS),\n ...customFields.map((f) => f.id),\n ...(options.sprintField ? [options.sprintField] : []),\n ]),\n ]\n\n const searchPage = (\n jql: string,\n opts: SearchOptions = {},\n ): Promise<JiraSearchPage> =>\n request<JiraSearchPage>(\"/rest/api/3/search/jql\", {\n method: \"POST\",\n body: JSON.stringify({\n jql,\n fields: fieldList(opts.fields),\n maxResults: opts.maxResults ?? 50,\n ...(opts.nextPageToken ? { nextPageToken: opts.nextPageToken } : {}),\n ...(opts.expand ? { expand: opts.expand } : {}),\n }),\n })\n\n /**\n * Walks the cursor to `limit` issues. Three separate stop conditions, because\n * the cursor is opaque and has been reported to loop: no token, an empty\n * page, or a token we have already followed. Trusting `isLast` alone would\n * page forever against an instance exhibiting that bug.\n */\n const searchIssues = async (\n jql: string,\n opts: SearchOptions & { limit?: number } = {},\n ): Promise<JiraIssue[]> => {\n const limit = opts.limit ?? 50\n const issues: JiraIssue[] = []\n const seenTokens = new Set<string>()\n let token = opts.nextPageToken\n\n while (issues.length < limit) {\n const page = await searchPage(jql, {\n ...opts,\n nextPageToken: token,\n maxResults: Math.min(100, limit - issues.length),\n })\n if (page.issues.length === 0) break\n issues.push(...page.issues)\n\n const next = page.nextPageToken\n if (!next || page.isLast || seenTokens.has(next)) break\n seenTokens.add(next)\n token = next\n }\n\n return issues.slice(0, limit)\n }\n\n return {\n request,\n customFields,\n sprintField: options.sprintField,\n\n searchPage,\n searchIssues,\n\n getIssue: (key: string): Promise<JiraIssue> =>\n request(\n `/rest/api/3/issue/${encodeURIComponent(key)}?fields=${fieldList([\n ...DEFAULT_FIELDS,\n \"description\",\n \"comment\",\n \"reporter\",\n \"created\",\n \"attachment\",\n ]).join(\",\")}`,\n ),\n\n getTransitions: (key: string): Promise<{ transitions: JiraTransition[] }> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/transitions`),\n\n // `fields` rides along with the transition rather than going through a\n // separate edit: Jira rejects a resolution set on an already-closed issue, so\n // the only moment a screen-required field like `resolution` can be written is\n // the transition itself.\n transitionIssue: (\n key: string,\n transitionId: string,\n fields?: Record<string, unknown>,\n ): Promise<void> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/transitions`, {\n method: \"POST\",\n body: JSON.stringify({\n transition: { id: transitionId },\n ...(fields ? { fields } : {}),\n }),\n }),\n\n addComment: (key: string, body: unknown): Promise<void> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/comment`, {\n method: \"POST\",\n body: JSON.stringify({ body }),\n }),\n\n getMe: (): Promise<JiraUser> => request(\"/rest/api/3/myself\"),\n\n getFields: (): Promise<JiraField[]> => request(\"/rest/api/3/field\"),\n\n getProjects: (): Promise<JiraProject[]> =>\n request(\"/rest/api/3/project?expand=description\"),\n\n getBoards: (): Promise<{ values: JiraBoard[] }> =>\n request(\"/rest/agile/1.0/board\"),\n\n getSprints: (\n boardId: number,\n state?: string,\n ): Promise<{ values: JiraSprint[] }> =>\n request(\n `/rest/agile/1.0/board/${boardId}/sprint${state ? `?state=${state}` : \"\"}`,\n ),\n\n // ── issues ────────────────────────────────────────────────────────────\n createIssue: (fields: Record<string, unknown>): Promise<JiraCreated> =>\n request(\"/rest/api/3/issue\", {\n method: \"POST\",\n body: JSON.stringify({ fields }),\n }),\n\n updateIssue: (\n key: string,\n fields: Record<string, unknown>,\n ): Promise<void> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}`, {\n method: \"PUT\",\n body: JSON.stringify({ fields }),\n }),\n\n deleteIssue: (key: string, deleteSubtasks = false): Promise<void> =>\n request(\n `/rest/api/3/issue/${encodeURIComponent(key)}?deleteSubtasks=${deleteSubtasks}`,\n { method: \"DELETE\" },\n ),\n\n assignIssue: (key: string, accountId: string | null): Promise<void> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/assignee`, {\n method: \"PUT\",\n body: JSON.stringify({ accountId }),\n }),\n\n getComments: (key: string): Promise<{ comments: JiraComment[] }> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/comment`),\n\n deleteComment: (key: string, commentId: string): Promise<void> =>\n request(\n `/rest/api/3/issue/${encodeURIComponent(key)}/comment/${encodeURIComponent(commentId)}`,\n { method: \"DELETE\" },\n ),\n\n getWatchers: (key: string): Promise<{ watchers: JiraUser[] }> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/watchers`),\n\n addWatcher: (key: string, accountId: string): Promise<void> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/watchers`, {\n method: \"POST\",\n body: JSON.stringify(accountId),\n }),\n\n removeWatcher: (key: string, accountId: string): Promise<void> =>\n request(\n `/rest/api/3/issue/${encodeURIComponent(key)}/watchers?accountId=${encodeURIComponent(accountId)}`,\n { method: \"DELETE\" },\n ),\n\n getWorklogs: (key: string): Promise<{ worklogs: JiraWorklog[] }> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/worklog`),\n\n addWorklog: (\n key: string,\n body: { timeSpent: string; comment?: unknown; started?: string },\n ): Promise<JiraWorklog> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/worklog`, {\n method: \"POST\",\n body: JSON.stringify(body),\n }),\n\n getChangelog: (key: string): Promise<{ values: JiraChangelogEntry[] }> =>\n request(`/rest/api/3/issue/${encodeURIComponent(key)}/changelog`),\n\n getIssueLinkTypes: (): Promise<{ issueLinkTypes: JiraIssueLinkType[] }> =>\n request(\"/rest/api/3/issueLinkType\"),\n\n linkIssues: (\n type: string,\n inwardKey: string,\n outwardKey: string,\n ): Promise<void> =>\n request(\"/rest/api/3/issueLink\", {\n method: \"POST\",\n body: JSON.stringify({\n type: { name: type },\n inwardIssue: { key: inwardKey },\n outwardIssue: { key: outwardKey },\n }),\n }),\n\n // ── projects ──────────────────────────────────────────────────────────\n getProject: (key: string): Promise<JiraProject> =>\n request(\n `/rest/api/3/project/${encodeURIComponent(key)}?expand=description,lead,url`,\n ),\n\n getProjectVersions: (key: string): Promise<JiraVersion[]> =>\n request(`/rest/api/3/project/${encodeURIComponent(key)}/versions`),\n\n getProjectComponents: (key: string): Promise<JiraComponent[]> =>\n request(`/rest/api/3/project/${encodeURIComponent(key)}/components`),\n\n getProjectStatuses: (key: string): Promise<JiraProjectStatuses[]> =>\n request(`/rest/api/3/project/${encodeURIComponent(key)}/statuses`),\n\n // ── agile ─────────────────────────────────────────────────────────────\n getBoard: (id: number): Promise<JiraBoard> =>\n request(`/rest/agile/1.0/board/${id}`),\n\n getBoardIssues: (\n id: number,\n jql?: string,\n ): Promise<{ issues: JiraIssue[] }> =>\n request(\n `/rest/agile/1.0/board/${id}/issue${jql ? `?jql=${encodeURIComponent(jql)}` : \"\"}`,\n ),\n\n getBacklog: (id: number): Promise<{ issues: JiraIssue[] }> =>\n request(`/rest/agile/1.0/board/${id}/backlog`),\n\n getSprint: (id: number): Promise<JiraSprint> =>\n request(`/rest/agile/1.0/sprint/${id}`),\n\n getSprintIssues: (id: number): Promise<{ issues: JiraIssue[] }> =>\n request(`/rest/agile/1.0/sprint/${id}/issue`),\n\n getBoardEpics: (id: number): Promise<{ values: JiraEpic[] }> =>\n request(`/rest/agile/1.0/board/${id}/epic`),\n\n getEpicIssues: (id: string): Promise<{ issues: JiraIssue[] }> =>\n request(`/rest/agile/1.0/epic/${encodeURIComponent(id)}/issue`),\n\n // ── people ────────────────────────────────────────────────────────────\n searchUsers: (query: string, maxResults = 20): Promise<JiraUser[]> =>\n request(\n `/rest/api/3/user/search?query=${encodeURIComponent(query)}&maxResults=${maxResults}`,\n ),\n\n searchAssignableUsers: (\n query: string,\n projectKey: string,\n maxResults = 20,\n ): Promise<JiraUser[]> =>\n request(\n `/rest/api/3/user/assignable/search?query=${encodeURIComponent(query)}&project=${encodeURIComponent(projectKey)}&maxResults=${maxResults}`,\n ),\n\n // ── instance metadata ─────────────────────────────────────────────────\n getIssueTypes: (): Promise<JiraIssueType[]> =>\n request(\"/rest/api/3/issuetype\"),\n\n getPriorities: (): Promise<JiraNamed[]> => request(\"/rest/api/3/priority\"),\n\n getResolutions: (): Promise<JiraNamed[]> =>\n request(\"/rest/api/3/resolution\"),\n\n getStatuses: (): Promise<JiraNamed[]> => request(\"/rest/api/3/status\"),\n\n getLabels: (): Promise<{ values: string[] }> =>\n request(\"/rest/api/3/label?maxResults=1000\"),\n\n getFilters: (): Promise<{ values: JiraFilter[] }> =>\n request(\"/rest/api/3/filter/search?expand=jql&maxResults=50\"),\n\n getDashboards: (): Promise<{ dashboards: JiraNamed[] }> =>\n request(\"/rest/api/3/dashboard\"),\n\n getServerInfo: (): Promise<Record<string, unknown>> =>\n request(\"/rest/api/3/serverInfo\"),\n\n getMyPermissions: (projectKey?: string): Promise<Record<string, unknown>> =>\n request(\n `/rest/api/3/mypermissions${projectKey ? `?projectKey=${encodeURIComponent(projectKey)}` : \"\"}`,\n ),\n\n /**\n * The count the removed `total` field used to give. Deliberately named\n * approximate because that is what Atlassian guarantees — it is an index\n * estimate, not a scan, and will disagree with a full page walk.\n */\n approximateCount: (jql: string): Promise<{ count: number }> =>\n request(\"/rest/api/3/search/approximate-count\", {\n method: \"POST\",\n body: JSON.stringify({ jql }),\n }),\n }\n}\n\nexport type JiraClient = ReturnType<typeof createJiraClient>\n","type AdfNode = {\n type?: string\n text?: string\n content?: AdfNode[]\n attrs?: Record<string, unknown>\n marks?: { type: string; attrs?: Record<string, unknown> }[]\n}\n\nconst markUp = (text: string, marks: AdfNode[\"marks\"]): string =>\n (marks ?? []).reduce((acc, mark) => {\n if (mark.type === \"code\") return `\\`${acc}\\``\n if (mark.type === \"strong\") return `**${acc}**`\n if (mark.type === \"em\") return `_${acc}_`\n if (mark.type === \"strike\") return `~~${acc}~~`\n if (mark.type === \"link\") return `[${acc}](${mark.attrs?.[\"href\"] ?? \"\"})`\n return acc\n }, text)\n\nexport type MediaResolver = (id: string) => string | undefined\n\nlet resolveMedia: MediaResolver = () => undefined\n\nconst children = (node: AdfNode, sep = \"\"): string =>\n (node.content ?? []).map(render).join(sep)\n\nconst listItems = (node: AdfNode, bullet: (i: number) => string): string =>\n (node.content ?? [])\n .map((item, i) => {\n const body = children(item, \"\\n\\n\").trim()\n const [first = \"\", ...rest] = body.split(\"\\n\")\n const marker = bullet(i)\n const indent = \" \".repeat(marker.length)\n return [\n `${marker}${first}`,\n ...rest.map((l) => (l ? `${indent}${l}` : l)),\n ].join(\"\\n\")\n })\n .join(\"\\n\")\n\n/** A table row rendered as a pipe row; the header separator is added by the caller. */\nconst row = (node: AdfNode): string =>\n `| ${(node.content ?? []).map((cell) => children(cell, \" \").trim().replace(/\\n+/g, \" \")).join(\" | \")} |`\n\nconst renderTable = (node: AdfNode): string => {\n const rows = node.content ?? []\n if (rows.length === 0) return \"\"\n const isHeader = (r: AdfNode): boolean =>\n (r.content ?? []).some((c) => c.type === \"tableHeader\")\n const [first] = rows\n const rendered = rows.map(row)\n if (first && isHeader(first)) {\n const columns = (first.content ?? []).length\n rendered.splice(1, 0, `|${\" --- |\".repeat(columns)}`)\n }\n return rendered.join(\"\\n\")\n}\n\nconst render = (node: AdfNode): string => {\n switch (node.type) {\n case \"doc\":\n return children(node, \"\\n\\n\")\n case \"paragraph\":\n return children(node)\n case \"text\":\n return markUp(node.text ?? \"\", node.marks)\n case \"hardBreak\":\n return \"\\n\"\n case \"heading\":\n return `${\"#\".repeat(Number(node.attrs?.[\"level\"] ?? 1))} ${children(node)}`\n case \"bulletList\":\n return listItems(node, () => \"- \")\n case \"orderedList\":\n return listItems(\n node,\n (i) => `${Number(node.attrs?.[\"order\"] ?? 1) + i}. `,\n )\n case \"codeBlock\":\n return `\\`\\`\\`${node.attrs?.[\"language\"] ?? \"\"}\\n${children(node)}\\n\\`\\`\\``\n case \"blockquote\":\n return children(node, \"\\n\\n\")\n .split(\"\\n\")\n .map((l) => `> ${l}`)\n .join(\"\\n\")\n case \"panel\":\n return `> [!${String(node.attrs?.[\"panelType\"] ?? \"note\").toUpperCase()}]\\n${children(\n node,\n \"\\n\\n\",\n )\n .split(\"\\n\")\n .map((l) => `> ${l}`)\n .join(\"\\n\")}`\n case \"rule\":\n return \"---\"\n case \"table\":\n return renderTable(node)\n case \"mediaSingle\":\n case \"mediaGroup\":\n return children(node, \"\\n\")\n case \"media\": {\n // `alt` is the filename Jira stored; the id is a media-platform UUID that\n // means nothing to a reader and does not match the attachment id either.\n const alt = node.attrs?.[\"alt\"]\n if (typeof alt === \"string\" && alt) return `[attachment: ${alt}]`\n const id = String(node.attrs?.[\"id\"] ?? \"\")\n const name = resolveMedia(id)\n return name ? `[attachment: ${name}]` : `[attachment: ${id || \"unknown\"}]`\n }\n case \"mention\": {\n // Jira stores the display text with its own leading @ most of the time,\n // but not always, so normalise rather than assume either way.\n const label = String(node.attrs?.[\"text\"] ?? node.attrs?.[\"id\"] ?? \"\")\n return label.startsWith(\"@\") ? label : `@${label}`\n }\n case \"emoji\":\n return String(node.attrs?.[\"text\"] ?? node.attrs?.[\"shortName\"] ?? \"\")\n case \"date\":\n return String(node.attrs?.[\"timestamp\"] ?? \"\")\n case \"status\":\n return `[${String(node.attrs?.[\"text\"] ?? \"\").toUpperCase()}]`\n case \"inlineCard\":\n return String(node.attrs?.[\"url\"] ?? \"\")\n default:\n return children(node, \"\\n\\n\")\n }\n}\n\n/**\n * Atlassian Document Format to Markdown. Deliberately stops at Markdown rather\n * than emitting ANSI: rendering is the surface's job, so a TUI, a pager and a\n * `--json` consumer all get the same text and only one of them styles it.\n */\nexport const adfToMarkdown = (\n doc: unknown,\n media?: MediaResolver,\n): string => {\n if (typeof doc === \"string\") return doc\n if (!doc || typeof doc !== \"object\") return \"\"\n const previous = resolveMedia\n resolveMedia = media ?? (() => undefined)\n try {\n return render(doc as AdfNode)\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim()\n } finally {\n resolveMedia = previous\n }\n}\n\ntype AdfDoc = { type: \"doc\"; version: 1; content: unknown[] }\n\nconst inline = (text: string): unknown[] => {\n // Only code spans and links are worth parsing: they are the two marks whose\n // absence changes meaning rather than appearance.\n const pattern = /(`[^`]+`)|(\\[[^\\]]+\\]\\([^)]+\\))/g\n const nodes: unknown[] = []\n let cursor = 0\n\n for (const match of text.matchAll(pattern)) {\n const at = match.index\n if (at > cursor)\n nodes.push({ type: \"text\", text: text.slice(cursor, at) })\n\n const token = match[0]\n if (token.startsWith(\"`\")) {\n nodes.push({\n type: \"text\",\n text: token.slice(1, -1),\n marks: [{ type: \"code\" }],\n })\n } else {\n const [, label = \"\", href = \"\"] =\n token.match(/\\[([^\\]]+)\\]\\(([^)]+)\\)/) ?? []\n nodes.push({\n type: \"text\",\n text: label,\n marks: [{ type: \"link\", attrs: { href } }],\n })\n }\n cursor = at + token.length\n }\n\n if (cursor < text.length) nodes.push({ type: \"text\", text: text.slice(cursor) })\n return nodes.length > 0 ? nodes : [{ type: \"text\", text }]\n}\n\nconst blockToAdf = (block: string): unknown => {\n const fence = block.match(/^```(\\w*)\\n([\\s\\S]*?)\\n?```$/)\n if (fence)\n return {\n type: \"codeBlock\",\n ...(fence[1] ? { attrs: { language: fence[1] } } : {}),\n content: [{ type: \"text\", text: fence[2] ?? \"\" }],\n }\n\n const heading = block.match(/^(#{1,6})\\s+(.*)$/)\n if (heading)\n return {\n type: \"heading\",\n attrs: { level: heading[1]?.length ?? 1 },\n content: inline(heading[2] ?? \"\"),\n }\n\n const lines = block.split(\"\\n\")\n if (lines.every((l) => /^\\s*[-*]\\s+/.test(l)))\n return {\n type: \"bulletList\",\n content: lines.map((l) => ({\n type: \"listItem\",\n content: [\n { type: \"paragraph\", content: inline(l.replace(/^\\s*[-*]\\s+/, \"\")) },\n ],\n })),\n }\n\n if (lines.every((l) => /^\\s*\\d+[.)]\\s+/.test(l)))\n return {\n type: \"orderedList\",\n content: lines.map((l) => ({\n type: \"listItem\",\n content: [\n { type: \"paragraph\", content: inline(l.replace(/^\\s*\\d+[.)]\\s+/, \"\")) },\n ],\n })),\n }\n\n return { type: \"paragraph\", content: inline(block) }\n}\n\n/**\n * Markdown to ADF, covering what someone actually types into a comment from a\n * terminal: paragraphs, fenced code, headings, lists, links and code spans.\n * Deliberately partial — anything richer is better authored in Jira, and a\n * half-supported table would corrupt more often than it would help.\n */\nexport const markdownToAdf = (text: string): AdfDoc => ({\n type: \"doc\",\n version: 1,\n content: text\n .replace(/\\r\\n/g, \"\\n\")\n .split(/\\n{2,}/)\n .map((b) => b.trim())\n .filter(Boolean)\n .map(blockToAdf),\n})\n","import type { JiraClient } from \"./client.js\"\nimport type { JiraIssue } from \"./types.js\"\n\nexport type JiraAttachment = {\n id: string\n filename: string\n mimeType: string\n size: number\n created?: string\n author?: { displayName: string }\n content?: string\n}\n\n/** Where an attachment was referenced from, so `issue view` can say so. */\nexport type AttachmentOrigin =\n | { kind: \"issue\" }\n | { kind: \"description\" }\n | { kind: \"comment\"; commentId: string; author?: string }\n\nexport type LocatedAttachment = JiraAttachment & { origins: AttachmentOrigin[] }\n\ntype AdfNode = {\n type?: string\n attrs?: Record<string, unknown>\n content?: AdfNode[]\n}\n\nexport type MediaRef = { id?: string; filename?: string }\n\n/**\n * Media nodes carry a media-platform UUID in `attrs.id`, which is a different\n * namespace from the numeric attachment id — joining on it matches nothing,\n * ever. `attrs.alt` holds the original filename and is the only field the two\n * representations share, so it is the real key and the id is the fallback.\n */\nconst mediaRefs = (node: unknown): MediaRef[] => {\n if (!node || typeof node !== \"object\") return []\n const n = node as AdfNode\n const here: MediaRef[] =\n n.type === \"media\"\n ? [\n {\n ...(typeof n.attrs?.[\"id\"] === \"string\"\n ? { id: n.attrs[\"id\"] as string }\n : {}),\n ...(typeof n.attrs?.[\"alt\"] === \"string\"\n ? { filename: n.attrs[\"alt\"] as string }\n : {}),\n },\n ]\n : []\n return [...here, ...(n.content ?? []).flatMap(mediaRefs)]\n}\n\n/**\n * Jira reports attachments once, on the issue, and never says where they were\n * embedded. Walking the description and each comment for media nodes and\n * joining them back is the only way to answer \"which comment did this come\n * from\" — a question the API cannot be asked directly.\n */\nexport const locateAttachments = (issue: JiraIssue): LocatedAttachment[] => {\n const attachments = (issue.fields[\"attachment\"] as JiraAttachment[]) ?? []\n const origins = new Map<string, AttachmentOrigin[]>()\n\n const note = (ref: MediaRef, origin: AttachmentOrigin): void => {\n const match = attachments.find(\n (a) =>\n (ref.filename !== undefined && a.filename === ref.filename) ||\n (ref.id !== undefined && a.id === ref.id),\n )\n if (!match) return\n origins.set(match.id, [...(origins.get(match.id) ?? []), origin])\n }\n\n for (const ref of mediaRefs(issue.fields.description))\n note(ref, { kind: \"description\" })\n\n for (const comment of issue.fields.comment?.comments ?? [])\n for (const ref of mediaRefs(comment.body))\n note(ref, {\n kind: \"comment\",\n commentId: comment.id,\n author: comment.author?.displayName,\n })\n\n return attachments.map((a) => ({\n ...a,\n origins: origins.get(a.id) ?? [{ kind: \"issue\" as const }],\n }))\n}\n\nconst TEXTUAL =\n /^(text\\/|application\\/(json|xml|x-yaml|yaml|javascript|sql|x-sh))/\n\n/** Extensions Jira commonly mislabels as application/octet-stream. */\nconst TEXTUAL_EXTENSIONS =\n /\\.(txt|md|markdown|log|json|ya?ml|csv|tsv|xml|html?|css|jsx?|tsx?|py|rb|go|rs|java|kt|sh|zsh|bash|sql|ini|toml|conf|env|diff|patch)$/i\n\nexport const isTextual = (attachment: JiraAttachment): boolean =>\n TEXTUAL.test(attachment.mimeType) ||\n TEXTUAL_EXTENSIONS.test(attachment.filename)\n\n/**\n * Fetches attachment bytes. The documented content endpoint 302s to a\n * short-lived media host, and the auth header must NOT follow: it is a Jira\n * credential and the redirect target is a different origin that neither needs\n * nor should see it. Hence manual redirect handling rather than fetch's default.\n */\nexport const downloadAttachment = async (\n client: JiraClient,\n id: string,\n fetchImpl: typeof globalThis.fetch = globalThis.fetch,\n): Promise<{ bytes: Uint8Array; mimeType: string | null }> => {\n const res = await client.request<Response>(\n `/rest/api/3/attachment/content/${encodeURIComponent(id)}`,\n { redirect: \"manual\", raw: true },\n )\n\n const location = res.headers.get(\"location\")\n const final =\n res.status >= 300 && res.status < 400 && location\n ? await fetchImpl(location)\n : res\n\n if (!final.ok) {\n throw new Error(\n `could not download attachment ${id}: ${final.status} ${final.statusText}`,\n )\n }\n\n return {\n bytes: new Uint8Array(await final.arrayBuffer()),\n mimeType: final.headers.get(\"content-type\"),\n }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kud/jira",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "Headless Jira client — issues, comments, attachments, ADF conversion, agile boards and instance metadata, with no environment or process dependencies",
6
6
  "exports": {