@reapit/elements 5.0.0-beta.71 → 5.0.0-beta.73
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/bin/elements.cjs +3 -5
- package/dist/codemods/at-a-glance-article-card/transform.js +253570 -0
- package/dist/codemods/bin.js +315 -0
- package/dist/codemods/codemods.js +44 -0
- package/{codemods → dist/codemods}/manifest.json +1 -1
- package/dist/codemods/runner.js +147 -0
- package/package.json +8 -6
- package/codemods/__tests__/codemods.test.ts +0 -178
- package/codemods/__tests__/generate-manifest.test.ts +0 -240
- package/codemods/__tests__/readme-parser.test.ts +0 -218
- package/codemods/__tests__/runner.test.ts +0 -530
- package/codemods/at-a-glance-article-card/README.md +0 -122
- package/codemods/at-a-glance-article-card/__tests__/transform.test.ts +0 -390
- package/codemods/at-a-glance-article-card/transform.ts +0 -291
- package/codemods/bin.ts +0 -205
- package/codemods/codemods.ts +0 -75
- package/codemods/generate-manifest.ts +0 -120
- package/codemods/manifest.schema.json +0 -39
- package/codemods/readme-parser.ts +0 -37
- package/codemods/runner.ts +0 -196
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
import { writeFileSync } from 'node:fs'
|
|
2
|
-
import { join, dirname } from 'node:path'
|
|
3
|
-
import { fileURLToPath } from 'node:url'
|
|
4
|
-
import { listCodemods, getCodemodReadme, getCodemodDescription, validateCodemodName } from '../codemods'
|
|
5
|
-
|
|
6
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
7
|
-
const __dirname = dirname(__filename)
|
|
8
|
-
const codemodDir = join(__dirname, '..')
|
|
9
|
-
|
|
10
|
-
describe('listCodemods', () => {
|
|
11
|
-
test('returns codemods from manifest', () => {
|
|
12
|
-
const result = listCodemods()
|
|
13
|
-
|
|
14
|
-
// Should return at least the codemods we know exist
|
|
15
|
-
expect(result).toContain('at-a-glance-article-card')
|
|
16
|
-
expect(Array.isArray(result)).toBe(true)
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
test('returns a copy to prevent external mutation', () => {
|
|
20
|
-
const result1 = listCodemods()
|
|
21
|
-
const result2 = listCodemods()
|
|
22
|
-
|
|
23
|
-
expect(result1).toEqual(result2)
|
|
24
|
-
expect(result1).not.toBe(result2) // Different array instances
|
|
25
|
-
})
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
describe('getCodemodReadme', () => {
|
|
29
|
-
test('returns README content when it exists', () => {
|
|
30
|
-
const result = getCodemodReadme('at-a-glance-article-card')
|
|
31
|
-
|
|
32
|
-
expect(result).not.toBeNull()
|
|
33
|
-
expect(typeof result).toBe('string')
|
|
34
|
-
expect(result!.length).toBeGreaterThan(0)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
test('returns null when README does not exist', () => {
|
|
38
|
-
const result = getCodemodReadme('nonexistent-codemod' as any)
|
|
39
|
-
|
|
40
|
-
expect(result).toBeNull()
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
test('reads complete README with front matter and body', () => {
|
|
44
|
-
const result = getCodemodReadme('at-a-glance-article-card')
|
|
45
|
-
|
|
46
|
-
expect(result).not.toBeNull()
|
|
47
|
-
// Check for front matter markers
|
|
48
|
-
expect(result).toContain('---')
|
|
49
|
-
// Check for typical README content
|
|
50
|
-
expect(result).toMatch(/[#\s]/i)
|
|
51
|
-
})
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
describe('getCodemodDescription', () => {
|
|
55
|
-
test('returns description from manifest', () => {
|
|
56
|
-
const result = getCodemodDescription('at-a-glance-article-card')
|
|
57
|
-
|
|
58
|
-
expect(result).toBe('Migrates AtAGlance.Card to AtAGlance.ArticleCard')
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
test('returns null when codemod does not exist in manifest', () => {
|
|
62
|
-
const result = getCodemodDescription('nonexistent-codemod')
|
|
63
|
-
|
|
64
|
-
expect(result).toBeNull()
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
test('returns description for all codemods in manifest', () => {
|
|
68
|
-
const codemods = listCodemods()
|
|
69
|
-
|
|
70
|
-
for (const name of codemods) {
|
|
71
|
-
const description = getCodemodDescription(name)
|
|
72
|
-
// Description can be null or a string
|
|
73
|
-
expect(description === null || typeof description === 'string').toBe(true)
|
|
74
|
-
}
|
|
75
|
-
})
|
|
76
|
-
})
|
|
77
|
-
|
|
78
|
-
describe('validateCodemodName', () => {
|
|
79
|
-
test('returns name when codemod exists in manifest', () => {
|
|
80
|
-
const result = validateCodemodName('at-a-glance-article-card')
|
|
81
|
-
|
|
82
|
-
expect(result).toBe('at-a-glance-article-card')
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
test('returns null when codemod does not exist', () => {
|
|
86
|
-
const result = validateCodemodName('nonexistent-codemod')
|
|
87
|
-
|
|
88
|
-
expect(result).toBeNull()
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
test('prevents path traversal attacks with ../', () => {
|
|
92
|
-
const result = validateCodemodName('../../../etc/passwd')
|
|
93
|
-
|
|
94
|
-
expect(result).toBeNull()
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
test('prevents path traversal attacks with absolute paths', () => {
|
|
98
|
-
const result = validateCodemodName('/etc/passwd')
|
|
99
|
-
|
|
100
|
-
expect(result).toBeNull()
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
test('prevents path traversal with encoded characters', () => {
|
|
104
|
-
const result = validateCodemodName('..%2F..%2Fetc%2Fpasswd')
|
|
105
|
-
|
|
106
|
-
expect(result).toBeNull()
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
test('prevents path components in name', () => {
|
|
110
|
-
const result = validateCodemodName('at-a-glance-article-card/index.ts')
|
|
111
|
-
|
|
112
|
-
expect(result).toBeNull()
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
test('case-sensitive validation', () => {
|
|
116
|
-
const result = validateCodemodName('AT-A-GLANCE-ARTICLE-CARD')
|
|
117
|
-
|
|
118
|
-
expect(result).toBeNull()
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
test('validates all codemods from manifest correctly', () => {
|
|
122
|
-
const codemods = listCodemods()
|
|
123
|
-
|
|
124
|
-
for (const name of codemods) {
|
|
125
|
-
const validated = validateCodemodName(name)
|
|
126
|
-
expect(validated).toBe(name)
|
|
127
|
-
}
|
|
128
|
-
})
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
describe('integration tests', () => {
|
|
132
|
-
test('workflow: list codemods, validate, and get description', () => {
|
|
133
|
-
// List codemods
|
|
134
|
-
const codemods = listCodemods()
|
|
135
|
-
expect(codemods.length).toBeGreaterThan(0)
|
|
136
|
-
expect(codemods).toContain('at-a-glance-article-card')
|
|
137
|
-
|
|
138
|
-
// Validate a known codemod
|
|
139
|
-
const validated = validateCodemodName('at-a-glance-article-card')
|
|
140
|
-
expect(validated).toBe('at-a-glance-article-card')
|
|
141
|
-
|
|
142
|
-
// Get description
|
|
143
|
-
const description = getCodemodDescription(validated!)
|
|
144
|
-
expect(description).toBe('Migrates AtAGlance.Card to AtAGlance.ArticleCard')
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
test('workflow: attempt to access invalid codemod', () => {
|
|
148
|
-
// List shows only valid codemods
|
|
149
|
-
const codemods = listCodemods()
|
|
150
|
-
expect(codemods.length).toBeGreaterThan(0)
|
|
151
|
-
|
|
152
|
-
// Attempt to validate malicious input
|
|
153
|
-
const validated = validateCodemodName('../../etc/passwd')
|
|
154
|
-
expect(validated).toBeNull()
|
|
155
|
-
|
|
156
|
-
// No description accessible for invalid name
|
|
157
|
-
const description = getCodemodDescription('../../etc/passwd')
|
|
158
|
-
expect(description).toBeNull()
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
test('consistency: manifest and file system match', () => {
|
|
162
|
-
const codemods = listCodemods()
|
|
163
|
-
|
|
164
|
-
// All codemods in manifest should have their directories accessible
|
|
165
|
-
for (const name of codemods) {
|
|
166
|
-
const validated = validateCodemodName(name)
|
|
167
|
-
expect(validated).toBe(name)
|
|
168
|
-
|
|
169
|
-
// Should be able to get README (or null if missing, which is OK)
|
|
170
|
-
const readme = getCodemodReadme(name)
|
|
171
|
-
expect(readme === null || typeof readme === 'string').toBe(true)
|
|
172
|
-
|
|
173
|
-
// Should be able to get description from manifest
|
|
174
|
-
const description = getCodemodDescription(name)
|
|
175
|
-
expect(description === null || typeof description === 'string').toBe(true)
|
|
176
|
-
}
|
|
177
|
-
})
|
|
178
|
-
})
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, writeFileSync, rmSync } from 'node:fs'
|
|
2
|
-
import { join } from 'node:path'
|
|
3
|
-
import { tmpdir } from 'node:os'
|
|
4
|
-
import { discoverCodemods, getCodemodMetadata } from '../generate-manifest'
|
|
5
|
-
|
|
6
|
-
let testDir: string
|
|
7
|
-
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
// Create a temporary test directory
|
|
10
|
-
testDir = join(tmpdir(), `codemods-test-${Date.now()}`)
|
|
11
|
-
mkdirSync(testDir, { recursive: true })
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
afterEach(() => {
|
|
15
|
-
// Clean up test directory
|
|
16
|
-
if (existsSync(testDir)) {
|
|
17
|
-
rmSync(testDir, { recursive: true, force: true })
|
|
18
|
-
}
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
describe('discoverCodemods', () => {
|
|
22
|
-
test('returns empty array when directory has no codemods', () => {
|
|
23
|
-
const result = discoverCodemods(testDir)
|
|
24
|
-
expect(result).toEqual([])
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
test('returns codemod names that have transform.ts files', () => {
|
|
28
|
-
// Create valid codemod directories
|
|
29
|
-
const codemod1Dir = join(testDir, 'codemod-1')
|
|
30
|
-
const codemod2Dir = join(testDir, 'codemod-2')
|
|
31
|
-
|
|
32
|
-
mkdirSync(codemod1Dir)
|
|
33
|
-
mkdirSync(codemod2Dir)
|
|
34
|
-
|
|
35
|
-
writeFileSync(join(codemod1Dir, 'transform.ts'), 'export default function() {}')
|
|
36
|
-
writeFileSync(join(codemod2Dir, 'transform.ts'), 'export default function() {}')
|
|
37
|
-
|
|
38
|
-
const result = discoverCodemods(testDir)
|
|
39
|
-
|
|
40
|
-
expect(result).toHaveLength(2)
|
|
41
|
-
expect(result).toContain('codemod-1')
|
|
42
|
-
expect(result).toContain('codemod-2')
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
test('ignores directories without transform.ts files', () => {
|
|
46
|
-
const validDir = join(testDir, 'valid-codemod')
|
|
47
|
-
const invalidDir = join(testDir, 'invalid-codemod')
|
|
48
|
-
|
|
49
|
-
mkdirSync(validDir)
|
|
50
|
-
mkdirSync(invalidDir)
|
|
51
|
-
|
|
52
|
-
writeFileSync(join(validDir, 'transform.ts'), 'export default function() {}')
|
|
53
|
-
writeFileSync(join(invalidDir, 'index.ts'), 'export default function() {}')
|
|
54
|
-
|
|
55
|
-
const result = discoverCodemods(testDir)
|
|
56
|
-
|
|
57
|
-
expect(result).toHaveLength(1)
|
|
58
|
-
expect(result).toContain('valid-codemod')
|
|
59
|
-
expect(result).not.toContain('invalid-codemod')
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
test('ignores files in the root directory', () => {
|
|
63
|
-
const validDir = join(testDir, 'valid-codemod')
|
|
64
|
-
mkdirSync(validDir)
|
|
65
|
-
writeFileSync(join(validDir, 'transform.ts'), 'export default function() {}')
|
|
66
|
-
|
|
67
|
-
// Create some files in root that should be ignored
|
|
68
|
-
writeFileSync(join(testDir, 'transform.ts'), 'export default function() {}')
|
|
69
|
-
writeFileSync(join(testDir, 'runner.ts'), 'export function run() {}')
|
|
70
|
-
|
|
71
|
-
const result = discoverCodemods(testDir)
|
|
72
|
-
|
|
73
|
-
expect(result).toHaveLength(1)
|
|
74
|
-
expect(result).toContain('valid-codemod')
|
|
75
|
-
})
|
|
76
|
-
|
|
77
|
-
test('returns codemods in consistent alphabetical order', () => {
|
|
78
|
-
const names = ['zebra', 'alpha', 'beta']
|
|
79
|
-
|
|
80
|
-
for (const name of names) {
|
|
81
|
-
const dir = join(testDir, name)
|
|
82
|
-
mkdirSync(dir)
|
|
83
|
-
writeFileSync(join(dir, 'transform.ts'), 'export default function() {}')
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const result = discoverCodemods(testDir)
|
|
87
|
-
|
|
88
|
-
expect(result).toEqual(['alpha', 'beta', 'zebra'])
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
test('ignores special directories', () => {
|
|
92
|
-
const validDir = join(testDir, 'valid-codemod')
|
|
93
|
-
const nodeModulesDir = join(testDir, 'node_modules')
|
|
94
|
-
const testsDir = join(testDir, '__tests__')
|
|
95
|
-
const hiddenDir = join(testDir, '.hidden')
|
|
96
|
-
|
|
97
|
-
mkdirSync(validDir)
|
|
98
|
-
mkdirSync(nodeModulesDir)
|
|
99
|
-
mkdirSync(testsDir)
|
|
100
|
-
mkdirSync(hiddenDir)
|
|
101
|
-
|
|
102
|
-
writeFileSync(join(validDir, 'transform.ts'), 'export default function() {}')
|
|
103
|
-
writeFileSync(join(nodeModulesDir, 'transform.ts'), 'export default function() {}')
|
|
104
|
-
writeFileSync(join(testsDir, 'transform.ts'), 'export default function() {}')
|
|
105
|
-
writeFileSync(join(hiddenDir, 'transform.ts'), 'export default function() {}')
|
|
106
|
-
|
|
107
|
-
const result = discoverCodemods(testDir)
|
|
108
|
-
|
|
109
|
-
expect(result).toHaveLength(1)
|
|
110
|
-
expect(result).toContain('valid-codemod')
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
test('handles non-existent directory gracefully', () => {
|
|
114
|
-
const nonExistentDir = join(testDir, 'does-not-exist')
|
|
115
|
-
const result = discoverCodemods(nonExistentDir)
|
|
116
|
-
|
|
117
|
-
expect(result).toEqual([])
|
|
118
|
-
})
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
describe('getCodemodMetadata', () => {
|
|
122
|
-
test('extracts description from README front matter', () => {
|
|
123
|
-
const codemodDir = join(testDir, 'test-codemod')
|
|
124
|
-
mkdirSync(codemodDir)
|
|
125
|
-
|
|
126
|
-
const readmeContent = `---
|
|
127
|
-
description: Transforms old API to new API
|
|
128
|
-
---
|
|
129
|
-
# Documentation
|
|
130
|
-
|
|
131
|
-
Body content.`
|
|
132
|
-
|
|
133
|
-
writeFileSync(join(codemodDir, 'README.md'), readmeContent)
|
|
134
|
-
|
|
135
|
-
const result = getCodemodMetadata(testDir, 'test-codemod')
|
|
136
|
-
|
|
137
|
-
expect(result).toEqual({
|
|
138
|
-
name: 'test-codemod',
|
|
139
|
-
description: 'Transforms old API to new API',
|
|
140
|
-
})
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
test('returns null description when README does not exist', () => {
|
|
144
|
-
const codemodDir = join(testDir, 'test-codemod')
|
|
145
|
-
mkdirSync(codemodDir)
|
|
146
|
-
|
|
147
|
-
const result = getCodemodMetadata(testDir, 'test-codemod')
|
|
148
|
-
|
|
149
|
-
expect(result).toEqual({
|
|
150
|
-
name: 'test-codemod',
|
|
151
|
-
description: null,
|
|
152
|
-
})
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
test('returns null description when README has no front matter', () => {
|
|
156
|
-
const codemodDir = join(testDir, 'test-codemod')
|
|
157
|
-
mkdirSync(codemodDir)
|
|
158
|
-
|
|
159
|
-
writeFileSync(join(codemodDir, 'README.md'), '# Test\n\nNo front matter.')
|
|
160
|
-
|
|
161
|
-
const result = getCodemodMetadata(testDir, 'test-codemod')
|
|
162
|
-
|
|
163
|
-
expect(result).toEqual({
|
|
164
|
-
name: 'test-codemod',
|
|
165
|
-
description: null,
|
|
166
|
-
})
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
test('returns null description when front matter has no description', () => {
|
|
170
|
-
const codemodDir = join(testDir, 'test-codemod')
|
|
171
|
-
mkdirSync(codemodDir)
|
|
172
|
-
|
|
173
|
-
const readmeContent = `---
|
|
174
|
-
author: John Doe
|
|
175
|
-
---
|
|
176
|
-
# Documentation`
|
|
177
|
-
|
|
178
|
-
writeFileSync(join(codemodDir, 'README.md'), readmeContent)
|
|
179
|
-
|
|
180
|
-
const result = getCodemodMetadata(testDir, 'test-codemod')
|
|
181
|
-
|
|
182
|
-
expect(result).toEqual({
|
|
183
|
-
name: 'test-codemod',
|
|
184
|
-
description: null,
|
|
185
|
-
})
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
test('extracts description with special characters', () => {
|
|
189
|
-
const codemodDir = join(testDir, 'test-codemod')
|
|
190
|
-
mkdirSync(codemodDir)
|
|
191
|
-
|
|
192
|
-
const readmeContent = `---
|
|
193
|
-
description: Transforms @reapit/elements v4 -> v5
|
|
194
|
-
---
|
|
195
|
-
# Documentation`
|
|
196
|
-
|
|
197
|
-
writeFileSync(join(codemodDir, 'README.md'), readmeContent)
|
|
198
|
-
|
|
199
|
-
const result = getCodemodMetadata(testDir, 'test-codemod')
|
|
200
|
-
|
|
201
|
-
expect(result).toEqual({
|
|
202
|
-
name: 'test-codemod',
|
|
203
|
-
description: 'Transforms @reapit/elements v4 -> v5',
|
|
204
|
-
})
|
|
205
|
-
})
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
describe('integration', () => {
|
|
209
|
-
test('workflow: discover codemods and extract metadata', () => {
|
|
210
|
-
// Create codemods
|
|
211
|
-
const codemod1Dir = join(testDir, 'my-codemod')
|
|
212
|
-
const codemod2Dir = join(testDir, 'another-codemod')
|
|
213
|
-
|
|
214
|
-
mkdirSync(codemod1Dir)
|
|
215
|
-
mkdirSync(codemod2Dir)
|
|
216
|
-
|
|
217
|
-
writeFileSync(join(codemod1Dir, 'transform.ts'), 'export default function() {}')
|
|
218
|
-
writeFileSync(
|
|
219
|
-
join(codemod1Dir, 'README.md'),
|
|
220
|
-
`---
|
|
221
|
-
description: My awesome codemod
|
|
222
|
-
---
|
|
223
|
-
# My Codemod`,
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
writeFileSync(join(codemod2Dir, 'transform.ts'), 'export default function() {}')
|
|
227
|
-
|
|
228
|
-
// Discover codemods
|
|
229
|
-
const codemods = discoverCodemods(testDir)
|
|
230
|
-
expect(codemods).toEqual(['another-codemod', 'my-codemod'])
|
|
231
|
-
|
|
232
|
-
// Get metadata for each
|
|
233
|
-
const metadata = codemods.map((name) => getCodemodMetadata(testDir, name))
|
|
234
|
-
|
|
235
|
-
expect(metadata).toEqual([
|
|
236
|
-
{ name: 'another-codemod', description: null },
|
|
237
|
-
{ name: 'my-codemod', description: 'My awesome codemod' },
|
|
238
|
-
])
|
|
239
|
-
})
|
|
240
|
-
})
|
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import { parseFrontMatter } from '../readme-parser'
|
|
2
|
-
|
|
3
|
-
describe('parseFrontMatter', () => {
|
|
4
|
-
test('parses front matter with description', () => {
|
|
5
|
-
const content = `---
|
|
6
|
-
description: My description
|
|
7
|
-
---
|
|
8
|
-
# My Content
|
|
9
|
-
|
|
10
|
-
Some body text.`
|
|
11
|
-
|
|
12
|
-
const result = parseFrontMatter(content)
|
|
13
|
-
|
|
14
|
-
expect(result).toEqual({
|
|
15
|
-
description: 'My description',
|
|
16
|
-
body: '# My Content\n\nSome body text.',
|
|
17
|
-
})
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
test('parses front matter with multiple fields, extracting only description', () => {
|
|
21
|
-
const content = `---
|
|
22
|
-
description: Transform components
|
|
23
|
-
author: John Doe
|
|
24
|
-
version: 1.0.0
|
|
25
|
-
---
|
|
26
|
-
# Documentation
|
|
27
|
-
|
|
28
|
-
This is the body.`
|
|
29
|
-
|
|
30
|
-
const result = parseFrontMatter(content)
|
|
31
|
-
|
|
32
|
-
expect(result).toEqual({
|
|
33
|
-
description: 'Transform components',
|
|
34
|
-
body: '# Documentation\n\nThis is the body.',
|
|
35
|
-
})
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
test('returns body only when no front matter exists', () => {
|
|
39
|
-
const content = `# My Content
|
|
40
|
-
|
|
41
|
-
This has no front matter.`
|
|
42
|
-
|
|
43
|
-
const result = parseFrontMatter(content)
|
|
44
|
-
|
|
45
|
-
expect(result).toEqual({
|
|
46
|
-
body: content,
|
|
47
|
-
})
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
test('returns body only when front matter has no description', () => {
|
|
51
|
-
const content = `---
|
|
52
|
-
author: Jane Smith
|
|
53
|
-
---
|
|
54
|
-
# Content
|
|
55
|
-
|
|
56
|
-
Body text here.`
|
|
57
|
-
|
|
58
|
-
const result = parseFrontMatter(content)
|
|
59
|
-
|
|
60
|
-
expect(result).toEqual({
|
|
61
|
-
description: undefined,
|
|
62
|
-
body: '# Content\n\nBody text here.',
|
|
63
|
-
})
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
test('handles empty body after front matter', () => {
|
|
67
|
-
const content = `---
|
|
68
|
-
description: Empty body
|
|
69
|
-
---
|
|
70
|
-
`
|
|
71
|
-
|
|
72
|
-
const result = parseFrontMatter(content)
|
|
73
|
-
|
|
74
|
-
expect(result).toEqual({
|
|
75
|
-
description: 'Empty body',
|
|
76
|
-
body: '',
|
|
77
|
-
})
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
test('handles empty content', () => {
|
|
81
|
-
const content = ''
|
|
82
|
-
|
|
83
|
-
const result = parseFrontMatter(content)
|
|
84
|
-
|
|
85
|
-
expect(result).toEqual({
|
|
86
|
-
body: '',
|
|
87
|
-
})
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
test('trims whitespace from body', () => {
|
|
91
|
-
const content = `---
|
|
92
|
-
description: Test
|
|
93
|
-
---
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
# Header
|
|
97
|
-
|
|
98
|
-
Content
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
`
|
|
102
|
-
|
|
103
|
-
const result = parseFrontMatter(content)
|
|
104
|
-
|
|
105
|
-
expect(result.body).toBe('# Header\n\nContent')
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
test('handles description with special characters', () => {
|
|
109
|
-
const content = `---
|
|
110
|
-
description: Transforms @reapit/elements v4 -> v5
|
|
111
|
-
---
|
|
112
|
-
Body content`
|
|
113
|
-
|
|
114
|
-
const result = parseFrontMatter(content)
|
|
115
|
-
|
|
116
|
-
expect(result.description).toBe('Transforms @reapit/elements v4 -> v5')
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
test('handles description with quotes', () => {
|
|
120
|
-
const content = `---
|
|
121
|
-
description: "Quoted description"
|
|
122
|
-
---
|
|
123
|
-
Body`
|
|
124
|
-
|
|
125
|
-
const result = parseFrontMatter(content)
|
|
126
|
-
|
|
127
|
-
expect(result.description).toBe('"Quoted description"')
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
test('handles multiline content in body', () => {
|
|
131
|
-
const content = `---
|
|
132
|
-
description: Short desc
|
|
133
|
-
---
|
|
134
|
-
# Heading 1
|
|
135
|
-
|
|
136
|
-
Paragraph 1
|
|
137
|
-
|
|
138
|
-
## Heading 2
|
|
139
|
-
|
|
140
|
-
Paragraph 2
|
|
141
|
-
|
|
142
|
-
\`\`\`typescript
|
|
143
|
-
code here
|
|
144
|
-
\`\`\`
|
|
145
|
-
|
|
146
|
-
Final paragraph.`
|
|
147
|
-
|
|
148
|
-
const result = parseFrontMatter(content)
|
|
149
|
-
|
|
150
|
-
expect(result.body).toContain('# Heading 1')
|
|
151
|
-
expect(result.body).toContain('## Heading 2')
|
|
152
|
-
expect(result.body).toContain('```typescript')
|
|
153
|
-
expect(result.body).toContain('Final paragraph.')
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
test('does not parse invalid front matter format', () => {
|
|
157
|
-
const content = `--
|
|
158
|
-
description: Invalid
|
|
159
|
-
--
|
|
160
|
-
Body`
|
|
161
|
-
|
|
162
|
-
const result = parseFrontMatter(content)
|
|
163
|
-
|
|
164
|
-
expect(result).toEqual({
|
|
165
|
-
body: content,
|
|
166
|
-
})
|
|
167
|
-
})
|
|
168
|
-
|
|
169
|
-
test('handles front matter without closing delimiter', () => {
|
|
170
|
-
const content = `---
|
|
171
|
-
description: No closing
|
|
172
|
-
Body content`
|
|
173
|
-
|
|
174
|
-
const result = parseFrontMatter(content)
|
|
175
|
-
|
|
176
|
-
expect(result).toEqual({
|
|
177
|
-
body: content,
|
|
178
|
-
})
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
test('extracts first description when multiple exist', () => {
|
|
182
|
-
const content = `---
|
|
183
|
-
description: First description
|
|
184
|
-
description: Second description
|
|
185
|
-
---
|
|
186
|
-
Body`
|
|
187
|
-
|
|
188
|
-
const result = parseFrontMatter(content)
|
|
189
|
-
|
|
190
|
-
expect(result.description).toBe('First description')
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
test('handles description with colons in value', () => {
|
|
194
|
-
const content = `---
|
|
195
|
-
description: Transform: Old API to New API
|
|
196
|
-
---
|
|
197
|
-
Body`
|
|
198
|
-
|
|
199
|
-
const result = parseFrontMatter(content)
|
|
200
|
-
|
|
201
|
-
expect(result.description).toBe('Transform: Old API to New API')
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
test('preserves newlines in body', () => {
|
|
205
|
-
const content = `---
|
|
206
|
-
description: Test
|
|
207
|
-
---
|
|
208
|
-
Line 1
|
|
209
|
-
|
|
210
|
-
Line 2
|
|
211
|
-
|
|
212
|
-
Line 3`
|
|
213
|
-
|
|
214
|
-
const result = parseFrontMatter(content)
|
|
215
|
-
|
|
216
|
-
expect(result.body).toBe('Line 1\n\nLine 2\n\nLine 3')
|
|
217
|
-
})
|
|
218
|
-
})
|