UNPKG

@mintlify/cli

Version:

The Mintlify CLI

222 lines (191 loc) 7.24 kB
import { getConfigObj, getConfigPath } from '@mintlify/prebuild'; import * as previewing from '@mintlify/previewing'; import { validateDocsConfig } from '@mintlify/validation'; import fs from 'fs'; import { outputFile } from 'fs-extra'; import { migrateMdx } from '../src/migrateMdx.js'; vi.mock('../src/constants.js', () => ({ HOME_DIR: '/home/test-user', CMD_EXEC_PATH: '/project', })); vi.mock('@mintlify/prebuild', async () => { const original = await vi.importActual<typeof import('@mintlify/prebuild')>('@mintlify/prebuild'); return { ...original, getConfigPath: vi.fn(), getConfigObj: vi.fn(), }; }); vi.mock('@mintlify/validation', async () => { const original = await vi.importActual<typeof import('@mintlify/validation')>('@mintlify/validation'); return { ...original, validateDocsConfig: vi.fn(), }; }); vi.mock('fs-extra', () => ({ outputFile: vi.fn(), })); const addLogSpy = vi.spyOn(previewing, 'addLog'); describe('migrateMdx', () => { beforeEach(() => { vi.clearAllMocks(); }); const mkDirent = (name: string, isDir: boolean) => ({ name, isDirectory: () => isDir, isFile: () => !isDir }) as unknown as fs.Dirent; const readdirSpy = vi.spyOn(fs.promises, 'readdir'); readdirSpy.mockImplementation(async (p: Parameters<typeof fs.promises.readdir>[0]) => { const value = typeof p === 'string' ? p : (p as unknown as { toString(): string }).toString(); if (value === '/project') { return [ mkDirent('api', true), mkDirent('webhooks', true), mkDirent('openapi.json', false), mkDirent('openapi1.json', false), ]; } if (value === '/project/api') { return [mkDirent('pets.mdx', false)]; } if (value === '/project/webhooks') { return [mkDirent('newPet.mdx', false)]; } return [] as unknown as fs.Dirent[]; }); it('logs and exits when docs.json is not found', async () => { vi.mocked(getConfigPath).mockResolvedValueOnce(undefined as unknown as string); await migrateMdx(); expect(addLogSpy).toHaveBeenCalledWith( expect.objectContaining({ props: { message: 'docs.json not found in current directory' } }) ); }); it('migrates a path operation MDX page to x-mint and updates docs.json and spec', async () => { vi.mocked(getConfigPath).mockResolvedValueOnce('/project/docs.json'); vi.mocked(getConfigObj).mockResolvedValueOnce({ navigation: { pages: ['api/pets'], }, } as unknown as object); vi.mocked(validateDocsConfig).mockResolvedValueOnce({ success: true, data: { navigation: { pages: ['api/pets'], }, }, } as unknown as ReturnType<typeof validateDocsConfig>); const existsSyncSpy = vi.spyOn(fs, 'existsSync'); existsSyncSpy.mockImplementation((p: fs.PathLike) => { const value = String(p); return value === '/project/api/pets.mdx' || value === 'openapi.json'; }); const readFileSpy = vi.spyOn(fs.promises, 'readFile'); readFileSpy.mockImplementation(async (p: Parameters<typeof fs.promises.readFile>[0]) => { const value = typeof p === 'string' ? p : (p as unknown as { toString(): string }).toString(); if (value === '/project/api/pets.mdx') { return `---\nopenapi: openapi.json GET /pets\n---\n\n# Title\nContent`; } if (value === '/project/openapi.json') { return JSON.stringify({ openapi: '3.0.0', info: { title: 'Test', version: '1.0.0' }, paths: { '/pets': { get: { summary: 'List pets', responses: { '200': { description: 'ok' } }, }, }, }, }); } throw new Error('Unexpected readFile path: ' + value); }); const unlinkSpy = vi.spyOn(fs.promises, 'unlink').mockResolvedValueOnce(); await migrateMdx(); expect(addLogSpy).toHaveBeenCalledWith( expect.objectContaining({ props: { message: 'docs.json updated' } }) ); expect(outputFile).toHaveBeenCalledWith( '/project/docs.json', expect.stringContaining('openapi.json get /pets') ); const specWriteCall = vi .mocked(outputFile) .mock.calls.find( (c) => typeof c[0] === 'string' && (c[0] as string).includes('openapi.json') ); expect(specWriteCall).toBeTruthy(); const writtenSpec = JSON.parse(specWriteCall?.[1] as string); expect(writtenSpec.paths['/pets'].get['x-mint']).toEqual( expect.objectContaining({ href: '/api/pets' }) ); expect(unlinkSpy).toHaveBeenCalledWith('/project/api/pets.mdx'); expect(addLogSpy).toHaveBeenCalledWith( expect.objectContaining({ props: { message: 'migration complete' } }) ); }); it('migrates a webhook MDX page to x-mint on the webhook operation', async () => { vi.mocked(getConfigPath).mockResolvedValueOnce('/project/docs.json'); vi.mocked(getConfigObj).mockResolvedValueOnce({ navigation: { pages: ['webhooks/newPet'], }, } as unknown as object); vi.mocked(validateDocsConfig).mockResolvedValueOnce({ success: true, data: { navigation: { pages: ['webhooks/newPet'], }, }, } as unknown as ReturnType<typeof validateDocsConfig>); const existsSyncSpy = vi.spyOn(fs, 'existsSync'); existsSyncSpy.mockImplementation((p: fs.PathLike) => { const value = String(p); return value === '/project/webhooks/newPet.mdx' || value === 'openapi1.json'; }); const readFileSpy = vi.spyOn(fs.promises, 'readFile'); readFileSpy.mockImplementation(async (p: Parameters<typeof fs.promises.readFile>[0]) => { const value = typeof p === 'string' ? p : (p as unknown as { toString(): string }).toString(); if (value === '/project/webhooks/newPet.mdx') { return `---\nopenapi: openapi1.json webhook newPet\n---\n\n# Webhook\nBody`; } if (value === '/project/openapi1.json') { return JSON.stringify({ openapi: '3.1.0', info: { title: 'Test', version: '1.0.0' }, webhooks: { newPet: { post: { summary: 'Webhook - new pet', requestBody: {}, responses: { '200': { description: 'ok' } }, }, }, }, }); } throw new Error('Unexpected readFile path: ' + value); }); vi.spyOn(fs.promises, 'unlink').mockResolvedValueOnce(); await migrateMdx(); expect(addLogSpy).toHaveBeenCalledWith( expect.objectContaining({ props: { message: 'docs.json updated' } }) ); const specWriteCall = vi .mocked(outputFile) .mock.calls.find( (c) => typeof c[0] === 'string' && (c[0] as string).includes('openapi1.json') ); expect(specWriteCall).toBeTruthy(); const writtenSpec = JSON.parse(specWriteCall?.[1] as string); expect(writtenSpec.webhooks.newPet.post['x-mint']).toEqual( expect.objectContaining({ href: '/webhooks/newPet' }) ); expect(addLogSpy).toHaveBeenCalledWith( expect.objectContaining({ props: { message: 'migration complete' } }) ); }); });