UNPKG

@dxtmisha/scripts

Version:

Development scripts and CLI tools for DXT UI projects - automated component generation, library building and project management tools

326 lines (279 loc) 10.1 kB
import { exec } from 'node:child_process' import { promisify } from 'node:util' import { getNameDirByPaths } from '../../functions/getNameDirByPaths' import { useAi } from '../../composables/useAi' import { PropertiesConfig } from '../Properties/PropertiesConfig' import { PropertiesFile } from '../Properties/PropertiesFile' import { ComponentWikiFile } from './ComponentWikiFile' import { UI_DIR_DIST, UI_DIR_TEMPORARY, UI_DIR_WIKI, UI_DIRS_COMPONENTS, UI_FILE_NAME_VITE, UI_FILE_NAME_VITE_WORKERS } from '../../config' import { isFilled } from '@dxtmisha/functional' // Sample Vite config template path / Путь к шаблону Vite-конфига const FILE_VITE_SAMPLE = [__dirname, '..', '..', 'media', 'templates', 'viteComponentTemplateConfig.ts'] // Sample AI prompt template path / Путь к шаблону AI-промпта const FILE_PROMPT_SAMPLE = [__dirname, '..', '..', 'media', 'templates', 'componentPrompt.en.txt'] // Async exec wrapper / Обёртка для асинхронного exec const execAsync = promisify(exec) /** * Generates wiki artifacts (code snapshot, stories, mdx) for a component. * Uses a temporary build + AI prompt to (re)create documentation. * * Генерирует wiki-артефакты (снимок кода, stories, mdx) для компонента. * Использует временный билд + AI промпт для (пере)создания документации. */ export class ComponentWiki { // Template vite config file / Файл шаблона vite-конфига protected readonly viteSample: ComponentWikiFile // Template prompt file / Файл шаблона промпта protected readonly promptSample: ComponentWikiFile // Aggregated built code file / Файл агрегированного собранного кода protected readonly codeFile: ComponentWikiFile // Types file (original or AI regenerated) / Файл типов (оригинал или пересозданный ИИ) protected readonly typesFile: ComponentWikiFile // Stories file (Storybook) / Файл сторис (Storybook) protected readonly storiesFile: ComponentWikiFile // MDX documentation file / Файл MDX документации protected readonly mdFile: ComponentWikiFile // Prepared AI prompt file / Файл подготовленного AI промпта protected readonly promptFile: ComponentWikiFile // Raw AI output file / Файл «сырого» вывода ИИ protected readonly aiFile: ComponentWikiFile /** * Constructor * @param path component relative path inside components dirs/ относительный путь компонента внутри директорий компонентов * @param prompt additional custom prompt text to append/ дополнительный кастомный текст промпта для добавления */ constructor( protected readonly path: string, protected readonly prompt: string = '' ) { this.viteSample = new ComponentWikiFile(FILE_VITE_SAMPLE) this.promptSample = new ComponentWikiFile(FILE_PROMPT_SAMPLE) this.codeFile = new ComponentWikiFile([ ...this.getPathTemporary(), 'code.txt' ]) this.typesFile = new ComponentWikiFile([ ...this.getRootComponent(), 'types.ts' ]) this.storiesFile = new ComponentWikiFile([ ...this.getPathWiki(), `${this.getName()}.stories.ts` ]) this.mdFile = new ComponentWikiFile([ ...this.getPathWiki(), `${this.getName()}.mdx` ]) this.promptFile = new ComponentWikiFile([ ...this.getPathTemporary(), 'prompt.txt' ]) this.aiFile = new ComponentWikiFile([ ...this.getPathTemporary(), 'ai.txt' ]) } /** * Orchestrates build + file extraction + AI generation. * * Оркестрирует билд + извлечение файлов + генерацию через ИИ. */ make(): void { console.log('Component wiki:', this.path) this.build() .then((status) => { if (status) { this.readAndWriteALlFiles() this.readAndWritePrompt() this.aiGenerate().then(() => console.log('End')) } else { console.log('Error!', this.path) } }) } /** * Returns root path segments for the component. * * Возвращает сегменты корневого пути компонента. */ protected getRootComponent(): string[] { return [ ...UI_DIRS_COMPONENTS, ...this.path.split('/') ] } /** * Returns wiki directory path segments. * * Возвращает сегменты пути wiki директории. */ protected getPathWiki(): string[] { return [ ...this.getRootComponent(), UI_DIR_WIKI ] } /** * Returns temporary directory path segments. * * Возвращает сегменты пути временной директории. */ protected getPathTemporary(): string[] { return [ ...this.getPathWiki(), UI_DIR_TEMPORARY ] } /** * Returns distribution temp directory path segments. * * Возвращает сегменты пути дистрибутива (temp). */ protected getPathTemporaryDist(): string[] { return [ ...this.getPathTemporary(), UI_DIR_DIST ] } /** * Derives component folder name. * * Получает имя директории компонента. */ protected getName(): string { return String(getNameDirByPaths(this.getRootComponent())) } /** * Saves existing vite config aside (rename) before custom build. * * Сохраняет существующий vite-конфиг (переименовывает) перед кастомным билдом. */ protected saveViteConfig() { if ( !PropertiesFile.is(UI_FILE_NAME_VITE_WORKERS) ) { PropertiesFile.rename( UI_FILE_NAME_VITE, UI_FILE_NAME_VITE_WORKERS ) } } /** * Restores original vite config after build. * * Восстанавливает оригинальный vite-конфиг после билда. */ protected resetViteConfig() { if ( PropertiesFile.is(UI_FILE_NAME_VITE_WORKERS) ) { PropertiesFile.removeFile(UI_FILE_NAME_VITE) PropertiesFile.rename( UI_FILE_NAME_VITE_WORKERS, UI_FILE_NAME_VITE ) } } /** * Runs build command (npm run build) capturing stdout/stderr. * * Запускает команду билда (npm run build), перехватывая stdout/stderr. */ protected async run(): Promise<boolean> { try { const { stdout, stderr } = await execAsync('npm run build') console.info(stdout) if (stderr) { console.error('STD error', stderr) } return true } catch (error) { console.error('Error', error) } return false } /** * Prepares temporary vite config, triggers build, then restores original. * * Готовит временный vite-конфиг, запускает билд, затем восстанавливает оригинал. */ protected async build(): Promise<boolean> { this.saveViteConfig() const path = this.getRootComponent().join('/') const vite = this.viteSample .read() .replace('[path]', path) PropertiesFile.writeByPath(UI_FILE_NAME_VITE, vite) const status = await this.run() this.resetViteConfig() return status } /** * Reads built file content and wraps with header comment (file name). * * Читает содержимое собранного файла и оборачивает заголовком с именем файла. * @param path relative built file path / относительный путь собранного файла */ protected readFile(path: string): string { const pathFull = PropertiesFile.joinPath([...this.getPathTemporaryDist(), path]) const content = PropertiesFile.readFileOnly(pathFull) return ` // File: ${path} ${content} `.trim() } /** * Aggregates all built files into a single code snapshot file. * * Агрегирует все собранные файлы в единый снимок кода. */ protected readAndWriteALlFiles(): void { const list = PropertiesFile.readDirRecursive(this.getPathTemporaryDist()) const content: string[] = [] list.forEach((file) => { content.push(this.readFile(file)) }) this.codeFile.write(content.join('\r\n')) } /** * Builds AI prompt by injecting dynamic code/types/stories/mdx content. * * Формирует AI промпт, подставляя динамический код/типы/stories/mdx. */ protected readAndWritePrompt(): void { this.promptFile.write( this.promptSample .read() .replace('[code]', this.codeFile.read()) .replace('[types]', this.typesFile.read()) .replace('[stories]', this.storiesFile.read()) .replace('[md]', this.mdFile.read()) .replace(/\[wikiLanguage]/g, PropertiesConfig.getWikiLanguage()) + (isFilled(this.prompt) ? `Additional conditions (these conditions take priority): ${this.prompt}` : '') ) } /** * Invokes AI generation and splits result into types / stories / mdx. * * Вызывает генерацию через ИИ и делит результат на types / stories / mdx. */ protected async aiGenerate(): Promise<void> { const prompt = this.promptFile.read() const generate = await useAi()?.generate(prompt) if (generate) { const files = generate.split('#########') this.typesFile.write(files[0] ?? '') this.storiesFile.write(files[1] ?? '') this.mdFile.write(files[2] ?? '') this.aiFile.write(generate) PropertiesFile.removeDir(this.getPathTemporary()) } } }