Skip to content

Commit 351905f

Browse files
feat(API): add text content API
1 parent 355d698 commit 351905f

File tree

21 files changed

+2333
-29
lines changed

21 files changed

+2333
-29
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@ pnpm-debug.log*
2929
.eslintcache
3030

3131
## Ignore content.ts
32-
src/content.ts
32+
src/content.ts
33+
34+
coverage/

cli/__tests__/createCollectionContent.test.ts

Lines changed: 213 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { createCollectionContent } from '../createCollectionContent'
22
import { getConfig } from '../getConfig'
33
import { writeFile } from 'fs/promises'
4-
import { existsSync } from 'fs'
4+
import { existsSync, readFileSync } from 'fs'
55

66
jest.mock('../getConfig')
77
jest.mock('fs/promises')
@@ -53,18 +53,19 @@ it('should call writeFile with the expected file location and content without th
5353
const mockContent = [
5454
{ name: 'test', base: 'src/docs', pattern: '**/*.md' }
5555
]
56-
;(getConfig as jest.Mock).mockResolvedValue({
56+
;(getConfig as jest.Mock).mockResolvedValue({
5757
content: mockContent,
5858
repoRoot: '.'
5959
})
60+
;(existsSync as jest.Mock).mockReturnValue(false) // No package.json
6061

6162
const mockConsoleError = jest.fn()
6263
jest.spyOn(console, 'error').mockImplementation(mockConsoleError)
6364

6465
await createCollectionContent('/foo/', '/config/dir/pf-docs.config.mjs', false)
6566

6667
const expectedContent = [
67-
{ name: 'test', base: '/config/dir/src/docs', pattern: '**/*.md' }
68+
{ name: 'test', base: '/config/dir/src/docs', pattern: '**/*.md', version: null }
6869
]
6970

7071
expect(writeFile).toHaveBeenCalledWith(
@@ -78,10 +79,11 @@ it('should log error if writeFile throws an error', async () => {
7879
const mockContent = [
7980
{ name: 'test', base: 'src/docs', pattern: '**/*.md' }
8081
]
81-
;(getConfig as jest.Mock).mockResolvedValue({
82+
;(getConfig as jest.Mock).mockResolvedValue({
8283
content: mockContent,
8384
repoRoot: '.'
8485
})
86+
;(existsSync as jest.Mock).mockReturnValue(false) // No package.json
8587

8688
const mockConsoleError = jest.fn()
8789
jest.spyOn(console, 'error').mockImplementation(mockConsoleError)
@@ -102,11 +104,12 @@ it('should log all verbose messages when run in verbose mode', async () => {
102104
{ name: 'docs', base: 'src/docs', pattern: '**/*.md' },
103105
{ name: 'components', packageName: '@patternfly/react-core', pattern: '**/*.md' }
104106
]
105-
;(getConfig as jest.Mock).mockResolvedValue({
107+
;(getConfig as jest.Mock).mockResolvedValue({
106108
content: mockContent,
107109
repoRoot: '../'
108110
})
109111
;(existsSync as jest.Mock).mockReturnValue(true)
112+
;(readFileSync as jest.Mock).mockReturnValue(JSON.stringify({ version: '6.2.2' }))
110113
;(writeFile as jest.Mock).mockResolvedValue(undefined)
111114

112115
const mockConsoleLog = jest.fn()
@@ -118,15 +121,19 @@ it('should log all verbose messages when run in verbose mode', async () => {
118121
expect(mockConsoleLog).toHaveBeenCalledWith('configuration content entry: ', mockContent, '\n')
119122
expect(mockConsoleLog).toHaveBeenCalledWith('Creating content file', '/foo/src/content.ts', '\n')
120123
expect(mockConsoleLog).toHaveBeenCalledWith('repoRootDir: ', '/config', '\n')
121-
124+
122125
// For the base entry
123126
expect(mockConsoleLog).toHaveBeenCalledWith('relative path: ', 'src/docs')
124127
expect(mockConsoleLog).toHaveBeenCalledWith('absolute path: ', '/config/dir/src/docs', '\n')
125-
128+
126129
// For the packageName entry
127130
expect(mockConsoleLog).toHaveBeenCalledWith('looking for package in ', '/config/dir/node_modules', '\n')
128131
expect(mockConsoleLog).toHaveBeenCalledWith('found package at ', '/config/dir/node_modules/@patternfly/react-core', '\n')
129-
132+
133+
// Version extraction logs
134+
expect(mockConsoleLog).toHaveBeenCalledWith('Extracted version v6 from /config/dir/src/docs/package.json\n')
135+
expect(mockConsoleLog).toHaveBeenCalledWith('Extracted version v6 from /config/dir/node_modules/@patternfly/react-core/package.json\n')
136+
130137
// Final log
131138
expect(mockConsoleLog).toHaveBeenCalledWith('Content file created')
132139
})
@@ -177,10 +184,11 @@ it('should not log to the console when not run in verbose mode', async () => {
177184
const mockContent = [
178185
{ name: 'test', base: 'src/docs', pattern: '**/*.md' }
179186
]
180-
;(getConfig as jest.Mock).mockResolvedValue({
187+
;(getConfig as jest.Mock).mockResolvedValue({
181188
content: mockContent,
182189
repoRoot: '.'
183190
})
191+
;(existsSync as jest.Mock).mockReturnValue(false)
184192
;(writeFile as jest.Mock).mockResolvedValue(undefined)
185193

186194
const mockConsoleLog = jest.fn()
@@ -195,11 +203,12 @@ it('should handle content with packageName by finding package in node_modules',
195203
const mockContent = [
196204
{ name: 'test', packageName: '@patternfly/react-core', pattern: '**/*.md' }
197205
]
198-
;(getConfig as jest.Mock).mockResolvedValue({
206+
;(getConfig as jest.Mock).mockResolvedValue({
199207
content: mockContent,
200208
repoRoot: '.'
201209
})
202210
;(existsSync as jest.Mock).mockReturnValue(true)
211+
;(readFileSync as jest.Mock).mockReturnValue(JSON.stringify({ version: '6.2.2' }))
203212
;(writeFile as jest.Mock).mockResolvedValue(undefined)
204213

205214
const mockConsoleError = jest.fn()
@@ -208,10 +217,11 @@ it('should handle content with packageName by finding package in node_modules',
208217
await createCollectionContent('/foo/', '/config/dir/pf-docs.config.mjs', false)
209218

210219
const expectedContent = [
211-
{
220+
{
212221
base: '/config/dir/node_modules/@patternfly/react-core',
213-
name: 'test',
214-
packageName: '@patternfly/react-core',
222+
version: 'v6',
223+
name: 'test',
224+
packageName: '@patternfly/react-core',
215225
pattern: '**/*.md'
216226
}
217227
]
@@ -227,13 +237,15 @@ it('should handle content with packageName when package is not found locally but
227237
const mockContent = [
228238
{ name: 'test', packageName: '@patternfly/react-core', pattern: '**/*.md' }
229239
]
230-
;(getConfig as jest.Mock).mockResolvedValue({
240+
;(getConfig as jest.Mock).mockResolvedValue({
231241
content: mockContent,
232242
repoRoot: '../../'
233243
})
234244
;(existsSync as jest.Mock)
235245
.mockReturnValueOnce(false) // not found in /config/dir/node_modules
236-
.mockReturnValueOnce(true) // found in /config/node_modules
246+
.mockReturnValueOnce(true) // found in /config/node_modules/package.json
247+
.mockReturnValueOnce(true) // package.json exists for version extraction
248+
;(readFileSync as jest.Mock).mockReturnValue(JSON.stringify({ version: '5.1.0' }))
237249
;(writeFile as jest.Mock).mockResolvedValue(undefined)
238250

239251
const mockConsoleError = jest.fn()
@@ -242,10 +254,11 @@ it('should handle content with packageName when package is not found locally but
242254
await createCollectionContent('/foo/', '/config/dir/pf-docs.config.mjs', false)
243255

244256
const expectedContent = [
245-
{
257+
{
246258
base: '/config/node_modules/@patternfly/react-core',
247-
name: 'test',
248-
packageName: '@patternfly/react-core',
259+
version: 'v5',
260+
name: 'test',
261+
packageName: '@patternfly/react-core',
249262
pattern: '**/*.md'
250263
}
251264
]
@@ -261,7 +274,7 @@ it('should handle content with packageName when package is not found anywhere',
261274
const mockContent = [
262275
{ name: 'test', packageName: '@patternfly/react-core', pattern: '**/*.md' }
263276
]
264-
;(getConfig as jest.Mock).mockResolvedValue({
277+
;(getConfig as jest.Mock).mockResolvedValue({
265278
content: mockContent,
266279
repoRoot: '../'
267280
})
@@ -274,10 +287,11 @@ it('should handle content with packageName when package is not found anywhere',
274287
await createCollectionContent('/foo/', '/config/dir/pf-docs.config.mjs', false)
275288

276289
const expectedContent = [
277-
{
290+
{
278291
base: null,
279-
name: 'test',
280-
packageName: '@patternfly/react-core',
292+
version: null,
293+
name: 'test',
294+
packageName: '@patternfly/react-core',
281295
pattern: '**/*.md'
282296
}
283297
]
@@ -294,11 +308,12 @@ it('should handle mixed content with both base and packageName entries', async (
294308
{ name: 'docs', base: 'src/docs', pattern: '**/*.md' },
295309
{ name: 'components', packageName: '@patternfly/react-core', pattern: '**/*.md' }
296310
]
297-
;(getConfig as jest.Mock).mockResolvedValue({
311+
;(getConfig as jest.Mock).mockResolvedValue({
298312
content: mockContent,
299313
repoRoot: '../'
300314
})
301315
;(existsSync as jest.Mock).mockReturnValue(true)
316+
;(readFileSync as jest.Mock).mockReturnValue(JSON.stringify({ version: '6.2.2' }))
302317
;(writeFile as jest.Mock).mockResolvedValue(undefined)
303318

304319
const mockConsoleError = jest.fn()
@@ -307,11 +322,12 @@ it('should handle mixed content with both base and packageName entries', async (
307322
await createCollectionContent('/foo/', '/config/dir/pf-docs.config.mjs', false)
308323

309324
const expectedContent = [
310-
{ name: 'docs', base: '/config/dir/src/docs', pattern: '**/*.md' },
311-
{
325+
{ name: 'docs', base: '/config/dir/src/docs', pattern: '**/*.md', version: 'v6' },
326+
{
312327
base: '/config/dir/node_modules/@patternfly/react-core',
313-
name: 'components',
314-
packageName: '@patternfly/react-core',
328+
version: 'v6',
329+
name: 'components',
330+
packageName: '@patternfly/react-core',
315331
pattern: '**/*.md'
316332
}
317333
]
@@ -322,3 +338,173 @@ it('should handle mixed content with both base and packageName entries', async (
322338
)
323339
expect(mockConsoleError).not.toHaveBeenCalled()
324340
})
341+
342+
describe('getPackageVersion function', () => {
343+
it('should extract major version from valid package.json with version 6.2.2', async () => {
344+
const mockContent = [
345+
{ name: 'test', base: 'src/docs', pattern: '**/*.md' }
346+
]
347+
;(getConfig as jest.Mock).mockResolvedValue({
348+
content: mockContent,
349+
repoRoot: '.'
350+
})
351+
;(existsSync as jest.Mock).mockReturnValue(true)
352+
;(readFileSync as jest.Mock).mockReturnValue(JSON.stringify({ version: '6.2.2' }))
353+
;(writeFile as jest.Mock).mockResolvedValue(undefined)
354+
355+
await createCollectionContent('/foo/', '/config/dir/pf-docs.config.mjs', false)
356+
357+
const expectedContent = [
358+
{ name: 'test', base: '/config/dir/src/docs', pattern: '**/*.md', version: 'v6' }
359+
]
360+
361+
expect(writeFile).toHaveBeenCalledWith(
362+
'/foo/src/content.ts',
363+
`export const content = ${JSON.stringify(expectedContent)}`,
364+
)
365+
})
366+
367+
it('should extract major version from valid package.json with version 5.1.0', async () => {
368+
const mockContent = [
369+
{ name: 'test', base: 'src/docs', pattern: '**/*.md' }
370+
]
371+
;(getConfig as jest.Mock).mockResolvedValue({
372+
content: mockContent,
373+
repoRoot: '.'
374+
})
375+
;(existsSync as jest.Mock).mockReturnValue(true)
376+
;(readFileSync as jest.Mock).mockReturnValue(JSON.stringify({ version: '5.1.0' }))
377+
;(writeFile as jest.Mock).mockResolvedValue(undefined)
378+
379+
await createCollectionContent('/foo/', '/config/dir/pf-docs.config.mjs', false)
380+
381+
const expectedContent = [
382+
{ name: 'test', base: '/config/dir/src/docs', pattern: '**/*.md', version: 'v5' }
383+
]
384+
385+
expect(writeFile).toHaveBeenCalledWith(
386+
'/foo/src/content.ts',
387+
`export const content = ${JSON.stringify(expectedContent)}`,
388+
)
389+
})
390+
391+
it('should return null version when package.json does not exist', async () => {
392+
const mockContent = [
393+
{ name: 'test', base: 'src/docs', pattern: '**/*.md' }
394+
]
395+
;(getConfig as jest.Mock).mockResolvedValue({
396+
content: mockContent,
397+
repoRoot: '.'
398+
})
399+
;(existsSync as jest.Mock).mockReturnValue(false)
400+
;(writeFile as jest.Mock).mockResolvedValue(undefined)
401+
402+
await createCollectionContent('/foo/', '/config/dir/pf-docs.config.mjs', false)
403+
404+
const expectedContent = [
405+
{ name: 'test', base: '/config/dir/src/docs', pattern: '**/*.md', version: null }
406+
]
407+
408+
expect(writeFile).toHaveBeenCalledWith(
409+
'/foo/src/content.ts',
410+
`export const content = ${JSON.stringify(expectedContent)}`,
411+
)
412+
})
413+
414+
it('should return null version when package.json has no version field', async () => {
415+
const mockContent = [
416+
{ name: 'test', base: 'src/docs', pattern: '**/*.md' }
417+
]
418+
;(getConfig as jest.Mock).mockResolvedValue({
419+
content: mockContent,
420+
repoRoot: '.'
421+
})
422+
;(existsSync as jest.Mock).mockReturnValue(true)
423+
;(readFileSync as jest.Mock).mockReturnValue(JSON.stringify({ name: 'test-package' }))
424+
;(writeFile as jest.Mock).mockResolvedValue(undefined)
425+
426+
await createCollectionContent('/foo/', '/config/dir/pf-docs.config.mjs', false)
427+
428+
const expectedContent = [
429+
{ name: 'test', base: '/config/dir/src/docs', pattern: '**/*.md', version: null }
430+
]
431+
432+
expect(writeFile).toHaveBeenCalledWith(
433+
'/foo/src/content.ts',
434+
`export const content = ${JSON.stringify(expectedContent)}`,
435+
)
436+
})
437+
438+
it('should handle malformed package.json gracefully', async () => {
439+
const mockContent = [
440+
{ name: 'test', base: 'src/docs', pattern: '**/*.md' }
441+
]
442+
;(getConfig as jest.Mock).mockResolvedValue({
443+
content: mockContent,
444+
repoRoot: '.'
445+
})
446+
;(existsSync as jest.Mock).mockReturnValue(true)
447+
;(readFileSync as jest.Mock).mockReturnValue('{ invalid json }')
448+
;(writeFile as jest.Mock).mockResolvedValue(undefined)
449+
450+
const mockConsoleError = jest.fn()
451+
jest.spyOn(console, 'error').mockImplementation(mockConsoleError)
452+
453+
await createCollectionContent('/foo/', '/config/dir/pf-docs.config.mjs', false)
454+
455+
const expectedContent = [
456+
{ name: 'test', base: '/config/dir/src/docs', pattern: '**/*.md', version: null }
457+
]
458+
459+
expect(writeFile).toHaveBeenCalledWith(
460+
'/foo/src/content.ts',
461+
`export const content = ${JSON.stringify(expectedContent)}`,
462+
)
463+
})
464+
465+
it('should handle version with pre-release tags (e.g., 6.0.0-beta.1)', async () => {
466+
const mockContent = [
467+
{ name: 'test', base: 'src/docs', pattern: '**/*.md' }
468+
]
469+
;(getConfig as jest.Mock).mockResolvedValue({
470+
content: mockContent,
471+
repoRoot: '.'
472+
})
473+
;(existsSync as jest.Mock).mockReturnValue(true)
474+
;(readFileSync as jest.Mock).mockReturnValue(JSON.stringify({ version: '6.0.0-beta.1' }))
475+
;(writeFile as jest.Mock).mockResolvedValue(undefined)
476+
477+
await createCollectionContent('/foo/', '/config/dir/pf-docs.config.mjs', false)
478+
479+
const expectedContent = [
480+
{ name: 'test', base: '/config/dir/src/docs', pattern: '**/*.md', version: 'v6' }
481+
]
482+
483+
expect(writeFile).toHaveBeenCalledWith(
484+
'/foo/src/content.ts',
485+
`export const content = ${JSON.stringify(expectedContent)}`,
486+
)
487+
})
488+
489+
it('should log version extraction in verbose mode', async () => {
490+
const mockContent = [
491+
{ name: 'test', base: 'src/docs', pattern: '**/*.md' }
492+
]
493+
;(getConfig as jest.Mock).mockResolvedValue({
494+
content: mockContent,
495+
repoRoot: '.'
496+
})
497+
;(existsSync as jest.Mock).mockReturnValue(true)
498+
;(readFileSync as jest.Mock).mockReturnValue(JSON.stringify({ version: '6.2.2' }))
499+
;(writeFile as jest.Mock).mockResolvedValue(undefined)
500+
501+
const mockConsoleLog = jest.fn()
502+
jest.spyOn(console, 'log').mockImplementation(mockConsoleLog)
503+
504+
await createCollectionContent('/foo/', '/config/dir/pf-docs.config.mjs', true)
505+
506+
expect(mockConsoleLog).toHaveBeenCalledWith(
507+
'Extracted version v6 from /config/dir/src/docs/package.json\n'
508+
)
509+
})
510+
})

0 commit comments

Comments
 (0)