UNPKG

anvy

Version:

A CLI tool for integrating Anvy UI components into your React.js and Next.js projects.

205 lines 8.03 kB
import * as parser from '@babel/parser'; import _traverse from '@babel/traverse'; import * as t from '@babel/types'; import { transformFromAstSync } from '@babel/core'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; import chalk from 'chalk'; import ora from 'ora'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); const transformTypescriptPlugin = require('@babel/plugin-transform-typescript'); const traverse = _traverse.default; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const spinner = ora(); export const ProjectTypes = { NEXT: "next", REACT: "react", VITE: "vite", }; const PARSE_OPTIONS = { sourceType: "module", allowImportExportEverywhere: true, allowReturnOutsideFunction: true, startLine: 1, tokens: true, plugins: [ "jsx", "typescript", "decorators-legacy", "classProperties", "classPrivateProperties", "classPrivateMethods", "exportDefaultFrom", "exportNamespaceFrom", "asyncGenerators", "functionBind", "functionSent", "dynamicImport", "numericSeparator", "optionalChaining", "importMeta", "bigInt", "optionalCatchBinding", "throwExpressions", "nullishCoalescingOperator", ], }; const handleSpecialCases = (content) => { // Handle React.CSSProperties type assertions content = content.replace(/\} as React\.CSSProperties/g, '}'); // Handle interface declarations content = content.replace(/interface \w+Props \{[\s\S]*?\n\}/g, ''); // Handle type imports content = content.replace(/import type.*?from.*?;/g, ''); // Handle type assertions in props destructuring content = content.replace(/(\w+)\s*:\s*\w+([,}])/g, '$1$2'); return content; }; const removeTypeScriptSyntax = (ast) => { try { traverse(ast, { TSTypeAnnotation(path) { path.remove(); }, TSTypeParameterDeclaration(path) { path.remove(); }, TSTypeParameterInstantiation(path) { path.remove(); }, TSInterfaceDeclaration(path) { path.remove(); }, TSTypeAliasDeclaration(path) { path.remove(); }, TSModuleDeclaration(path) { path.remove(); }, TSAsExpression(path) { if (path.node.expression) { path.replaceWith(t.cloneNode(path.node.expression)); } }, TSTypeAssertion(path) { if (path.node.expression) { path.replaceWith(t.cloneNode(path.node.expression)); } }, ImportDeclaration(path) { if (path.node.importKind === 'type') { path.remove(); } else { path.node.specifiers = path.node.specifiers.filter((specifier) => specifier.importKind !== 'type'); if (path.node.specifiers.length === 0) { path.remove(); } } }, ExportNamedDeclaration(path) { if (path.node.exportKind === 'type') { path.remove(); } } }); } catch (error) { console.warn('Error in removeTypeScriptSyntax:', error); } }; const isTypeScriptFile = (content) => { try { const ast = parser.parse(content, { ...PARSE_OPTIONS, plugins: ['typescript', 'jsx'] }); let hasTypeScript = false; traverse(ast, { enter(path) { if (t.isTSTypeAnnotation(path.node) || t.isTSTypeParameterDeclaration(path.node) || t.isTSInterfaceDeclaration(path.node)) { hasTypeScript = true; path.stop(); } } }); return hasTypeScript; } catch (error) { console.warn('Error checking TypeScript syntax:', error); return false; } }; export const processComponentContent = (content, config, isExternalRegistry) => { try { spinner.start("Processing component content..."); let processedContent = content; const isTypescript = isTypeScriptFile(content); // Only convert if necessary if ((isExternalRegistry || isTypescript) && config.language === 'javascript') { spinner.text = "Converting TypeScript to JavaScript..."; try { // First handle special cases that the AST transformer might miss processedContent = handleSpecialCases(processedContent); const ast = parser.parse(processedContent, PARSE_OPTIONS); if (config.type !== ProjectTypes.NEXT) { // Remove 'use client' directive for non-Next.js projects const useClientDirective = ast.program.body.find(node => t.isExpressionStatement(node) && t.isStringLiteral(node.expression) && node.expression.value === 'use client'); if (useClientDirective) { ast.program.body = ast.program.body.filter(node => node !== useClientDirective); } } removeTypeScriptSyntax(ast); const result = transformFromAstSync(ast, processedContent, { plugins: [[transformTypescriptPlugin, { isTSX: true, allowNamespaces: true, allowDeclareFields: true }]], retainLines: true, configFile: false, }); if (!result || !result.code) { throw new Error("Failed to transform TypeScript"); } processedContent = result.code; // Clean up any remaining TypeScript artifacts processedContent = processedContent .replace(/:\s*any/g, '') .replace(/:\s*void/g, '') .replace(/:\s*never/g, '') .replace(/:\s*unknown/g, '') .replace(/as\s+const/g, '') .replace(/declare\s+/g, ''); spinner.succeed("Successfully converted TypeScript to JavaScript"); } catch (error) { spinner.warn(chalk.yellow(`Warning: TypeScript conversion failed, using original content. Error: ${error.message}`)); // If AST transformation fails, fall back to regex-based cleaning processedContent = handleSpecialCases(content); } } // Handle Next.js specific code if (config.type !== ProjectTypes.NEXT) { processedContent = processedContent .replace(/import\s+(\{?\s*Link\s*\}?)\s+from\s+['"]next\/link['"];?/g, "") .replace(/import\s+(\{?\s*Image\s*\}?)\s+from\s+['"]next\/image['"];?/g, "") .replace(/<Link\s+href/g, "<a href") .replace(/<\/Link>/g, "</a>") .replace(/<Image\s/g, "<img ") .replace(/priority\s*=\s*\{[^}]+\}/g, ""); } // Fix relative imports for components processedContent = processedContent.replace(/@\/components\/ui\/(.*?)['"]/g, (_, componentPath) => `'./${componentPath}'`); spinner.succeed("Component processing completed successfully"); return processedContent; } catch (error) { spinner.fail(chalk.red(`Error processing component: ${error.message}`)); throw error; } }; //# sourceMappingURL=process-component-content.js.map