From 9934bc9ad4471d09a7340bd81ec63e99cb7f0da3 Mon Sep 17 00:00:00 2001 From: Tobbe Lundberg Date: Wed, 11 Mar 2026 07:56:11 +0100 Subject: [PATCH 1/2] chore(test-project): Use string based codemods for speed --- .../api/src/services/users/users.ts | 2 - .../components/BlogPostCell/BlogPostCell.tsx | 28 +- .../BlogPostsCell/BlogPostsCell.tsx | 23 +- .../web/src/pages/AboutPage/AboutPage.tsx | 2 + .../src/pages/BlogPostPage/BlogPostPage.tsx | 4 +- .../web/src/pages/HomePage/HomePage.tsx | 2 + .../web/src/pages/ProfilePage/ProfilePage.tsx | 2 +- .../src/pages/WaterfallPage/WaterfallPage.tsx | 4 +- tasks/test-project/base-tasks.mts | 8 + tasks/test-project/util.mts | 681 ++++++++++++++++++ 10 files changed, 715 insertions(+), 41 deletions(-) diff --git a/__fixtures__/test-project/api/src/services/users/users.ts b/__fixtures__/test-project/api/src/services/users/users.ts index 1160d12f84..006ffa8a29 100644 --- a/__fixtures__/test-project/api/src/services/users/users.ts +++ b/__fixtures__/test-project/api/src/services/users/users.ts @@ -2,8 +2,6 @@ import type { QueryResolvers, UserRelationResolvers } from 'types/graphql' import { db } from 'src/lib/db' -export {} - export const user: QueryResolvers['user'] = ({ id }) => { return db.user.findUnique({ where: { id }, diff --git a/__fixtures__/test-project/web/src/components/BlogPostCell/BlogPostCell.tsx b/__fixtures__/test-project/web/src/components/BlogPostCell/BlogPostCell.tsx index 7fde4f232b..c5c7a601a8 100644 --- a/__fixtures__/test-project/web/src/components/BlogPostCell/BlogPostCell.tsx +++ b/__fixtures__/test-project/web/src/components/BlogPostCell/BlogPostCell.tsx @@ -1,20 +1,6 @@ -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` +export const QUERY = gql` query FindBlogPostQuery($id: Int!) { blogPost: post(id: $id) { id @@ -33,14 +19,10 @@ export const Loading = () =>
Loading...
export const Empty = () =>
Empty
-export const Failure = ({ - error, -}: CellFailureProps) => ( +export const Failure = ({ error }) => (
Error: {error?.message}
) -export const Success = ({ - blogPost, -}: CellSuccessProps) => ( - -) +export const Success = ({ blogPost }) => { + return +} diff --git a/__fixtures__/test-project/web/src/components/BlogPostsCell/BlogPostsCell.tsx b/__fixtures__/test-project/web/src/components/BlogPostsCell/BlogPostsCell.tsx index 3805fcc0cf..785392860f 100644 --- a/__fixtures__/test-project/web/src/components/BlogPostsCell/BlogPostsCell.tsx +++ b/__fixtures__/test-project/web/src/components/BlogPostsCell/BlogPostsCell.tsx @@ -1,10 +1,7 @@ +import type { TypedDocumentNode } from '@graphql-typed-document-node/core' import type { BlogPostsQuery, BlogPostsQueryVariables } from 'types/graphql' -import type { - CellSuccessProps, - CellFailureProps, - TypedDocumentNode, -} from '@cedarjs/web' +import type { CellFailureProps, CellSuccessProps } from '@cedarjs/web' import BlogPost from 'src/components/BlogPost' @@ -36,10 +33,12 @@ export const Failure = ({ export const Success = ({ blogPosts, -}: CellSuccessProps) => ( -
- {blogPosts.map((post) => ( - - ))} -
-) +}: CellSuccessProps) => { + 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..39031a01a4 100644 --- a/tasks/test-project/util.mts +++ b/tasks/test-project/util.mts @@ -11,7 +11,688 @@ 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

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KeyValue
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) { + let nextSource = insertImportBeforeFirstImportOrThrow( + source, + "import BlogPost from 'src/components/BlogPost'", + 'Could not find where to insert BlogPost import in BlogPostsCell', + ) + + nextSource = replaceOrThrow( + nextSource, + /export const QUERY: TypedDocumentNode = gql`\n[\s\S]*?`\n/m, + `export const QUERY: TypedDocumentNode = gql\`${BLOG_POSTS_CELL_QUERY}\`\n`, + 'Could not replace BlogPostsCell QUERY', + ) + + return replaceOrThrow( + nextSource, + /export const Success = \(\{\s*blogPosts,\s*}: CellSuccessProps\) => \{[\s\S]*?^}\n/m, + `export const Success = ({ + blogPosts, +}: CellSuccessProps) => { + return (${BLOG_POSTS_CELL_SUCCESS}) +} +`, + 'Could not replace BlogPostsCell Success', + ) +} + +function transformBlogPostCell(source: string) { + let nextSource = insertImportBeforeFirstImportOrThrow( + source, + "import BlogPost from 'src/components/BlogPost'", + 'Could not find where to insert BlogPost import in BlogPostCell', + ) + + nextSource = replaceOrThrow( + nextSource, + /export const QUERY: TypedDocumentNode = gql`\n[\s\S]*?`\n/m, + `export const QUERY: TypedDocumentNode = gql\`${BLOG_POST_CELL_QUERY}\`\n`, + 'Could not replace BlogPostCell QUERY', + ) + + return replaceOrThrow( + nextSource, + /export const Success = \(\{\s*blogPost,\s*}: CellSuccessProps\) => \{[\s\S]*?^}\n/m, + `export const Success = ({ + blogPost, +}: CellSuccessProps) => { + return ${BLOG_POST_CELL_SUCCESS} +} +`, + 'Could not replace BlogPostCell 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', From c55fb9706993957267077d93e48f882a29ee7058 Mon Sep 17 00:00:00 2001 From: Tobbe Lundberg Date: Wed, 11 Mar 2026 08:08:58 +0100 Subject: [PATCH 2/2] make rebuild-test-project-fixture work --- .../BlogPostsCell/BlogPostsCell.tsx | 38 ++-- tasks/test-project/util.mts | 195 ++++++++++++++---- 2 files changed, 173 insertions(+), 60 deletions(-) diff --git a/__fixtures__/test-project/web/src/components/BlogPostsCell/BlogPostsCell.tsx b/__fixtures__/test-project/web/src/components/BlogPostsCell/BlogPostsCell.tsx index 785392860f..fc59d97a8c 100644 --- a/__fixtures__/test-project/web/src/components/BlogPostsCell/BlogPostsCell.tsx +++ b/__fixtures__/test-project/web/src/components/BlogPostsCell/BlogPostsCell.tsx @@ -1,39 +1,29 @@ -import type { TypedDocumentNode } from '@graphql-typed-document-node/core' -import type { BlogPostsQuery, BlogPostsQueryVariables } from 'types/graphql' - -import type { CellFailureProps, CellSuccessProps } from '@cedarjs/web' - import BlogPost from 'src/components/BlogPost' -export const QUERY: TypedDocumentNode = - gql` - query BlogPostsQuery { - blogPosts: posts { - id - title - body - author { - email - fullName - } - createdAt +export const QUERY = gql` + query BlogPostsQuery { + blogPosts: posts { + id + title + body + author { + email + fullName } + createdAt } - ` + } +` export const Loading = () =>
Loading...
export const Empty = () =>
Empty
-export const Failure = ({ - error, -}: CellFailureProps) => ( +export const Failure = ({ error }) => (
Error: {error?.message}
) -export const Success = ({ - blogPosts, -}: CellSuccessProps) => { +export const Success = ({ blogPosts }) => { return (
{blogPosts.map((post) => ( diff --git a/tasks/test-project/util.mts b/tasks/test-project/util.mts index 39031a01a4..531a32254d 100644 --- a/tasks/test-project/util.mts +++ b/tasks/test-project/util.mts @@ -415,57 +415,180 @@ export default BlogLayout`, } function transformBlogPostsCell(source: string) { - let nextSource = insertImportBeforeFirstImportOrThrow( - source, - "import BlogPost from 'src/components/BlogPost'", - 'Could not find where to insert BlogPost import in BlogPostsCell', - ) + const typedCellPattern = + /import type \{\s*BlogPostsQuery,\s*BlogPostsQueryVariables,\s*\} from 'types\/graphql'[\s\S]*?export const Success = \(\{[\s\S]*?\n\)\n?/m - nextSource = replaceOrThrow( - nextSource, - /export const QUERY: TypedDocumentNode = gql`\n[\s\S]*?`\n/m, - `export const QUERY: TypedDocumentNode = gql\`${BLOG_POSTS_CELL_QUERY}\`\n`, - 'Could not replace BlogPostsCell QUERY', - ) + if (typedCellPattern.test(source)) { + return `import type { BlogPostsQuery, BlogPostsQueryVariables } from 'types/graphql' - return replaceOrThrow( - nextSource, - /export const Success = \(\{\s*blogPosts,\s*}: CellSuccessProps\) => \{[\s\S]*?^}\n/m, - `export const Success = ({ +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) => { +}: 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', - ) + '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) { - let nextSource = insertImportBeforeFirstImportOrThrow( - source, - "import BlogPost from 'src/components/BlogPost'", - 'Could not find where to insert BlogPost import in BlogPostCell', - ) + const typedCellPattern = + /import type \{\s*FindBlogPostQuery,\s*FindBlogPostQueryVariables,\s*\} from 'types\/graphql'[\s\S]*?export const Success = \(\{[\s\S]*?\n\)\n?/m - nextSource = replaceOrThrow( - nextSource, - /export const QUERY: TypedDocumentNode = gql`\n[\s\S]*?`\n/m, - `export const QUERY: TypedDocumentNode = gql\`${BLOG_POST_CELL_QUERY}\`\n`, - 'Could not replace BlogPostCell QUERY', - ) + if (typedCellPattern.test(source)) { + return `import type { + FindBlogPostQuery, + FindBlogPostQueryVariables, +} from 'types/graphql' - return replaceOrThrow( - nextSource, - /export const Success = \(\{\s*blogPost,\s*}: CellSuccessProps\) => \{[\s\S]*?^}\n/m, - `export const Success = ({ +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) => { +}: 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', - ) + '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) {