UNPKG

@ts-dev-tools/core

Version:
199 lines (192 loc) 8.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.up = exports.hooks = void 0; const node_fs_1 = require("node:fs"); const node_path_1 = require("node:path"); const CmdService_1 = require("../../services/CmdService"); const FileService_1 = require("../../services/FileService"); const PackageJson_1 = require("../../services/PackageJson"); const BIOME_CONFIG_FILE_NAME = "biome.json"; const ESLINT_CONFIG_FILE_NAME = "eslint.config.mjs"; const PRE_COMMIT_HOOK_NAME = "pre-commit"; const BIOME_INIT_COMMAND = "npx @biomejs/biome init"; const NEW_PRE_COMMIT_COMMAND = "npx --no-install biome check --error-on-warnings --staged --write --no-errors-on-unmatched"; exports.hooks = [ { name: PRE_COMMIT_HOOK_NAME, command: NEW_PRE_COMMIT_COMMAND, }, ]; const MANAGED_ESLINT_MARKERS = [ "tsDevToolsCore", "tsDevToolsReact", "export default tsDevTools", ]; const VITE_VANILLA_APP_ELEMENT_PATTERN = "document.querySelector<HTMLDivElement>('#app')!.innerHTML = `"; const VITE_VANILLA_APP_ELEMENT_REPLACEMENT = `const appElement = document.querySelector<HTMLDivElement>("#app"); if (!appElement) { throw new Error("Could not find #app element"); } appElement.innerHTML = \``; const VITE_VANILLA_COUNTER_PATTERN = "setupCounter(document.querySelector<HTMLButtonElement>('#counter')!)"; const VITE_VANILLA_COUNTER_REPLACEMENT = `const counterElement = document.querySelector<HTMLButtonElement>("#counter"); if (!counterElement) { throw new Error("Could not find #counter element"); } setupCounter(counterElement)`; const VITE_REACT_ROOT_PATTERN = "createRoot(document.getElementById('root')!).render("; const VITE_REACT_ROOT_REPLACEMENT = `const rootElement = document.getElementById("root"); if (!rootElement) { throw new Error("Could not find #root element"); } createRoot(rootElement).render(`; const VITE_REACT_BUTTON_PATTERN = "<button onClick={"; const VITE_REACT_BUTTON_REPLACEMENT = '<button type="button" onClick={'; const VITE_REACT_DOCS_LINK_TEXT_PATTERN = /(href="https:\/\/react\.dev\/"[^>]*>[\s\S]*?)Learn more([\s\S]*?<\/a>)/; const up = async (absoluteProjectDir) => { const packageJson = PackageJson_1.PackageJson.fromDirPath(absoluteProjectDir); const packageJsonContent = packageJson.getContent(); const hasManagedEslintConfigFile = projectHasManagedEslintConfigFile(absoluteProjectDir); const hasBiomeConfig = projectHasBiomeConfig(absoluteProjectDir); if (!hasBiomeConfig) { await runBiomeInit(absoluteProjectDir); } delete packageJsonContent.eslintConfig; delete packageJsonContent.prettier; delete packageJsonContent.importSort; delete packageJsonContent["lint-staged"]; packageJsonContent.scripts = { ...packageJsonContent.scripts, format: "biome format --write .", lint: "biome lint --error-on-warnings .", check: "biome check --error-on-warnings --write .", }; packageJson.setContent(packageJsonContent); enableBiomeVcsIntegration(absoluteProjectDir); migrateCommonViteStarterFiles(absoluteProjectDir); deleteManagedEslintConfig(absoluteProjectDir, hasManagedEslintConfigFile); }; exports.up = up; async function runBiomeInit(absoluteProjectDir) { await CmdService_1.CmdService.execCmd(BIOME_INIT_COMMAND, absoluteProjectDir, true); } function enableBiomeVcsIntegration(absoluteProjectDir) { const biomeConfigFilePath = (0, node_path_1.join)(absoluteProjectDir, BIOME_CONFIG_FILE_NAME); if (!FileService_1.FileService.fileExists(biomeConfigFilePath)) { return; } let biomeConfigContent; try { biomeConfigContent = JSON.parse(FileService_1.FileService.getFileContent(biomeConfigFilePath)); } catch { console.warn('Skipping Biome VCS integration because "biome.json" contains invalid JSON.'); return; } const existingVcs = biomeConfigContent.vcs ?? {}; const vcsRoot = getBiomeVcsRoot(absoluteProjectDir); biomeConfigContent.vcs = { ...existingVcs, clientKind: existingVcs.clientKind ?? "git", enabled: true, ...(vcsRoot ? { root: vcsRoot } : {}), useIgnoreFile: true, }; FileService_1.FileService.putFileContent(biomeConfigFilePath, `${JSON.stringify(biomeConfigContent, null, 2)} `); } function getBiomeVcsRoot(absoluteProjectDir) { const vcsRootPath = getEnclosingVcsRootPath(absoluteProjectDir); if (!vcsRootPath || vcsRootPath === absoluteProjectDir) { return undefined; } return (0, node_path_1.relative)(absoluteProjectDir, vcsRootPath); } function getEnclosingVcsRootPath(absoluteProjectDir) { let currentDir = absoluteProjectDir; while (true) { if ((0, node_fs_1.existsSync)((0, node_path_1.join)(currentDir, ".git"))) { return currentDir; } const parentDir = (0, node_path_1.dirname)(currentDir); if (parentDir === currentDir) { return undefined; } currentDir = parentDir; } } function migrateCommonViteStarterFiles(absoluteProjectDir) { updateFileContent((0, node_path_1.join)(absoluteProjectDir, "src", "main.ts"), migrateViteVanillaTsMainFile); updateFileContent((0, node_path_1.join)(absoluteProjectDir, "src", "main.tsx"), migrateViteReactTsMainFile); updateFileContent((0, node_path_1.join)(absoluteProjectDir, "src", "App.tsx"), migrateViteReactTsAppFile); updateFileContent((0, node_path_1.join)(absoluteProjectDir, "public", "favicon.svg"), (content) => addTitleToSvg(content, "Vite favicon")); updateFileContent((0, node_path_1.join)(absoluteProjectDir, "public", "icons.svg"), (content) => addTitleToSvg(content, "Vite icon sprite")); } function updateFileContent(filePath, updateContent) { if (!FileService_1.FileService.fileExists(filePath)) { return; } const content = FileService_1.FileService.getFileContent(filePath); const updatedContent = updateContent(content); if (updatedContent === content) { return; } FileService_1.FileService.putFileContent(filePath, updatedContent); } function migrateViteVanillaTsMainFile(content) { return replaceExactPattern(replaceExactPattern(content, VITE_VANILLA_APP_ELEMENT_PATTERN, VITE_VANILLA_APP_ELEMENT_REPLACEMENT), VITE_VANILLA_COUNTER_PATTERN, VITE_VANILLA_COUNTER_REPLACEMENT); } function migrateViteReactTsMainFile(content) { return replaceExactPattern(content, VITE_REACT_ROOT_PATTERN, VITE_REACT_ROOT_REPLACEMENT); } function migrateViteReactTsAppFile(content) { return replaceExactPattern(replaceReactDocsLinkText(addRelNoopenerToBlankTargetLinks(content)), VITE_REACT_BUTTON_PATTERN, VITE_REACT_BUTTON_REPLACEMENT); } function replaceReactDocsLinkText(content) { return content.replace(VITE_REACT_DOCS_LINK_TEXT_PATTERN, "$1Explore React docs$2"); } function addRelNoopenerToBlankTargetLinks(content) { return content.replaceAll(/target="_blank"(?![^>]*\srel=)/g, 'target="_blank" rel="noopener"'); } function addTitleToSvg(content, title) { if (content.includes("<title>")) { return content; } const openingTagMatch = content.match(/^<svg\b[^>]*>/); if (!openingTagMatch) { return content; } const [openingTag] = openingTagMatch; const titleMarkup = content.includes("\n") ? `${openingTag}\n <title>${title}</title>` : `${openingTag}<title>${title}</title>`; return content.replace(openingTag, titleMarkup); } function replaceExactPattern(content, pattern, replacement) { return content.includes(pattern) ? content.replace(pattern, replacement) : content; } function projectHasBiomeConfig(absoluteProjectDir) { return FileService_1.FileService.fileExists((0, node_path_1.join)(absoluteProjectDir, BIOME_CONFIG_FILE_NAME)); } function projectHasManagedEslintConfigFile(absoluteProjectDir) { const eslintConfigFilePath = (0, node_path_1.join)(absoluteProjectDir, ESLINT_CONFIG_FILE_NAME); if (!(0, node_fs_1.existsSync)(eslintConfigFilePath)) { return false; } return isManagedEslintConfig((0, node_fs_1.readFileSync)(eslintConfigFilePath, "utf-8")); } function isManagedEslintConfig(eslintConfigContent) { return MANAGED_ESLINT_MARKERS.some((managedMarker) => eslintConfigContent.includes(managedMarker)); } function deleteManagedEslintConfig(absoluteProjectDir, hasManagedEslintConfig) { if (!hasManagedEslintConfig) { return; } const eslintConfigFilePath = (0, node_path_1.join)(absoluteProjectDir, ESLINT_CONFIG_FILE_NAME); if (!(0, node_fs_1.existsSync)(eslintConfigFilePath)) { return; } (0, node_fs_1.unlinkSync)(eslintConfigFilePath); }