confluence-exporter 1.0.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/.eslintrc.cjs +18 -0
- package/.github/copilot-instructions.md +3 -0
- package/.github/prompts/analyze.prompt.md +101 -0
- package/.github/prompts/clarify.prompt.md +158 -0
- package/.github/prompts/constitution.prompt.md +73 -0
- package/.github/prompts/implement.prompt.md +56 -0
- package/.github/prompts/plan.prompt.md +50 -0
- package/.github/prompts/specify.prompt.md +21 -0
- package/.github/prompts/tasks.prompt.md +69 -0
- package/LICENSE +21 -0
- package/README.md +332 -0
- package/agents.md +1174 -0
- package/dist/api.d.ts +73 -0
- package/dist/api.js +387 -0
- package/dist/api.js.map +1 -0
- package/dist/commands/download.command.d.ts +18 -0
- package/dist/commands/download.command.js +257 -0
- package/dist/commands/download.command.js.map +1 -0
- package/dist/commands/executor.d.ts +22 -0
- package/dist/commands/executor.js +52 -0
- package/dist/commands/executor.js.map +1 -0
- package/dist/commands/help.command.d.ts +8 -0
- package/dist/commands/help.command.js +68 -0
- package/dist/commands/help.command.js.map +1 -0
- package/dist/commands/index.command.d.ts +14 -0
- package/dist/commands/index.command.js +95 -0
- package/dist/commands/index.command.js.map +1 -0
- package/dist/commands/index.d.ts +13 -0
- package/dist/commands/index.js +13 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/plan.command.d.ts +54 -0
- package/dist/commands/plan.command.js +272 -0
- package/dist/commands/plan.command.js.map +1 -0
- package/dist/commands/registry.d.ts +12 -0
- package/dist/commands/registry.js +32 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/commands/transform.command.d.ts +69 -0
- package/dist/commands/transform.command.js +951 -0
- package/dist/commands/transform.command.js.map +1 -0
- package/dist/commands/types.d.ts +12 -0
- package/dist/commands/types.js +5 -0
- package/dist/commands/types.js.map +1 -0
- package/dist/commands/update.command.d.ts +10 -0
- package/dist/commands/update.command.js +201 -0
- package/dist/commands/update.command.js.map +1 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +2 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +110 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +15 -0
- package/dist/logger.js +52 -0
- package/dist/logger.js.map +1 -0
- package/dist/types.d.ts +167 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +56 -0
- package/dist/utils.js +178 -0
- package/dist/utils.js.map +1 -0
- package/eslint.config.js +29 -0
- package/jest.config.cjs +25 -0
- package/migrate-meta.js +132 -0
- package/package.json +53 -0
- package/src/api.ts +469 -0
- package/src/commands/download.command.ts +324 -0
- package/src/commands/executor.ts +62 -0
- package/src/commands/help.command.ts +72 -0
- package/src/commands/index.command.ts +111 -0
- package/src/commands/index.ts +14 -0
- package/src/commands/plan.command.ts +318 -0
- package/src/commands/registry.ts +39 -0
- package/src/commands/transform.command.ts +1103 -0
- package/src/commands/types.ts +16 -0
- package/src/commands/update.command.ts +229 -0
- package/src/constants.ts +0 -0
- package/src/index.ts +120 -0
- package/src/logger.ts +60 -0
- package/src/test.sh +66 -0
- package/src/types.ts +176 -0
- package/src/utils.ts +204 -0
- package/tests/commands/README.md +123 -0
- package/tests/commands/download.command.test.ts +8 -0
- package/tests/commands/help.command.test.ts +8 -0
- package/tests/commands/index.command.test.ts +8 -0
- package/tests/commands/plan.command.test.ts +15 -0
- package/tests/commands/transform.command.test.ts +8 -0
- package/tests/fixtures/_index.yaml +38 -0
- package/tests/fixtures/mock-pages.ts +62 -0
- package/tsconfig.json +25 -0
- package/vite.config.ts +45 -0
package/agents.md
ADDED
|
@@ -0,0 +1,1174 @@
|
|
|
1
|
+
# Agents Guide: Confluence to Markdown Exporter
|
|
2
|
+
|
|
3
|
+
This document provides comprehensive guidance for AI agents (like GitHub Copilot) working on this project. It includes architecture, patterns, conventions, and implementation details.
|
|
4
|
+
|
|
5
|
+
> **⚠️ IMPORTANT: Keep This Document Updated**
|
|
6
|
+
> When making changes to project structure, architecture, CLI commands, API methods, or core logic, **ALWAYS update this agents.md file** to reflect those changes. This ensures AI agents have accurate context for future development work.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Project Overview
|
|
11
|
+
|
|
12
|
+
**Name:** Confluence to Markdown Exporter
|
|
13
|
+
**Type:** CLI Tool
|
|
14
|
+
**Language:** TypeScript 5.x
|
|
15
|
+
**Runtime:** Node.js 18+
|
|
16
|
+
**Purpose:** Export Confluence spaces to Markdown files with metadata preservation
|
|
17
|
+
|
|
18
|
+
### Key Features
|
|
19
|
+
- ✅ Minimal dependencies (uses native Node.js fetch)
|
|
20
|
+
- ✅ Command-based CLI with five commands: `help`, `index`, `plan`, `download`, `transform`
|
|
21
|
+
- ✅ Four-phase export workflow (indexing → planning → downloading → transforming)
|
|
22
|
+
- ✅ **Hierarchical folder structure** based on page tree (mirrors Confluence hierarchy)
|
|
23
|
+
- ✅ Separate HTML download and Markdown transformation for flexibility
|
|
24
|
+
- ✅ HTML to Markdown transformation with Confluence macro support
|
|
25
|
+
- ✅ User link resolution with intelligent caching
|
|
26
|
+
- ✅ Image/attachment downloading with automatic slugification
|
|
27
|
+
- ✅ YAML-based indexing with resume capability
|
|
28
|
+
- ✅ Prettier formatting for consistent output
|
|
29
|
+
|
|
30
|
+
### Quick Start
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Full space export (4-phase workflow)
|
|
34
|
+
npm run dev -- index plan download transform -u URL -n USER -p TOKEN -s SPACE -o ./output
|
|
35
|
+
|
|
36
|
+
# Full space export with limit (process first 10 pages only)
|
|
37
|
+
npm run dev -- index plan download transform -u URL -n USER -p TOKEN -s SPACE -o ./output -l 10
|
|
38
|
+
|
|
39
|
+
# Single page HTML download only
|
|
40
|
+
npm run dev -- download -i PAGE_ID -u URL -n USER -p TOKEN -s SPACE -o ./output
|
|
41
|
+
|
|
42
|
+
# Transform existing HTML files to Markdown
|
|
43
|
+
npm run dev -- transform -u URL -n USER -p TOKEN -s SPACE -o ./output
|
|
44
|
+
|
|
45
|
+
# Resume from existing index
|
|
46
|
+
npm run dev -- plan download transform -u URL -n USER -p TOKEN -s SPACE -o ./output
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Architecture
|
|
52
|
+
|
|
53
|
+
### Core Components
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
src/
|
|
57
|
+
├── index.ts # CLI entry point (arg parsing, config validation)
|
|
58
|
+
├── types.ts # TypeScript type definitions
|
|
59
|
+
├── api.ts # Confluence REST API client
|
|
60
|
+
├── transformer.ts # HTML → Markdown conversion
|
|
61
|
+
├── cleaner.ts # Post-processing cleanup
|
|
62
|
+
└── commands/ # Command handlers (modular architecture)
|
|
63
|
+
├── types.ts # Command-related type definitions
|
|
64
|
+
├── help.command.ts # Help command handler
|
|
65
|
+
├── index.command.ts # Index command handler
|
|
66
|
+
├── plan.command.ts # Plan command handler
|
|
67
|
+
├── download.command.ts # Download command handler (HTML only)
|
|
68
|
+
├── transform.command.ts # Transform command handler (HTML → MD)
|
|
69
|
+
├── registry.ts # Command registry (maps commands to handlers)
|
|
70
|
+
├── executor.ts # Command executor (orchestrates execution)
|
|
71
|
+
└── index.ts # Exports for easy importing
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Data Flow
|
|
75
|
+
|
|
76
|
+
```mermaid
|
|
77
|
+
flowchart TD
|
|
78
|
+
A[CLI Args/Env] --> B[Config Validation]
|
|
79
|
+
B --> C[CommandExecutor]
|
|
80
|
+
|
|
81
|
+
C --> D[Phase 1: IndexCommand]
|
|
82
|
+
C --> E[Phase 2: PlanCommand]
|
|
83
|
+
C --> F[Phase 3: DownloadCommand]
|
|
84
|
+
C --> G[Phase 4: TransformCommand]
|
|
85
|
+
|
|
86
|
+
D --> D1[API.getAllPages]
|
|
87
|
+
D1 --> D2[_index.yaml]
|
|
88
|
+
|
|
89
|
+
E --> E1{Has pageId?}
|
|
90
|
+
E1 -->|Yes| E2[API.getChildPages recursively]
|
|
91
|
+
E1 -->|No| E3[Read _index.yaml]
|
|
92
|
+
E2 --> E4[_queue.yaml + _tree.yaml]
|
|
93
|
+
E3 --> E4
|
|
94
|
+
|
|
95
|
+
F --> F1[Read _queue.yaml]
|
|
96
|
+
F1 --> F2[For each page]
|
|
97
|
+
F2 --> F3[API.getPage]
|
|
98
|
+
F3 --> F4[Save .html]
|
|
99
|
+
|
|
100
|
+
G --> G1[Read HTML files]
|
|
101
|
+
G1 --> G2{MD exists?}
|
|
102
|
+
G2 -->|No| G3[Transformer.transform]
|
|
103
|
+
G2 -->|Yes| G4[Skip]
|
|
104
|
+
G3 --> G5[Download images]
|
|
105
|
+
G5 --> G6[Apply cleaner]
|
|
106
|
+
G6 --> G7[Prettier format]
|
|
107
|
+
G7 --> G8[Save .md]
|
|
108
|
+
|
|
109
|
+
style D fill:#e1f5ff
|
|
110
|
+
style E fill:#fff4e1
|
|
111
|
+
style F fill:#e8f5e9
|
|
112
|
+
style G fill:#f3e5f5
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Workflow Summary:**
|
|
116
|
+
1. **Index Phase** - Scan space and create `_index.yaml` with all page metadata
|
|
117
|
+
2. **Plan Phase** - Create `_queue.yaml` from index or specific page tree
|
|
118
|
+
3. **Download Phase** - Process queue and download HTML files
|
|
119
|
+
4. **Transform Phase** - Convert HTML to Markdown (skips existing MD files)
|
|
120
|
+
|
|
121
|
+
### Key Design Patterns
|
|
122
|
+
|
|
123
|
+
#### 1. Command Pattern
|
|
124
|
+
Each command is a separate class implementing `CommandHandler` interface:
|
|
125
|
+
- **Benefits:** Separation of concerns, testable, extensible
|
|
126
|
+
- **Registry:** `CommandRegistry` maps command names to handlers
|
|
127
|
+
- **Executor:** `CommandExecutor` orchestrates command execution
|
|
128
|
+
- **Commands:** `help`, `index`, `plan`, `download`, `transform`
|
|
129
|
+
|
|
130
|
+
#### 2. Four-Phase Export Workflow
|
|
131
|
+
Separates concerns for resumability and transparency:
|
|
132
|
+
- **Phase 1 (Index):** Scan space → `_index.yaml` with metadata
|
|
133
|
+
- **Phase 2 (Plan):** Create `_queue.yaml` from index or page tree
|
|
134
|
+
- **Phase 3 (Download):** Process queue → download HTML files only
|
|
135
|
+
- **Phase 4 (Transform):** Convert HTML to Markdown (checks for existing MD files)
|
|
136
|
+
|
|
137
|
+
#### 3. Async Generators
|
|
138
|
+
Memory-efficient pagination in `api.getAllPages()`:
|
|
139
|
+
- Yields pages one at a time
|
|
140
|
+
- Avoids loading entire space into memory
|
|
141
|
+
- Enables progress tracking
|
|
142
|
+
|
|
143
|
+
#### 4. Smart Caching
|
|
144
|
+
Optimizes API calls:
|
|
145
|
+
- User lookups cached in `Map<string, User>`
|
|
146
|
+
- Prevents duplicate API requests
|
|
147
|
+
- Reduces export time
|
|
148
|
+
|
|
149
|
+
#### 5. Error Handling Strategy
|
|
150
|
+
- **Non-fatal:** Logged as warnings (e.g., failed image downloads)
|
|
151
|
+
- **Fatal:** Throw error and exit with code 1 (e.g., API auth failure)
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Type System
|
|
156
|
+
|
|
157
|
+
### Configuration
|
|
158
|
+
```typescript
|
|
159
|
+
interface ConfluenceConfig {
|
|
160
|
+
baseUrl: string; // Confluence instance URL
|
|
161
|
+
username: string; // Email/username
|
|
162
|
+
password: string; // API token
|
|
163
|
+
spaceKey: string; // Space identifier
|
|
164
|
+
outputDir: string; // Export destination
|
|
165
|
+
pageId?: string; // Optional: single page export
|
|
166
|
+
pageSize?: number; // Optional: pagination size (default: 25)
|
|
167
|
+
limit?: number; // Optional: limit number of pages to process
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Core Domain
|
|
172
|
+
```typescript
|
|
173
|
+
interface Page {
|
|
174
|
+
id: string;
|
|
175
|
+
title: string;
|
|
176
|
+
body: string; // HTML storage format
|
|
177
|
+
version?: number;
|
|
178
|
+
parentId?: string;
|
|
179
|
+
modifiedDate?: string;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
interface PageIndexEntry {
|
|
183
|
+
id: string;
|
|
184
|
+
title: string;
|
|
185
|
+
version?: number;
|
|
186
|
+
parentId?: string;
|
|
187
|
+
modifiedDate?: string;
|
|
188
|
+
indexedDate: string; // When indexed
|
|
189
|
+
pageNumber: number; // API page number
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
interface PageTreeNode {
|
|
193
|
+
id: string;
|
|
194
|
+
title: string;
|
|
195
|
+
version?: number;
|
|
196
|
+
parentId?: string;
|
|
197
|
+
modifiedDate?: string;
|
|
198
|
+
children?: PageTreeNode[]; // Hierarchical structure
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Transformation
|
|
203
|
+
```typescript
|
|
204
|
+
interface MarkdownResult {
|
|
205
|
+
content: string; // Markdown body
|
|
206
|
+
frontMatter: { // YAML front matter
|
|
207
|
+
title: string;
|
|
208
|
+
id: string;
|
|
209
|
+
version?: number;
|
|
210
|
+
parentId?: string;
|
|
211
|
+
};
|
|
212
|
+
images: Array<{ // Downloaded images
|
|
213
|
+
filename: string;
|
|
214
|
+
data: Buffer;
|
|
215
|
+
}>;
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## API Client (api.ts)
|
|
222
|
+
|
|
223
|
+
### Authentication
|
|
224
|
+
- Uses HTTP Basic Auth with base64-encoded credentials
|
|
225
|
+
- Token stored in `authHeader` property
|
|
226
|
+
|
|
227
|
+
### Key Methods
|
|
228
|
+
|
|
229
|
+
#### `getPage(pageId: string): Promise<Page>`
|
|
230
|
+
Fetches a single page with full content.
|
|
231
|
+
- Expands: `body.storage`, `version`, `history.lastUpdated`
|
|
232
|
+
- Returns: Normalized `Page` object
|
|
233
|
+
|
|
234
|
+
#### `listPages(spaceKey, start, limit): Promise<PaginatedResponse<Page>>`
|
|
235
|
+
Fetches paginated page list from a space.
|
|
236
|
+
- Includes body content (use for small exports only)
|
|
237
|
+
- Returns: Results + pagination metadata
|
|
238
|
+
|
|
239
|
+
#### `getAllPages(spaceKey, pageSize, startFrom): AsyncGenerator<Page & {apiPageNumber}>`
|
|
240
|
+
Memory-efficient async generator for all pages.
|
|
241
|
+
- Handles pagination automatically
|
|
242
|
+
- Yields pages one at a time
|
|
243
|
+
- Tracks API page number for logging
|
|
244
|
+
- **Resume support:** `startFrom` parameter allows starting from a specific position (efficient resume without fetching already-indexed pages)
|
|
245
|
+
|
|
246
|
+
#### `getChildPages(pageId): Promise<Page[]>`
|
|
247
|
+
Fetches child pages for hierarchy resolution.
|
|
248
|
+
- Used by `list-children` macro transformation
|
|
249
|
+
- Returns page IDs and titles only (no body)
|
|
250
|
+
|
|
251
|
+
#### `downloadAttachment(pageId, filename): Promise<Buffer | null>`
|
|
252
|
+
Downloads binary attachment data.
|
|
253
|
+
- Returns null on failure (non-fatal)
|
|
254
|
+
- Used for image downloads
|
|
255
|
+
|
|
256
|
+
#### `getUserByUsername(username): Promise<User | null>`
|
|
257
|
+
#### `getUserByKey(userKey): Promise<User | null>`
|
|
258
|
+
Resolve user links to display names.
|
|
259
|
+
- Results cached in `userCache` Map
|
|
260
|
+
- Returns null on failure (falls back to username)
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Commands (src/commands/)
|
|
265
|
+
|
|
266
|
+
The application uses a modular command architecture where each command is self-contained.
|
|
267
|
+
|
|
268
|
+
### Command Reference
|
|
269
|
+
|
|
270
|
+
| Command | Purpose | Output | Resume Support |
|
|
271
|
+
|---------|---------|--------|----------------|
|
|
272
|
+
| `help` | Display usage information | Console output | N/A |
|
|
273
|
+
| `index` | Create page inventory | `_index.yaml` | ✅ Yes |
|
|
274
|
+
| `plan` | Create download queue and tree | `_queue.yaml` + `_tree.yaml` | ❌ No |
|
|
275
|
+
| `download` | Download HTML pages | `.html` files | ❌ No |
|
|
276
|
+
| `transform` | Transform HTML to Markdown | `.md` files + images | ✅ Yes (skips existing) |
|
|
277
|
+
|
|
278
|
+
### Command Handlers
|
|
279
|
+
|
|
280
|
+
#### HelpCommand (`help.command.ts`)
|
|
281
|
+
```bash
|
|
282
|
+
npm run dev -- help
|
|
283
|
+
```
|
|
284
|
+
Displays usage information, options, and examples.
|
|
285
|
+
|
|
286
|
+
#### IndexCommand (`index.command.ts`)
|
|
287
|
+
```bash
|
|
288
|
+
npm run dev -- index -u URL -n USER -p TOKEN -s SPACE -o ./output
|
|
289
|
+
|
|
290
|
+
# With limit (index only first 10 pages)
|
|
291
|
+
npm run dev -- index -u URL -n USER -p TOKEN -s SPACE -o ./output -l 10
|
|
292
|
+
```
|
|
293
|
+
**Purpose:** Create complete page inventory (Phase 1)
|
|
294
|
+
|
|
295
|
+
**Behavior:**
|
|
296
|
+
- Creates output directory if missing
|
|
297
|
+
- Streams all pages via `api.getAllPages()` (memory-efficient)
|
|
298
|
+
- Appends each page to `_index.yaml` as YAML array entry
|
|
299
|
+
- **Resume:** Automatically resumes from where it left off by calculating the start position from existing pages (does NOT re-fetch already-indexed pages)
|
|
300
|
+
- **Limit:** If `--limit` is specified, stops after indexing that many pages
|
|
301
|
+
- **Logging:** `[N] Indexed: Title (ID) [API Page N]`
|
|
302
|
+
|
|
303
|
+
**Output:** `_index.yaml` with metadata for all pages in space
|
|
304
|
+
|
|
305
|
+
#### PlanCommand (`plan.command.ts`)
|
|
306
|
+
```bash
|
|
307
|
+
# Plan entire space (from index)
|
|
308
|
+
npm run dev -- plan -u URL -n USER -p TOKEN -s SPACE -o ./output
|
|
309
|
+
|
|
310
|
+
# Plan specific page tree
|
|
311
|
+
npm run dev -- plan -i PAGE_ID -u URL -n USER -p TOKEN -s SPACE -o ./output
|
|
312
|
+
|
|
313
|
+
# Plan with limit (first 10 pages from index)
|
|
314
|
+
npm run dev -- plan -u URL -n USER -p TOKEN -s SPACE -o ./output -l 10
|
|
315
|
+
```
|
|
316
|
+
**Purpose:** Create download queue and tree structure (Phase 2)
|
|
317
|
+
|
|
318
|
+
**Behavior:**
|
|
319
|
+
- **Mode A (No pageId):** Reads `_index.yaml` → creates `_queue.yaml` (flat) + `_tree.yaml` (hierarchical)
|
|
320
|
+
- **Mode B (With pageId):** Fetches page tree recursively → creates `_queue.yaml` + `_tree.yaml`
|
|
321
|
+
- Uses `collectPageTree()` for recursive hierarchy traversal
|
|
322
|
+
- Uses `buildTreeFromIndex()` to construct tree from flat index
|
|
323
|
+
- **Limit:** If `--limit` is specified, only includes first N pages in queue
|
|
324
|
+
- **Logging:** `[N] Found: Title (ID)` (indented for hierarchy)
|
|
325
|
+
|
|
326
|
+
**Output:**
|
|
327
|
+
- `_queue.yaml` - Flat list of pages to download (depth-first order)
|
|
328
|
+
- `_tree.yaml` - Hierarchical tree structure showing parent-child relationships
|
|
329
|
+
|
|
330
|
+
#### DownloadCommand (`download.command.ts`)
|
|
331
|
+
```bash
|
|
332
|
+
# Download from queue (hierarchical structure if _tree.yaml exists)
|
|
333
|
+
npm run dev -- download -u URL -n USER -p TOKEN -s SPACE -o ./output
|
|
334
|
+
|
|
335
|
+
# Download single page directly
|
|
336
|
+
npm run dev -- download -i PAGE_ID -u URL -n USER -p TOKEN -s SPACE -o ./output
|
|
337
|
+
```
|
|
338
|
+
**Purpose:** Download HTML pages (Phase 3)
|
|
339
|
+
|
|
340
|
+
**Behavior:**
|
|
341
|
+
- **Mode A (No pageId):**
|
|
342
|
+
- Checks for `_tree.yaml` first (preferred for hierarchical structure)
|
|
343
|
+
- If `_tree.yaml` exists:
|
|
344
|
+
- Creates root folder: `{outputDir}/{spaceKey}/`
|
|
345
|
+
- Recursively downloads pages following tree hierarchy
|
|
346
|
+
- For pages with children: creates both HTML file AND folder with same name
|
|
347
|
+
- Children are downloaded into parent's folder
|
|
348
|
+
- If `_tree.yaml` not found:
|
|
349
|
+
- Falls back to `_queue.yaml` (flat structure)
|
|
350
|
+
- Downloads all to `outputDir` (throws error if missing)
|
|
351
|
+
- **Mode B (With pageId):** Downloads single page directly (no queue needed)
|
|
352
|
+
- For each page:
|
|
353
|
+
1. Fetch via `api.getPage(id)`
|
|
354
|
+
2. Format HTML with Prettier
|
|
355
|
+
3. Save `.html` file in appropriate directory
|
|
356
|
+
4. If page has children (in tree mode), create folder for children
|
|
357
|
+
- **Limit:** If `--limit` is specified, only downloads first N pages from queue
|
|
358
|
+
- **Logging:** `[N] Downloading: Title (ID)` (indented for hierarchy in tree mode)
|
|
359
|
+
|
|
360
|
+
**Output:** HTML files in hierarchical folder structure (if `_tree.yaml` exists) or flat structure
|
|
361
|
+
|
|
362
|
+
#### TransformCommand (`transform.command.ts`)
|
|
363
|
+
```bash
|
|
364
|
+
# Transform all HTML files recursively in output directory
|
|
365
|
+
npm run dev -- transform -u URL -n USER -p TOKEN -s SPACE -o ./output
|
|
366
|
+
```
|
|
367
|
+
**Purpose:** Transform HTML to Markdown (Phase 4)
|
|
368
|
+
|
|
369
|
+
**Behavior:**
|
|
370
|
+
- Recursively scans output directory for `.html` files (walks folder tree)
|
|
371
|
+
- Skips `_*` files and `images` folders during traversal
|
|
372
|
+
- For each HTML file:
|
|
373
|
+
1. Check if corresponding `.md` file exists in same directory
|
|
374
|
+
2. If `.md` exists, skip (no overwrite)
|
|
375
|
+
3. If `.md` missing, transform HTML to Markdown
|
|
376
|
+
4. Download images to `images/` subdirectory (relative to page)
|
|
377
|
+
5. Apply cleaner and format with Prettier
|
|
378
|
+
6. Save `.md` file in same directory as HTML
|
|
379
|
+
- **Limit:** If `--limit` is specified, only processes first N HTML files
|
|
380
|
+
- **Logging:** `[N/Total] Checking: relative/path/to/file.html`
|
|
381
|
+
- **Smart Skip:** Only transforms pages missing Markdown files
|
|
382
|
+
|
|
383
|
+
**Output:** Markdown files and downloaded images in hierarchical structure (skips existing MD files)
|
|
384
|
+
|
|
385
|
+
### Export Modes
|
|
386
|
+
|
|
387
|
+
#### Mode 1: Single Page Export
|
|
388
|
+
When `config.pageId` is set (via `download` command):
|
|
389
|
+
1. Fetch single page via `api.getPage(pageId)`
|
|
390
|
+
2. Save .html file
|
|
391
|
+
3. Run `transform` command separately to generate .md
|
|
392
|
+
|
|
393
|
+
#### Mode 2: Full Space Export
|
|
394
|
+
When `config.pageId` is undefined (via `index`, `plan`, `download`, and `transform` commands):
|
|
395
|
+
|
|
396
|
+
**Phase 1: Create Index** (`IndexCommand`)
|
|
397
|
+
1. Check if `_index.yaml` exists (resume if found)
|
|
398
|
+
2. Stream pages via `api.getAllPages()`
|
|
399
|
+
3. Append each page as YAML array entry
|
|
400
|
+
4. Log: `[N] Indexed: Title (ID) [API Page N]`
|
|
401
|
+
|
|
402
|
+
**Phase 2: Create Queue** (`PlanCommand`)
|
|
403
|
+
1. Option A: From _index.yaml (no pageId)
|
|
404
|
+
- Read `_index.yaml`
|
|
405
|
+
- Copy to `_queue.yaml`
|
|
406
|
+
2. Option B: From specific page tree (with pageId)
|
|
407
|
+
- Fetch page via `api.getPage(pageId)`
|
|
408
|
+
- Recursively fetch all children via `api.getChildPages()`
|
|
409
|
+
- Write to `_queue.yaml`
|
|
410
|
+
|
|
411
|
+
**Phase 3: Download Pages** (`DownloadCommand`)
|
|
412
|
+
1. Check for `_tree.yaml` (preferred) or `_queue.yaml` (fallback)
|
|
413
|
+
2. If `_tree.yaml` exists:
|
|
414
|
+
- Create root folder: `{outputDir}/{spaceKey}/`
|
|
415
|
+
- Recursively process tree structure:
|
|
416
|
+
- Download page via `api.getPage(id)` to current directory
|
|
417
|
+
- Format with Prettier
|
|
418
|
+
- Save `.html` file
|
|
419
|
+
- If page has children, create folder `{pageId}-{slug}/` and recurse
|
|
420
|
+
3. If only `_queue.yaml` exists:
|
|
421
|
+
- Download pages to flat structure in `outputDir`
|
|
422
|
+
- For each entry:
|
|
423
|
+
- Fetch full page via `api.getPage(id)`
|
|
424
|
+
- Format with Prettier
|
|
425
|
+
- Save .html file
|
|
426
|
+
4. Log: `[N] Downloading: Title (ID)` (indented for hierarchy)
|
|
427
|
+
|
|
428
|
+
**Phase 4: Transform Pages** (`TransformCommand`)
|
|
429
|
+
1. Recursively scan output directory for `.html` files
|
|
430
|
+
2. Skip `_*` files and `images` folders
|
|
431
|
+
3. For each HTML file:
|
|
432
|
+
- Check if `.md` exists in same directory (skip if found)
|
|
433
|
+
- Transform HTML to markdown
|
|
434
|
+
- Download images to `images/` folder (relative to page)
|
|
435
|
+
- Apply cleaner
|
|
436
|
+
- Format with Prettier
|
|
437
|
+
- Save `.md` file in same directory
|
|
438
|
+
4. Log: `[N/Total] Checking: relative/path/file.html`
|
|
439
|
+
|
|
440
|
+
### Common Workflows
|
|
441
|
+
|
|
442
|
+
```bash
|
|
443
|
+
# Workflow 1: Full space export (all phases)
|
|
444
|
+
npm run dev -- index plan download transform -u URL -n USER -p TOKEN -s SPACE -o ./output
|
|
445
|
+
|
|
446
|
+
# Workflow 2: Resume from existing index
|
|
447
|
+
npm run dev -- plan download transform -u URL -n USER -p TOKEN -s SPACE -o ./output
|
|
448
|
+
|
|
449
|
+
# Workflow 3: Export specific page and children
|
|
450
|
+
npm run dev -- plan download transform -i PAGE_ID -u URL -n USER -p TOKEN -s SPACE -o ./output
|
|
451
|
+
|
|
452
|
+
# Workflow 4: Single page only (fastest)
|
|
453
|
+
npm run dev -- download -i PAGE_ID -u URL -n USER -p TOKEN -s SPACE -o ./output
|
|
454
|
+
|
|
455
|
+
# Workflow 5: Re-index space (update metadata)
|
|
456
|
+
rm ./output/_index.yaml
|
|
457
|
+
npm run dev -- index -u URL -n USER -p TOKEN -s SPACE -o ./output
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### File Structure
|
|
461
|
+
|
|
462
|
+
**Hierarchical Structure (when `_tree.yaml` exists):**
|
|
463
|
+
```
|
|
464
|
+
outputDir/
|
|
465
|
+
├── _index.yaml # Page index (YAML array)
|
|
466
|
+
├── _queue.yaml # Download queue (YAML array)
|
|
467
|
+
├── _tree.yaml # Hierarchical page tree structure
|
|
468
|
+
└── PR000299/ # Root folder (space key)
|
|
469
|
+
├── 95956404-fcs-fidelity-charitable.html
|
|
470
|
+
├── 95956404-fcs-fidelity-charitable.md
|
|
471
|
+
└── 95956404-fcs-fidelity-charitable/ # Folder for children
|
|
472
|
+
├── images/ # Images for child pages
|
|
473
|
+
│ └── logo.png
|
|
474
|
+
├── 115361447-agile-and-resource-center.html
|
|
475
|
+
├── 115361447-agile-and-resource-center.md
|
|
476
|
+
└── 115361447-agile-and-resource-center/ # Nested children
|
|
477
|
+
├── 95956405-getting-started.html
|
|
478
|
+
├── 95956405-getting-started.md
|
|
479
|
+
└── 95956405-getting-started/
|
|
480
|
+
├── 95956406-making-a-template.html
|
|
481
|
+
└── 95956406-making-a-template.md
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
**Flat Structure (fallback when only `_queue.yaml` exists):**
|
|
485
|
+
```
|
|
486
|
+
outputDir/
|
|
487
|
+
├── _index.yaml # Page index (YAML array)
|
|
488
|
+
├── _queue.yaml # Download queue (YAML array)
|
|
489
|
+
├── page-title-1.md # Formatted markdown
|
|
490
|
+
├── page-title-1.html # Original HTML (formatted)
|
|
491
|
+
├── page-title-2.md
|
|
492
|
+
├── page-title-2.html
|
|
493
|
+
└── images/ # Shared images folder
|
|
494
|
+
├── image-1.png
|
|
495
|
+
└── image-2.jpg
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### Filename Slugification
|
|
499
|
+
```typescript
|
|
500
|
+
slugify(text: string): string {
|
|
501
|
+
return text
|
|
502
|
+
.toLowerCase()
|
|
503
|
+
.replace(/[^\w\s-]/g, '') // Remove special chars
|
|
504
|
+
.replace(/\s+/g, '-') // Spaces → hyphens
|
|
505
|
+
.replace(/-+/g, '-') // Collapse hyphens
|
|
506
|
+
.trim();
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### Front Matter Format
|
|
511
|
+
```yaml
|
|
512
|
+
---
|
|
513
|
+
title: "Page Title"
|
|
514
|
+
id: "123456789"
|
|
515
|
+
url: "https://site.atlassian.net/pages/viewpage.action?pageId=123456789"
|
|
516
|
+
version: 5
|
|
517
|
+
parentId: "987654321"
|
|
518
|
+
---
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
### Prettier Formatting
|
|
522
|
+
|
|
523
|
+
**Markdown:**
|
|
524
|
+
- `printWidth: 120`
|
|
525
|
+
- `proseWrap: 'preserve'` (don't reflow text)
|
|
526
|
+
- `tabWidth: 2`
|
|
527
|
+
|
|
528
|
+
**HTML:**
|
|
529
|
+
- `printWidth: 120`
|
|
530
|
+
- `htmlWhitespaceSensitivity: 'ignore'`
|
|
531
|
+
- Consistent 2-space indentation
|
|
532
|
+
|
|
533
|
+
Formatting failures are non-fatal (saves unformatted with warning).
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
## CLI (index.ts)
|
|
538
|
+
|
|
539
|
+
### Command Structure
|
|
540
|
+
The CLI uses a command-based architecture with five commands:
|
|
541
|
+
|
|
542
|
+
| Command | Purpose | Required Files | Output |
|
|
543
|
+
|---------|---------|----------------|--------|
|
|
544
|
+
| `help` | Display help | None | Console |
|
|
545
|
+
| `index` | Scan space pages | None | `_index.yaml` |
|
|
546
|
+
| `plan` | Create download queue | `_index.yaml` (if no pageId) | `_queue.yaml` |
|
|
547
|
+
| `download` | Download HTML pages | `_queue.yaml` (if no pageId) | `.html` files |
|
|
548
|
+
| `transform` | Transform HTML to MD | `.html` files | `.md` files + images |
|
|
549
|
+
|
|
550
|
+
**Chaining:** Commands can be chained to run in sequence:
|
|
551
|
+
```bash
|
|
552
|
+
npm run dev -- index plan download transform -u URL -n USER -p TOKEN -s SPACE
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
### Argument Parsing
|
|
556
|
+
Uses `minimist` for flexible CLI arguments.
|
|
557
|
+
|
|
558
|
+
```bash
|
|
559
|
+
# Full sync workflow (no commands provided)
|
|
560
|
+
node index.js -u https://site.atlassian.net \
|
|
561
|
+
-n user@example.com \
|
|
562
|
+
-p token123 \
|
|
563
|
+
-s MYSPACE \
|
|
564
|
+
-o ./export
|
|
565
|
+
|
|
566
|
+
# Show help
|
|
567
|
+
node index.js help
|
|
568
|
+
node index.js --help
|
|
569
|
+
|
|
570
|
+
# Create index only (Phase 1)
|
|
571
|
+
node index.js index -u https://site.atlassian.net \
|
|
572
|
+
-n user@example.com \
|
|
573
|
+
-p token123 \
|
|
574
|
+
-s MYSPACE \
|
|
575
|
+
-o ./export
|
|
576
|
+
|
|
577
|
+
# Create queue from existing index
|
|
578
|
+
node index.js plan -u https://site.atlassian.net \
|
|
579
|
+
-n user@example.com \
|
|
580
|
+
-p token123 \
|
|
581
|
+
-s MYSPACE \
|
|
582
|
+
-o ./export
|
|
583
|
+
|
|
584
|
+
# Create queue for specific page and all children
|
|
585
|
+
node index.js plan -i 123456789 -u https://site.atlassian.net \
|
|
586
|
+
-n user@example.com \
|
|
587
|
+
-p token123 \
|
|
588
|
+
-s MYSPACE \
|
|
589
|
+
-o ./export
|
|
590
|
+
|
|
591
|
+
# Download from existing queue or index (Phase 2)
|
|
592
|
+
node index.js download -u https://site.atlassian.net \
|
|
593
|
+
-n user@example.com \
|
|
594
|
+
-p token123 \
|
|
595
|
+
-s MYSPACE \
|
|
596
|
+
-o ./export
|
|
597
|
+
|
|
598
|
+
# Run both phases in sequence
|
|
599
|
+
node index.js index download -u URL -n USER -p PASS -s SPACE
|
|
600
|
+
|
|
601
|
+
# Download single page (no index/queue needed)
|
|
602
|
+
node index.js download -i 123456789 -u URL -n USER -p PASS -s SPACE
|
|
603
|
+
|
|
604
|
+
# Short flags
|
|
605
|
+
node index.js download -u URL -n USER -p PASS -s SPACE -o DIR -i ID
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### Command Behavior
|
|
609
|
+
|
|
610
|
+
1. **No commands provided**: Runs full sync workflow (update index if exists or create new, then plan, download, transform)
|
|
611
|
+
2. **Invalid command**: Shows error + help and exits with code 1
|
|
612
|
+
3. **Multiple commands**: Executes in sequence with visual separators
|
|
613
|
+
4. **`help` command**: Shows help and exits (other commands ignored)
|
|
614
|
+
5. **`plan` with pageId**: Creates _queue.yaml with specified page and all children
|
|
615
|
+
6. **`plan` without pageId**: Creates _queue.yaml from existing _index.yaml
|
|
616
|
+
7. **`download`**: Requires _queue.yaml to exist (errors if missing with instruction to run plan)
|
|
617
|
+
8. **`transform`**: Scans for .html files and creates .md files (skips if .md exists)
|
|
618
|
+
|
|
619
|
+
### Options
|
|
620
|
+
|
|
621
|
+
| Flag | Long Form | Description | Default |
|
|
622
|
+
|------|-----------|-------------|---------|
|
|
623
|
+
| `-u` | `--url` | Confluence base URL | env: `CONFLUENCE_BASE_URL` |
|
|
624
|
+
| `-n` | `--username` | Username/email | env: `CONFLUENCE_USERNAME` |
|
|
625
|
+
| `-p` | `--password` | API token | env: `CONFLUENCE_PASSWORD` |
|
|
626
|
+
| `-s` | `--space` | Space key | env: `CONFLUENCE_SPACE_KEY` |
|
|
627
|
+
| `-o` | `--output` | Output directory | `./output` or env: `OUTPUT_DIR` |
|
|
628
|
+
| `-i` | `--pageId` | Single page ID (optional) | none |
|
|
629
|
+
| `-l` | `--limit` | Limit number of pages to process | none |
|
|
630
|
+
| | `--pageSize` | API page size | `25` |
|
|
631
|
+
| `-h` | `--help` | Show help | |
|
|
632
|
+
|
|
633
|
+
### Environment Variables
|
|
634
|
+
Fallback if CLI args not provided:
|
|
635
|
+
- `CONFLUENCE_BASE_URL`
|
|
636
|
+
- `CONFLUENCE_USERNAME`
|
|
637
|
+
- `CONFLUENCE_PASSWORD`
|
|
638
|
+
- `CONFLUENCE_SPACE_KEY`
|
|
639
|
+
- `OUTPUT_DIR`
|
|
640
|
+
|
|
641
|
+
### Validation
|
|
642
|
+
Required fields (for `index`, `download`, and `transform` commands):
|
|
643
|
+
- `baseUrl`
|
|
644
|
+
- `username`
|
|
645
|
+
- `password`
|
|
646
|
+
- `spaceKey`
|
|
647
|
+
|
|
648
|
+
Exits with code 1 and error message if missing.
|
|
649
|
+
|
|
650
|
+
### Help Text
|
|
651
|
+
```bash
|
|
652
|
+
node index.js
|
|
653
|
+
node index.js help
|
|
654
|
+
node index.js --help
|
|
655
|
+
```
|
|
656
|
+
Shows usage, commands, options, environment variables, and examples.
|
|
657
|
+
|
|
658
|
+
---
|
|
659
|
+
|
|
660
|
+
## Development Workflow
|
|
661
|
+
|
|
662
|
+
### Build & Run
|
|
663
|
+
|
|
664
|
+
```bash
|
|
665
|
+
# Build TypeScript
|
|
666
|
+
npm run build # Uses Vite
|
|
667
|
+
npm run build:tsc # Uses tsc directly
|
|
668
|
+
|
|
669
|
+
# Run compiled
|
|
670
|
+
npm start -- [args]
|
|
671
|
+
|
|
672
|
+
# Development mode
|
|
673
|
+
npm run dev -- [args] # Run once
|
|
674
|
+
npm run dev:watch -- [args] # Watch mode
|
|
675
|
+
```
|
|
676
|
+
|
|
677
|
+
### Testing
|
|
678
|
+
|
|
679
|
+
```bash
|
|
680
|
+
npm test # Run all tests
|
|
681
|
+
npm run test:watch # Watch mode
|
|
682
|
+
npm run test:coverage # With coverage
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
### Linting & Type Checking
|
|
686
|
+
|
|
687
|
+
```bash
|
|
688
|
+
npm run lint # ESLint
|
|
689
|
+
npm run typecheck # TypeScript --noEmit
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
### Cleaning
|
|
693
|
+
|
|
694
|
+
```bash
|
|
695
|
+
npm run clean # Remove dist/
|
|
696
|
+
npm run rebuild # Clean + build
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
---
|
|
700
|
+
|
|
701
|
+
## Coding Conventions
|
|
702
|
+
|
|
703
|
+
### TypeScript Style
|
|
704
|
+
|
|
705
|
+
1. **Explicit Types**
|
|
706
|
+
- Always type function parameters
|
|
707
|
+
- Always type function return values
|
|
708
|
+
- Use interfaces for objects, type aliases for unions
|
|
709
|
+
|
|
710
|
+
2. **Imports**
|
|
711
|
+
- Use `.js` extension in imports (ES modules)
|
|
712
|
+
- Import types with `import type {}`
|
|
713
|
+
- Order: types, then classes, then functions
|
|
714
|
+
|
|
715
|
+
3. **Async/Await**
|
|
716
|
+
- Prefer async/await over Promises
|
|
717
|
+
- Use `for await...of` for async generators
|
|
718
|
+
- Handle errors with try/catch
|
|
719
|
+
|
|
720
|
+
4. **Error Handling**
|
|
721
|
+
- Throw `Error` objects with descriptive messages
|
|
722
|
+
- Log warnings to console for non-fatal errors
|
|
723
|
+
- Return null for optional operations that fail
|
|
724
|
+
|
|
725
|
+
5. **Naming**
|
|
726
|
+
- Classes: PascalCase (e.g., `ConfluenceApi`)
|
|
727
|
+
- Functions/methods: camelCase (e.g., `getPage`)
|
|
728
|
+
- Constants: UPPER_SNAKE_CASE (e.g., `DEFAULT_PAGE_SIZE`)
|
|
729
|
+
- Interfaces: PascalCase (e.g., `ConfluenceConfig`)
|
|
730
|
+
|
|
731
|
+
### Documentation
|
|
732
|
+
|
|
733
|
+
```typescript
|
|
734
|
+
/**
|
|
735
|
+
* Brief description of function/class
|
|
736
|
+
*
|
|
737
|
+
* Detailed explanation if needed.
|
|
738
|
+
*
|
|
739
|
+
* @param paramName - Description
|
|
740
|
+
* @returns Description
|
|
741
|
+
* @throws ErrorType - When error occurs
|
|
742
|
+
*/
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
### Diagrams
|
|
746
|
+
|
|
747
|
+
**Using Mermaid:**
|
|
748
|
+
When documenting architecture, data flows, or complex relationships, use Mermaid diagrams in markdown files. Mermaid is supported by GitHub, VS Code, and many markdown viewers.
|
|
749
|
+
|
|
750
|
+
**Supported Diagram Types:**
|
|
751
|
+
- `flowchart` / `graph` - Flow diagrams and directed graphs
|
|
752
|
+
- `sequenceDiagram` - Sequence/interaction diagrams
|
|
753
|
+
- `classDiagram` - Class relationships
|
|
754
|
+
- `stateDiagram` - State machines
|
|
755
|
+
- `erDiagram` - Entity-relationship diagrams
|
|
756
|
+
- `gantt` - Gantt charts for timelines
|
|
757
|
+
- `architecture-beta` - Architecture diagrams (experimental)
|
|
758
|
+
|
|
759
|
+
**Example - Architecture Diagram:**
|
|
760
|
+
```mermaid
|
|
761
|
+
architecture-beta
|
|
762
|
+
service user(mdi:account)[User]
|
|
763
|
+
service cli(logos:terminal)[CLI]
|
|
764
|
+
service api(logos:aws-lambda)[Confluence API]
|
|
765
|
+
service transformer(mdi:transform)[Transformer]
|
|
766
|
+
service files(mdi:file-document)[Output Files]
|
|
767
|
+
|
|
768
|
+
user:R --> L:cli
|
|
769
|
+
cli:R --> L:api
|
|
770
|
+
cli:R --> L:transformer
|
|
771
|
+
transformer:R --> L:files
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
**Example - Flow Diagram:**
|
|
775
|
+
```mermaid
|
|
776
|
+
flowchart TD
|
|
777
|
+
A[CLI Args] --> B{Validate Config}
|
|
778
|
+
B -->|Valid| C[Command Executor]
|
|
779
|
+
B -->|Invalid| D[Show Error]
|
|
780
|
+
C --> E[Index Command]
|
|
781
|
+
C --> F[Plan Command]
|
|
782
|
+
C --> G[Download Command]
|
|
783
|
+
E --> H[_index.yaml]
|
|
784
|
+
F --> I[_queue.yaml]
|
|
785
|
+
G --> J[.md/.html files]
|
|
786
|
+
```
|
|
787
|
+
|
|
788
|
+
**Example - Sequence Diagram:**
|
|
789
|
+
```mermaid
|
|
790
|
+
sequenceDiagram
|
|
791
|
+
participant User
|
|
792
|
+
participant CLI
|
|
793
|
+
participant API
|
|
794
|
+
participant Transformer
|
|
795
|
+
|
|
796
|
+
User->>CLI: Run download command
|
|
797
|
+
CLI->>API: getPage(id)
|
|
798
|
+
API-->>CLI: HTML content
|
|
799
|
+
CLI->>Transformer: transform(html)
|
|
800
|
+
Transformer->>API: downloadAttachment(image)
|
|
801
|
+
API-->>Transformer: image data
|
|
802
|
+
Transformer-->>CLI: Markdown + images
|
|
803
|
+
CLI->>User: Save files
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
**Best Practices:**
|
|
807
|
+
1. Use diagrams to complement text documentation, not replace it
|
|
808
|
+
2. Keep diagrams simple and focused on one concept
|
|
809
|
+
3. Add descriptive labels to nodes and edges
|
|
810
|
+
4. Place diagrams near the relevant text explanation
|
|
811
|
+
5. Use consistent naming with code (class names, method names)
|
|
812
|
+
6. Test diagram rendering in your markdown viewer
|
|
813
|
+
|
|
814
|
+
### File Organization
|
|
815
|
+
|
|
816
|
+
```typescript
|
|
817
|
+
// 1. Imports
|
|
818
|
+
import type { TypeA, TypeB } from './types.js';
|
|
819
|
+
import { ClassA } from './class-a.js';
|
|
820
|
+
|
|
821
|
+
// 2. Constants
|
|
822
|
+
const DEFAULT_VALUE = 42;
|
|
823
|
+
|
|
824
|
+
// 3. Interfaces (if not in types.ts)
|
|
825
|
+
interface LocalType { ... }
|
|
826
|
+
|
|
827
|
+
// 4. Class/function implementation
|
|
828
|
+
export class MyClass { ... }
|
|
829
|
+
|
|
830
|
+
// 5. Helper functions (private)
|
|
831
|
+
function helperFunction() { ... }
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
---
|
|
835
|
+
|
|
836
|
+
## Common Tasks
|
|
837
|
+
|
|
838
|
+
### Adding a New Macro Transformation
|
|
839
|
+
|
|
840
|
+
1. Identify macro pattern in Confluence HTML:
|
|
841
|
+
```html
|
|
842
|
+
<ac:structured-macro ac:name="macro-name">
|
|
843
|
+
<!-- content -->
|
|
844
|
+
</ac:structured-macro>
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
2. Add regex replacement in `transformer.ts` → `transformMacros()`:
|
|
848
|
+
```typescript
|
|
849
|
+
result = result.replace(
|
|
850
|
+
/<ac:structured-macro[^>]*ac:name="macro-name"[^>]*>(.*?)<\/ac:structured-macro>/gis,
|
|
851
|
+
'<!-- Macro: macro-name -->\n$1\n'
|
|
852
|
+
);
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
3. Test with sample page containing macro
|
|
856
|
+
|
|
857
|
+
### Adding a New CLI Option
|
|
858
|
+
|
|
859
|
+
1. Add to `minimist` config in `index.ts`:
|
|
860
|
+
```typescript
|
|
861
|
+
const args = minimist(process.argv.slice(2), {
|
|
862
|
+
string: ['url', 'username', ..., 'newOption'],
|
|
863
|
+
alias: { o: 'newOption' }
|
|
864
|
+
});
|
|
865
|
+
```
|
|
866
|
+
|
|
867
|
+
2. Add to config building:
|
|
868
|
+
```typescript
|
|
869
|
+
const config: ConfluenceConfig = {
|
|
870
|
+
// ...
|
|
871
|
+
newOption: args.newOption || process.env.NEW_OPTION || 'default'
|
|
872
|
+
};
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
3. Update help text
|
|
876
|
+
4. Update type definition in `types.ts`
|
|
877
|
+
|
|
878
|
+
### Adding a New API Endpoint
|
|
879
|
+
|
|
880
|
+
1. Add response interface to `types.ts`:
|
|
881
|
+
```typescript
|
|
882
|
+
export interface NewEndpointResponse {
|
|
883
|
+
// fields
|
|
884
|
+
}
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
2. Add method to `api.ts`:
|
|
888
|
+
```typescript
|
|
889
|
+
async newEndpoint(param: string): Promise<Result> {
|
|
890
|
+
const url = `${this.baseUrl}/rest/api/endpoint/${param}`;
|
|
891
|
+
const response = await fetch(url, {
|
|
892
|
+
headers: {
|
|
893
|
+
'Authorization': this.authHeader,
|
|
894
|
+
'Accept': 'application/json'
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
// ... error handling, parsing
|
|
898
|
+
return data;
|
|
899
|
+
}
|
|
900
|
+
```
|
|
901
|
+
|
|
902
|
+
3. Call from transformer or runner as needed
|
|
903
|
+
|
|
904
|
+
### Adding a Cleanup Pattern
|
|
905
|
+
|
|
906
|
+
1. Add pattern to `cleaner.ts`:
|
|
907
|
+
```typescript
|
|
908
|
+
// In clean() or cleanConfluencePatterns()
|
|
909
|
+
cleaned = cleaned.replace(/pattern/g, 'replacement');
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
2. Add comment explaining what it fixes
|
|
913
|
+
3. Test with malformed markdown sample
|
|
914
|
+
|
|
915
|
+
---
|
|
916
|
+
|
|
917
|
+
## Testing Guidelines
|
|
918
|
+
|
|
919
|
+
### Test Coverage
|
|
920
|
+
|
|
921
|
+
The project has **test coverage** for core functionality and basic command structure:
|
|
922
|
+
|
|
923
|
+
**Command Tests** (`tests/commands/`):
|
|
924
|
+
- ✅ `help.command.test.ts` - Basic instantiation test
|
|
925
|
+
- ✅ `index.command.test.ts` - Basic instantiation test
|
|
926
|
+
- ✅ `plan.command.test.ts` - Basic instantiation test
|
|
927
|
+
- ✅ `download.command.test.ts` - Basic instantiation test
|
|
928
|
+
- ✅ `transform.command.test.ts` - Basic instantiation test
|
|
929
|
+
|
|
930
|
+
**Core Functionality Tests** (`tests/`):
|
|
931
|
+
- ✅ `transformer.test.ts` - HTML to Markdown transformation (7 tests)
|
|
932
|
+
- ✅ `cleaner.test.ts` - Markdown cleanup patterns (9 tests)
|
|
933
|
+
|
|
934
|
+
**Note:** Command tests are basic because commands create their own `ConfluenceApi` instances internally, which cannot be easily mocked with Jest + ESM modules. To enable full command testing, commands would need refactoring to use dependency injection.
|
|
935
|
+
|
|
936
|
+
### Test Architecture
|
|
937
|
+
|
|
938
|
+
Uses **manual mock classes** compatible with ESM:
|
|
939
|
+
|
|
940
|
+
```typescript
|
|
941
|
+
// Manual mock class for API (used in transformer tests)
|
|
942
|
+
class MockApi implements Partial<ConfluenceApi> {
|
|
943
|
+
async getPage(id: string): Promise<Page> {
|
|
944
|
+
return mockPageData;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// Console output capture
|
|
949
|
+
let consoleOutput: string[];
|
|
950
|
+
beforeEach(() => {
|
|
951
|
+
consoleOutput = [];
|
|
952
|
+
console.log = (...args: unknown[]) => {
|
|
953
|
+
consoleOutput.push(args.join(' '));
|
|
954
|
+
};
|
|
955
|
+
});
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
### Test Structure
|
|
959
|
+
|
|
960
|
+
All tests follow consistent AAA pattern:
|
|
961
|
+
|
|
962
|
+
```typescript
|
|
963
|
+
describe('CommandName', () => {
|
|
964
|
+
let command: CommandClass;
|
|
965
|
+
let mockContext: CommandContext;
|
|
966
|
+
|
|
967
|
+
beforeEach(() => {
|
|
968
|
+
// Arrange - Setup mocks and context
|
|
969
|
+
command = new CommandClass();
|
|
970
|
+
mockContext = { config: {...}, args: {} };
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
afterEach(() => {
|
|
974
|
+
// Cleanup - Remove temp files, restore console
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
describe('feature group', () => {
|
|
978
|
+
it('should do something specific', async () => {
|
|
979
|
+
// Arrange - Prepare test data
|
|
980
|
+
// Act - Execute the command
|
|
981
|
+
// Assert - Verify results
|
|
982
|
+
});
|
|
983
|
+
});
|
|
984
|
+
});
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
### Running Tests
|
|
988
|
+
|
|
989
|
+
```bash
|
|
990
|
+
# Run all tests
|
|
991
|
+
npm test
|
|
992
|
+
|
|
993
|
+
# Run tests in watch mode
|
|
994
|
+
npm run test:watch
|
|
995
|
+
|
|
996
|
+
# Run tests with coverage
|
|
997
|
+
npm run test:coverage
|
|
998
|
+
|
|
999
|
+
# Run specific test file
|
|
1000
|
+
npm test -- help.command.test.ts
|
|
1001
|
+
|
|
1002
|
+
# Run tests matching pattern
|
|
1003
|
+
npm test -- --testNamePattern="should transform"
|
|
1004
|
+
```
|
|
1005
|
+
|
|
1006
|
+
### Coverage Summary
|
|
1007
|
+
|
|
1008
|
+
| Component | Test File | Coverage |
|
|
1009
|
+
|-----------|-----------|----------|
|
|
1010
|
+
| Help Command | `help.command.test.ts` | ✅ Basic |
|
|
1011
|
+
| Index Command | `index.command.test.ts` | ✅ Basic |
|
|
1012
|
+
| Plan Command | `plan.command.test.ts` | ✅ Basic |
|
|
1013
|
+
| Download Command | `download.command.test.ts` | ✅ Basic |
|
|
1014
|
+
| Transform Command | `transform.command.test.ts` | ✅ Basic |
|
|
1015
|
+
| HTML→MD Transformer | `transformer.test.ts` | ✅ Complete (7 tests) |
|
|
1016
|
+
| Markdown Cleaner | `cleaner.test.ts` | ✅ Complete (9 tests) |
|
|
1017
|
+
|
|
1018
|
+
**Test Coverage Areas:**
|
|
1019
|
+
- ✅ Command instantiation
|
|
1020
|
+
- ✅ HTML to Markdown conversion
|
|
1021
|
+
- ✅ Confluence macro transformations
|
|
1022
|
+
- ✅ User link resolution
|
|
1023
|
+
- ✅ Markdown cleanup patterns
|
|
1024
|
+
- ⚠️ Full command execution workflows (requires DI refactoring)
|
|
1025
|
+
|
|
1026
|
+
See `tests/commands/README.md` for detailed test documentation.
|
|
1027
|
+
|
|
1028
|
+
### Improving Test Coverage
|
|
1029
|
+
|
|
1030
|
+
To enable full command testing:
|
|
1031
|
+
|
|
1032
|
+
1. **Refactor for Dependency Injection**
|
|
1033
|
+
```typescript
|
|
1034
|
+
// Instead of creating API internally:
|
|
1035
|
+
export class IndexCommand implements CommandHandler {
|
|
1036
|
+
async execute(context: CommandContext): Promise<void> {
|
|
1037
|
+
const api = new ConfluenceApi(config); // Hard to mock
|
|
1038
|
+
// ...
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// Use dependency injection:
|
|
1043
|
+
export class IndexCommand implements CommandHandler {
|
|
1044
|
+
constructor(private api?: ConfluenceApi) {}
|
|
1045
|
+
|
|
1046
|
+
async execute(context: CommandContext): Promise<void> {
|
|
1047
|
+
const api = this.api || new ConfluenceApi(config); // Easy to mock
|
|
1048
|
+
// ...
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
2. **Add Integration Tests**
|
|
1054
|
+
- Use recorded API responses (fixtures)
|
|
1055
|
+
- Test full workflows end-to-end
|
|
1056
|
+
- Validate file outputs
|
|
1057
|
+
|
|
1058
|
+
---
|
|
1059
|
+
|
|
1060
|
+
## Troubleshooting
|
|
1061
|
+
|
|
1062
|
+
### Common Issues
|
|
1063
|
+
|
|
1064
|
+
**Issue: "Failed to fetch page: 401 Unauthorized"**
|
|
1065
|
+
- Solution: Check API token validity, ensure username is correct email
|
|
1066
|
+
|
|
1067
|
+
**Issue: Empty markdown files**
|
|
1068
|
+
- Solution: Check if page body is in storage format (not view/export format)
|
|
1069
|
+
|
|
1070
|
+
**Issue: Images not downloading**
|
|
1071
|
+
- Solution: Verify attachment exists on page, check filename encoding
|
|
1072
|
+
|
|
1073
|
+
**Issue: Malformed markdown (e.g., `## **`)**
|
|
1074
|
+
- Solution: Run through cleaner, add new pattern if needed
|
|
1075
|
+
|
|
1076
|
+
**Issue: Index.yaml incomplete**
|
|
1077
|
+
- Solution: Delete index.yaml and restart (resumable from any point)
|
|
1078
|
+
|
|
1079
|
+
### Debug Mode
|
|
1080
|
+
|
|
1081
|
+
Enable verbose logging:
|
|
1082
|
+
```typescript
|
|
1083
|
+
// In command handlers or api.ts
|
|
1084
|
+
console.log('[DEBUG]', detailedInfo);
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
### API Rate Limiting
|
|
1088
|
+
|
|
1089
|
+
Confluence Cloud has rate limits:
|
|
1090
|
+
- Consider adding delays: `await new Promise(r => setTimeout(r, 100));`
|
|
1091
|
+
- Use smaller `pageSize` if hitting limits
|
|
1092
|
+
|
|
1093
|
+
---
|
|
1094
|
+
|
|
1095
|
+
## Dependencies
|
|
1096
|
+
|
|
1097
|
+
### Runtime
|
|
1098
|
+
- `minimist`: ^1.2.8 - CLI argument parsing
|
|
1099
|
+
- `dotenv`: ^17.2.3 - Environment variable loading
|
|
1100
|
+
- `prettier`: ^3.6.2 - Code formatting
|
|
1101
|
+
- `yaml`: ^2.8.1 - YAML parsing/stringifying
|
|
1102
|
+
|
|
1103
|
+
### Development
|
|
1104
|
+
- `typescript`: ^5.0.0 - TypeScript compiler
|
|
1105
|
+
- `vite`: ^7.1.10 - Build tool
|
|
1106
|
+
- `vite-node`: ^3.2.4 - Development runner
|
|
1107
|
+
- `jest`: ^30.2.0 - Testing framework
|
|
1108
|
+
- `ts-jest`: ^29.4.5 - TypeScript Jest support
|
|
1109
|
+
- `@types/node`: ^20.0.0 - Node.js type definitions
|
|
1110
|
+
- `@types/jest`: ^30.0.0 - Jest type definitions
|
|
1111
|
+
- `@types/minimist`: ^1.2.5 - Minimist type definitions
|
|
1112
|
+
|
|
1113
|
+
---
|
|
1114
|
+
|
|
1115
|
+
## Future Enhancements
|
|
1116
|
+
|
|
1117
|
+
### Potential Features
|
|
1118
|
+
- [ ] Incremental exports (only changed pages)
|
|
1119
|
+
- [ ] Link rewriting (internal page references)
|
|
1120
|
+
- [ ] Attachment downloads (all file types)
|
|
1121
|
+
- [ ] Custom macro handlers (plugin system)
|
|
1122
|
+
- [ ] Multi-space exports
|
|
1123
|
+
- [ ] Parallel downloads (with concurrency limit)
|
|
1124
|
+
- [ ] Export to other formats (HTML, PDF)
|
|
1125
|
+
- [ ] Page hierarchy preservation in directory structure
|
|
1126
|
+
- [ ] Git integration (commit per page)
|
|
1127
|
+
|
|
1128
|
+
### Known Limitations
|
|
1129
|
+
- Basic HTML conversion (may miss edge cases)
|
|
1130
|
+
- No retry logic for failed API calls
|
|
1131
|
+
- No progress persistence within a single page
|
|
1132
|
+
- Limited macro support (only common ones)
|
|
1133
|
+
- No table formatting improvements
|
|
1134
|
+
|
|
1135
|
+
---
|
|
1136
|
+
|
|
1137
|
+
## Resources
|
|
1138
|
+
|
|
1139
|
+
### Confluence REST API
|
|
1140
|
+
- [Confluence Cloud REST API](https://developer.atlassian.com/cloud/confluence/rest/v1/intro/)
|
|
1141
|
+
- [Storage Format Reference](https://confluence.atlassian.com/doc/confluence-storage-format-790796544.html)
|
|
1142
|
+
|
|
1143
|
+
### TypeScript
|
|
1144
|
+
- [TypeScript Handbook](https://www.typescriptlang.org/docs/)
|
|
1145
|
+
- [ES Modules in Node.js](https://nodejs.org/api/esm.html)
|
|
1146
|
+
|
|
1147
|
+
### Markdown
|
|
1148
|
+
- [CommonMark Spec](https://commonmark.org/)
|
|
1149
|
+
- [GitHub Flavored Markdown](https://github.github.com/gfm/)
|
|
1150
|
+
|
|
1151
|
+
---
|
|
1152
|
+
|
|
1153
|
+
## License
|
|
1154
|
+
|
|
1155
|
+
MIT - Same as parent project
|
|
1156
|
+
|
|
1157
|
+
---
|
|
1158
|
+
|
|
1159
|
+
## Maintainer Notes
|
|
1160
|
+
|
|
1161
|
+
**Last Updated:** October 17, 2025
|
|
1162
|
+
**Agent Compatibility:** Optimized for GitHub Copilot, Claude, GPT-4
|
|
1163
|
+
**Document Version:** 2.1.0
|
|
1164
|
+
|
|
1165
|
+
### When to Update This Document
|
|
1166
|
+
|
|
1167
|
+
Update `agents.md` whenever you make changes to:
|
|
1168
|
+
- ✏️ Project architecture or design patterns
|
|
1169
|
+
- ✏️ CLI commands, arguments, or workflows
|
|
1170
|
+
- ✏️ API methods or core functionality
|
|
1171
|
+
- ✏️ File structure or naming conventions
|
|
1172
|
+
- ✏️ Coding standards or best practices
|
|
1173
|
+
|
|
1174
|
+
Keeping this document current ensures AI agents have accurate context for development work.
|