UNPKG

@react-lib-tech/react-jsx-to-tsx

Version:

A tiny React component library (JS) for converting TSX → JSX and extracting text

259 lines (254 loc) 10.3 kB
var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.js var index_exports = {}; __export(index_exports, { LovableDownload: () => LovableDownload, convertFolder: () => rename_jsx_to_tsx_default }); module.exports = __toCommonJS(index_exports); // src/utils/restore-project.jsx var import_axios = __toESM(require("axios"), 1); var import_fs = __toESM(require("fs"), 1); var import_path = __toESM(require("path"), 1); var import_https = __toESM(require("https"), 1); var httpsAgent = new import_https.default.Agent({ rejectUnauthorized: false }); async function LovableDownload({ API_URL = "https://lovable-api.com/projects/5b9f446c-d608-4948-aed1-7e5ba9ad04c0/source-code", Authorization = "" }) { try { const res = await import_axios.default.get(API_URL, { httpsAgent, headers: { Authorization } }); const data = res == null ? void 0 : res.data; for (const file of data.files) { const filePath = import_path.default.join(process.cwd(), file.name); if (file.binary) { console.log(`\u26A0\uFE0F Skipping binary file: ${file.name}`); continue; } import_fs.default.mkdirSync(import_path.default.dirname(filePath), { recursive: true }); import_fs.default.writeFileSync(filePath, file.contents, "utf8"); console.log(`\u2705 Created: ${filePath}`); } console.log("\u{1F389} Project restored with correct folder structure!"); } catch (err) { console.error("\u274C Error:", err.message); } } // src/utils/rename-jsx-to-tsx.jsx var import_fs2 = __toESM(require("fs"), 1); var import_path2 = __toESM(require("path"), 1); var import_core = __toESM(require("@babel/core"), 1); var presets = [ [ "@babel/preset-typescript", { isTSX: true, allExtensions: true } ] ]; function loadTsConfigAliases() { var _a, _b; const tsconfigPath = import_path2.default.resolve("tsconfig.json"); if (!import_fs2.default.existsSync(tsconfigPath)) return {}; try { const tsconfig = JSON.parse(import_fs2.default.readFileSync(tsconfigPath, "utf-8")); const paths = ((_a = tsconfig.compilerOptions) == null ? void 0 : _a.paths) || {}; const baseUrl = ((_b = tsconfig.compilerOptions) == null ? void 0 : _b.baseUrl) || "."; const aliases = {}; for (const alias in paths) { const target = paths[alias][0].replace(/\/\*$/, ""); const aliasKey = alias.replace(/\/\*$/, ""); aliases[aliasKey] = import_path2.default.resolve(baseUrl, target); } return aliases; } catch { console.warn("\u26A0\uFE0F Could not parse tsconfig.json paths"); return {}; } } function fixJsxAttributes(code) { return code.replace(/\bclass=/g, "className=").replace(/\bfor=/g, "htmlFor=").replace(/\bonclick=/gi, "onClick=").replace(/\bonchange=/gi, "onChange=").replace(/\bonfocus=/gi, "onFocus=").replace(/\bonblur=/gi, "onBlur="); } function extractPropTypes(code, componentName) { const propTypesRegex = new RegExp( `${componentName}\\.propTypes\\s*=\\s*{([\\s\\S]*?)};?`, "m" ); const match = code.match(propTypesRegex); if (!match) return null; const propsBlock = match[1]; const lines = propsBlock.split("\n").map((l) => l.trim()).filter(Boolean); const fields = lines.map((line) => { const [rawKey, rawType] = line.replace(/[,}]/g, "").split(":").map((s) => s.trim()); let tsType = "any"; let optional = true; if (/isRequired/.test(rawType)) optional = false; if (/string/.test(rawType)) tsType = "string"; else if (/number/.test(rawType)) tsType = "number"; else if (/bool/.test(rawType)) tsType = "boolean"; else if (/array/.test(rawType)) tsType = "any[]"; else if (/object/.test(rawType)) tsType = "Record<string, any>"; else if (/func/.test(rawType)) tsType = "() => void"; return ` ${rawKey}${optional ? "?" : ""}: ${tsType};`; }); return ` interface ${componentName}Props { ${fields.join("\n")} } `; } function extractDefaultProps(code, componentName) { const defaultPropsRegex = new RegExp( `${componentName}\\.defaultProps\\s*=\\s*{([\\s\\S]*?)};?`, "m" ); const match = code.match(defaultPropsRegex); if (!match) return {}; const propsBlock = match[1]; const lines = propsBlock.split("\n").map((l) => l.trim()).filter(Boolean); const defaults = {}; for (const line of lines) { const [key, value] = line.replace(/[,}]/g, "").split(":").map((s) => s.trim()); if (key && value) defaults[key] = value; } return defaults; } function addInterfaceForProps(code, defaults) { const funcRegex = /function\s+([A-Z][A-Za-z0-9_]*)\s*\(\s*{([^}]*)}\s*\)/; const arrowRegex = /const\s+([A-Z][A-Za-z0-9_]*)\s*=\s*\(\s*{([^}]*)}\s*\)\s*=>/; let match = code.match(funcRegex) || code.match(arrowRegex); if (!match) return code; const componentName = match[1]; const propsRaw = match[2].split(",").map((p) => p.trim()).filter(Boolean); if (propsRaw.length === 0) return code; const interfaceName = `${componentName}Props`; const interfaceFields = propsRaw.map((p) => ` ${p}?: any;`).join("\n"); const interfaceCode = ` interface ${interfaceName} { ${interfaceFields} } `; const withDefaults = propsRaw.map((p) => defaults[p] ? `${p} = ${defaults[p]}` : p).join(", "); if (funcRegex.test(code)) { code = code.replace( funcRegex, `function ${componentName}({ ${withDefaults} }: ${interfaceName}): JSX.Element` ); } else { code = code.replace( arrowRegex, `const ${componentName}: React.FC<${interfaceName}> = ({ ${withDefaults} }) =>` ); } if (!code.includes(`interface ${interfaceName}`)) { code = interfaceCode + code; } if (/React\.FC/.test(code) && !/import\s+.*React/.test(code)) { code = `import React from "react"; ` + code; } return code; } function addTypesForForwardRef(code) { const forwardRefRegex = /const\s+([A-Z][A-Za-z0-9_]*)\s*=\s*React\.forwardRef\(\s*\(([^)]*)\)\s*=>/; const match = code.match(forwardRefRegex); if (!match) return code; const componentName = match[1]; return code.replace( forwardRefRegex, `const ${componentName} = React.forwardRef<React.ElementRef<typeof Button>, React.ComponentProps<typeof Button>>(($2) =>` ); } async function jsxToTsx(srcPath, outPath) { try { const code = import_fs2.default.readFileSync(srcPath, "utf-8"); const result = await import_core.default.transformAsync(code, { filename: srcPath, presets }); if (!(result == null ? void 0 : result.code)) throw new Error("Babel failed"); let output = result.code; const componentMatch = output.match(/(function|const)\s+([A-Z][A-Za-z0-9_]*)/); const componentName = componentMatch ? componentMatch[2] : null; let defaults = {}; if (componentName) { defaults = extractDefaultProps(code, componentName); output = addInterfaceForProps(output, defaults); const interfaceFromPT = extractPropTypes(code, componentName); if (interfaceFromPT && !output.includes(`interface ${componentName}Props`)) { output = interfaceFromPT + output; } } output = addTypesForForwardRef(output); output = fixJsxAttributes(output); const aliases = loadTsConfigAliases(); for (const alias in aliases) { const target = aliases[alias]; const aliasRegex = new RegExp(`${alias}/`, "g"); output = output.replace(aliasRegex, target + "/"); } if (componentName) { const propTypesRegex = new RegExp(`${componentName}\\.propTypes\\s*=\\s*{[\\s\\S]*?};?`, "m"); const defaultPropsRegex = new RegExp(`${componentName}\\.defaultProps\\s*=\\s*{[\\s\\S]*?};?`, "m"); output = output.replace(propTypesRegex, ""); output = output.replace(defaultPropsRegex, ""); } import_fs2.default.mkdirSync(import_path2.default.dirname(outPath), { recursive: true }); import_fs2.default.writeFileSync(outPath, output, "utf-8"); console.log(`\u2705 Converted: ${srcPath} \u2192 ${outPath}`); } catch (err) { console.error(`\u274C Error converting ${srcPath}: ${err.message}`); } } async function convertFolder(srcFolder, outFolder) { const entries = import_fs2.default.readdirSync(srcFolder, { withFileTypes: true }); for (const entry of entries) { const srcPath = import_path2.default.join(srcFolder, entry.name); const outPath = import_path2.default.join(outFolder, entry.name); if (entry.isDirectory()) { await convertFolder(srcPath, outPath); } else if (entry.isFile()) { if (entry.name.endsWith(".jsx")) { await jsxToTsx(srcPath, outPath.replace(/\.jsx$/, ".tsx")); } else if (entry.name.endsWith(".js")) { await jsxToTsx(srcPath, outPath.replace(/\.js$/, ".ts")); } } } } var rename_jsx_to_tsx_default = convertFolder; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { LovableDownload, convertFolder }); //# sourceMappingURL=index.cjs.map