@reliverse/rse
Version:
@reliverse/rse is your all-in-one companion for bootstrapping and improving any kind of projects (especially web apps built with frameworks like Next.js) — whether you're kicking off something new or upgrading an existing app. It is also a little AI-power
106 lines (105 loc) • 3.56 kB
JavaScript
import path from "@reliverse/pathkit";
import fs from "@reliverse/relifso";
import { relinka } from "@reliverse/relinka";
import { readPackageJSON, writePackageJSON } from "pkg-types";
import { tsconfigJson } from "../../constants.js";
function generateTypeDefinitions(content) {
let result = content;
result = result.replace(
/function\s+(\w+)\s*\((.*?)\)/g,
(_, name, params) => {
const typedParams = params.split(",").map((p) => p.trim()).filter(Boolean).map((p) => `${p}: any`);
return `function ${name}(${typedParams.join(", ")})`;
}
);
result = result.replace(
/function\s+(\w+)\s*\((.*?)\)\s*{/g,
(match) => `${match}: any`
);
result = result.replace(
/(const|let|var)\s+(\w+)\s*=/g,
(_, dec, name) => `${dec} ${name}: any =`
);
result = result.replace(
/class\s+(\w+)\s*{([^}]+)}/g,
(_, name, body) => {
const typedBody = body.replace(
/(\w+)\s*=/g,
(_2, propName) => `${propName}: any =`
);
return `class ${name} {${typedBody}}`;
}
);
result = result.replace(
/const\s+(\w+)\s*=\s*{([^}]+)}/g,
(_, name, props) => {
const interfaceProps = props.split(",").map((p) => p.trim()).filter(Boolean).map((p) => {
const [propName] = p.split(":");
return ` ${propName}: any;`;
});
return `interface ${name}Type {
${interfaceProps.join("\n")}
}
const ${name}: ${name}Type = {${props}}`;
}
);
return result;
}
export async function convertJsToTs(cwd) {
const jsFiles = await fs.readdir(cwd, { recursive: true });
const jsFilePaths = jsFiles.filter((file) => typeof file === "string").filter(
(file) => file.endsWith(".js") && !file.endsWith(".config.js") && !file.endsWith(".test.js") && !file.includes("node_modules")
);
for (const jsFile of jsFilePaths) {
const fullPath = path.join(cwd, jsFile);
const tsFile = jsFile.replace(/\.js$/, ".ts");
const tsPath = path.join(cwd, tsFile);
try {
const content = await fs.readFile(fullPath, "utf-8");
const tsContent = generateTypeDefinitions(content);
await fs.writeFile(tsPath, tsContent);
relinka("success", `Converted ${jsFile} to TypeScript`);
await fs.remove(fullPath);
} catch (error) {
relinka(
"error",
`Failed to convert ${jsFile}: ${error instanceof Error ? error.message : String(error)}`
);
}
}
const tsconfigPath = path.join(cwd, tsconfigJson);
if (!await fs.pathExists(tsconfigPath)) {
const tsconfig = {
compilerOptions: {
target: "ES2020",
module: "ESNext",
moduleResolution: "node",
esModuleInterop: true,
strict: true,
skipLibCheck: true,
forceConsistentCasingInFileNames: true,
outDir: "dist"
},
include: ["src/**/*"],
exclude: ["node_modules", "dist"]
};
await fs.writeJson(tsconfigPath, tsconfig, { spaces: 2 });
relinka("success", "Created tsconfig.json");
}
const packageJsonPath = path.join(cwd, "package.json");
if (await fs.pathExists(packageJsonPath)) {
const packageJson = await readPackageJSON(packageJsonPath);
packageJson.devDependencies = {
...packageJson.devDependencies,
typescript: "latest",
"@types/node": "latest"
};
packageJson.scripts = {
...packageJson.scripts,
build: "tsc",
"type-check": "tsc --noEmit"
};
await writePackageJSON(packageJsonPath, packageJson);
relinka("success", "Updated package.json with TypeScript configuration");
}
}