tdecollab 0.3.3 → 0.3.5

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.
@@ -1,6 +1,7 @@
1
1
  import {
2
+ __require,
2
3
  logger
3
- } from "./chunk-IFYMZLQI.js";
4
+ } from "./chunk-6RIA6AJ3.js";
4
5
 
5
6
  // tools/common/env-loader.ts
6
7
  import dotenv from "dotenv";
@@ -38,14 +39,14 @@ var ConfluenceContentApi = class {
38
39
  }
39
40
  async getPage(id, expand) {
40
41
  const expandParam = expand ? expand.join(",") : "body.storage,version,space,metadata.labels";
41
- const response = await this.client.get(`/rest/api/content/${id}`, {
42
+ const response = await this.client.get(`rest/api/content/${id}`, {
42
43
  params: { expand: expandParam }
43
44
  });
44
45
  return response.data;
45
46
  }
46
47
  async getPageByTitle(spaceKey, title, expand) {
47
48
  const expandParam = expand ? expand.join(",") : "body.storage,version,space";
48
- const response = await this.client.get("/rest/api/content", {
49
+ const response = await this.client.get("rest/api/content", {
49
50
  params: {
50
51
  spaceKey,
51
52
  title,
@@ -73,7 +74,7 @@ var ConfluenceContentApi = class {
73
74
  if (params.parentId) {
74
75
  data.ancestors = [{ id: params.parentId }];
75
76
  }
76
- const response = await this.client.post("/rest/api/content", data);
77
+ const response = await this.client.post("rest/api/content", data);
77
78
  if (params.labels && params.labels.length > 0) {
78
79
  await this.addLabels(response.data.id, params.labels);
79
80
  }
@@ -91,14 +92,14 @@ var ConfluenceContentApi = class {
91
92
  }
92
93
  }
93
94
  };
94
- const response = await this.client.put(`/rest/api/content/${params.id}`, data);
95
+ const response = await this.client.put(`rest/api/content/${params.id}`, data);
95
96
  return response.data;
96
97
  }
97
98
  async deletePage(id) {
98
- await this.client.delete(`/rest/api/content/${id}`);
99
+ await this.client.delete(`rest/api/content/${id}`);
99
100
  }
100
101
  async getChildPages(id, start = 0, limit = 25) {
101
- const response = await this.client.get(`/rest/api/content/${id}/child/page`, {
102
+ const response = await this.client.get(`rest/api/content/${id}/child/page`, {
102
103
  params: { start, limit }
103
104
  });
104
105
  return response.data.results;
@@ -112,7 +113,7 @@ var ConfluenceContentApi = class {
112
113
  // For now, simple implementation to support createPage.
113
114
  async addLabels(id, labels) {
114
115
  const data = labels.map((name) => ({ prefix: "global", name }));
115
- await this.client.post(`/rest/api/content/${id}/label`, data);
116
+ await this.client.post(`rest/api/content/${id}/label`, data);
116
117
  }
117
118
  // Attachment 관련 메서드 (upsert: 기존 파일이 있으면 업데이트, 없으면 신규 업로드)
118
119
  async uploadAttachment(pageId, filename, fileContent, contentType) {
@@ -132,13 +133,13 @@ var ConfluenceContentApi = class {
132
133
  let response;
133
134
  if (existing) {
134
135
  response = await this.client.post(
135
- `/rest/api/content/${pageId}/child/attachment/${existing.id}/data`,
136
+ `rest/api/content/${pageId}/child/attachment/${existing.id}/data`,
136
137
  form,
137
138
  { headers }
138
139
  );
139
140
  } else {
140
141
  response = await this.client.post(
141
- `/rest/api/content/${pageId}/child/attachment`,
142
+ `rest/api/content/${pageId}/child/attachment`,
142
143
  form,
143
144
  { headers }
144
145
  );
@@ -149,7 +150,7 @@ var ConfluenceContentApi = class {
149
150
  return response.data;
150
151
  }
151
152
  async getAttachments(pageId, filename) {
152
- const response = await this.client.get(`/rest/api/content/${pageId}/child/attachment`, {
153
+ const response = await this.client.get(`rest/api/content/${pageId}/child/attachment`, {
153
154
  params: {
154
155
  filename,
155
156
  expand: "version"
@@ -171,7 +172,7 @@ var ConfluenceSpaceApi = class {
171
172
  this.client = client;
172
173
  }
173
174
  async getSpaces(type = "global", start = 0, limit = 25) {
174
- const response = await this.client.get("/rest/api/space", {
175
+ const response = await this.client.get("rest/api/space", {
175
176
  params: { type, start, limit }
176
177
  });
177
178
  return response.data.results;
@@ -189,7 +190,7 @@ var ConfluenceSearchApi = class {
189
190
  }
190
191
  async searchByCql(cql, start = 0, limit = 25, expand) {
191
192
  const expandParam = expand ? expand.join(",") : "body.storage,version,space,metadata.labels";
192
- const response = await this.client.get("/rest/api/content/search", {
193
+ const response = await this.client.get("rest/api/content/search", {
193
194
  params: {
194
195
  cql,
195
196
  start,
@@ -237,20 +238,23 @@ var ConflictError = class extends TdeCollabError {
237
238
 
238
239
  // tools/common/http-client.ts
239
240
  function createHttpClient(config) {
241
+ const normalizedBaseUrl = config.baseUrl.endsWith("/") ? config.baseUrl : `${config.baseUrl}/`;
240
242
  const client = axios.create({
241
- baseURL: config.baseUrl,
243
+ baseURL: normalizedBaseUrl,
242
244
  timeout: 3e4,
243
245
  // 30초 타임아웃
246
+ adapter: "http",
247
+ // Electron 환경(Obsidian)에서 XHR 대신 Node.js http 모듈을 사용하여 CORS 우회
244
248
  headers: {
245
249
  "Content-Type": "application/json"
246
250
  }
247
251
  });
248
252
  client.interceptors.request.use((reqConfig) => {
249
- if (config.auth.token && !reqConfig.headers.Authorization && !reqConfig.headers["PRIVATE-TOKEN"]) {
250
- reqConfig.headers.Authorization = `Bearer ${config.auth.token}`;
251
- } else if (config.auth.username && config.auth.token && !reqConfig.headers.Authorization) {
253
+ if (config.auth.username && config.auth.token && !reqConfig.headers.Authorization) {
252
254
  const token = Buffer.from(`${config.auth.username}:${config.auth.token}`).toString("base64");
253
255
  reqConfig.headers.Authorization = `Basic ${token}`;
256
+ } else if (config.auth.token && !reqConfig.headers.Authorization && !reqConfig.headers["PRIVATE-TOKEN"]) {
257
+ reqConfig.headers.Authorization = `Bearer ${config.auth.token}`;
254
258
  }
255
259
  logger.debug(`[HTTP Request] ${reqConfig.method?.toUpperCase()} ${reqConfig.url}`, {
256
260
  headers: reqConfig.headers,
@@ -446,10 +450,13 @@ var MarkdownToStorageConverter = class {
446
450
  // tools/confluence/converters/storage-to-md.ts
447
451
  import TurndownService from "turndown";
448
452
  import { gfm } from "turndown-plugin-gfm";
449
- import { JSDOM } from "jsdom";
450
453
  var StorageToMarkdownConverter = class {
451
454
  turndown;
452
- constructor() {
455
+ jiraBaseUrl;
456
+ jiraIssueMap;
457
+ constructor(options = {}) {
458
+ this.jiraBaseUrl = options.jiraBaseUrl || process.env.JIRA_BASE_URL || "";
459
+ this.jiraIssueMap = void 0;
453
460
  this.turndown = new TurndownService({
454
461
  headingStyle: "atx",
455
462
  hr: "---",
@@ -525,17 +532,46 @@ ${body.trim()}
525
532
  \`\`\`
526
533
  `;
527
534
  }
535
+ if (macroName === "expand") {
536
+ const title = element.querySelector('[data-macro-param-name="title"]')?.textContent?.trim() || "\uB354\uBCF4\uAE30";
537
+ const richBody = element.querySelector("[data-macro-rich-body]");
538
+ const bodyMd = richBody ? this.turndown.turndown(richBody.innerHTML).trim() : "";
539
+ return `
540
+
541
+ <details>
542
+ <summary>${title}</summary>
543
+
544
+ ${bodyMd}
545
+
546
+ </details>
547
+
548
+ `;
549
+ }
550
+ if (macroName === "jira") {
551
+ const key = element.querySelector('[data-macro-param-name="key"]')?.textContent?.trim();
552
+ if (key) {
553
+ const base = this.jiraBaseUrl.replace(/\/$/, "");
554
+ const url = base ? `${base}/browse/${key}` : `https://jira.atlassian.com/browse/${key}`;
555
+ const info = this.jiraIssueMap?.get(key);
556
+ if (info) {
557
+ return ` [${key}](${url}) ${info.summary} \`${info.status}\` `;
558
+ }
559
+ return ` [${key}](${url}) `;
560
+ }
561
+ return "";
562
+ }
528
563
  return `
529
564
  <!-- Macro: ${macroName} -->
530
565
  `;
531
566
  }
532
567
  });
533
568
  }
534
- convert(storageHtml, imageUrlMap) {
569
+ convert(storageHtml, imageUrlMap, jiraIssueMap) {
570
+ this.jiraIssueMap = jiraIssueMap;
535
571
  if (!storageHtml) return "";
536
572
  let processedHtml = storageHtml.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/gi, (match, p1) => {
537
573
  return `__CDATA_START__${p1}__CDATA_END__`;
538
- }).replace(/<ac:structured-macro\s+ac:name="([^"]*)"/gi, '<div data-macro-name-tag data-macro-name="$1"').replace(/<\/ac:structured-macro>/gi, "</div>").replace(/<ac:parameter\s+ac:name="([^"]*)"/gi, '<div data-macro-param-tag data-macro-param-name="$1"').replace(/<\/ac:parameter>/gi, "</div>").replace(/<ac:plain-text-body>/gi, "<pre data-macro-body>").replace(/<\/ac:plain-text-body>/gi, "</pre>").replace(/<ac:image([^>]*)>[\s\S]*?<ri:attachment\s+ri:filename="([^"]*)"\s*\/?>[\s\S]*?<\/ac:image>/gi, (match, attrs, filename) => {
574
+ }).replace(/<ac:structured-macro\s+ac:name="([^"]*)"/gi, '<div data-macro-name-tag data-macro-name="$1"').replace(/<\/ac:structured-macro>/gi, "</div>").replace(/<ac:parameter\s+ac:name="([^"]*)"/gi, '<div data-macro-param-tag data-macro-param-name="$1"').replace(/<\/ac:parameter>/gi, "</div>").replace(/<ac:plain-text-body>/gi, "<pre data-macro-body>").replace(/<\/ac:plain-text-body>/gi, "</pre>").replace(/<ac:rich-text-body>/gi, "<div data-macro-rich-body>").replace(/<\/ac:rich-text-body>/gi, "</div>").replace(/<ac:image([^>]*)>[\s\S]*?<ri:attachment\s+ri:filename="([^"]*)"\s*\/?>[\s\S]*?<\/ac:image>/gi, (match, attrs, filename) => {
539
575
  const altMatch = attrs.match(/ac:alt="([^"]*)"/i);
540
576
  const alt = altMatch ? altMatch[1] : filename;
541
577
  return `<img src="${filename}" alt="${alt}" />`;
@@ -544,8 +580,15 @@ ${body.trim()}
544
580
  const alt = altMatch ? altMatch[1] : "";
545
581
  return `<img src="${url}" alt="${alt}" />`;
546
582
  });
547
- const dom = new JSDOM(processedHtml);
548
- const document = dom.window.document;
583
+ let document;
584
+ if (typeof window !== "undefined" && typeof window.DOMParser !== "undefined") {
585
+ const parser = new window.DOMParser();
586
+ document = parser.parseFromString(processedHtml, "text/html");
587
+ } else {
588
+ const { JSDOM } = __require("jsdom");
589
+ const dom = new JSDOM(processedHtml);
590
+ document = dom.window.document;
591
+ }
549
592
  if (imageUrlMap && imageUrlMap.size > 0) {
550
593
  const images = document.querySelectorAll("img");
551
594
  images.forEach((img) => {
@@ -557,6 +600,7 @@ ${body.trim()}
557
600
  }
558
601
  let markdown = this.turndown.turndown(document.body.innerHTML).trim();
559
602
  markdown = markdown.replace(/^(#{1,6}\s.*?)\\\./gm, "$1.");
603
+ this.jiraIssueMap = void 0;
560
604
  return markdown + "\n";
561
605
  }
562
606
  };
@@ -596,7 +640,7 @@ var JiraIssueApi = class {
596
640
  if (params.customFields) {
597
641
  Object.assign(fields, params.customFields);
598
642
  }
599
- const response = await this.client.post("/rest/api/2/issue", { fields });
643
+ const response = await this.client.post("rest/api/2/issue", { fields });
600
644
  return response.data;
601
645
  }
602
646
  async updateIssue(issueKey, params) {
@@ -621,6 +665,53 @@ var JiraIssueApi = class {
621
665
  }
622
666
  };
623
667
 
668
+ // tools/jira/api/client.ts
669
+ function createJiraClient(config) {
670
+ return createHttpClient(config);
671
+ }
672
+
673
+ // tools/confluence/converters/jira-enricher.ts
674
+ function extractJiraKeys(storageXml) {
675
+ const keys = /* @__PURE__ */ new Set();
676
+ const macroRegex = /<ac:structured-macro\s+ac:name="jira"[\s\S]*?<\/ac:structured-macro>/gi;
677
+ const keyRegex = /<ac:parameter\s+ac:name="key">([^<]+)<\/ac:parameter>/i;
678
+ let match;
679
+ while ((match = macroRegex.exec(storageXml)) !== null) {
680
+ const keyMatch = keyRegex.exec(match[0]);
681
+ if (keyMatch) keys.add(keyMatch[1].trim());
682
+ }
683
+ return [...keys];
684
+ }
685
+ async function buildJiraIssueMap(keys, jiraApi) {
686
+ const map = /* @__PURE__ */ new Map();
687
+ if (keys.length === 0) return map;
688
+ await Promise.allSettled(
689
+ keys.map(async (key) => {
690
+ try {
691
+ const issue = await jiraApi.getIssue(key, ["summary", "status"]);
692
+ map.set(key, {
693
+ summary: issue.fields.summary ?? "",
694
+ status: issue.fields.status?.name ?? ""
695
+ });
696
+ } catch {
697
+ }
698
+ })
699
+ );
700
+ return map;
701
+ }
702
+ async function tryBuildJiraIssueMap(storageXml) {
703
+ try {
704
+ const keys = extractJiraKeys(storageXml);
705
+ if (keys.length === 0) return /* @__PURE__ */ new Map();
706
+ const config = loadJiraConfig();
707
+ const client = createJiraClient(config);
708
+ const api = new JiraIssueApi(client);
709
+ return await buildJiraIssueMap(keys, api);
710
+ } catch {
711
+ return /* @__PURE__ */ new Map();
712
+ }
713
+ }
714
+
624
715
  // tools/jira/api/search.ts
625
716
  var JiraSearchApi = class {
626
717
  constructor(client) {
@@ -635,16 +726,11 @@ var JiraSearchApi = class {
635
726
  if (fields && fields.length > 0) {
636
727
  params.fields = fields.join(",");
637
728
  }
638
- const response = await this.client.get("/rest/api/2/search", { params });
729
+ const response = await this.client.get("rest/api/2/search", { params });
639
730
  return response.data;
640
731
  }
641
732
  };
642
733
 
643
- // tools/jira/api/client.ts
644
- function createJiraClient(config) {
645
- return createHttpClient(config);
646
- }
647
-
648
734
  // tools/gitlab/api/merge-request.ts
649
735
  var GitlabMergeRequestApi = class {
650
736
  constructor(client) {
@@ -764,10 +850,11 @@ export {
764
850
  MarkdownToStorageConverter,
765
851
  StorageToMarkdownConverter,
766
852
  JiraIssueApi,
767
- JiraSearchApi,
768
853
  createJiraClient,
854
+ tryBuildJiraIssueMap,
855
+ JiraSearchApi,
769
856
  GitlabMergeRequestApi,
770
857
  GitlabPipelineApi,
771
858
  createGitlabClient
772
859
  };
773
- //# sourceMappingURL=chunk-BWZT27NL.js.map
860
+ //# sourceMappingURL=chunk-JUM5ZG64.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../tools/common/env-loader.ts","../tools/confluence/api/content.ts","../tools/confluence/api/space.ts","../tools/confluence/api/search.ts","../tools/common/http-client.ts","../tools/common/errors.ts","../tools/confluence/api/client.ts","../tools/common/config.ts","../tools/confluence/converters/md-to-storage.ts","../tools/confluence/converters/storage-to-md.ts","../tools/jira/api/issue.ts","../tools/jira/api/client.ts","../tools/confluence/converters/jira-enricher.ts","../tools/jira/api/search.ts","../tools/gitlab/api/merge-request.ts","../tools/gitlab/api/pipeline.ts","../tools/gitlab/api/client.ts"],"sourcesContent":["import dotenv from 'dotenv';\nimport path from 'path';\nimport os from 'os';\n\n// 환경변수 로딩 우선순위:\n// 1. 셸 환경변수 (이미 process.env 에 존재하므로 자동 우선)\n// 2. 현재 작업 디렉토리의 tdecollab.env\n// 3. ~/.config/tdecollab/.env (사용자 글로벌 설정)\n//\n// dotenv 기본 동작이 override: false 이므로,\n// 이미 process.env 에 있는 키는 덮어쓰지 않는다.\n// 따라서 우선순위 높은 출처(env vars)부터 로드하면 자연스럽게 우선순위가 적용된다.\n\nlet loaded = false;\n\nexport interface LoadEnvResult {\n loadedFiles: string[];\n skippedFiles: string[];\n}\n\nfunction getHomeDir(): string {\n return process.env.HOME || process.env.USERPROFILE || os.homedir();\n}\n\nexport function loadEnv(): LoadEnvResult {\n const result: LoadEnvResult = { loadedFiles: [], skippedFiles: [] };\n if (loaded) return result;\n loaded = true;\n\n const candidates = [\n // 우선순위 2: 현재 디렉토리\n path.resolve(process.cwd(), 'tdecollab.env'),\n // 우선순위 3: 사용자 글로벌\n path.join(getHomeDir(), '.config', 'tdecollab', '.env'),\n ];\n\n for (const filepath of candidates) {\n const out = dotenv.config({ path: filepath, override: false });\n if (out.error) {\n // 파일이 없으면 자연스럽게 skip (오류로 취급하지 않음)\n result.skippedFiles.push(filepath);\n } else {\n result.loadedFiles.push(filepath);\n }\n }\n\n return result;\n}\n\n// 디버그용 — 어떤 파일이 로드되었는지 확인할 때 사용\nexport function getEnvLoadDebug(): string[] {\n return loaded ? ['env-loader: 1회 호출됨'] : ['env-loader: 호출되지 않음'];\n}\n","import { AxiosInstance } from 'axios';\nimport { ConfluencePageResponse, CreatePageParams, UpdatePageParams, ConfluenceLabel, ConfluenceAttachmentResponse } from '../types.js';\n\n\nexport class ConfluenceContentApi {\n constructor(private client: AxiosInstance) { }\n\n async getPage(id: string, expand?: string[]): Promise<ConfluencePageResponse> {\n const expandParam = expand ? expand.join(',') : 'body.storage,version,space,metadata.labels';\n const response = await this.client.get(`rest/api/content/${id}`, {\n params: { expand: expandParam }\n });\n return response.data;\n }\n\n async getPageByTitle(spaceKey: string, title: string, expand?: string[]): Promise<ConfluencePageResponse | null> {\n const expandParam = expand ? expand.join(',') : 'body.storage,version,space';\n const response = await this.client.get('rest/api/content', {\n params: {\n spaceKey,\n title,\n expand: expandParam,\n limit: 1\n }\n });\n\n if (response.data.results && response.data.results.length > 0) {\n return response.data.results[0];\n }\n return null;\n }\n\n async createPage(params: CreatePageParams): Promise<ConfluencePageResponse> {\n const data: any = {\n type: 'page',\n title: params.title,\n space: { key: params.spaceKey },\n body: {\n storage: {\n value: params.body,\n representation: 'storage'\n }\n }\n };\n\n if (params.parentId) {\n data.ancestors = [{ id: params.parentId }];\n }\n\n const response = await this.client.post('rest/api/content', data);\n\n // add labels if provided\n if (params.labels && params.labels.length > 0) {\n await this.addLabels(response.data.id, params.labels);\n // refetch to include labels in response if needed, \n // but simpler to just return the create response.\n // Or we can construct the object.\n }\n\n return response.data;\n }\n\n async updatePage(params: UpdatePageParams): Promise<ConfluencePageResponse> {\n const data = {\n version: { number: params.version + 1 },\n title: params.title,\n type: 'page',\n body: {\n storage: {\n value: params.body,\n representation: 'storage'\n }\n }\n };\n\n const response = await this.client.put(`rest/api/content/${params.id}`, data);\n return response.data;\n }\n\n async deletePage(id: string): Promise<void> {\n await this.client.delete(`rest/api/content/${id}`);\n }\n\n async getChildPages(id: string, start = 0, limit = 25): Promise<ConfluencePageResponse[]> {\n const response = await this.client.get(`rest/api/content/${id}/child/page`, {\n params: { start, limit }\n });\n return response.data.results;\n }\n\n // Label helper inside content api or separate? \n // Let's implement basic label addition here since it's used in create.\n // Actually, label logic is in label.ts, but due to circular dependency or convenience...\n // Let's implement it here privately or import it.\n // Better to keep it separate as per plan, but `this.client` is available here.\n // I'll implement a simple one here or use the separate class later. \n // For now, simple implementation to support createPage.\n private async addLabels(id: string, labels: string[]): Promise<void> {\n const data = labels.map(name => ({ prefix: 'global', name }));\n await this.client.post(`rest/api/content/${id}/label`, data);\n }\n\n // Attachment 관련 메서드 (upsert: 기존 파일이 있으면 업데이트, 없으면 신규 업로드)\n async uploadAttachment(pageId: string, filename: string, fileContent: Buffer, contentType?: string): Promise<ConfluenceAttachmentResponse> {\n const FormData = (await import('form-data')).default;\n\n // 기존 첨부파일 존재 여부 확인\n const existingAttachments = await this.getAttachments(pageId, filename);\n const existing = existingAttachments.find(a => a.title === filename);\n\n const form = new FormData();\n form.append('file', fileContent, {\n filename: filename,\n contentType: contentType || 'application/octet-stream'\n });\n\n const headers = {\n ...form.getHeaders(),\n 'X-Atlassian-Token': 'nocheck',\n 'Accept': 'application/json',\n };\n\n let response;\n if (existing) {\n // 기존 파일 업데이트 (POST to /data endpoint)\n response = await this.client.post(\n `rest/api/content/${pageId}/child/attachment/${existing.id}/data`,\n form,\n { headers }\n );\n } else {\n // 신규 업로드\n response = await this.client.post(\n `rest/api/content/${pageId}/child/attachment`,\n form,\n { headers }\n );\n }\n\n // Confluence API returns { results: [Attachment] } \n if (response.data && response.data.results && response.data.results.length > 0) {\n return response.data.results[0];\n }\n return response.data;\n }\n\n async getAttachments(pageId: string, filename?: string): Promise<ConfluenceAttachmentResponse[]> {\n const response = await this.client.get(`rest/api/content/${pageId}/child/attachment`, {\n params: {\n filename,\n expand: 'version'\n }\n });\n return response.data.results;\n }\n\n async downloadAttachment(downloadUrl: string): Promise<Buffer> {\n const response = await this.client.get(downloadUrl, {\n responseType: 'arraybuffer'\n });\n return Buffer.from(response.data);\n }\n}\n","import { AxiosInstance } from 'axios';\nimport { ConfluenceSpaceResponse } from '../types.js';\n\nexport class ConfluenceSpaceApi {\n constructor(private client: AxiosInstance) { }\n\n async getSpaces(type: string = 'global', start = 0, limit = 25): Promise<ConfluenceSpaceResponse[]> {\n const response = await this.client.get('rest/api/space', {\n params: { type, start, limit }\n });\n return response.data.results;\n }\n\n async getSpace(spaceKey: string): Promise<ConfluenceSpaceResponse> {\n const response = await this.client.get(`/rest/api/space/${spaceKey}`);\n return response.data;\n }\n}\n","import { AxiosInstance } from 'axios';\nimport { ConfluenceSearchResponse } from '../types.js';\n\nexport class ConfluenceSearchApi {\n constructor(private client: AxiosInstance) { }\n\n async searchByCql(cql: string, start = 0, limit = 25, expand?: string[]): Promise<ConfluenceSearchResponse> {\n const expandParam = expand ? expand.join(',') : 'body.storage,version,space,metadata.labels';\n const response = await this.client.get('rest/api/content/search', {\n params: {\n cql,\n start,\n limit,\n expand: expandParam\n }\n });\n\n return response.data;\n }\n}\n","import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios';\nimport { ApiError, AuthError, NotFoundError, ConflictError } from './errors.js';\nimport { logger } from './logger.js';\nimport { ServiceConfig } from './types.js';\n\nexport function createHttpClient(config: ServiceConfig): AxiosInstance {\n // URL 정규화 (항상 슬래시로 끝나도록 하여 하위 경로가 유지되게 함)\n const normalizedBaseUrl = config.baseUrl.endsWith('/') ? config.baseUrl : `${config.baseUrl}/`;\n\n const client = axios.create({\n baseURL: normalizedBaseUrl,\n timeout: 30000, // 30초 타임아웃\n adapter: 'http', // Electron 환경(Obsidian)에서 XHR 대신 Node.js http 모듈을 사용하여 CORS 우회\n headers: {\n 'Content-Type': 'application/json',\n },\n });\n\n // 요청 인터셉터: 인증 헤더 주입 및 로깅\n client.interceptors.request.use((reqConfig) => {\n // 1. Basic 인증 (Username + Token)\n if (config.auth.username && config.auth.token && !reqConfig.headers.Authorization) {\n const token = Buffer.from(`${config.auth.username}:${config.auth.token}`).toString('base64');\n reqConfig.headers.Authorization = `Basic ${token}`;\n }\n // 2. 토큰 기반 Bearer 인증 (Confluence/JIRA PAT 권장 방식)\n else if (config.auth.token && !reqConfig.headers.Authorization && !reqConfig.headers['PRIVATE-TOKEN']) {\n // GitLab은 PRIVATE-TOKEN 헤더를 선호하므로 제외\n reqConfig.headers.Authorization = `Bearer ${config.auth.token}`;\n }\n\n logger.debug(`[HTTP Request] ${reqConfig.method?.toUpperCase()} ${reqConfig.url}`, {\n headers: reqConfig.headers,\n params: reqConfig.params,\n data: reqConfig.data,\n });\n\n return reqConfig;\n });\n\n // 응답 인터셉터: 에러 핸들링 및 로깅\n client.interceptors.response.use(\n (response: AxiosResponse) => {\n logger.debug(`[HTTP Response] ${response.status} ${response.config.method?.toUpperCase()} ${response.config.url}`, {\n headers: response.headers,\n data: response.data,\n });\n return response;\n },\n (error: AxiosError) => {\n if (error.response) {\n const status = error.response.status;\n const method = error.config?.method?.toUpperCase();\n const url = error.config?.url;\n const message = (error.response.data as any)?.message || error.message;\n\n logger.error(`[HTTP Error] ${status} ${method} ${url}`, {\n message,\n responseData: error.response.data,\n requestHeaders: error.config?.headers,\n });\n\n // 상태 코드별 커스텀 에러 매핑\n if (status === 401 || status === 403) {\n throw new AuthError(`인증 실패: ${message}`);\n }\n if (status === 404) {\n throw new NotFoundError('리소스', url || 'unknown');\n }\n if (status === 409) {\n throw new ConflictError(`충돌 발생: ${message}`);\n }\n\n throw new ApiError(status, message, error.response.data);\n } else if (error.request) {\n logger.error(`[HTTP] No Response: ${error.message}`);\n throw new ApiError(0, `서버로부터 응답이 없습니다: ${error.message}`);\n } else {\n logger.error(`[HTTP] Request Error: ${error.message}`);\n throw new ApiError(0, `요청 설정 중 오류 발생: ${error.message}`);\n }\n }\n );\n\n return client;\n}\n","// TDE Collab 기본 에러 클래스\nexport class TdeCollabError extends Error {\n constructor(message: string) {\n super(message);\n this.name = this.constructor.name;\n Error.captureStackTrace(this, this.constructor);\n }\n}\n\n// API 요청 실패 에러\nexport class ApiError extends TdeCollabError {\n constructor(\n public statusCode: number,\n message: string,\n public data?: any\n ) {\n super(`API 요청 실패 (${statusCode}): ${message}`);\n }\n}\n\n// 인증 실패 에러\nexport class AuthError extends TdeCollabError {\n constructor(message: string = '인증에 실패했습니다') {\n super(message);\n }\n}\n\n// 리소스 미발견 에러\nexport class NotFoundError extends TdeCollabError {\n constructor(resource: string, id: string) {\n super(`${resource} '${id}'를 찾을 수 없습니다`);\n }\n}\n\n// 리소스 충돌 에러\nexport class ConflictError extends TdeCollabError {\n constructor(message: string) {\n super(message);\n }\n}\n","import { AxiosInstance } from 'axios';\nimport { ConfluenceConfig } from '../../common/types.js';\nimport { createHttpClient } from '../../common/http-client.js';\n\nexport function createConfluenceClient(config: ConfluenceConfig): AxiosInstance {\n // Confluence API base context path handling\n // If user provides 'https://example.atlassian.net/wiki', we should use that.\n // API path usually appends '/rest/api/content' etc.\n\n // Ensure config.baseUrl includes '/wiki' if it's a cloud instance, usually users put it in env.\n // But standard is commonly base domain.\n // We'll trust the config for now, but client consumers should append specific endpoints.\n // Typically, Confluence API is at /wiki/rest/api if cloud, or /rest/api if server.\n // Let's assume baseUrl in config points to the root of the instance, e.g. https://site.atlassian.net/wiki\n\n return createHttpClient(config);\n}\n","import { ConfluenceConfig, GitlabConfig, JiraConfig } from './types.js';\nimport { logger } from './logger.js';\nimport { loadEnv } from './env-loader.js';\n\n// 환경변수 로드 (우선순위: shell env > ./tdecollab.env > ~/.config/tdecollab/.env)\nloadEnv();\n\n// 환경변수 조회 및 미설정 시 에러 발생\nfunction getEnvOrThrow(key: string, description: string): string {\n const value = process.env[key];\n if (!value) {\n const errorMsg = `환경변수 '${key}'가 설정되지 않았습니다. (${description})`;\n logger.error(errorMsg);\n throw new Error(errorMsg);\n }\n return value;\n}\n\n// Confluence 설정 로드 (PAT 인증 권장)\nexport function loadConfluenceConfig(): ConfluenceConfig & { mermaidMacroName: string, inlineCodeStyle: string } {\n const baseUrl = getEnvOrThrow('CONFLUENCE_BASE_URL', 'Confluence 기본 URL');\n // PAT 사용 시 username은 불필요 (Basic Auth 사용 시에만 필요)\n const username = process.env.CONFLUENCE_USERNAME;\n const token = getEnvOrThrow('CONFLUENCE_API_TOKEN', 'Confluence PAT 토큰');\n \n // Mermaid 매크로 이름 (기본값: mermaiddiagram)\n const mermaidMacroName = process.env.CONFLUENCE_MERMAID_MACRO_NAME || 'mermaiddiagram';\n\n // 인라인 코드 강조 스타일 (기본값: 붉은색 굵게)\n const inlineCodeStyle = process.env.CONFLUENCE_INLINE_CODE_STYLE || 'color: #d04437; font-weight: bold;';\n\n return {\n baseUrl,\n auth: {\n username,\n token,\n },\n mermaidMacroName,\n inlineCodeStyle,\n };\n}\n\n// AI 서비스 설정 로드\nexport function loadAIConfig() {\n return {\n openaiApiKey: process.env.OPENAI_API_KEY,\n anthropicApiKey: process.env.ANTHROPIC_API_KEY,\n defaultProvider: process.env.AI_PROVIDER || 'openai',\n defaultModel: process.env.AI_MODEL || 'gpt-4o',\n };\n}\n\n// JIRA 설정 로드 (PAT 인증 권장)\nexport function loadJiraConfig(): JiraConfig {\n const baseUrl = getEnvOrThrow('JIRA_BASE_URL', 'JIRA 기본 URL');\n // PAT 사용 시 username은 불필요\n const username = process.env.JIRA_USERNAME;\n const token = getEnvOrThrow('JIRA_API_TOKEN', 'JIRA PAT 토큰');\n\n return {\n baseUrl,\n auth: {\n username,\n token,\n },\n };\n}\n\n// GitLab 설정 로드\nexport function loadGitlabConfig(): GitlabConfig {\n const baseUrl = process.env.GITLAB_BASE_URL || 'https://gitlab.com';\n const token = getEnvOrThrow('GITLAB_PRIVATE_TOKEN', 'GitLab Private Token');\n\n return {\n baseUrl,\n auth: {\n token,\n },\n };\n}\n","import MarkdownIt from 'markdown-it';\nimport { loadConfluenceConfig } from '../../common/config.js';\n\nexport class MarkdownToStorageConverter {\n private md: MarkdownIt;\n private mermaidMacroName: string;\n private inlineCodeStyle: string;\n\n constructor() {\n const config = loadConfluenceConfig();\n this.mermaidMacroName = config.mermaidMacroName;\n this.inlineCodeStyle = config.inlineCodeStyle;\n\n this.md = new MarkdownIt({\n html: true,\n linkify: true,\n breaks: true,\n xhtmlOut: true // Confluence XML 파서와의 호환성을 위해 XHTML 출력 활성화\n });\n\n // Custom renderer for code blocks\n this.md.renderer.rules.fence = (tokens, idx) => {\n const token = tokens[idx];\n const code = token.content.trim();\n const lang = token.info.trim().toLowerCase();\n\n // Mermaid 처리 (기존 로직 유지)\n if (lang === 'mermaid') {\n return `<ac:structured-macro ac:name=\"${this.mermaidMacroName}\" ac:schema-version=\"1\">\n <ac:plain-text-body><![CDATA[${code}]]></ac:plain-text-body>\n</ac:structured-macro>`;\n }\n\n // PlantUML 처리 → plantuml 매크로로 변환\n if (lang === 'plantuml') {\n return `<ac:structured-macro ac:name=\"plantuml\" ac:schema-version=\"1\">\n <ac:parameter ac:name=\"atlassian-macro-output-type\">INLINE</ac:parameter>\n <ac:plain-text-body><![CDATA[${code}]]></ac:plain-text-body>\n</ac:structured-macro>`;\n }\n\n // 일반 코드 블록\n return `<ac:structured-macro ac:name=\"code\" ac:schema-version=\"1\">\n <ac:parameter ac:name=\"language\">${lang || 'text'}</ac:parameter>\n <ac:plain-text-body><![CDATA[${code}]]></ac:plain-text-body>\n</ac:structured-macro>`;\n };\n\n // Custom renderer for images\n this.md.renderer.rules.image = (tokens, idx, options, env, self) => {\n const token = tokens[idx];\n const src = token.attrGet('src') || '';\n const alt = token.content || '';\n\n // Handle URL vs Local file\n const isExternal = src.startsWith('http://') || src.startsWith('https://');\n\n // 외부 URL이면 URL 매크로, 아니면 첨부파일 매크로\n const altAttr = alt ? ` ac:alt=\"${this.md.utils.escapeHtml(alt)}\"` : '';\n if (isExternal) {\n return `<ac:image${altAttr}><ri:url ri:value=\"${src}\" /></ac:image>`;\n } else {\n // filename can just be the basename of the src path\n const filename = src.split('/').pop() || src;\n return `<ac:image${altAttr}><ri:attachment ri:filename=\"${filename}\" /></ac:image>`;\n }\n };\n\n // Custom renderer for inline code\n this.md.renderer.rules.code_inline = (tokens, idx) => {\n const token = tokens[idx];\n const content = this.md.utils.escapeHtml(token.content);\n return `<code style=\"${this.inlineCodeStyle}\">${content}</code>`;\n };\n\n // Task List 처리 (markdown-it 플러그인 없이 수동 처리 예시 - 실제로는 플러그인 도입 권장)\n }\n\n convert(markdown: string): string {\n return this.md.render(markdown);\n }\n\n extractLocalImages(markdown: string): string[] {\n const tokens = this.md.parse(markdown, {});\n const localImages = new Set<string>();\n\n const walk = (tokens: any[]) => {\n for (const token of tokens) {\n if (token.type === 'image') {\n const src = token.attrGet('src') || '';\n if (!src.startsWith('http://') && !src.startsWith('https://')) {\n localImages.add(src);\n }\n }\n if (token.children) {\n walk(token.children);\n }\n }\n };\n\n walk(tokens);\n return Array.from(localImages);\n }\n}\n","import TurndownService from 'turndown';\nimport { gfm } from 'turndown-plugin-gfm';\nimport { JSDOM } from 'jsdom';\n\nexport interface JiraIssueInfo {\n summary: string;\n status: string;\n}\n\nexport interface StorageToMarkdownOptions {\n /** JIRA 티켓 링크 생성에 사용할 베이스 URL (예: https://jira.example.com). 미설정 시 JIRA_BASE_URL 환경변수 사용. */\n jiraBaseUrl?: string;\n}\n\nexport class StorageToMarkdownConverter {\n private turndown: TurndownService;\n private jiraBaseUrl: string;\n private jiraIssueMap: Map<string, JiraIssueInfo> | undefined;\n\n constructor(options: StorageToMarkdownOptions = {}) {\n this.jiraBaseUrl = options.jiraBaseUrl || process.env.JIRA_BASE_URL || '';\n this.jiraIssueMap = undefined;\n this.turndown = new TurndownService({\n headingStyle: 'atx',\n hr: '---',\n bulletListMarker: '-',\n codeBlockStyle: 'fenced'\n });\n\n this.turndown.use(gfm);\n this.setupRules();\n }\n\n private setupRules() {\n // Table 변환 규칙 강화 (정렬 정보 보존)\n this.turndown.addRule('tables', {\n filter: ['table'],\n replacement: (content, node) => {\n const element = node as HTMLTableElement;\n const rows = Array.from(element.rows);\n if (rows.length === 0) return '';\n\n let mdTable = '\\n\\n';\n\n rows.forEach((row, index) => {\n const cells = Array.from(row.cells);\n const cellContents = cells.map(cell => {\n // 셀 내부의 개행은 공백으로 치환하여 테이블 깨짐 방지\n return this.turndown.turndown(cell.innerHTML).replace(/\\n/g, ' ').trim();\n });\n\n mdTable += `| ${cellContents.join(' | ')} |\\n`;\n\n // 헤더 행 구분선 (정렬 정보 포함)\n if (index === 0) {\n const separators = cells.map(cell => {\n const style = (cell as HTMLElement).getAttribute('style') || '';\n const align = style.match(/text-align:\\s*(\\w+)/i)?.[1]?.toLowerCase();\n if (align === 'center') return ':---:';\n if (align === 'right') return '---:';\n if (align === 'left') return ':---';\n return '---';\n });\n mdTable += `| ${separators.join(' | ')} |\\n`;\n }\n });\n\n return mdTable + '\\n';\n }\n });\n\n // Confluence 매크로 (변환된 div 태그) 처리\n this.turndown.addRule('confluenceMacro', {\n filter: (node) => {\n return node.nodeName === 'DIV' && node.getAttribute('data-macro-name-tag') !== null;\n },\n replacement: (content, node) => {\n const element = node as HTMLElement;\n const macroName = element.getAttribute('data-macro-name');\n\n if (macroName === 'code') {\n const lang = element.querySelector('[data-macro-param-name=\"language\"]')?.textContent || 'text';\n let body = element.querySelector('[data-macro-body]')?.textContent || '';\n // 임시 CDATA 태그 제거\n body = body.replace(/__CDATA_START__/g, '').replace(/__CDATA_END__/g, '');\n return `\\n\\`\\`\\`${lang}\\n${body.trim()}\\n\\`\\`\\`\\n`;\n }\n\n if (macroName === 'mermaid' || macroName === 'mermaiddiagram' || macroName === 'capable-mermaid' || macroName === 'mermaid-macro') {\n let body = element.querySelector('[data-macro-body]')?.textContent || '';\n // 임시 CDATA 태그 제거\n body = body.replace(/__CDATA_START__/g, '').replace(/__CDATA_END__/g, '');\n return `\\n\\`\\`\\`mermaid\\n${body.trim()}\\n\\`\\`\\`\\n`;\n }\n\n if (macroName === 'plantuml') {\n let body = element.querySelector('[data-macro-body]')?.textContent || '';\n body = body.replace(/__CDATA_START__/g, '').replace(/__CDATA_END__/g, '');\n return `\\n\\`\\`\\`plantuml\\n${body.trim()}\\n\\`\\`\\`\\n`;\n }\n\n if (macroName === 'expand') {\n const title = element.querySelector('[data-macro-param-name=\"title\"]')?.textContent?.trim() || '더보기';\n const richBody = element.querySelector('[data-macro-rich-body]');\n const bodyMd = richBody\n ? this.turndown.turndown(richBody.innerHTML).trim()\n : '';\n return `\\n\\n<details>\\n<summary>${title}</summary>\\n\\n${bodyMd}\\n\\n</details>\\n\\n`;\n }\n\n if (macroName === 'jira') {\n const key = element.querySelector('[data-macro-param-name=\"key\"]')?.textContent?.trim();\n if (key) {\n const base = this.jiraBaseUrl.replace(/\\/$/, '');\n const url = base ? `${base}/browse/${key}` : `https://jira.atlassian.com/browse/${key}`;\n const info = this.jiraIssueMap?.get(key);\n if (info) {\n return ` [${key}](${url}) ${info.summary} \\`${info.status}\\` `;\n }\n return ` [${key}](${url}) `;\n }\n return '';\n }\n\n return `\\n<!-- Macro: ${macroName} -->\\n`;\n }\n });\n }\n\n convert(storageHtml: string, imageUrlMap?: Map<string, string>, jiraIssueMap?: Map<string, JiraIssueInfo>): string {\n this.jiraIssueMap = jiraIssueMap;\n if (!storageHtml) return '';\n\n // 1. Confluence 전용 태그를 표준 HTML 태그로 치환 (JSDOM 호환성)\n // CDATA 섹션을 텍스트로 보존하기 위해 임시 치환\n let processedHtml = storageHtml\n .replace(/<!\\[CDATA\\[([\\s\\S]*?)\\]\\]>/gi, (match, p1) => {\n return `__CDATA_START__${p1}__CDATA_END__`;\n })\n .replace(/<ac:structured-macro\\s+ac:name=\"([^\"]*)\"/gi, '<div data-macro-name-tag data-macro-name=\"$1\"')\n .replace(/<\\/ac:structured-macro>/gi, '</div>')\n .replace(/<ac:parameter\\s+ac:name=\"([^\"]*)\"/gi, '<div data-macro-param-tag data-macro-param-name=\"$1\"')\n .replace(/<\\/ac:parameter>/gi, '</div>')\n .replace(/<ac:plain-text-body>/gi, '<pre data-macro-body>')\n .replace(/<\\/ac:plain-text-body>/gi, '</pre>')\n .replace(/<ac:rich-text-body>/gi, '<div data-macro-rich-body>')\n .replace(/<\\/ac:rich-text-body>/gi, '</div>')\n .replace(/<ac:image([^>]*)>[\\s\\S]*?<ri:attachment\\s+ri:filename=\"([^\"]*)\"\\s*\\/?>[\\s\\S]*?<\\/ac:image>/gi, (match, attrs, filename) => {\n const altMatch = attrs.match(/ac:alt=\"([^\"]*)\"/i);\n const alt = altMatch ? altMatch[1] : filename;\n return `<img src=\"${filename}\" alt=\"${alt}\" />`;\n })\n .replace(/<ac:image([^>]*)>[\\s\\S]*?<ri:url\\s+ri:value=\"([^\"]*)\"\\s*\\/?>[\\s\\S]*?<\\/ac:image>/gi, (match, attrs, url) => {\n const altMatch = attrs.match(/ac:alt=\"([^\"]*)\"/i);\n const alt = altMatch ? altMatch[1] : '';\n return `<img src=\"${url}\" alt=\"${alt}\" />`;\n });\n\n // 2. DOM 파싱 (Browser/Node 분기)\n let document: Document;\n if (typeof window !== 'undefined' && typeof window.DOMParser !== 'undefined') {\n const parser = new window.DOMParser();\n document = parser.parseFromString(processedHtml, 'text/html');\n } else {\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const { JSDOM } = require('jsdom');\n const dom = new JSDOM(processedHtml);\n document = dom.window.document;\n }\n\n // 이미지 처리\n if (imageUrlMap && imageUrlMap.size > 0) {\n const images = document.querySelectorAll('img');\n images.forEach(img => {\n const src = img.getAttribute('src');\n if (src && imageUrlMap.has(src)) {\n img.setAttribute('src', imageUrlMap.get(src)!);\n }\n });\n }\n\n // 3. Turndown 변환\n let markdown = this.turndown.turndown(document.body.innerHTML).trim();\n\n // 4. 헤딩 내 숫자 뒤 점 이스케이프 제거 (예: `## 1\\.` → `## 1.`)\n markdown = markdown.replace(/^(#{1,6}\\s.*?)\\\\\\./gm, '$1.');\n\n this.jiraIssueMap = undefined;\n return markdown + '\\n';\n }\n}\n","import { AxiosInstance } from 'axios';\nimport { JiraIssueResponse, CreateIssueParams, UpdateIssueParams } from '../types.js';\n\nexport class JiraIssueApi {\n constructor(private client: AxiosInstance) {}\n\n async getIssue(issueKey: string, fields?: string[], expand?: string[]): Promise<JiraIssueResponse> {\n const params: Record<string, string> = {};\n if (fields && fields.length > 0) {\n params.fields = fields.join(',');\n }\n if (expand && expand.length > 0) {\n params.expand = expand.join(',');\n }\n const response = await this.client.get(`/rest/api/2/issue/${issueKey}`, { params });\n return response.data;\n }\n\n async createIssue(params: CreateIssueParams): Promise<JiraIssueResponse> {\n const fields: Record<string, unknown> = {\n project: { key: params.projectKey },\n summary: params.summary,\n issuetype: { name: params.issueType },\n };\n\n if (params.description) fields.description = params.description;\n if (params.assignee) fields.assignee = { name: params.assignee };\n if (params.priority) fields.priority = { name: params.priority };\n if (params.labels) fields.labels = params.labels;\n if (params.components) {\n fields.components = params.components.map((name) => ({ name }));\n }\n if (params.parentKey) {\n fields.parent = { key: params.parentKey };\n }\n if (params.customFields) {\n Object.assign(fields, params.customFields);\n }\n\n const response = await this.client.post('rest/api/2/issue', { fields });\n return response.data;\n }\n\n async updateIssue(issueKey: string, params: UpdateIssueParams): Promise<void> {\n const fields: Record<string, unknown> = {};\n\n if (params.summary) fields.summary = params.summary;\n if (params.description !== undefined) fields.description = params.description;\n if (params.assignee !== undefined) fields.assignee = params.assignee ? { name: params.assignee } : null;\n if (params.priority) fields.priority = { name: params.priority };\n if (params.labels) fields.labels = params.labels;\n if (params.components) {\n fields.components = params.components.map((name) => ({ name }));\n }\n if (params.customFields) {\n Object.assign(fields, params.customFields);\n }\n\n await this.client.put(`/rest/api/2/issue/${issueKey}`, { fields });\n }\n\n async deleteIssue(issueKey: string, deleteSubtasks = false): Promise<void> {\n await this.client.delete(`/rest/api/2/issue/${issueKey}`, {\n params: { deleteSubtasks },\n });\n }\n}\n","import { AxiosInstance } from 'axios';\nimport { JiraConfig } from '../../common/types.js';\nimport { createHttpClient } from '../../common/http-client.js';\n\nexport function createJiraClient(config: JiraConfig): AxiosInstance {\n return createHttpClient(config);\n}\n","import { JiraIssueApi } from '../../jira/api/issue.js';\nimport { createJiraClient } from '../../jira/api/client.js';\nimport { loadJiraConfig } from '../../common/config.js';\nimport type { JiraIssueInfo } from './storage-to-md.js';\n\n/**\n * Confluence Storage XML에서 JIRA 매크로의 key 파라미터를 추출한다.\n * 중복 키는 제거하여 반환한다.\n */\nexport function extractJiraKeys(storageXml: string): string[] {\n const keys = new Set<string>();\n const macroRegex = /<ac:structured-macro\\s+ac:name=\"jira\"[\\s\\S]*?<\\/ac:structured-macro>/gi;\n const keyRegex = /<ac:parameter\\s+ac:name=\"key\">([^<]+)<\\/ac:parameter>/i;\n let match;\n while ((match = macroRegex.exec(storageXml)) !== null) {\n const keyMatch = keyRegex.exec(match[0]);\n if (keyMatch) keys.add(keyMatch[1].trim());\n }\n return [...keys];\n}\n\n/**\n * JIRA API에서 이슈 목록을 배치 조회하여 key → JiraIssueInfo 맵을 반환한다.\n * 개별 이슈 조회 실패 시 해당 키를 맵에서 제외한다 (링크만 표시됨).\n */\nexport async function buildJiraIssueMap(\n keys: string[],\n jiraApi: JiraIssueApi,\n): Promise<Map<string, JiraIssueInfo>> {\n const map = new Map<string, JiraIssueInfo>();\n if (keys.length === 0) return map;\n\n await Promise.allSettled(\n keys.map(async (key) => {\n try {\n const issue = await jiraApi.getIssue(key, ['summary', 'status']);\n map.set(key, {\n summary: issue.fields.summary ?? '',\n status: issue.fields.status?.name ?? '',\n });\n } catch {\n // 조회 실패 시 해당 키 생략 — 링크만 표시\n }\n }),\n );\n return map;\n}\n\n/**\n * Storage XML에서 JIRA 키를 추출하고, JIRA 설정이 가능한 경우 이슈 정보를 가져온다.\n * JIRA 설정이 없거나 오류 발생 시 빈 맵을 반환한다 (graceful fallback).\n */\nexport async function tryBuildJiraIssueMap(\n storageXml: string,\n): Promise<Map<string, JiraIssueInfo>> {\n try {\n const keys = extractJiraKeys(storageXml);\n if (keys.length === 0) return new Map();\n\n const config = loadJiraConfig();\n const client = createJiraClient(config);\n const api = new JiraIssueApi(client);\n return await buildJiraIssueMap(keys, api);\n } catch {\n return new Map();\n }\n}\n","import { AxiosInstance } from 'axios';\nimport { JiraSearchResponse } from '../types.js';\n\nexport class JiraSearchApi {\n constructor(private client: AxiosInstance) {}\n\n async searchByJql(\n jql: string,\n startAt = 0,\n maxResults = 50,\n fields?: string[],\n ): Promise<JiraSearchResponse> {\n const params: Record<string, unknown> = {\n jql,\n startAt,\n maxResults,\n };\n if (fields && fields.length > 0) {\n params.fields = fields.join(',');\n }\n const response = await this.client.get('rest/api/2/search', { params });\n return response.data;\n }\n}\n","import { AxiosInstance } from 'axios';\nimport { GitlabMergeRequest, GitlabNote } from '../types.js';\n\nexport class GitlabMergeRequestApi {\n constructor(private client: AxiosInstance) {}\n\n async getMergeRequests(\n projectId: number,\n params?: { state?: string; scope?: string; labels?: string; perPage?: number },\n ): Promise<GitlabMergeRequest[]> {\n const response = await this.client.get(`/projects/${projectId}/merge_requests`, {\n params: {\n state: params?.state || 'opened',\n scope: params?.scope,\n labels: params?.labels,\n per_page: params?.perPage || 20,\n },\n });\n return response.data;\n }\n\n async getMergeRequest(projectId: number, mrIid: number): Promise<GitlabMergeRequest> {\n const response = await this.client.get(\n `/projects/${projectId}/merge_requests/${mrIid}`,\n );\n return response.data;\n }\n\n async getMergeRequestChanges(projectId: number, mrIid: number): Promise<GitlabMergeRequest> {\n const response = await this.client.get(\n `/projects/${projectId}/merge_requests/${mrIid}/changes`,\n );\n return response.data;\n }\n\n async createMergeRequest(\n projectId: number,\n data: {\n source_branch: string;\n target_branch: string;\n title: string;\n description?: string;\n assignee_id?: number;\n reviewer_ids?: number[];\n labels?: string;\n },\n ): Promise<GitlabMergeRequest> {\n const response = await this.client.post(`/projects/${projectId}/merge_requests`, data);\n return response.data;\n }\n\n async updateMergeRequest(\n projectId: number,\n mrIid: number,\n data: {\n title?: string;\n description?: string;\n assignee_id?: number;\n reviewer_ids?: number[];\n labels?: string;\n state_event?: 'close' | 'reopen';\n },\n ): Promise<GitlabMergeRequest> {\n const response = await this.client.put(\n `/projects/${projectId}/merge_requests/${mrIid}`,\n data,\n );\n return response.data;\n }\n\n async mergeMergeRequest(\n projectId: number,\n mrIid: number,\n params?: {\n merge_commit_message?: string;\n squash?: boolean;\n should_remove_source_branch?: boolean;\n },\n ): Promise<GitlabMergeRequest> {\n const response = await this.client.put(\n `/projects/${projectId}/merge_requests/${mrIid}/merge`,\n params,\n );\n return response.data;\n }\n\n async getMergeRequestNotes(projectId: number, mrIid: number): Promise<GitlabNote[]> {\n const response = await this.client.get(\n `/projects/${projectId}/merge_requests/${mrIid}/notes`,\n );\n return response.data;\n }\n\n async addMergeRequestNote(\n projectId: number,\n mrIid: number,\n body: string,\n ): Promise<GitlabNote> {\n const response = await this.client.post(\n `/projects/${projectId}/merge_requests/${mrIid}/notes`,\n { body },\n );\n return response.data;\n }\n}\n","import { AxiosInstance } from 'axios';\nimport { GitlabPipeline, GitlabJob } from '../types.js';\n\nexport class GitlabPipelineApi {\n constructor(private client: AxiosInstance) {}\n\n async getPipelines(\n projectId: number,\n params?: { status?: string; ref?: string; perPage?: number },\n ): Promise<GitlabPipeline[]> {\n const response = await this.client.get(`/projects/${projectId}/pipelines`, {\n params: {\n status: params?.status,\n ref: params?.ref,\n per_page: params?.perPage || 20,\n },\n });\n return response.data;\n }\n\n async getPipeline(projectId: number, pipelineId: number): Promise<GitlabPipeline> {\n const response = await this.client.get(\n `/projects/${projectId}/pipelines/${pipelineId}`,\n );\n return response.data;\n }\n\n async getPipelineJobs(projectId: number, pipelineId: number): Promise<GitlabJob[]> {\n const response = await this.client.get(\n `/projects/${projectId}/pipelines/${pipelineId}/jobs`,\n );\n return response.data;\n }\n\n async getMergeRequestPipelines(\n projectId: number,\n mrIid: number,\n ): Promise<GitlabPipeline[]> {\n const response = await this.client.get(\n `/projects/${projectId}/merge_requests/${mrIid}/pipelines`,\n );\n return response.data;\n }\n}\n","import { AxiosInstance } from 'axios';\nimport { GitlabConfig } from '../../common/types.js';\nimport { createHttpClient } from '../../common/http-client.js';\n\nexport function createGitlabClient(config: GitlabConfig): AxiosInstance {\n const client = createHttpClient({\n ...config,\n baseUrl: `${config.baseUrl}/api/v4`,\n });\n\n // GitLab Self-hosted는 PRIVATE-TOKEN 헤더 사용\n client.defaults.headers.common['PRIVATE-TOKEN'] = config.auth.token!;\n\n return client;\n}\n"],"mappings":"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","names":["tokens"]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  logger
3
- } from "./chunk-IFYMZLQI.js";
3
+ } from "./chunk-6RIA6AJ3.js";
4
4
 
5
5
  // tools/confluence/utils/image-downloader.ts
6
6
  import fs from "fs";
@@ -122,4 +122,4 @@ var ImageDownloader = class {
122
122
  export {
123
123
  ImageDownloader
124
124
  };
125
- //# sourceMappingURL=chunk-JBDK5WP3.js.map
125
+ //# sourceMappingURL=chunk-R4YTIP7E.js.map
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  JiraCommentApi,
7
7
  JiraProjectApi,
8
8
  JiraTransitionApi
9
- } from "./chunk-JI2YUE7N.js";
9
+ } from "./chunk-4QW3PXPJ.js";
10
10
  import {
11
11
  ConfluenceContentApi,
12
12
  ConfluenceSearchApi,
@@ -23,11 +23,12 @@ import {
23
23
  loadConfluenceConfig,
24
24
  loadEnv,
25
25
  loadGitlabConfig,
26
- loadJiraConfig
27
- } from "./chunk-BWZT27NL.js";
26
+ loadJiraConfig,
27
+ tryBuildJiraIssueMap
28
+ } from "./chunk-JUM5ZG64.js";
28
29
  import {
29
30
  logger
30
- } from "./chunk-IFYMZLQI.js";
31
+ } from "./chunk-6RIA6AJ3.js";
31
32
 
32
33
  // tools/cli.ts
33
34
  import { Command } from "commander";
@@ -77,7 +78,7 @@ function registerConfluenceCommands(program2) {
77
78
  if (page.body?.storage?.value) {
78
79
  let imageUrlMap;
79
80
  if (options.downloadImages) {
80
- const { ImageDownloader } = await import("./image-downloader-VKPGS3TY.js");
81
+ const { ImageDownloader } = await import("./image-downloader-AKNR5YKJ.js");
81
82
  let baseDir = process.cwd();
82
83
  if (options.output) {
83
84
  baseDir = path.dirname(path.resolve(process.cwd(), options.output));
@@ -97,7 +98,8 @@ function registerConfluenceCommands(program2) {
97
98
  }
98
99
  }
99
100
  const storageToMd = new StorageToMarkdownConverter();
100
- content = storageToMd.convert(page.body.storage.value, imageUrlMap);
101
+ const jiraIssueMap = await tryBuildJiraIssueMap(page.body.storage.value);
102
+ content = storageToMd.convert(page.body.storage.value, imageUrlMap, jiraIssueMap);
101
103
  } else {
102
104
  if (!options.quiet) console.log(chalk.yellow("(No content)"));
103
105
  }
@@ -869,7 +871,7 @@ registerConfluenceCommands(program);
869
871
  registerJiraCommands(program);
870
872
  registerGitlabCommands(program);
871
873
  program.command("mcp").description("Run MCP Server").action(async () => {
872
- const { runServer } = await import("./server-MHNYWUKQ.js");
874
+ const { runServer } = await import("./server-FKROUVDL.js");
873
875
  await runServer();
874
876
  });
875
877
  program.parse(args);