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/src/utils.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { parse, stringify } from 'yaml';
|
|
2
|
+
import fs, { readFileSync, existsSync, writeFileSync, readdirSync, statSync } from 'fs';
|
|
3
|
+
import { join, dirname, basename } from 'path';
|
|
4
|
+
import type { ConfluenceConfig, PageIndexEntry, PageMeta } from './types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Utility functions used across the application
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Convert text to safe filename/slug
|
|
12
|
+
*
|
|
13
|
+
* @param text - Text to slugify
|
|
14
|
+
* @returns Slugified text (lowercase, hyphens, no special chars)
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* slugify("My Page Title!") // "my-page-title"
|
|
18
|
+
*/
|
|
19
|
+
export function slugify(text: string): string {
|
|
20
|
+
return text
|
|
21
|
+
.toLowerCase()
|
|
22
|
+
.replace(/[^\w\s-]/g, '') // Remove special chars
|
|
23
|
+
.replace(/\s+/g, '-') // Replace spaces with hyphens
|
|
24
|
+
.replace(/-+/g, '-') // Replace multiple hyphens with single
|
|
25
|
+
.trim();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Attempt to reverse slugification (best effort)
|
|
30
|
+
* Converts hyphens to spaces and capitalizes first letter of each word
|
|
31
|
+
*
|
|
32
|
+
* @param slug - Slugified text to convert back
|
|
33
|
+
* @returns Title-cased text with spaces
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* unslugify("my-page-title") // "My Page Title"
|
|
37
|
+
*/
|
|
38
|
+
export function unslugify(slug: string): string {
|
|
39
|
+
return slug
|
|
40
|
+
.replace(/-/g, ' ') // Replace hyphens with spaces
|
|
41
|
+
.replace(/\b\w/g, c => c.toUpperCase()); // Capitalize first letter of each word
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const pageFilename = (item: PageIndexEntry, ext='.md') => {
|
|
45
|
+
const slug = slugify(item.title);
|
|
46
|
+
return `${item.id}-${slug}${ext}`;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const pagePath = (id: string, config: ConfluenceConfig) => {
|
|
50
|
+
const indexFile = join(config.outputDir, '_index.yaml');
|
|
51
|
+
const indexContent = readFileSync(indexFile, 'utf-8');
|
|
52
|
+
const index = parse(indexContent) as PageIndexEntry[];
|
|
53
|
+
|
|
54
|
+
const findParents = (id: string, path: string[]): string[] => {
|
|
55
|
+
const entry = index.find(e => e.id === id);
|
|
56
|
+
if (!entry) return [];
|
|
57
|
+
path.unshift(pageFilename(entry, ''));
|
|
58
|
+
if (entry.parentId) {
|
|
59
|
+
return findParents(entry.parentId, path);
|
|
60
|
+
}
|
|
61
|
+
return path;
|
|
62
|
+
};
|
|
63
|
+
const item = index.find(e => e.id === id);
|
|
64
|
+
if (!item) {
|
|
65
|
+
throw new Error(`Page with ID ${id} not found in index`);
|
|
66
|
+
}
|
|
67
|
+
const parents = item.parentId ? findParents(item.parentId, []) : [];
|
|
68
|
+
return join(config.outputDir, config.spaceKey, ...parents, pageFilename(item, '.html'));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// Page Metadata Utilities (Index-based)
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Read a specific entry from _index.yaml
|
|
77
|
+
*/
|
|
78
|
+
export function readIndexEntry(indexPath: string, pageId: string): PageIndexEntry | null {
|
|
79
|
+
if (!existsSync(indexPath)) return null;
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const content = readFileSync(indexPath, 'utf-8');
|
|
83
|
+
const index: PageIndexEntry[] = parse(content);
|
|
84
|
+
return index.find(entry => entry.id === pageId) || null;
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Update a specific entry in _index.yaml
|
|
92
|
+
*/
|
|
93
|
+
export function updateIndexEntry(
|
|
94
|
+
indexPath: string,
|
|
95
|
+
pageId: string,
|
|
96
|
+
updates: Partial<PageIndexEntry>
|
|
97
|
+
): boolean {
|
|
98
|
+
if (!existsSync(indexPath)) return false;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const content = readFileSync(indexPath, 'utf-8');
|
|
102
|
+
const index: PageIndexEntry[] = parse(content);
|
|
103
|
+
|
|
104
|
+
const entryIndex = index.findIndex(entry => entry.id === pageId);
|
|
105
|
+
if (entryIndex === -1) return false;
|
|
106
|
+
|
|
107
|
+
// Update the entry
|
|
108
|
+
index[entryIndex] = { ...index[entryIndex], ...updates };
|
|
109
|
+
|
|
110
|
+
// Write back to file
|
|
111
|
+
const yamlContent = stringify(index, {
|
|
112
|
+
indent: 2,
|
|
113
|
+
lineWidth: 0
|
|
114
|
+
});
|
|
115
|
+
writeFileSync(indexPath, yamlContent, 'utf-8');
|
|
116
|
+
|
|
117
|
+
return true;
|
|
118
|
+
} catch {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Find existing HTML file for a page by ID
|
|
125
|
+
* Searches recursively in the output directory for {pageId}-*.html pattern
|
|
126
|
+
*
|
|
127
|
+
* @param outputDir - Root directory to search
|
|
128
|
+
* @param pageId - Page ID to find
|
|
129
|
+
* @returns Path to HTML file or null if not found
|
|
130
|
+
*/
|
|
131
|
+
export function findExistingFile(outputDir: string, pageId: string): string | null {
|
|
132
|
+
const pattern = new RegExp(`^${pageId}-.*\\.html$`);
|
|
133
|
+
|
|
134
|
+
function searchDir(dir: string): string | null {
|
|
135
|
+
if (!existsSync(dir)) return null;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
139
|
+
|
|
140
|
+
for (const entry of entries) {
|
|
141
|
+
const fullPath = join(dir, entry.name);
|
|
142
|
+
|
|
143
|
+
if (entry.isFile() && pattern.test(entry.name)) {
|
|
144
|
+
return fullPath;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (entry.isDirectory() && !entry.name.startsWith('_') && entry.name !== 'images') {
|
|
148
|
+
const found = searchDir(fullPath);
|
|
149
|
+
if (found) return found;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch {
|
|
153
|
+
// Ignore permission errors, etc.
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return searchDir(outputDir);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Check if a page needs to be downloaded based on version comparison
|
|
164
|
+
* Uses index entry download tracking fields
|
|
165
|
+
*
|
|
166
|
+
* @param indexEntry - Page entry from index with current version
|
|
167
|
+
* @returns Object with needsDownload boolean and reason
|
|
168
|
+
*/
|
|
169
|
+
export function checkPageStatus(
|
|
170
|
+
indexEntry: PageIndexEntry
|
|
171
|
+
): { needsDownload: boolean; reason: 'new' | 'updated' | 'up-to-date'; details?: string } {
|
|
172
|
+
const downloadedVersion = indexEntry.downloadedVersion ?? 0;
|
|
173
|
+
const currentVersion = indexEntry.version ?? 0;
|
|
174
|
+
|
|
175
|
+
// If never downloaded, it's new
|
|
176
|
+
if (indexEntry.downloadedAt === undefined) {
|
|
177
|
+
return { needsDownload: true, reason: 'new' };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Compare versions (primary check)
|
|
181
|
+
if (currentVersion > downloadedVersion) {
|
|
182
|
+
return {
|
|
183
|
+
needsDownload: true,
|
|
184
|
+
reason: 'updated',
|
|
185
|
+
details: `v${downloadedVersion} → v${currentVersion}`
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// If versions match but downloadedVersion is 0 (fallback), compare dates
|
|
190
|
+
if (downloadedVersion === 0 && indexEntry.modifiedDate && indexEntry.downloadedAt) {
|
|
191
|
+
const currentDate = new Date(indexEntry.modifiedDate);
|
|
192
|
+
const downloadedDate = new Date(indexEntry.downloadedAt);
|
|
193
|
+
|
|
194
|
+
if (currentDate > downloadedDate) {
|
|
195
|
+
return {
|
|
196
|
+
needsDownload: true,
|
|
197
|
+
reason: 'updated',
|
|
198
|
+
details: `modified after download`
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return { needsDownload: false, reason: 'up-to-date' };
|
|
204
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Command Tests
|
|
2
|
+
|
|
3
|
+
This directory contains tests for all CLI commands in the Confluence to Markdown Exporter.
|
|
4
|
+
|
|
5
|
+
## Test Files
|
|
6
|
+
|
|
7
|
+
### `help.command.test.ts`
|
|
8
|
+
Basic constructor test for the Help command.
|
|
9
|
+
|
|
10
|
+
**Coverage:**
|
|
11
|
+
- ✅ Instance creation
|
|
12
|
+
|
|
13
|
+
### `index.command.test.ts`
|
|
14
|
+
Basic constructor test for the Index command.
|
|
15
|
+
|
|
16
|
+
**Coverage:**
|
|
17
|
+
- ✅ Instance creation
|
|
18
|
+
|
|
19
|
+
**Note:** Full API-dependent tests are not included because IndexCommand creates its own ConfluenceApi instance internally, which cannot be easily mocked with Jest + ESM modules. To fully test this command, it would need refactoring to use dependency injection.
|
|
20
|
+
|
|
21
|
+
### `plan.command.test.ts`
|
|
22
|
+
Basic constructor test for the Plan command.
|
|
23
|
+
|
|
24
|
+
**Coverage:**
|
|
25
|
+
- ✅ Instance creation
|
|
26
|
+
|
|
27
|
+
**Note:** Full API-dependent tests require dependency injection refactoring.
|
|
28
|
+
|
|
29
|
+
### `download.command.test.ts`
|
|
30
|
+
Basic constructor test for the Download command.
|
|
31
|
+
|
|
32
|
+
**Coverage:**
|
|
33
|
+
- ✅ Instance creation
|
|
34
|
+
|
|
35
|
+
**Note:** Full API-dependent tests require dependency injection refactoring.
|
|
36
|
+
|
|
37
|
+
### `transform.command.test.ts`
|
|
38
|
+
Basic constructor test for the Transform command.
|
|
39
|
+
|
|
40
|
+
**Coverage:**
|
|
41
|
+
- ✅ Instance creation
|
|
42
|
+
|
|
43
|
+
**Note:** Full API-dependent tests require dependency injection refactoring.
|
|
44
|
+
|
|
45
|
+
## Running Tests
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Run all tests
|
|
49
|
+
npm test
|
|
50
|
+
|
|
51
|
+
# Run tests in watch mode
|
|
52
|
+
npm run test:watch
|
|
53
|
+
|
|
54
|
+
# Run tests with coverage
|
|
55
|
+
npm run test:coverage
|
|
56
|
+
|
|
57
|
+
# Run specific test file
|
|
58
|
+
npm test -- help.command.test.ts
|
|
59
|
+
|
|
60
|
+
# Run tests matching pattern
|
|
61
|
+
npm test -- --testNamePattern="should transform"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Test Structure
|
|
65
|
+
|
|
66
|
+
All command tests follow a consistent structure:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
describe('CommandName', () => {
|
|
70
|
+
let command: CommandClass;
|
|
71
|
+
let mockContext: CommandContext;
|
|
72
|
+
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
// Setup
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
afterEach(() => {
|
|
78
|
+
// Cleanup
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('feature group', () => {
|
|
82
|
+
it('should do something specific', async () => {
|
|
83
|
+
// Arrange
|
|
84
|
+
// Act
|
|
85
|
+
// Assert
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Mocking Strategy
|
|
92
|
+
|
|
93
|
+
Due to ESM module constraints with Jest, most commands cannot be fully tested without refactoring:
|
|
94
|
+
|
|
95
|
+
- Commands create their own `ConfluenceApi` instances internally
|
|
96
|
+
- `jest.mock()` doesn't work reliably with ESM modules
|
|
97
|
+
- To enable full testing, commands would need to use **dependency injection** (accepting an API instance as a parameter)
|
|
98
|
+
|
|
99
|
+
## Current Test Coverage
|
|
100
|
+
|
|
101
|
+
- ✅ **Command instantiation** - All 5 commands can be constructed
|
|
102
|
+
- ✅ **Core transformations** - Transformer and Cleaner have full coverage
|
|
103
|
+
- ⚠️ **Command execution** - Requires refactoring for dependency injection
|
|
104
|
+
|
|
105
|
+
## Future Enhancements
|
|
106
|
+
|
|
107
|
+
To improve test coverage:
|
|
108
|
+
|
|
109
|
+
1. **Refactor commands for dependency injection**
|
|
110
|
+
- Pass `ConfluenceApi` instance to command constructors
|
|
111
|
+
- Enables easy mocking in tests
|
|
112
|
+
|
|
113
|
+
2. **Add integration tests**
|
|
114
|
+
- Full workflow end-to-end testing
|
|
115
|
+
- Mock Confluence server or use recorded responses
|
|
116
|
+
|
|
117
|
+
3. **Add performance tests**
|
|
118
|
+
- Large space exports
|
|
119
|
+
- Memory usage monitoring
|
|
120
|
+
|
|
121
|
+
4. **Add resilience tests**
|
|
122
|
+
- API rate limiting
|
|
123
|
+
- Network timeouts and retries
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PlanCommand } from '../../src/commands/plan.command.js';
|
|
2
|
+
|
|
3
|
+
describe('PlanCommand', () => {
|
|
4
|
+
it('should create an instance', () => {
|
|
5
|
+
const mockConfig = {
|
|
6
|
+
baseUrl: 'https://example.com',
|
|
7
|
+
username: 'user',
|
|
8
|
+
password: 'pass',
|
|
9
|
+
spaceKey: 'TEST',
|
|
10
|
+
outputDir: './output'
|
|
11
|
+
};
|
|
12
|
+
const command = new PlanCommand(mockConfig);
|
|
13
|
+
expect(command).toBeInstanceOf(PlanCommand);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Confluence Export Index
|
|
2
|
+
# Space: TEST
|
|
3
|
+
# Export Date: 2025-10-17T10:00:00.000Z
|
|
4
|
+
# Page Size: 25
|
|
5
|
+
|
|
6
|
+
- id: "100001"
|
|
7
|
+
title: Parent Page 1
|
|
8
|
+
version: 1
|
|
9
|
+
modifiedDate: 2025-10-01T10:00:00.000Z
|
|
10
|
+
indexedDate: 2025-10-17T10:00:00.000Z
|
|
11
|
+
pageNumber: 1
|
|
12
|
+
- id: "100002"
|
|
13
|
+
title: Parent Page 2
|
|
14
|
+
version: 2
|
|
15
|
+
parentId: "100001"
|
|
16
|
+
modifiedDate: 2025-10-02T10:00:00.000Z
|
|
17
|
+
indexedDate: 2025-10-17T10:00:01.000Z
|
|
18
|
+
pageNumber: 1
|
|
19
|
+
- id: "100003"
|
|
20
|
+
title: Parent Page 3
|
|
21
|
+
version: 3
|
|
22
|
+
modifiedDate: 2025-10-03T10:00:00.000Z
|
|
23
|
+
indexedDate: 2025-10-17T10:00:02.000Z
|
|
24
|
+
pageNumber: 1
|
|
25
|
+
- id: "100004"
|
|
26
|
+
title: Child Page A
|
|
27
|
+
version: 1
|
|
28
|
+
parentId: "100001"
|
|
29
|
+
modifiedDate: 2025-10-04T10:00:00.000Z
|
|
30
|
+
indexedDate: 2025-10-17T10:00:03.000Z
|
|
31
|
+
pageNumber: 2
|
|
32
|
+
- id: "100005"
|
|
33
|
+
title: Child Page B
|
|
34
|
+
version: 1
|
|
35
|
+
parentId: "100002"
|
|
36
|
+
modifiedDate: 2025-10-05T10:00:00.000Z
|
|
37
|
+
indexedDate: 2025-10-17T10:00:04.000Z
|
|
38
|
+
pageNumber: 2
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock page data for testing
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Page } from '../../src/types.js';
|
|
6
|
+
|
|
7
|
+
export const mockRootPage: Page = {
|
|
8
|
+
id: '200001',
|
|
9
|
+
title: 'Root Page',
|
|
10
|
+
body: '<p>Root content</p>',
|
|
11
|
+
version: 1,
|
|
12
|
+
modifiedDate: '2025-10-01T10:00:00.000Z',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const mockChild1: Page = {
|
|
16
|
+
id: '200002',
|
|
17
|
+
title: 'Child 1',
|
|
18
|
+
body: '<p>Child 1 content</p>',
|
|
19
|
+
version: 1,
|
|
20
|
+
parentId: '200001',
|
|
21
|
+
modifiedDate: '2025-10-02T10:00:00.000Z',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const mockChild2: Page = {
|
|
25
|
+
id: '200003',
|
|
26
|
+
title: 'Child 2',
|
|
27
|
+
body: '<p>Child 2 content</p>',
|
|
28
|
+
version: 1,
|
|
29
|
+
parentId: '200001',
|
|
30
|
+
modifiedDate: '2025-10-03T10:00:00.000Z',
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const mockGrandchild: Page = {
|
|
34
|
+
id: '200004',
|
|
35
|
+
title: 'Grandchild',
|
|
36
|
+
body: '<p>Grandchild content</p>',
|
|
37
|
+
version: 1,
|
|
38
|
+
parentId: '200002',
|
|
39
|
+
modifiedDate: '2025-10-04T10:00:00.000Z',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const mockSinglePage: Page = {
|
|
43
|
+
id: '300001',
|
|
44
|
+
title: 'Single Page',
|
|
45
|
+
body: '<p>Content</p>',
|
|
46
|
+
version: 1,
|
|
47
|
+
modifiedDate: '2025-10-01T10:00:00.000Z',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export const mockQueuePage: Page = {
|
|
51
|
+
id: '500002',
|
|
52
|
+
title: 'Queue Page',
|
|
53
|
+
body: '<p>Queue content</p>',
|
|
54
|
+
version: 1,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const mockIndexPage: Page = {
|
|
58
|
+
id: '600001',
|
|
59
|
+
title: 'Index Page',
|
|
60
|
+
body: '<p>Index content</p>',
|
|
61
|
+
version: 1,
|
|
62
|
+
};
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"allowImportingTsExtensions": false,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"forceConsistentCasingInFileNames": true,
|
|
9
|
+
"strict": false,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"rootDir": "src",
|
|
14
|
+
"outDir": "dist",
|
|
15
|
+
"declaration": true,
|
|
16
|
+
"sourceMap": true,
|
|
17
|
+
"baseUrl": ".",
|
|
18
|
+
"paths": {
|
|
19
|
+
"@/cleanupRules/*": ["src/transform/cleanupRules/*"],
|
|
20
|
+
"@/cleanup": ["src/services/markdownCleanupService"]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"include": ["src"],
|
|
24
|
+
"exclude": ["node_modules", "dist", "tests"]
|
|
25
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import { resolve } from 'path';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
build: {
|
|
6
|
+
lib: {
|
|
7
|
+
entry: resolve(__dirname, 'src/index.ts'),
|
|
8
|
+
formats: ['es'],
|
|
9
|
+
fileName: 'index',
|
|
10
|
+
},
|
|
11
|
+
rollupOptions: {
|
|
12
|
+
external: [
|
|
13
|
+
// Node.js built-ins
|
|
14
|
+
'fs',
|
|
15
|
+
'path',
|
|
16
|
+
'url',
|
|
17
|
+
'util',
|
|
18
|
+
'stream',
|
|
19
|
+
'crypto',
|
|
20
|
+
'events',
|
|
21
|
+
'buffer',
|
|
22
|
+
'process',
|
|
23
|
+
// Dependencies
|
|
24
|
+
'dotenv',
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
outDir: 'dist',
|
|
28
|
+
sourcemap: true,
|
|
29
|
+
target: 'node18',
|
|
30
|
+
minify: false,
|
|
31
|
+
},
|
|
32
|
+
resolve: {
|
|
33
|
+
alias: {
|
|
34
|
+
'@/cleanupRules': resolve(__dirname, './src/transform/cleanupRules'),
|
|
35
|
+
'@/cleanup': resolve(__dirname, './src/services/markdownCleanupService'),
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
// Optimize dependencies for Node.js
|
|
39
|
+
optimizeDeps: {
|
|
40
|
+
disabled: true,
|
|
41
|
+
},
|
|
42
|
+
ssr: {
|
|
43
|
+
noExternal: true,
|
|
44
|
+
},
|
|
45
|
+
});
|