) => (
-
- {blogPosts.map((post) => (
-
- ))}
-
-)
+export const Success = ({ blogPosts }) => {
+ return (
+
+ {blogPosts.map((post) => (
+
+ ))}
+
+ )
+}
diff --git a/__fixtures__/test-project/web/src/pages/AboutPage/AboutPage.tsx b/__fixtures__/test-project/web/src/pages/AboutPage/AboutPage.tsx
index 17d809137d..7131237434 100644
--- a/__fixtures__/test-project/web/src/pages/AboutPage/AboutPage.tsx
+++ b/__fixtures__/test-project/web/src/pages/AboutPage/AboutPage.tsx
@@ -1,3 +1,5 @@
+// import { Link, routes } from '@cedarjs/router'
+
const AboutPage = () => {
return (
diff --git a/__fixtures__/test-project/web/src/pages/BlogPostPage/BlogPostPage.tsx b/__fixtures__/test-project/web/src/pages/BlogPostPage/BlogPostPage.tsx
index 52aac7a3d6..abb32bc557 100644
--- a/__fixtures__/test-project/web/src/pages/BlogPostPage/BlogPostPage.tsx
+++ b/__fixtures__/test-project/web/src/pages/BlogPostPage/BlogPostPage.tsx
@@ -1,12 +1,12 @@
// import { Link, routes } from '@cedarjs/router'
import { Metadata } from '@cedarjs/web'
+import BlogPostCell from 'src/components/BlogPostCell'
+
type BlogPostPageProps = {
id: number
}
-import BlogPostCell from 'src/components/BlogPostCell'
-
const BlogPostPage = ({ id }: BlogPostPageProps) => {
return (
<>
diff --git a/__fixtures__/test-project/web/src/pages/HomePage/HomePage.tsx b/__fixtures__/test-project/web/src/pages/HomePage/HomePage.tsx
index d86fe65702..732bb7156b 100644
--- a/__fixtures__/test-project/web/src/pages/HomePage/HomePage.tsx
+++ b/__fixtures__/test-project/web/src/pages/HomePage/HomePage.tsx
@@ -1,3 +1,5 @@
+// import { Link, routes } from '@cedarjs/router'
+
import BlogPostsCell from 'src/components/BlogPostsCell'
const HomePage = () => {
diff --git a/__fixtures__/test-project/web/src/pages/ProfilePage/ProfilePage.tsx b/__fixtures__/test-project/web/src/pages/ProfilePage/ProfilePage.tsx
index 0209c00efe..0085bae7c6 100644
--- a/__fixtures__/test-project/web/src/pages/ProfilePage/ProfilePage.tsx
+++ b/__fixtures__/test-project/web/src/pages/ProfilePage/ProfilePage.tsx
@@ -1,7 +1,7 @@
+// import { Link, routes } from '@cedarjs/router'
import { Metadata } from '@cedarjs/web'
import { useAuth } from 'src/auth'
-// import { Link, routes } from '@cedarjs/router'
const ProfilePage = () => {
const { currentUser, isAuthenticated, hasRole, loading } = useAuth()
diff --git a/__fixtures__/test-project/web/src/pages/WaterfallPage/WaterfallPage.tsx b/__fixtures__/test-project/web/src/pages/WaterfallPage/WaterfallPage.tsx
index 6c4f24a14c..651af7ce23 100644
--- a/__fixtures__/test-project/web/src/pages/WaterfallPage/WaterfallPage.tsx
+++ b/__fixtures__/test-project/web/src/pages/WaterfallPage/WaterfallPage.tsx
@@ -1,9 +1,11 @@
-import WaterfallBlogPostCell from 'src/components/WaterfallBlogPostCell'
+// import { Link, routes } from '@cedarjs/router'
type WaterfallPageProps = {
id: number
}
+import WaterfallBlogPostCell from 'src/components/WaterfallBlogPostCell'
+
const WaterfallPage = ({ id }: WaterfallPageProps) => (
)
diff --git a/tasks/test-project/base-tasks.mts b/tasks/test-project/base-tasks.mts
index d00dad3525..fe439b68a8 100644
--- a/tasks/test-project/base-tasks.mts
+++ b/tasks/test-project/base-tasks.mts
@@ -29,12 +29,20 @@ function getPagesTasks() {
{
title: 'Creating home page',
task: async () => {
+ const now = new Date()
await createPage('home /')
+ const middle = new Date()
await applyCodemod(
'homePage.js',
fullPath('web/src/pages/HomePage/HomePage'),
)
+ const after = new Date()
+
+ console.log(`home page took ${middle.getTime() - now.getTime()}ms`)
+ console.log(
+ `home page codemod took ${after.getTime() - middle.getTime()}ms`,
+ )
},
},
{
diff --git a/tasks/test-project/util.mts b/tasks/test-project/util.mts
index 5e7f2937df..531a32254d 100644
--- a/tasks/test-project/util.mts
+++ b/tasks/test-project/util.mts
@@ -11,7 +11,811 @@ import { getOutputPath } from './paths.mts'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
+const BLOG_POST_PAGE_STORY_RENDER = `export const Primary: Story = {
+ render: (args) => {
+ return
+ },
+}
+`
+
+const WATERFALL_PAGE_STORY_RENDER = `export const Primary: Story = {
+ render: (args) => {
+ return
+ },
+}
+`
+
+const PROFILE_PAGE_BODY = `{ const { currentUser, isAuthenticated, hasRole, loading } = useAuth()
+
+if (loading) {
+ return
Loading...
+}
+
+return (
+ <>
+
+
+ Profile
+
+
+
+
+ | Key |
+ Value |
+
+
+
+
+ | ID |
+ {currentUser.id} |
+
+
+ | ROLES |
+ {currentUser.roles} |
+
+
+ | EMAIL |
+ {currentUser.email} |
+
+
+
+ | isAuthenticated |
+ {JSON.stringify(isAuthenticated)} |
+
+
+
+ | Is Admin |
+ {JSON.stringify(hasRole('ADMIN'))} |
+
+
+
+ >
+)
+ }`
+
+const BLOG_POST_PAGE_BODY = `{
+ return (
+ <>
+
+
+
+ >
+ )
+}`
+
+const WATERFALL_PAGE_BODY = `
+
+`
+
+const ABOUT_PAGE_BODY = `
+
+This site was created to demonstrate my mastery of Cedar: Look on my
+works, ye mighty, and despair!
+
+`
+
+const BLOG_LAYOUT_BODY = `
+const { logOut, isAuthenticated } = useAuth()
+
+return (
+ <>
+
+
+
+ Cedar Blog
+
+
+
+
+
+ {children}
+
+ >
+)
+`
+
+const BLOG_POSTS_CELL_QUERY = `
+ query BlogPostsQuery {
+ blogPosts: posts {
+ id
+ title
+ body
+ author {
+ email
+ fullName
+ }
+ createdAt
+ }
+ }
+`
+
+const BLOG_POSTS_CELL_SUCCESS = `
+{blogPosts.map((post) => )}
+
`
+
+const BLOG_POST_CELL_QUERY = `
+ query FindBlogPostQuery($id: Int!) {
+ blogPost: post(id: $id) {
+ id
+ title
+ body
+ author {
+ email
+ fullName
+ }
+ createdAt
+ }
+ }
+`
+
+const BLOG_POST_CELL_SUCCESS = ``
+
+const AUTHOR_COMPONENT_BODY = '{author.fullName} ({author.email})'
+
+const CLASS_WITH_CLASS_FIELD_SOURCE = `class Bar {}
+
+class Foo {
+ // Without the correct babel plugins this will throw an error
+ public bar = new Bar()
+}
+
+const ClassWithClassField = () => {
+ return {new Foo().bar.toString()}
+}
+
+export default ClassWithClassField
+`
+
+function replaceOrThrow(
+ source: string,
+ searchValue: string | RegExp,
+ replacement: string,
+ errorMessage: string,
+) {
+ const nextSource = source.replace(searchValue, replacement)
+
+ if (nextSource === source) {
+ throw new Error(errorMessage)
+ }
+
+ return nextSource
+}
+
+function removeImportByModule(
+ source: string,
+ moduleSpecifier: string,
+ { required = false }: { required?: boolean } = {},
+) {
+ const escapedModuleSpecifier = moduleSpecifier.replace(
+ /[.*+?^${}()|[\]\\]/g,
+ '\\$&',
+ )
+ const importRegex = new RegExp(
+ `^import .* from ['"]${escapedModuleSpecifier}['"]\\r?\\n`,
+ 'gm',
+ )
+ const nextSource = source.replace(importRegex, '')
+
+ if (required && nextSource === source) {
+ throw new Error(`Could not find import for ${moduleSpecifier}`)
+ }
+
+ return nextSource
+}
+
+function insertImportBeforeFirstImportOrThrow(
+ source: string,
+ importStatement: string,
+ errorMessage: string,
+) {
+ if (source.includes(importStatement)) {
+ return source
+ }
+
+ const importRegex = /^import .*$/m
+ const match = source.match(importRegex)
+
+ if (match?.index !== undefined) {
+ return (
+ source.slice(0, match.index) +
+ importStatement +
+ '\n' +
+ source.slice(match.index)
+ )
+ }
+
+ const componentRegex = /const \w+(Page|Layout|Cell|Component) =/
+ const componentMatch = source.match(componentRegex)
+
+ if (componentMatch?.index === undefined) {
+ throw new Error(errorMessage)
+ }
+
+ return (
+ source.slice(0, componentMatch.index) +
+ importStatement +
+ '\n\n' +
+ source.slice(componentMatch.index)
+ )
+}
+
+function insertImportAfterLastImportOrThrow(
+ source: string,
+ importStatement: string,
+ errorMessage: string,
+) {
+ if (source.includes(importStatement)) {
+ return source
+ }
+
+ const importRegex = /^import .*$/gm
+ const matches = [...source.matchAll(importRegex)]
+ const lastMatch = matches.at(-1)
+
+ if (lastMatch?.index !== undefined) {
+ const insertAt = lastMatch.index + lastMatch[0].length
+
+ return (
+ source.slice(0, insertAt) +
+ '\n' +
+ importStatement +
+ source.slice(insertAt)
+ )
+ }
+
+ const componentRegex = /const \w+(Page|Layout|Cell|Component) =/
+ const componentMatch = source.match(componentRegex)
+
+ if (componentMatch?.index === undefined) {
+ throw new Error(errorMessage)
+ }
+
+ return (
+ source.slice(0, componentMatch.index) +
+ importStatement +
+ '\n\n' +
+ source.slice(componentMatch.index)
+ )
+}
+
+function transformHomePage(source: string) {
+ let nextSource = removeImportByModule(source, '@cedarjs/web')
+ nextSource = insertImportAfterLastImportOrThrow(
+ nextSource,
+ "import BlogPostsCell from 'src/components/BlogPostsCell'",
+ 'Could not find where to insert BlogPostsCell import',
+ )
+
+ return replaceOrThrow(
+ nextSource,
+ /return\s*\(\s*<>\s*[\s\S]*?\s*<\/>\s*\)/m,
+ 'return ',
+ 'Could not replace HomePage body',
+ )
+}
+
+function transformAboutPage(source: string) {
+ const nextSource = removeImportByModule(source, '@cedarjs/web')
+
+ return replaceOrThrow(
+ nextSource,
+ /return\s*\(\s*<>\s*[\s\S]*?\s*<\/>\s*\)/m,
+ `return (${ABOUT_PAGE_BODY})`,
+ 'Could not replace AboutPage body',
+ )
+}
+
+function transformBlogPostPage(source: string) {
+ const nextSource = insertImportAfterLastImportOrThrow(
+ source,
+ "import BlogPostCell from 'src/components/BlogPostCell'",
+ 'Could not find where to insert BlogPostCell import',
+ )
+
+ const blogPostComponentPatterns = [
+ /const BlogPostPage = \(\{ id \}: BlogPostPageProps\) => [\s\S]*?export default BlogPostPage/m,
+ /const BlogPostPage = \(\{ id \}\) => [\s\S]*?export default BlogPostPage/m,
+ /const BlogPostPage = \(\) => [\s\S]*?export default BlogPostPage/m,
+ /const BlogPostPage = \(([^)]*)\) => [\s\S]*?export default BlogPostPage/m,
+ ]
+
+ for (const pattern of blogPostComponentPatterns) {
+ const updatedSource = nextSource.replace(
+ pattern,
+ `const BlogPostPage = ({ id }: BlogPostPageProps) => ${BLOG_POST_PAGE_BODY}
+
+export default BlogPostPage`,
+ )
+
+ if (updatedSource !== nextSource) {
+ return updatedSource
+ }
+ }
+
+ throw new Error('Could not replace BlogPostPage component')
+}
+
+function transformBlogLayout(source: string) {
+ let nextSource = insertImportBeforeFirstImportOrThrow(
+ source,
+ "import { Link, NavLink, routes } from '@cedarjs/router'",
+ 'Could not find where to insert BlogLayout router import',
+ )
+ nextSource = insertImportBeforeFirstImportOrThrow(
+ nextSource,
+ "import { useAuth } from 'src/auth'",
+ 'Could not find where to insert BlogLayout useAuth import',
+ )
+
+ return replaceOrThrow(
+ nextSource,
+ /const BlogLayout = \(\{ children \}: BlogLayoutProps\) => \{[\s\S]*?^}\n\nexport default BlogLayout/m,
+ `const BlogLayout = ({ children }: BlogLayoutProps) => {${BLOG_LAYOUT_BODY}
+}
+
+export default BlogLayout`,
+ 'Could not replace BlogLayout component',
+ )
+}
+
+function transformBlogPostsCell(source: string) {
+ const typedCellPattern =
+ /import type \{\s*BlogPostsQuery,\s*BlogPostsQueryVariables,\s*\} from 'types\/graphql'[\s\S]*?export const Success = \(\{[\s\S]*?\n\)\n?/m
+
+ if (typedCellPattern.test(source)) {
+ return `import type { BlogPostsQuery, BlogPostsQueryVariables } from 'types/graphql'
+
+import type {
+ CellSuccessProps,
+ CellFailureProps,
+ TypedDocumentNode,
+} from '@cedarjs/web'
+
+import BlogPost from 'src/components/BlogPost'
+
+export const QUERY: TypedDocumentNode<
+ BlogPostsQuery,
+ BlogPostsQueryVariables
+> = gql\`${BLOG_POSTS_CELL_QUERY}\`
+
+export const Loading = () => Loading...
+
+export const Empty = () => Empty
+
+export const Failure = ({
+ error,
+}: CellFailureProps) => (
+ Error: {error?.message}
+)
+
+export const Success = ({
+ blogPosts,
+}: CellSuccessProps) => (
+
+ {blogPosts.map((post) => (
+
+ ))}
+
+)
+`
+ }
+
+ const queryPattern = /export const QUERY = gql`\n[\s\S]*?`\n/m
+ const successPattern =
+ /export const Success = \(\{\s*blogPosts\s*\}\) => \{[\s\S]*?^}\n/m
+
+ if (queryPattern.test(source) && successPattern.test(source)) {
+ let nextSource = insertImportBeforeFirstImportOrThrow(
+ source,
+ "import BlogPost from 'src/components/BlogPost'",
+ 'Could not find where to insert BlogPost import in BlogPostsCell',
+ )
+
+ nextSource = replaceOrThrow(
+ nextSource,
+ queryPattern,
+ `export const QUERY = gql\`${BLOG_POSTS_CELL_QUERY}\`\n`,
+ 'Could not replace BlogPostsCell QUERY',
+ )
+
+ return replaceOrThrow(
+ nextSource,
+ successPattern,
+ `export const Success = ({ blogPosts }) => {
+ return (${BLOG_POSTS_CELL_SUCCESS})
+}
+`,
+ 'Could not replace BlogPostsCell Success',
+ )
+ }
+
+ return `import BlogPost from 'src/components/BlogPost'
+
+export const QUERY = gql\`${BLOG_POSTS_CELL_QUERY}\`
+
+export const Loading = () => Loading...
+
+export const Empty = () => Empty
+
+export const Failure = ({ error }) => (
+ Error: {error?.message}
+)
+
+export const Success = ({ blogPosts }) => {
+ return (${BLOG_POSTS_CELL_SUCCESS})
+}
+`
+}
+
+function transformBlogPostCell(source: string) {
+ const typedCellPattern =
+ /import type \{\s*FindBlogPostQuery,\s*FindBlogPostQueryVariables,\s*\} from 'types\/graphql'[\s\S]*?export const Success = \(\{[\s\S]*?\n\)\n?/m
+
+ if (typedCellPattern.test(source)) {
+ return `import type {
+ FindBlogPostQuery,
+ FindBlogPostQueryVariables,
+} from 'types/graphql'
+
+import type {
+ CellSuccessProps,
+ CellFailureProps,
+ TypedDocumentNode,
+} from '@cedarjs/web'
+
+import BlogPost from 'src/components/BlogPost'
+
+export const QUERY: TypedDocumentNode<
+ FindBlogPostQuery,
+ FindBlogPostQueryVariables
+> = gql\`${BLOG_POST_CELL_QUERY}\`
+
+export const Loading = () => Loading...
+
+export const Empty = () => Empty
+
+export const Failure = ({
+ error,
+}: CellFailureProps) => (
+ Error: {error?.message}
+)
+
+export const Success = ({
+ blogPost,
+}: CellSuccessProps) => (
+
+)
+`
+ }
+
+ const queryPattern = /export const QUERY = gql`\n[\s\S]*?`\n/m
+ const successPattern =
+ /export const Success = \(\{\s*blogPost\s*\}\) => \{[\s\S]*?^}\n/m
+
+ if (queryPattern.test(source) && successPattern.test(source)) {
+ let nextSource = insertImportBeforeFirstImportOrThrow(
+ source,
+ "import BlogPost from 'src/components/BlogPost'",
+ 'Could not find where to insert BlogPost import in BlogPostCell',
+ )
+
+ nextSource = replaceOrThrow(
+ nextSource,
+ queryPattern,
+ `export const QUERY = gql\`${BLOG_POST_CELL_QUERY}\`\n`,
+ 'Could not replace BlogPostCell QUERY',
+ )
+
+ return replaceOrThrow(
+ nextSource,
+ successPattern,
+ `export const Success = ({ blogPost }) => {
+ return ${BLOG_POST_CELL_SUCCESS}
+}
+`,
+ 'Could not replace BlogPostCell Success',
+ )
+ }
+
+ return `import BlogPost from 'src/components/BlogPost'
+
+export const QUERY = gql\`${BLOG_POST_CELL_QUERY}\`
+
+export const Loading = () => Loading...
+
+export const Empty = () => Empty
+
+export const Failure = ({ error }) => (
+ Error: {error?.message}
+)
+
+export const Success = ({ blogPost }) => {
+ return ${BLOG_POST_CELL_SUCCESS}
+}
+`
+}
+
+function transformAuthor(source: string, target: string) {
+ if (target.endsWith('.tsx')) {
+ return `interface Props {
+ author: {
+ email: string
+ fullName: string
+ }
+}
+
+const Author = ({ author }: Props) => {
+ return ${AUTHOR_COMPONENT_BODY}
+}
+
+export default Author
+`
+ }
+
+ return `const Author = ({ author }) => {
+ return ${AUTHOR_COMPONENT_BODY}
+}
+
+export default Author
+`
+}
+
+function transformClassWithClassField() {
+ return CLASS_WITH_CLASS_FIELD_SOURCE
+}
+
+function transformUsersSdl(source: string) {
+ return source
+ .split('\n')
+ .map((line) => {
+ if (
+ line.includes('hashedPassword:') ||
+ line.includes('salt:') ||
+ line.includes('resetToken:') ||
+ line.includes('resetTokenExpiresAt:')
+ ) {
+ return undefined
+ }
+
+ if (line.trim() === 'users: [User!]! @requireAuth') {
+ return ' user(id: String!): User @skipAuth'
+ }
+
+ return line
+ })
+ .filter((line): line is string => line !== undefined)
+ .join('\n')
+}
+
+function transformProfilePage(source: string) {
+ const nextSource = insertImportAfterLastImportOrThrow(
+ source,
+ "import { useAuth } from 'src/auth'",
+ 'Could not find where to insert useAuth import',
+ )
+
+ return replaceOrThrow(
+ nextSource,
+ /const ProfilePage = \(\) => [\s\S]*?export default ProfilePage/m,
+ `const ProfilePage = () => ${PROFILE_PAGE_BODY}
+
+export default ProfilePage`,
+ 'Could not replace ProfilePage component',
+ )
+}
+
+function transformWaterfallPage(source: string) {
+ let nextSource = removeImportByModule(source, '@cedarjs/web')
+ nextSource = insertImportAfterLastImportOrThrow(
+ nextSource,
+ "import WaterfallBlogPostCell from 'src/components/WaterfallBlogPostCell'",
+ 'Could not find where to insert WaterfallBlogPostCell import',
+ )
+
+ const waterfallComponentPatterns = [
+ /const WaterfallPage = \(\{ id \}: WaterfallPageProps\) => [\s\S]*?export default WaterfallPage/m,
+ /const WaterfallPage = \(\{ id \}\) => [\s\S]*?export default WaterfallPage/m,
+ /const WaterfallPage = \(\) => [\s\S]*?export default WaterfallPage/m,
+ /const WaterfallPage = \(([^)]*)\) => [\s\S]*?export default WaterfallPage/m,
+ ]
+
+ for (const pattern of waterfallComponentPatterns) {
+ const updatedSource = nextSource.replace(
+ pattern,
+ `const WaterfallPage = ({ id }: WaterfallPageProps) => ${WATERFALL_PAGE_BODY}
+
+export default WaterfallPage`,
+ )
+
+ if (updatedSource !== nextSource) {
+ return updatedSource
+ }
+ }
+
+ throw new Error('Could not replace WaterfallPage component')
+}
+
+function transformUsersService(source: string) {
+ let nextSource = replaceOrThrow(
+ source,
+ /export const users:[\s\S]*?\n}\n/m,
+ '',
+ 'Could not remove users service function',
+ )
+
+ nextSource = nextSource.replace(/\nexport \{\}\n/, '\n')
+
+ return nextSource
+}
+
+function transformScenarioValueSuffix(source: string) {
+ return source
+ .split('\n')
+ .map((line, index) => {
+ const lineNumber = index + 1
+
+ return line
+ .replace(/String\d+/g, `String${lineNumber}`)
+ .replace(/foo\d+@bar\.com/g, `foo${lineNumber}@bar.com`)
+ })
+ .join('\n')
+}
+
+function transformBlogPostPageStories(source: string) {
+ return replaceOrThrow(
+ source,
+ /export const Primary: Story = \{\}/,
+ BLOG_POST_PAGE_STORY_RENDER.trimEnd(),
+ 'Could not replace Primary story in BlogPostPage stories',
+ )
+}
+
+function transformWaterfallPageStories(source: string) {
+ return replaceOrThrow(
+ source,
+ /export const Primary: Story = \{\}/,
+ WATERFALL_PAGE_STORY_RENDER.trimEnd(),
+ 'Could not replace Primary story in WaterfallPage stories',
+ )
+}
+
+function applyStringTransform(codemod: string, target: string) {
+ const source = fs.readFileSync(target, 'utf-8')
+
+ let nextSource = source
+
+ switch (codemod) {
+ case 'homePage.js': {
+ nextSource = transformHomePage(source)
+ break
+ }
+ case 'aboutPage.js': {
+ nextSource = transformAboutPage(source)
+ break
+ }
+ case 'blogPostPage.js': {
+ nextSource = transformBlogPostPage(source)
+ break
+ }
+ case 'blogLayout.js': {
+ nextSource = transformBlogLayout(source)
+ break
+ }
+ case 'blogPostsCell.js': {
+ nextSource = transformBlogPostsCell(source)
+ break
+ }
+ case 'blogPostCell.js': {
+ nextSource = transformBlogPostCell(source)
+ break
+ }
+ case 'author.js': {
+ nextSource = transformAuthor(source, target)
+ break
+ }
+ case 'classWithClassField.ts': {
+ nextSource = transformClassWithClassField()
+ break
+ }
+ case 'usersSdl.js': {
+ nextSource = transformUsersSdl(source)
+ break
+ }
+ case 'profilePage.js': {
+ nextSource = transformProfilePage(source)
+ break
+ }
+ case 'waterfallPage.js': {
+ nextSource = transformWaterfallPage(source)
+ break
+ }
+ case 'usersService.js': {
+ nextSource = transformUsersService(source)
+ break
+ }
+ case 'scenarioValueSuffix.js': {
+ nextSource = transformScenarioValueSuffix(source)
+ break
+ }
+ case 'updateBlogPostPageStories.js': {
+ nextSource = transformBlogPostPageStories(source)
+ break
+ }
+ case 'updateWaterfallPageStories.js': {
+ nextSource = transformWaterfallPageStories(source)
+ break
+ }
+ default: {
+ return false
+ }
+ }
+
+ fs.writeFileSync(target, nextSource)
+
+ return true
+}
+
export async function applyCodemod(codemod: string, target: string) {
+ if (applyStringTransform(codemod, target)) {
+ return { stdout: '', stderr: '', exitCode: 0 }
+ }
+
const args = [
'--fail-on-error',
'-t',