anvy
Version:
A CLI tool for integrating Anvy UI components into your React.js and Next.js projects.
205 lines • 8.03 kB
JavaScript
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