import { select, input } from '@inquirer/prompts'; import { addLogs, addLog, SpinnerLog, removeLastLog } from '@mintlify/previewing'; import { docsConfigSchema, validatePathWithinCwd } from '@mintlify/validation'; import AdmZip from 'adm-zip'; import fse from 'fs-extra'; import { Box, Text } from 'ink'; import { isAI } from './helpers.js'; import { fetchAvailableTemplates, installFromTemplate, promptForTemplate, validateTemplateName, } from './templates.js'; const sendOnboardingMessage = (installDir: string) => { addLogs( Documentation Setup!, To see your docs run, cd {installDir} , mint dev ); if (isAI()) { addLog( When the docs are ready, run `mint validate` in {installDir} to check the build and get publish steps. ); } }; const sendUsageMessageForAI = ( directory: string, contentsOccupied: boolean, themes: string[], templateNames?: string[] ) => { const templateInfo = templateNames === undefined ? 'Unable to fetch templates — use --template flag if you know the name' : templateNames.length > 0 ? `Templates: ${JSON.stringify(templateNames)}` : 'No templates are currently available'; addLogs( Agent Detected - sending AI friendly prompt, {''}, Help the user set up a Mintlify docs site with `mint new`. Ask each step one at a time unless the user asks you to skip questions or use your best judgment. , {[ `- [ ] Pick a theme or clone a template? (${templateInfo})`, `- [ ] If template: which one? If theme: which one? (Themes: ${JSON.stringify(themes)})`, '- [ ] Project name?', `- [ ] Directory? (default: "${directory}", auto-created if needed)${contentsOccupied ? ` ⚠️ "${directory}" is occupied — subdirectory, overwrite (--force), or different path?` : ''}`, ].join('\n')} , Command: `mint new [dir] --theme [theme] --name [name]` or `mint new [dir] --template [template] --name [name]`. --theme optionally overrides a template default. --force overwrites non-empty dirs. Use AskQuestion to present choices. , {''} ); }; async function sendAIUsageMessage(directory: string, contentsOccupied: boolean, themes: string[]) { const templateNames = await fetchAvailableTemplates().catch(() => undefined); sendUsageMessageForAI(directory, contentsOccupied, themes, templateNames); } async function resolveInstallDir( installDir: string, force: boolean, contentsOccupied: boolean ): Promise { if (!contentsOccupied) return installDir; if (isAI()) { if (force) return installDir; return undefined; } const choice = await select({ message: `Directory ${installDir} is not empty. What would you like to do?`, choices: [ { name: 'Create in a subdirectory', value: 'subdir' as const }, { name: 'Overwrite current directory (may lose contents)', value: 'overwrite' as const }, { name: 'Cancel', value: 'cancel' as const }, ], }); if (choice === 'cancel') return undefined; if (choice === 'subdir') { const subdir = await input({ message: 'Subdirectory name:', default: 'docs', }); if (!subdir || subdir.trim() === '') { throw new Error('Subdirectory name cannot be empty'); } const resolved = installDir === '.' ? subdir : `${installDir}/${subdir}`; validatePathWithinCwd(resolved, process.cwd()); return resolved; } return installDir; } async function promptForProjectName(installDir: string, currentName?: string): Promise { if (currentName) return currentName; const defaultProject = installDir === '.' ? 'Mintlify' : installDir; return input({ message: 'Project Name', default: defaultProject }); } async function promptForApproach(): Promise { const approach = await select({ message: 'How would you like to set up your docs?', choices: [ { name: 'Pick a theme', value: 'theme' as const }, { name: 'Clone a template', value: 'template' as const }, ], }); if (approach === 'template') return promptForTemplate(); return undefined; } export async function init( installDir: string, force: boolean, theme?: string, name?: string, template?: string ): Promise { validatePathWithinCwd(installDir); // eslint-disable-next-line @typescript-eslint/no-explicit-any const themes: string[] = docsConfigSchema.options.map((option: any) => { return option.shape.theme._def.value as string; }); const dirContents = await fse.readdir(installDir).catch(() => [] as string[]); const contentsOccupied = dirContents.length > 0; if (isAI() && (!name || (!template && !theme))) { await sendAIUsageMessage(installDir, contentsOccupied, themes); return; } if (isAI() && contentsOccupied && !force) { await sendAIUsageMessage(installDir, contentsOccupied, themes); return; } const selectedTemplate = template ? await validateTemplateName(template).then(() => template) : !isAI() && !theme ? await promptForApproach() : undefined; if (selectedTemplate) { const resolved = await resolveInstallDir(installDir, force, contentsOccupied); if (resolved === undefined) { if (isAI()) await sendAIUsageMessage(installDir, contentsOccupied, themes); return; } const projectName = await promptForProjectName(resolved, name); await fse.ensureDir(resolved); await installFromTemplate(resolved, selectedTemplate, projectName, theme); sendOnboardingMessage(resolved); return; } // Standard theme-based path const resolved = await resolveInstallDir(installDir, force, contentsOccupied); if (resolved === undefined) { if (isAI()) await sendAIUsageMessage(installDir, contentsOccupied, themes); return; } let projectName = name; let selectedTheme = theme; if (!isAI() && (!selectedTheme || !projectName)) { projectName = await promptForProjectName(resolved, projectName); if (!selectedTheme) { selectedTheme = await select({ message: 'Theme', choices: themes.map((t) => ({ name: t, value: t })), }); } } if (projectName === undefined || selectedTheme === undefined) { await sendAIUsageMessage(resolved, contentsOccupied, themes); return; } await fse.ensureDir(resolved); await install(resolved, projectName, selectedTheme); sendOnboardingMessage(resolved); } const install = async (installDir: string, projectName: string, theme: string) => { addLog(); const response = await fetch('https://github.com/mintlify/starter/archive/refs/heads/main.zip'); const buffer = await response.arrayBuffer(); await fse.writeFile(installDir + '/starter.zip', Buffer.from(buffer)); removeLastLog(); addLog(); new AdmZip(installDir + '/starter.zip').extractAllTo(installDir, true); removeLastLog(); await fse.copy(installDir + '/starter-main', installDir, { overwrite: true, filter: (src) => !src.includes('starter-main/starter-main'), }); await fse.remove(installDir + '/starter.zip'); await fse.remove(installDir + '/starter-main'); const docsJsonPath = installDir + '/docs.json'; const docsConfig = await fse.readJson(docsJsonPath); docsConfig.theme = theme; docsConfig.name = projectName; await fse.writeJson(docsJsonPath, docsConfig, { spaces: 2 }); };