@bestend/confluence-cli 1.15.8 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md ADDED
@@ -0,0 +1,105 @@
1
+ # Confluence CLI — 프로젝트 가이드
2
+
3
+ ## 아키텍처
4
+
5
+ ```
6
+ bin/
7
+ index.js # 엔트리포인트 (Node 버전 체크 → confluence.js 로딩)
8
+ confluence.js # Commander CLI 정의 (모든 커맨드 등록)
9
+
10
+ lib/
11
+ confluence-client.js # 핵심 클라이언트 (API 호출, 포맷 변환, 페이지 CRUD)
12
+ config.js # 설정 로드 (~/.confluence-cli/config.json)
13
+ analytics.js # 익명 사용 통계
14
+
15
+ tests/
16
+ confluence-client.test.js # Jest 단위 테스트
17
+
18
+ .github/workflows/
19
+ ci.yml # push/PR → lint + test (Node 18.x, 20.x)
20
+ publish.yml # tag push (v*) → npm publish with OIDC provenance
21
+ ```
22
+
23
+ ## 코드 스타일
24
+
25
+ - ESLint flat config (`eslint.config.js`)
26
+ - 들여쓰기: 2칸 스페이스
27
+ - 따옴표: 작은따옴표
28
+ - 세미콜론: 필수
29
+ - `no-unused-vars`: `_` 접두사 무시
30
+
31
+ ## 핵심 변환 파이프라인
32
+
33
+ ```
34
+ Markdown → MarkdownIt.render() → HTML → htmlToConfluenceStorage() → Storage XML
35
+ ```
36
+
37
+ `htmlToConfluenceStorage()`는 MarkdownIt이 생성한 HTML 중 Confluence 전용 처리가 필요한 것만 변환:
38
+ - `<li>` → `<li><p>...</p></li>` (Confluence 필수)
39
+ - `<pre><code>` → `ac:structured-macro` code block with CDATA
40
+ - `<blockquote>` → info/warning/note 매크로
41
+ - `<th>`, `<td>` → `<p>` 래핑
42
+ - `<hr>` → `<hr />` (self-closing)
43
+
44
+ 나머지 HTML 태그(h1-h6, p, strong, em, ul, ol, table 등)는 이미 Confluence storage와 호환되므로 변환하지 않음.
45
+
46
+ ## 버전 관리
47
+
48
+ - `package.json`의 version은 직접 올리지 않아도 됨
49
+ - publish.yml이 git tag에서 버전을 추출해서 package.json을 자동 맞춤
50
+ - 단, 로컬 개발 시 혼동 방지를 위해 릴리스 전 맞춰두는 것을 권장
51
+
52
+ ## 릴리스 절차
53
+
54
+ ```bash
55
+ # 1. 변경 커밋 (Conventional Commits)
56
+ git add -A
57
+ git commit -m "feat: add --parent option to create command"
58
+
59
+ # 2. 태그 생성 (SemVer)
60
+ git tag v1.16.0
61
+
62
+ # 3. 푸시 (커밋 + 태그)
63
+ git push origin main --follow-tags
64
+ ```
65
+
66
+ 태그 push 시 자동으로:
67
+ 1. `ci.yml` → lint + test (push to main 트리거)
68
+ 2. `publish.yml` → npm publish with OIDC provenance (tag 트리거)
69
+
70
+ ### 버전 결정 기준
71
+
72
+ | 변경 유형 | 예시 | 버전 |
73
+ |-----------|------|------|
74
+ | 새 명령어/옵션 추가 | `--parent` 옵션 | minor (x.Y.0) |
75
+ | 버그 수정, 내부 정리 | no-op regex 제거 | patch (x.y.Z) |
76
+ | 하위 호환 깨짐 | 명령어 인터페이스 변경 | major (X.0.0) |
77
+
78
+ ### npm OIDC Trusted Publishing
79
+
80
+ publish.yml은 `id-token: write` 권한으로 npm OIDC provenance를 사용.
81
+ npm 토큰이 아닌 GitHub Actions OIDC로 인증하므로 시크릿 관리 불필요.
82
+ Node.js 24.x (npm 11.5.1+) 필요.
83
+
84
+ ## 테스트
85
+
86
+ ```bash
87
+ npm test # Jest 단위 테스트 (40개)
88
+ npm run lint # ESLint
89
+ ```
90
+
91
+ 테스트는 `axios-mock-adapter`로 HTTP 모킹. 실제 Confluence 인스턴스 불필요.
92
+
93
+ ## agent-rules 연동
94
+
95
+ 이 CLI는 [mona/agent-rules](https://oss.navercorp.com/mona/agent-rules)의 `confluence-cli` 스킬로 사용됨:
96
+ - 스킬 문서: `sources/skills/confluence-cli/SKILL.md`
97
+ - 설치: `npm install -g @bestend/confluence-cli@latest`
98
+
99
+ CLI 버전 릴리스 후 agent-rules의 SKILL.md `metadata.version`도 맞춰 업데이트할 것.
100
+
101
+ ## 금지 사항
102
+
103
+ - `htmlToConfluenceStorage()`에 no-op regex 추가 금지 (X→X 변환은 의미 없음)
104
+ - 전역 entity unescape (`&lt;`→`<` 등) 금지 — code block 내부는 이미 별도 디코딩됨
105
+ - OIDC publish 설정 변경 시 Node.js 24.x 이상 유지 필수
package/bin/confluence.js CHANGED
@@ -141,11 +141,12 @@ program
141
141
 
142
142
  // Create command
143
143
  program
144
- .command('create <title> <spaceKey>')
144
+ .command('create <title> [spaceKey]')
145
145
  .description('Create a new Confluence page')
146
146
  .option('-f, --file <file>', 'Read content from file')
147
147
  .option('-c, --content <content>', 'Page content as string')
148
148
  .option('--format <format>', 'Content format (storage, html, markdown)', 'storage')
149
+ .option('--parent <parentId>', 'Parent page ID (creates page as child of this page)')
149
150
  .option('--validate-storage', 'Validate storage content (XML well-formed) before sending')
150
151
  .option('--no-sanitize-storage', 'Disable auto-escaping raw ampersands in storage format')
151
152
  .action(async (title, spaceKey, options) => {
@@ -168,14 +169,30 @@ program
168
169
  throw new Error('Either --file or --content option is required');
169
170
  }
170
171
 
171
- const result = await client.createPage(title, spaceKey, content, options.format, {
172
- validateStorage: options.validateStorage,
173
- sanitizeStorage: options.sanitizeStorage
174
- });
175
-
176
- console.log(chalk.green('✅ Page created successfully!'));
177
- console.log(`Title: ${chalk.blue(result.title)}`);
178
- console.log(`ID: ${chalk.blue(result.id)}`);
172
+ let result;
173
+ if (options.parent) {
174
+ const parentInfo = await client.getPageInfo(options.parent);
175
+ const derivedSpaceKey = parentInfo.space.key;
176
+ result = await client.createChildPage(title, derivedSpaceKey, options.parent, content, options.format, {
177
+ validateStorage: options.validateStorage,
178
+ sanitizeStorage: options.sanitizeStorage
179
+ });
180
+ console.log(chalk.green('✅ Page created successfully!'));
181
+ console.log(`Title: ${chalk.blue(result.title)}`);
182
+ console.log(`ID: ${chalk.blue(result.id)}`);
183
+ console.log(`Parent: ${chalk.blue(parentInfo.title)} (${options.parent})`);
184
+ } else {
185
+ if (!spaceKey) {
186
+ throw new Error('Space key is required when --parent is not specified');
187
+ }
188
+ result = await client.createPage(title, spaceKey, content, options.format, {
189
+ validateStorage: options.validateStorage,
190
+ sanitizeStorage: options.sanitizeStorage
191
+ });
192
+ console.log(chalk.green('✅ Page created successfully!'));
193
+ console.log(`Title: ${chalk.blue(result.title)}`);
194
+ console.log(`ID: ${chalk.blue(result.id)}`);
195
+ }
179
196
  console.log(`Space: ${chalk.blue(result.space.name)} (${result.space.key})`);
180
197
  console.log(`URL: ${chalk.gray(`https://${config.domain}/wiki${result._links.webui}`)}`);
181
198
 
@@ -853,27 +853,18 @@ class ConfluenceClient {
853
853
  * Convert HTML to native Confluence storage format
854
854
  */
855
855
  htmlToConfluenceStorage(html) {
856
+ // MarkdownIt already produces valid HTML tags (h1-h6, p, strong, em, ul, ol, table, etc.)
857
+ // This function only transforms elements that need Confluence-specific handling:
858
+ // - li content wrapped in p (Confluence requirement)
859
+ // - pre/code → ac:structured-macro code blocks with CDATA
860
+ // - blockquote → info/warning/note macros
861
+ // - th/td content wrapped in p
862
+ // - hr → self-closing hr /
856
863
  let storage = html;
857
864
 
858
- // Convert headings to native Confluence format
859
- storage = storage.replace(/<h([1-6])>(.*?)<\/h[1-6]>/g, '<h$1>$2</h$1>');
860
-
861
- // Convert paragraphs
862
- storage = storage.replace(/<p>(.*?)<\/p>/g, '<p>$1</p>');
863
-
864
- // Convert strong/bold text
865
- storage = storage.replace(/<strong>(.*?)<\/strong>/g, '<strong>$1</strong>');
866
-
867
- // Convert emphasis/italic text
868
- storage = storage.replace(/<em>(.*?)<\/em>/g, '<em>$1</em>');
869
-
870
- // Convert unordered lists
871
- storage = storage.replace(/<ul>(.*?)<\/ul>/gs, '<ul>$1</ul>');
865
+ // Wrap li content in p tags (Confluence requirement)
872
866
  storage = storage.replace(/<li>(.*?)<\/li>/g, '<li><p>$1</p></li>');
873
867
 
874
- // Convert ordered lists
875
- storage = storage.replace(/<ol>(.*?)<\/ol>/gs, '<ol>$1</ol>');
876
-
877
868
  // Convert code blocks to Confluence code macro
878
869
  storage = storage.replace(/<pre><code(?:\s+class="language-(\w+)")?>(.*?)<\/code><\/pre>/gs, (_, lang, code) => {
879
870
  const language = lang || 'text';
@@ -892,9 +883,6 @@ class ConfluenceClient {
892
883
  </ac:structured-macro>`;
893
884
  });
894
885
 
895
- // Convert inline code
896
- storage = storage.replace(/<code>(.*?)<\/code>/g, '<code>$1</code>');
897
-
898
886
  // Convert blockquotes to appropriate macros based on content
899
887
  storage = storage.replace(/<blockquote>(.*?)<\/blockquote>/gs, (_, content) => {
900
888
  // Check for admonition patterns
@@ -921,11 +909,7 @@ class ConfluenceClient {
921
909
  }
922
910
  });
923
911
 
924
- // Convert tables
925
- storage = storage.replace(/<table>(.*?)<\/table>/gs, '<table>$1</table>');
926
- storage = storage.replace(/<thead>(.*?)<\/thead>/gs, '<thead>$1</thead>');
927
- storage = storage.replace(/<tbody>(.*?)<\/tbody>/gs, '<tbody>$1</tbody>');
928
- storage = storage.replace(/<tr>(.*?)<\/tr>/gs, '<tr>$1</tr>');
912
+ // Wrap th/td content in p tags (Confluence requirement)
929
913
  storage = storage.replace(/<th>(.*?)<\/th>/g, '<th><p>$1</p></th>');
930
914
  storage = storage.replace(/<td>(.*?)<\/td>/g, '<td><p>$1</p></td>');
931
915
 
@@ -934,9 +918,6 @@ class ConfluenceClient {
934
918
  // Convert horizontal rules
935
919
  storage = storage.replace(/<hr\s*\/?>/g, '<hr />');
936
920
 
937
- // Clean up any remaining HTML entities and normalize whitespace
938
- storage = storage.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
939
-
940
921
  return storage;
941
922
  }
942
923
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bestend/confluence-cli",
3
- "version": "1.15.8",
3
+ "version": "1.16.0",
4
4
  "description": "A command-line interface for Atlassian Confluence with page creation and editing capabilities",
5
5
  "main": "index.js",
6
6
  "bin": {