From 5507cb7f57741de1279910579ec8e95ed7e176b0 Mon Sep 17 00:00:00 2001 From: Dmitry Maklygin Date: Tue, 27 Jun 2023 14:55:39 +0300 Subject: [PATCH] chore: adjust codemod --- .changeset/lemon-tigers-pull.md | 7 + packages/picasso-codemod/README.md | 8 + packages/picasso-codemod/bin/cli.mjs | 10 +- .../__testfixtures__/imports.input.tsx | 13 +- .../__testfixtures__/imports.output.tsx | 9 +- .../{package.input.json => package.json} | 5 +- .../__testfixtures__/package.output.json | 14 -- .../v36.0.0/__testfixtures__/types.input.ts | 0 .../v36.0.0/__testfixtures__/types.output.ts | 0 .../__testfixtures__/util-imports.input.tsx | 1 - .../__testfixtures__/util-imports.output.tsx | 1 - .../src/v36.0.0/__tests__/test.ts | 5 + .../v36.0.0/rich-text-editor-replacement.ts | 193 +++++++++++++++++- 13 files changed, 239 insertions(+), 27 deletions(-) create mode 100644 .changeset/lemon-tigers-pull.md rename packages/picasso-codemod/src/v36.0.0/__testfixtures__/{package.input.json => package.json} (63%) delete mode 100644 packages/picasso-codemod/src/v36.0.0/__testfixtures__/package.output.json delete mode 100644 packages/picasso-codemod/src/v36.0.0/__testfixtures__/types.input.ts delete mode 100644 packages/picasso-codemod/src/v36.0.0/__testfixtures__/types.output.ts delete mode 100644 packages/picasso-codemod/src/v36.0.0/__testfixtures__/util-imports.input.tsx delete mode 100644 packages/picasso-codemod/src/v36.0.0/__testfixtures__/util-imports.output.tsx create mode 100644 packages/picasso-codemod/src/v36.0.0/__tests__/test.ts diff --git a/.changeset/lemon-tigers-pull.md b/.changeset/lemon-tigers-pull.md new file mode 100644 index 0000000000..1ce25b4a2e --- /dev/null +++ b/.changeset/lemon-tigers-pull.md @@ -0,0 +1,7 @@ +--- +'@toptal/picasso-codemod': minor +--- + +--- + +- add codemod for replacing RichTextEditor imports diff --git a/packages/picasso-codemod/README.md b/packages/picasso-codemod/README.md index e27b95b9f0..f3e8da7e94 100644 --- a/packages/picasso-codemod/README.md +++ b/packages/picasso-codemod/README.md @@ -41,6 +41,14 @@ Codemods do not guarantee the code format preservation. Therefore be sure to run ## Included Scripts +### v36.0.0 + +Replaces all imports of RichTextEditor related components to `@toptal/picasso-rich-text-editor` and updates package.json with a new version of `@toptal/picasso`, `@toptal/picasso-rich-text-editor` and `@toptal/picasso-forms` + +```sh +npx @toptal/picasso-codemod v36.0.0 --run-in-band +``` + ### v52.2.0 Replaces compound `Form` with `FormNonCompound` and adjusts all the compound components to be directly imported from `picasso-forms`. diff --git a/packages/picasso-codemod/bin/cli.mjs b/packages/picasso-codemod/bin/cli.mjs index 37079fa20d..44e241a393 100644 --- a/packages/picasso-codemod/bin/cli.mjs +++ b/packages/picasso-codemod/bin/cli.mjs @@ -14,7 +14,7 @@ const require = createRequire(import.meta.url) const codemodsDirectory = path.join(__dirname, '../', 'src') const jscodeshift = require.resolve('.bin/jscodeshift') const paths = { - spa: 'src/**/*.tsx', + spa: 'src/**/*.ts*', monorepo: { libs: '*libs/*/src/*', hosts: '*hosts/*/src/*', @@ -28,7 +28,7 @@ const findFilesInMonorepo = () => execaSync('find', [ '.', '-name', - '*.tsx', + '*.ts*', '(', ...['-path', paths.monorepo.libs], ...['-or', '-path', paths.monorepo.apps], @@ -51,7 +51,7 @@ const findCodemodPath = codemod => .map(ext => path.join(codemodsDirectory, `${codemod}/index.${ext}`)) .find(fs.existsSync) -const runTransform = async ({ codemod, inputFiles, parserConfig }) => { +const runTransform = async ({ codemod, inputFiles, parserConfig, runInBand }) => { const codemodPath = findCodemodPath(codemod) const isMonorepo = await checkIsMonorepo() @@ -60,6 +60,7 @@ const runTransform = async ({ codemod, inputFiles, parserConfig }) => { args = args.concat(['--parser', 'tsx']) args = args.concat(['--transform', codemodPath]) + args = args.concat(['--run-in-band', runInBand]) if (parserConfig) { args.push(`--parser-config=${parserConfig}`) @@ -105,6 +106,7 @@ export const run = () => { Options --parser-config Add parser config + --run-in-band Run serially in the current process (default: false) Examples $ npx @toptal/picasso-codemod v17.0.0/typography-sizes @@ -124,10 +126,12 @@ export const run = () => { const codemod = cli.input[0] const inputFiles = cli.input.slice(1) const parserConfig = cli.flags.parserConfig + const runInBand = cli.flags.runInBand return runTransform({ codemod, inputFiles, parserConfig, + runInBand, }) } diff --git a/packages/picasso-codemod/src/v36.0.0/__testfixtures__/imports.input.tsx b/packages/picasso-codemod/src/v36.0.0/__testfixtures__/imports.input.tsx index 1bb1b6ac46..b6417b4806 100644 --- a/packages/picasso-codemod/src/v36.0.0/__testfixtures__/imports.input.tsx +++ b/packages/picasso-codemod/src/v36.0.0/__testfixtures__/imports.input.tsx @@ -1,2 +1,13 @@ +import { RichTextEditor } from '@toptal/picasso-forms' +import type { CustomEmojiGroup } from '@toptal/picasso/RichTextEditor' +import type { CustomEmoji } from '@toptal/picasso/RichTextEditor/types' +import type { RichTextEditorProps as LocalRichTextEditorProps } from '@toptal/picasso' +import { + Container, + RichText as LocalRichText, + Typography, +} from '@toptal/picasso' +import { RichText } from '@toptal/picasso' +import { Icon } from '@toptal/picasso' import { Something, transformString } from 'some-module' -import { Container, RichText, Typography } from '@toptal/picasso' +import { htmlToHast } from '@toptal/picasso/utils' diff --git a/packages/picasso-codemod/src/v36.0.0/__testfixtures__/imports.output.tsx b/packages/picasso-codemod/src/v36.0.0/__testfixtures__/imports.output.tsx index 06351d3310..3a26beb44d 100644 --- a/packages/picasso-codemod/src/v36.0.0/__testfixtures__/imports.output.tsx +++ b/packages/picasso-codemod/src/v36.0.0/__testfixtures__/imports.output.tsx @@ -1,3 +1,10 @@ -import { Something, transformString } from 'some-module' +import { RichTextEditor } from '@toptal/picasso-forms' +import type { CustomEmojiGroup } from '@toptal/picasso-rich-text-editor/RichTextEditor' +import type { CustomEmoji } from '@toptal/picasso-rich-text-editor/RichTextEditor/types' +import type { RichTextEditorProps as LocalRichTextEditorProps } from '@toptal/picasso-rich-text-editor' import { Container, Typography } from '@toptal/picasso' +import { RichText as LocalRichText } from '@toptal/picasso-rich-text-editor' import { RichText } from '@toptal/picasso-rich-text-editor' +import { Icon } from '@toptal/picasso' +import { Something, transformString } from 'some-module' +import { htmlToHast } from '@toptal/picasso-rich-text-editor/utils' diff --git a/packages/picasso-codemod/src/v36.0.0/__testfixtures__/package.input.json b/packages/picasso-codemod/src/v36.0.0/__testfixtures__/package.json similarity index 63% rename from packages/picasso-codemod/src/v36.0.0/__testfixtures__/package.input.json rename to packages/picasso-codemod/src/v36.0.0/__testfixtures__/package.json index 9d3702a0fb..c28ea505fb 100644 --- a/packages/picasso-codemod/src/v36.0.0/__testfixtures__/package.input.json +++ b/packages/picasso-codemod/src/v36.0.0/__testfixtures__/package.json @@ -6,8 +6,9 @@ "license": "ISC", "main": "./src/index.ts", "dependencies": { - "@toptal/picasso": "35.0.2", - "@toptal/picasso-forms": "57.0.0" + "@toptal/picasso": "36.0.0", + "@toptal/picasso-forms": "58.0.0", + "@toptal/picasso-rich-text-editor": "1.0.2" }, "private": true, "sideEffects": false diff --git a/packages/picasso-codemod/src/v36.0.0/__testfixtures__/package.output.json b/packages/picasso-codemod/src/v36.0.0/__testfixtures__/package.output.json deleted file mode 100644 index 9d3702a0fb..0000000000 --- a/packages/picasso-codemod/src/v36.0.0/__testfixtures__/package.output.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "somemodule", - "description": "test", - "version": "0.0.3", - "author": "Toptal", - "license": "ISC", - "main": "./src/index.ts", - "dependencies": { - "@toptal/picasso": "35.0.2", - "@toptal/picasso-forms": "57.0.0" - }, - "private": true, - "sideEffects": false -} diff --git a/packages/picasso-codemod/src/v36.0.0/__testfixtures__/types.input.ts b/packages/picasso-codemod/src/v36.0.0/__testfixtures__/types.input.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/picasso-codemod/src/v36.0.0/__testfixtures__/types.output.ts b/packages/picasso-codemod/src/v36.0.0/__testfixtures__/types.output.ts deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/packages/picasso-codemod/src/v36.0.0/__testfixtures__/util-imports.input.tsx b/packages/picasso-codemod/src/v36.0.0/__testfixtures__/util-imports.input.tsx deleted file mode 100644 index dbe7fc013e..0000000000 --- a/packages/picasso-codemod/src/v36.0.0/__testfixtures__/util-imports.input.tsx +++ /dev/null @@ -1 +0,0 @@ -import { htmlToHast } from '@toptal/picasso/utils' diff --git a/packages/picasso-codemod/src/v36.0.0/__testfixtures__/util-imports.output.tsx b/packages/picasso-codemod/src/v36.0.0/__testfixtures__/util-imports.output.tsx deleted file mode 100644 index 07df1cf088..0000000000 --- a/packages/picasso-codemod/src/v36.0.0/__testfixtures__/util-imports.output.tsx +++ /dev/null @@ -1 +0,0 @@ -import { htmlToHast } from '@toptal/picasso-rich-text-editor/utils' diff --git a/packages/picasso-codemod/src/v36.0.0/__tests__/test.ts b/packages/picasso-codemod/src/v36.0.0/__tests__/test.ts new file mode 100644 index 0000000000..815f6bbff9 --- /dev/null +++ b/packages/picasso-codemod/src/v36.0.0/__tests__/test.ts @@ -0,0 +1,5 @@ +import { defineTest } from 'jscodeshift/src/testUtils' + +defineTest(__dirname, 'rich-text-editor-replacement', {}, 'imports', { + parser: 'tsx', +}) diff --git a/packages/picasso-codemod/src/v36.0.0/rich-text-editor-replacement.ts b/packages/picasso-codemod/src/v36.0.0/rich-text-editor-replacement.ts index ec31075242..164a17b31e 100644 --- a/packages/picasso-codemod/src/v36.0.0/rich-text-editor-replacement.ts +++ b/packages/picasso-codemod/src/v36.0.0/rich-text-editor-replacement.ts @@ -1,9 +1,194 @@ import type { Transform } from 'jscodeshift' +import path from 'path' +import fs from 'fs' -const transform: Transform = (file, api) => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const j = api.jscodeshift - // const root = j(file.source) +const specificModules = [ + 'RichText', + 'htmlToHast', + 'RichTextEditor', + 'RichTextEditorProps', + 'ASTType', + 'CustomEmojiGroup', + 'CustomEmoji', +] +const picassoVersion = '36.0.0' +const picassoFormsVersion = '58.0.0' +const picassoRichTextEditorVersion = '1.0.2' + +// Get current execution directory +const execDir = process.cwd() + +const findPackageJson = (dirPath: string) => { + // Start with the directory provided + let currentPath = dirPath + + while (currentPath !== '/' && currentPath !== execDir) { + // Attempt to read package.json at current path + try { + const packageJsonPath = path.join(currentPath, 'package.json') + + if (fs.existsSync(packageJsonPath)) { + return packageJsonPath + } + } catch (err) { + console.error(err) + } + + // If package.json doesn't exist, move up one directory + currentPath = path.dirname(currentPath) + } + + throw new Error('Could not find package.json') +} + +const updatePackageJsonVersions = ( + path: string, + { addRichTextEditorDependency }: { addRichTextEditorDependency: boolean } +) => { + try { + const packageJsonPath = findPackageJson(path) + + // If it exists, read and parse it + try { + const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')) + + if (!packageJson.dependencies) { + return + } + + let inDevDependencies = false + + // Update Picasso and Picasso forms dependencies + if (packageJson.dependencies['@toptal/picasso']) { + packageJson.dependencies['@toptal/picasso'] = picassoVersion + } else if ( + packageJson.devDependencies && + packageJson.devDependencies['@toptal/picasso'] + ) { + packageJson.devDependencies['@toptal/picasso'] = picassoVersion + inDevDependencies = true + } + + if (packageJson.dependencies['@toptal/picasso-forms']) { + packageJson.dependencies['@toptal/picasso-forms'] = picassoFormsVersion + // if picasso form is defined, it depends on RichTextEditor, we should also add a dependency + packageJson.dependencies['@toptal/picasso-rich-text-editor'] = + picassoRichTextEditorVersion + } else if ( + packageJson.devDependencies && + packageJson.devDependencies['@toptal/picasso-forms'] + ) { + packageJson.devDependencies['@toptal/picasso-forms'] = + picassoFormsVersion + // if picasso form is defined, it depends on RichTextEditor, we should also add a dependency + packageJson.devDependencies['@toptal/picasso-rich-text-editor'] = + picassoRichTextEditorVersion + inDevDependencies = true + } + + // if RTE is used inside package, we should add a dependency + if (addRichTextEditorDependency) { + if (inDevDependencies) { + packageJson.devDependencies['@toptal/picasso-rich-text-editor'] = + picassoRichTextEditorVersion + } else { + packageJson.dependencies['@toptal/picasso-rich-text-editor'] = + picassoRichTextEditorVersion + } + } + + fs.writeFileSync( + packageJsonPath, + JSON.stringify(packageJson, null, 2) + '\n' + ) + } catch (err) { + console.log(`Could not parse package.json at ${packageJsonPath}: ${err}`) + } + } catch (err) { + console.log('err: ', err) + console.error(`Package json not found for ${path}: ${err}`) + } +} + +const transform: Transform = (file, { jscodeshift: j }) => { + const source = j(file.source) + let fileContainsRichTextEditorImport = false + let hasPicassoForms = false + + const picassoImports = source.find(j.ImportDeclaration).filter(path => { + const pattern = /@toptal\/picasso($|\/[a-z\d]*)/gi + + hasPicassoForms = + hasPicassoForms || path.node.source.value === '@toptal/picasso-forms' + + return ( + pattern.test(path.node.source.value as string) && + path.node.source.value !== '@toptal/picasso-forms' + ) + }) + + // Iterate over react imports + picassoImports.forEach(picassoImport => { + const richTextSpecifiers = j(picassoImport) + .find(j.ImportSpecifier) + .filter(path => { + return specificModules.includes(path.node.imported.name) + }) + + // there is only single specifier, replace the whole import + if (picassoImport.node.specifiers?.length === richTextSpecifiers.length) { + j(picassoImport).replaceWith( + // Build a new import declaration node based on the existing one + j.importDeclaration( + picassoImport.node.specifiers, // copy over the existing import specificers + j.stringLiteral( + (picassoImport.node.source.value as string).replace( + '@toptal/picasso', + '@toptal/picasso-rich-text-editor' + ) + ), + picassoImport.node.importKind + ) + ) + + fileContainsRichTextEditorImport = true + } else if (richTextSpecifiers.length > 0) { + // insert specifiers for rich text editor + // const importSpecifier = j.importSpecifier(richTextSpecifiers); + // Generate new specifiers for the new import + const newSpecifiers = richTextSpecifiers.nodes().map(node => { + return j.importSpecifier( + j.identifier(node.imported.name), + node.local ? j.identifier(node.local.name) : undefined + ) + }) + // Create a new import declaration + const newImport = j.importDeclaration( + newSpecifiers, + j.literal('@toptal/picasso-rich-text-editor'), + picassoImport.node.importKind + ) + + j(picassoImport).insertAfter(newImport) + + // there are some specifiers left, remove the ones that are replaced + j(picassoImport) + .find(j.ImportSpecifier) + .filter(path => specificModules.includes(path.node.imported.name)) + .remove() + + fileContainsRichTextEditorImport = true + } + }) + + // we must update package.json as well. We have to upgrade @toptal/picasso, @toptal/picasso-forms and add new dependency @toptal/picasso-rich-text-editor + // first of all, we need to search for a package json, it must be in the root of the package + updatePackageJsonVersions(file.path, { + addRichTextEditorDependency: + fileContainsRichTextEditorImport || hasPicassoForms, + }) + + return source.toSource({ trailingComma: false, quote: 'single' }) } export default transform