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.
- package/README.md +10 -0
- package/dist/{chunk-JI2YUE7N.js → chunk-4QW3PXPJ.js} +4 -4
- package/dist/chunk-4QW3PXPJ.js.map +1 -0
- package/dist/{chunk-PNYGNVQ4.js → chunk-5IY42AV6.js} +9 -7
- package/dist/chunk-5IY42AV6.js.map +1 -0
- package/dist/{chunk-IFYMZLQI.js → chunk-6RIA6AJ3.js} +9 -1
- package/dist/{chunk-IFYMZLQI.js.map → chunk-6RIA6AJ3.js.map} +1 -1
- package/dist/{chunk-BWZT27NL.js → chunk-JUM5ZG64.js} +119 -32
- package/dist/chunk-JUM5ZG64.js.map +1 -0
- package/dist/{chunk-JBDK5WP3.js → chunk-R4YTIP7E.js} +2 -2
- package/dist/cli.js +9 -7
- package/dist/cli.js.map +1 -1
- package/dist/image-downloader-AKNR5YKJ.js +8 -0
- package/dist/index.js +4 -4
- package/dist/server-FKROUVDL.js +10 -0
- package/dist/tui/index.js +8 -6
- package/dist/tui/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-BWZT27NL.js.map +0 -1
- package/dist/chunk-JI2YUE7N.js.map +0 -1
- package/dist/chunk-PNYGNVQ4.js.map +0 -1
- package/dist/image-downloader-VKPGS3TY.js +0 -8
- package/dist/server-MHNYWUKQ.js +0 -10
- /package/dist/{chunk-JBDK5WP3.js.map → chunk-R4YTIP7E.js.map} +0 -0
- /package/dist/{image-downloader-VKPGS3TY.js.map → image-downloader-AKNR5YKJ.js.map} +0 -0
- /package/dist/{server-MHNYWUKQ.js.map → server-FKROUVDL.js.map} +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
__require,
|
|
2
3
|
logger
|
|
3
|
-
} from "./chunk-
|
|
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(
|
|
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("
|
|
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("
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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("
|
|
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("
|
|
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:
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
548
|
-
|
|
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("
|
|
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("
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
26
|
+
loadJiraConfig,
|
|
27
|
+
tryBuildJiraIssueMap
|
|
28
|
+
} from "./chunk-JUM5ZG64.js";
|
|
28
29
|
import {
|
|
29
30
|
logger
|
|
30
|
-
} from "./chunk-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
874
|
+
const { runServer } = await import("./server-FKROUVDL.js");
|
|
873
875
|
await runServer();
|
|
874
876
|
});
|
|
875
877
|
program.parse(args);
|