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.
Files changed (91) hide show
  1. package/.eslintrc.cjs +18 -0
  2. package/.github/copilot-instructions.md +3 -0
  3. package/.github/prompts/analyze.prompt.md +101 -0
  4. package/.github/prompts/clarify.prompt.md +158 -0
  5. package/.github/prompts/constitution.prompt.md +73 -0
  6. package/.github/prompts/implement.prompt.md +56 -0
  7. package/.github/prompts/plan.prompt.md +50 -0
  8. package/.github/prompts/specify.prompt.md +21 -0
  9. package/.github/prompts/tasks.prompt.md +69 -0
  10. package/LICENSE +21 -0
  11. package/README.md +332 -0
  12. package/agents.md +1174 -0
  13. package/dist/api.d.ts +73 -0
  14. package/dist/api.js +387 -0
  15. package/dist/api.js.map +1 -0
  16. package/dist/commands/download.command.d.ts +18 -0
  17. package/dist/commands/download.command.js +257 -0
  18. package/dist/commands/download.command.js.map +1 -0
  19. package/dist/commands/executor.d.ts +22 -0
  20. package/dist/commands/executor.js +52 -0
  21. package/dist/commands/executor.js.map +1 -0
  22. package/dist/commands/help.command.d.ts +8 -0
  23. package/dist/commands/help.command.js +68 -0
  24. package/dist/commands/help.command.js.map +1 -0
  25. package/dist/commands/index.command.d.ts +14 -0
  26. package/dist/commands/index.command.js +95 -0
  27. package/dist/commands/index.command.js.map +1 -0
  28. package/dist/commands/index.d.ts +13 -0
  29. package/dist/commands/index.js +13 -0
  30. package/dist/commands/index.js.map +1 -0
  31. package/dist/commands/plan.command.d.ts +54 -0
  32. package/dist/commands/plan.command.js +272 -0
  33. package/dist/commands/plan.command.js.map +1 -0
  34. package/dist/commands/registry.d.ts +12 -0
  35. package/dist/commands/registry.js +32 -0
  36. package/dist/commands/registry.js.map +1 -0
  37. package/dist/commands/transform.command.d.ts +69 -0
  38. package/dist/commands/transform.command.js +951 -0
  39. package/dist/commands/transform.command.js.map +1 -0
  40. package/dist/commands/types.d.ts +12 -0
  41. package/dist/commands/types.js +5 -0
  42. package/dist/commands/types.js.map +1 -0
  43. package/dist/commands/update.command.d.ts +10 -0
  44. package/dist/commands/update.command.js +201 -0
  45. package/dist/commands/update.command.js.map +1 -0
  46. package/dist/constants.d.ts +1 -0
  47. package/dist/constants.js +2 -0
  48. package/dist/constants.js.map +1 -0
  49. package/dist/index.d.ts +5 -0
  50. package/dist/index.js +110 -0
  51. package/dist/index.js.map +1 -0
  52. package/dist/logger.d.ts +15 -0
  53. package/dist/logger.js +52 -0
  54. package/dist/logger.js.map +1 -0
  55. package/dist/types.d.ts +167 -0
  56. package/dist/types.js +5 -0
  57. package/dist/types.js.map +1 -0
  58. package/dist/utils.d.ts +56 -0
  59. package/dist/utils.js +178 -0
  60. package/dist/utils.js.map +1 -0
  61. package/eslint.config.js +29 -0
  62. package/jest.config.cjs +25 -0
  63. package/migrate-meta.js +132 -0
  64. package/package.json +53 -0
  65. package/src/api.ts +469 -0
  66. package/src/commands/download.command.ts +324 -0
  67. package/src/commands/executor.ts +62 -0
  68. package/src/commands/help.command.ts +72 -0
  69. package/src/commands/index.command.ts +111 -0
  70. package/src/commands/index.ts +14 -0
  71. package/src/commands/plan.command.ts +318 -0
  72. package/src/commands/registry.ts +39 -0
  73. package/src/commands/transform.command.ts +1103 -0
  74. package/src/commands/types.ts +16 -0
  75. package/src/commands/update.command.ts +229 -0
  76. package/src/constants.ts +0 -0
  77. package/src/index.ts +120 -0
  78. package/src/logger.ts +60 -0
  79. package/src/test.sh +66 -0
  80. package/src/types.ts +176 -0
  81. package/src/utils.ts +204 -0
  82. package/tests/commands/README.md +123 -0
  83. package/tests/commands/download.command.test.ts +8 -0
  84. package/tests/commands/help.command.test.ts +8 -0
  85. package/tests/commands/index.command.test.ts +8 -0
  86. package/tests/commands/plan.command.test.ts +15 -0
  87. package/tests/commands/transform.command.test.ts +8 -0
  88. package/tests/fixtures/_index.yaml +38 -0
  89. package/tests/fixtures/mock-pages.ts +62 -0
  90. package/tsconfig.json +25 -0
  91. package/vite.config.ts +45 -0
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Command-related type definitions
3
+ */
4
+
5
+ import type { ConfluenceConfig } from '../types.js';
6
+
7
+ export type Command = 'help' | 'index' | 'update' | 'plan' | 'download' | 'transform';
8
+
9
+ export interface CommandContext {
10
+ config: ConfluenceConfig;
11
+ args: Record<string, unknown>;
12
+ }
13
+
14
+ export interface CommandHandler {
15
+ execute(context: CommandContext): Promise<void>;
16
+ }
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Update command handler - Checks for new/updated pages and updates _index.yaml
3
+ */
4
+
5
+ import { promises as fs } from 'fs';
6
+ import path from 'path';
7
+ import yaml from 'yaml';
8
+ import { ConfluenceApi } from '../api.js';
9
+ import type { ConfluenceConfig, PageIndexEntry, PageMetadata } from '../types.js';
10
+ import type { CommandContext, CommandHandler } from './types.js';
11
+
12
+ export class UpdateCommand implements CommandHandler {
13
+ constructor(private config: ConfluenceConfig) {}
14
+
15
+ async execute(context: CommandContext): Promise<void> {
16
+ const api = new ConfluenceApi(this.config);
17
+ const indexPath = path.join(this.config.outputDir, '_index.yaml');
18
+
19
+ console.log(`Checking for updates in space: ${this.config.spaceKey}`);
20
+ console.log(`Index file: ${indexPath}\n`);
21
+
22
+ // Load existing index
23
+ let existingIndex: Map<string, PageIndexEntry>;
24
+ let oldestIndexedDate: string | undefined;
25
+
26
+ try {
27
+ const existingContent = await fs.readFile(indexPath, 'utf-8');
28
+ const existingPages = yaml.parse(existingContent) as PageIndexEntry[];
29
+ existingIndex = new Map(existingPages.map(p => [p.id, p]));
30
+
31
+ // Find the oldest indexedDate to use as the starting point for CQL search
32
+ for (const page of existingPages) {
33
+ if (page.indexedDate) {
34
+ if (!oldestIndexedDate || page.indexedDate < oldestIndexedDate) {
35
+ oldestIndexedDate = page.indexedDate;
36
+ }
37
+ }
38
+ }
39
+
40
+ console.log(`Loaded existing index with ${existingIndex.size} pages`);
41
+ if (oldestIndexedDate) {
42
+ console.log(`Oldest indexed date: ${oldestIndexedDate}\n`);
43
+ }
44
+ } catch (error) {
45
+ console.error(`Error: _index.yaml not found. Run 'index' command first.`);
46
+ process.exit(1);
47
+ }
48
+
49
+ // Use CQL to fetch only pages modified since oldest indexed date
50
+ // This is much more efficient than fetching all pages
51
+ let modifiedPages: PageMetadata[] = [];
52
+
53
+ if (oldestIndexedDate) {
54
+ // Format date for CQL: "yyyy-MM-dd" or "yyyy-MM-dd HH:mm"
55
+ const searchDate = oldestIndexedDate.split('T')[0]; // Get just the date part
56
+ const cql = `space = "${this.config.spaceKey}" AND type = page AND lastmodified >= "${searchDate}" ORDER BY lastmodified DESC`;
57
+
58
+ console.log(`Searching for pages modified since ${searchDate}...`);
59
+ console.log(`CQL: ${cql}\n`);
60
+
61
+ try {
62
+ modifiedPages = await api.searchPages(cql, this.config.pageSize || 100);
63
+ console.log(`Found ${modifiedPages.length} pages modified since ${searchDate}\n`);
64
+ } catch (error) {
65
+ console.warn(`CQL search failed, falling back to full space scan...`);
66
+ console.warn(`Error: ${error}\n`);
67
+ modifiedPages = [];
68
+ }
69
+ }
70
+
71
+ // If CQL search returned results, use them; otherwise fall back to full scan
72
+ let currentPages: Map<string, PageMetadata>;
73
+ let needsFullScan = modifiedPages.length === 0;
74
+
75
+ if (!needsFullScan) {
76
+ // Build a map from modified pages and merge with existing index
77
+ // This approach won't detect deleted pages without a full scan
78
+ currentPages = new Map(Array.from(existingIndex.entries()).map(([id, entry]) => [id, {
79
+ id: entry.id,
80
+ title: entry.title,
81
+ version: entry.version,
82
+ parentId: entry.parentId,
83
+ modifiedDate: entry.modifiedDate
84
+ }] as [string, PageMetadata]));
85
+
86
+ // Update with modified pages
87
+ for (const page of modifiedPages) {
88
+ currentPages.set(page.id, page);
89
+ }
90
+
91
+ console.log(`Note: Using incremental update. To detect deleted pages, run with --full flag.\n`);
92
+ } else {
93
+ // Full scan - fetch all pages metadata
94
+ console.log('Fetching all pages metadata (metadata only, no content)...\n');
95
+
96
+ currentPages = new Map();
97
+ let pageCount = 0;
98
+
99
+ for await (const page of api.getAllPagesMetadata(this.config.spaceKey, this.config.pageSize || 100)) {
100
+ currentPages.set(page.id, page);
101
+ pageCount++;
102
+
103
+ // Progress indicator every 100 pages
104
+ if (pageCount % 100 === 0) {
105
+ console.log(` Fetched ${pageCount} pages...`);
106
+ }
107
+ }
108
+
109
+ console.log(`\nFetched ${currentPages.size} pages from space\n`);
110
+ }
111
+
112
+ // Compare and find changes
113
+ const newPages: PageMetadata[] = [];
114
+ const updatedPages: Array<{ current: PageMetadata; indexed: PageIndexEntry }> = [];
115
+ const deletedPages: PageIndexEntry[] = [];
116
+
117
+ // Find new and updated pages
118
+ for (const [id, current] of currentPages) {
119
+ const indexed = existingIndex.get(id);
120
+
121
+ if (!indexed) {
122
+ newPages.push(current);
123
+ } else if (current.version && indexed.version && current.version > indexed.version) {
124
+ updatedPages.push({ current, indexed });
125
+ }
126
+ }
127
+
128
+ // Find deleted pages (only if we did a full scan)
129
+ if (!needsFullScan) {
130
+ // Skip deleted page detection for incremental updates
131
+ } else {
132
+ for (const [id, indexed] of existingIndex) {
133
+ if (!currentPages.has(id)) {
134
+ deletedPages.push(indexed);
135
+ }
136
+ }
137
+ }
138
+
139
+ // Report findings
140
+ console.log('=== Update Summary ===');
141
+ console.log(`New pages: ${newPages.length}`);
142
+ console.log(`Updated pages: ${updatedPages.length}`);
143
+ if (needsFullScan) {
144
+ console.log(`Deleted pages: ${deletedPages.length}`);
145
+ } else {
146
+ console.log(`Deleted pages: (skipped - use --full for deletion detection)`);
147
+ }
148
+ console.log('');
149
+
150
+ if (newPages.length === 0 && updatedPages.length === 0 && deletedPages.length === 0) {
151
+ console.log('✓ Index is up to date. No changes needed.');
152
+ return;
153
+ }
154
+
155
+ // Show details
156
+ if (newPages.length > 0) {
157
+ console.log('\n--- New Pages ---');
158
+ for (const page of newPages) {
159
+ console.log(` + ${page.title} (${page.id})`);
160
+ }
161
+ }
162
+
163
+ if (updatedPages.length > 0) {
164
+ console.log('\n--- Updated Pages ---');
165
+ for (const { current, indexed } of updatedPages) {
166
+ console.log(` ~ ${current.title} (${current.id}) v${indexed.version} → v${current.version}`);
167
+ }
168
+ }
169
+
170
+ if (deletedPages.length > 0) {
171
+ console.log('\n--- Deleted Pages ---');
172
+ for (const page of deletedPages) {
173
+ console.log(` - ${page.title} (${page.id})`);
174
+ }
175
+ }
176
+
177
+ // Update the index
178
+ console.log('\n\nUpdating _index.yaml...');
179
+
180
+ // Build updated index
181
+ const updatedIndex: PageIndexEntry[] = [];
182
+ const now = new Date().toISOString();
183
+
184
+ for (const [id, current] of currentPages) {
185
+ const existing = existingIndex.get(id);
186
+
187
+ if (existing && (!current.version || !existing.version || current.version === existing.version)) {
188
+ // Keep existing entry unchanged
189
+ updatedIndex.push(existing);
190
+ } else {
191
+ // New or updated page
192
+ updatedIndex.push({
193
+ id: current.id,
194
+ title: current.title,
195
+ version: current.version,
196
+ parentId: current.parentId,
197
+ modifiedDate: current.modifiedDate,
198
+ indexedDate: now,
199
+ pageNumber: existing?.pageNumber || 0
200
+ });
201
+ }
202
+ }
203
+
204
+ // Write updated index
205
+ const header = `# Confluence Export Index
206
+ # Space: ${this.config.spaceKey}
207
+ # Export Date: ${now}
208
+ # Page Size: ${this.config.pageSize || 100}
209
+
210
+ `;
211
+
212
+ const yamlContent = updatedIndex.map(entry => {
213
+ const yamlDoc = yaml.stringify(entry).trim();
214
+ const lines = yamlDoc.split('\n');
215
+ return lines.map((line, index) => {
216
+ if (index === 0) {
217
+ return `- ${line}`;
218
+ }
219
+ return ` ${line}`;
220
+ }).join('\n');
221
+ }).join('\n');
222
+
223
+ await fs.writeFile(indexPath, header + yamlContent + '\n', 'utf-8');
224
+
225
+ console.log(`\n✓ Index updated successfully!`);
226
+ console.log(` Total pages in index: ${updatedIndex.length}`);
227
+ console.log(` Added: ${newPages.length}, Updated: ${updatedPages.length}, Removed: ${deletedPages.length}`);
228
+ }
229
+ }
File without changes
package/src/index.ts ADDED
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Minimal Confluence to Markdown Exporter - CLI Entry Point
4
+ */
5
+
6
+ import minimist from 'minimist';
7
+ import { config as loadEnv } from 'dotenv';
8
+ import { promises as fs } from 'fs';
9
+ import path from 'path';
10
+ import { CommandExecutor } from './commands/executor.js';
11
+ import { HelpCommand } from './commands/help.command.js';
12
+ import type { ConfluenceConfig } from './types.js';
13
+ import type { CommandContext } from './commands/types.js';
14
+
15
+ async function main() {
16
+ // Load .env file if it exists
17
+ loadEnv();
18
+
19
+ // Parse command line arguments
20
+ const args = minimist(process.argv.slice(2), {
21
+ string: ['url', 'username', 'password', 'space', 'output', 'pageId', 'pageSize', 'limit', 'parallel'],
22
+ boolean: ['clear', 'force', 'debug'],
23
+ alias: {
24
+ u: 'url',
25
+ n: 'username',
26
+ p: 'password',
27
+ s: 'space',
28
+ o: 'output',
29
+ i: 'pageId',
30
+ l: 'limit',
31
+ f: 'force',
32
+ d: 'debug',
33
+ h: 'help'
34
+ }
35
+ });
36
+
37
+ // Show help if requested
38
+ if (args.help) {
39
+ const helpCommand = new HelpCommand();
40
+ await helpCommand.execute({ config: {} as ConfluenceConfig, args });
41
+ process.exit(0);
42
+ }
43
+
44
+ // Build config from args or environment variables
45
+ const config: ConfluenceConfig = {
46
+ baseUrl: args.url || process.env.CONFLUENCE_BASE_URL || '',
47
+ username: args.username || process.env.CONFLUENCE_USERNAME || '',
48
+ password: args.password || process.env.CONFLUENCE_PASSWORD || '',
49
+ spaceKey: args.space || process.env.CONFLUENCE_SPACE_KEY || '',
50
+ outputDir: args.output || process.env.OUTPUT_DIR || './output',
51
+ pageId: args.pageId || undefined,
52
+ pageSize: args.pageSize ? parseInt(args.pageSize, 10) : 100,
53
+ limit: args.limit ? parseInt(args.limit, 10) : undefined,
54
+ clear: args.clear || false,
55
+ force: args.force || false,
56
+ debug: args.debug || false,
57
+ parallel: args.parallel ? parseInt(args.parallel, 10) : 5
58
+ };
59
+
60
+ // Extract commands from positional arguments
61
+ let commands: string[];
62
+ if (args._.length === 0) {
63
+ // No commands provided - run full sync workflow
64
+ const indexPath = path.join(config.outputDir, '_index.yaml');
65
+ try {
66
+ await fs.access(indexPath);
67
+ commands = ['update', 'plan', 'download', 'transform'];
68
+ } catch {
69
+ commands = ['index', 'plan', 'download', 'transform'];
70
+ }
71
+ } else {
72
+ commands = args._ as string[];
73
+ }
74
+
75
+ // Configure logger debug mode if requested
76
+ if ((config as any).debug) {
77
+ // Lazy import to avoid top-level cycles
78
+ const { logger } = await import('./logger.js');
79
+ logger.setDebug(true);
80
+ }
81
+
82
+ // Validate config (except for help command which doesn't need it)
83
+ if (!config.baseUrl || !config.username || !config.password || !config.spaceKey) {
84
+ console.error('Error: Missing required configuration.\n');
85
+ console.error('Please provide all required options or set environment variables.');
86
+ console.error('Run with --help for usage information.\n');
87
+ process.exit(1);
88
+ }
89
+
90
+ const executor = new CommandExecutor(config);
91
+
92
+ // Validate commands
93
+ let requestedCommands: Awaited<ReturnType<typeof executor.validateCommands>>;
94
+ try {
95
+ requestedCommands = executor.validateCommands(commands);
96
+ } catch (error) {
97
+ console.error(`Error: ${error instanceof Error ? error.message : error}\n`);
98
+ const helpCommand = new HelpCommand();
99
+ await helpCommand.execute({ config: {} as ConfluenceConfig, args });
100
+ process.exit(1);
101
+ }
102
+
103
+ // Handle help command
104
+ if (requestedCommands.includes('help')) {
105
+ const helpCommand = new HelpCommand();
106
+ await helpCommand.execute({ config: {} as ConfluenceConfig, args });
107
+ process.exit(0);
108
+ }
109
+
110
+ try {
111
+ const context: CommandContext = { config, args };
112
+ await executor.executeCommands(requestedCommands, context);
113
+ process.exit(0);
114
+ } catch (error) {
115
+ console.error('\n✗ Command failed:', error instanceof Error ? error.message : error);
116
+ process.exit(1);
117
+ }
118
+ }
119
+
120
+ main();
package/src/logger.ts ADDED
@@ -0,0 +1,60 @@
1
+ export class Logger {
2
+ private debugEnabled: boolean;
3
+ private lastDebugTime: number;
4
+
5
+ // ANSI color codes
6
+ private colors = {
7
+ reset: '\x1b[0m',
8
+ gray: '\x1b[90m',
9
+ cyan: '\x1b[36m',
10
+ yellow: '\x1b[33m',
11
+ red: '\x1b[31m',
12
+ green: '\x1b[32m',
13
+ blue: '\x1b[34m',
14
+ magenta: '\x1b[35m',
15
+ white: '\x1b[37m'
16
+ };
17
+
18
+ constructor(debug = false) {
19
+ this.debugEnabled = debug || !!process.env.DEBUG;
20
+ this.lastDebugTime = Date.now();
21
+ }
22
+
23
+ debug(...args: unknown[]) {
24
+ if (this.debugEnabled) {
25
+ const now = Date.now();
26
+ const elapsed = now - this.lastDebugTime;
27
+ this.lastDebugTime = now;
28
+ // eslint-disable-next-line no-console
29
+ console.debug(`${this.colors.cyan}[DEBUG +${elapsed}ms]${this.colors.reset}`, ...args);
30
+ }
31
+ }
32
+
33
+ setDebug(enabled: boolean) {
34
+ this.debugEnabled = !!enabled;
35
+ }
36
+
37
+ info(...args: unknown[]) {
38
+ // eslint-disable-next-line no-console
39
+ console.log(`${this.colors.blue}[INFO]${this.colors.reset}`, ...args);
40
+ }
41
+
42
+ warn(...args: unknown[]) {
43
+ // eslint-disable-next-line no-console
44
+ console.warn(`${this.colors.yellow}[WARN]${this.colors.reset}`, ...args);
45
+ }
46
+
47
+ error(...args: unknown[]) {
48
+ // eslint-disable-next-line no-console
49
+ console.error(`${this.colors.red}[ERROR]${this.colors.reset}`, ...args);
50
+ }
51
+
52
+ success(...args: unknown[]) {
53
+ // eslint-disable-next-line no-console
54
+ console.log(`${this.colors.green}[SUCCESS]${this.colors.reset}`, ...args);
55
+ }
56
+ }
57
+
58
+ const logger = new Logger();
59
+ export { logger };
60
+ export default logger;
package/src/test.sh ADDED
@@ -0,0 +1,66 @@
1
+ #!/bin/bash
2
+
3
+ # Test script for minimal Confluence exporter
4
+ # This script demonstrates how to test the exporter
5
+
6
+ echo "╔════════════════════════════════════════════════════╗"
7
+ echo "║ Testing Minimal Confluence Exporter ║"
8
+ echo "╚════════════════════════════════════════════════════╝"
9
+ echo ""
10
+
11
+ # Check if Node.js version is adequate
12
+ NODE_VERSION=$(node --version | cut -d'v' -f2 | cut -d'.' -f1)
13
+ if [ "$NODE_VERSION" -lt 18 ]; then
14
+ echo "❌ Error: Node.js 18+ required (current: $(node --version))"
15
+ exit 1
16
+ fi
17
+
18
+ echo "✓ Node.js version: $(node --version)"
19
+ echo ""
20
+
21
+ # Build the project
22
+ echo "📦 Building TypeScript..."
23
+ npm run build
24
+
25
+ if [ $? -ne 0 ]; then
26
+ echo "❌ Build failed!"
27
+ exit 1
28
+ fi
29
+
30
+ echo "✓ Build successful"
31
+ echo ""
32
+
33
+ # Check if environment variables are set
34
+ if [ -z "$CONFLUENCE_BASE_URL" ] || [ -z "$CONFLUENCE_USERNAME" ] || [ -z "$CONFLUENCE_PASSWORD" ] || [ -z "$CONFLUENCE_SPACE_KEY" ]; then
35
+ echo "⚠️ Environment variables not set. Please set:"
36
+ echo ""
37
+ echo " export CONFLUENCE_BASE_URL='https://your-instance.atlassian.net'"
38
+ echo " export CONFLUENCE_USERNAME='your-email@example.com'"
39
+ echo " export CONFLUENCE_PASSWORD='your-api-token'"
40
+ echo " export CONFLUENCE_SPACE_KEY='YOURSPACE'"
41
+ echo " export OUTPUT_DIR='./test-output' # optional"
42
+ echo ""
43
+ echo "Or provide as command line arguments:"
44
+ echo " npm run start -- <baseUrl> <username> <password> <spaceKey> [outputDir]"
45
+ echo ""
46
+ exit 1
47
+ fi
48
+
49
+ # Run the exporter
50
+ echo "🚀 Running export..."
51
+ echo " Space: $CONFLUENCE_SPACE_KEY"
52
+ echo " Output: ${OUTPUT_DIR:-./output}"
53
+ echo ""
54
+
55
+ npm run start
56
+
57
+ if [ $? -eq 0 ]; then
58
+ echo ""
59
+ echo "✓ Test completed successfully!"
60
+ echo ""
61
+ echo "Check the output directory for exported markdown files."
62
+ else
63
+ echo ""
64
+ echo "❌ Test failed!"
65
+ exit 1
66
+ fi
package/src/types.ts ADDED
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Minimal type definitions for Confluence export
3
+ */
4
+
5
+ // ============================================================================
6
+ // Configuration Types
7
+ // ============================================================================
8
+
9
+ export interface ConfluenceConfig {
10
+ baseUrl: string;
11
+ username: string;
12
+ password: string;
13
+ spaceKey: string;
14
+ outputDir: string;
15
+ pageId?: string; // Optional: if specified, export only this page
16
+ pageSize?: number; // Optional: number of items per API page (default: 25)
17
+ limit?: number; // Optional: maximum number of pages to process
18
+ clear?: boolean; // Optional: if specified, clears the output directory before export
19
+ force?: boolean; // Optional: if specified, forces re-download of all pages regardless of status
20
+ debug?: boolean; // Optional: enable debug logging
21
+ parallel?: number; // Optional: number of concurrent operations (default: 5)
22
+ }
23
+
24
+ // ============================================================================
25
+ // Core Domain Types
26
+ // ============================================================================
27
+
28
+ export interface Page {
29
+ id: string;
30
+ title: string;
31
+ body: string;
32
+ version?: number;
33
+ parentId?: string;
34
+ modifiedDate?: string;
35
+ }
36
+
37
+ export interface User {
38
+ userKey: string;
39
+ username: string;
40
+ displayName: string;
41
+ email?: string;
42
+ }
43
+
44
+ // ============================================================================
45
+ // API Response Types
46
+ // ============================================================================
47
+
48
+ export interface PaginatedResponse<T> {
49
+ results: T[];
50
+ start: number;
51
+ limit: number;
52
+ size: number;
53
+ _links?: {
54
+ next?: string;
55
+ };
56
+ }
57
+
58
+ export interface PageResponse {
59
+ id: string;
60
+ title: string;
61
+ body?: { storage?: { value: string } };
62
+ version?: { number: number; when?: string };
63
+ ancestors?: Array<{ id: string }>;
64
+ history?: { lastUpdated?: { when?: string } };
65
+ }
66
+
67
+ export interface RawPage {
68
+ id: string;
69
+ title: string;
70
+ body?: { storage?: { value: string } };
71
+ version?: { number: number; when?: string };
72
+ ancestors?: Array<{ id: string }>;
73
+ history?: { lastUpdated?: { when?: string } };
74
+ }
75
+
76
+ export interface ListPagesResponse {
77
+ results: RawPage[];
78
+ start: number;
79
+ limit: number;
80
+ size: number;
81
+ _links?: {
82
+ next?: string;
83
+ };
84
+ }
85
+
86
+ export interface ChildPageResponse {
87
+ id: string;
88
+ title: string;
89
+ version?: { number: number };
90
+ }
91
+
92
+ export interface ChildPagesResponse {
93
+ results: ChildPageResponse[];
94
+ }
95
+
96
+ export interface AttachmentResult {
97
+ id: string;
98
+ title: string;
99
+ _links: {
100
+ download: string;
101
+ };
102
+ }
103
+
104
+ export interface AttachmentResponse {
105
+ results: AttachmentResult[];
106
+ }
107
+
108
+ // ============================================================================
109
+ // Index & Export Types
110
+ // ============================================================================
111
+
112
+ export interface PageMetadata {
113
+ id: string;
114
+ title: string;
115
+ version?: number;
116
+ parentId?: string;
117
+ modifiedDate?: string;
118
+ }
119
+
120
+ export interface PageIndexEntry {
121
+ id: string;
122
+ title: string;
123
+ version?: number;
124
+ parentId?: string;
125
+ modifiedDate?: string;
126
+ indexedDate: string;
127
+ pageNumber: number;
128
+ downloadedVersion?: number; // Last downloaded version
129
+ downloadedAt?: string; // Last download timestamp (ISO 8601)
130
+ queueReason?: 'new' | 'updated';
131
+ }
132
+
133
+ export interface PageIndex {
134
+ spaceKey: string;
135
+ exportDate: string;
136
+ totalPages: number;
137
+ pages: PageIndexEntry[];
138
+ }
139
+
140
+ export interface PageTreeNode {
141
+ id: string;
142
+ title: string;
143
+ version?: number;
144
+ parentId?: string;
145
+ modifiedDate?: string;
146
+ children?: PageTreeNode[];
147
+ }
148
+
149
+ // ============================================================================
150
+ // Transformation Types
151
+ // ============================================================================
152
+
153
+ export interface MarkdownResult {
154
+ content: string;
155
+ frontMatter: {
156
+ title: string;
157
+ id: string;
158
+ version?: number;
159
+ parentId?: string;
160
+ };
161
+ images: Array<{
162
+ filename: string;
163
+ data: Buffer;
164
+ }>;
165
+ }
166
+
167
+ /**
168
+ * Metadata sidecar file for tracking download state
169
+ * Stored as .meta.json alongside each .html file
170
+ */
171
+ export interface PageMeta {
172
+ pageId: string;
173
+ version: number;
174
+ modifiedDate: string;
175
+ downloadedAt: string;
176
+ }