pankod/refine

View on GitHub
packages/codemod/src/transformations/refine2-to-refine3.ts

Summary

Maintainability
D
1 day
Test Coverage
import {
  API,
  JSCodeshift,
  Collection,
  FileInfo,
  ImportSpecifier,
  JSXExpressionContainer,
  ObjectExpression,
  ObjectProperty,
  Identifier,
} from "jscodeshift";
import fs from "fs";
import path from "path";
import { install, remove } from "../helpers";
import { checkPackageLock } from "../helpers";

export const parser = "tsx";

const availableCoreImports = [
  "Authenticated",
  "AuthenticatedProps",
  "CanAccess",
  "CanAccessProps",
  "Refine",
  "RefineProps",
  "LayoutWrapperProps",
  "LayoutWrapper",
  "LayoutProps",
  "DefaultLayout",
  "RouteChangeHandler",
  "UndoableQueue",
  "defaultAccessControlContext",
  "AccessControlContext",
  "AccessControlContextProvider",
  "CanParams",
  "CanReturnType",
  "IAccessControlContext",
  "TLogoutVariables",
  "TLogoutData",
  "IAuthContext",
  "Pagination",
  "Search",
  "CrudOperators",
  "CrudFilter",
  "CrudSort",
  "CrudFilters",
  "CrudSorting",
  "CustomResponse",
  "GetListResponse",
  "CreateResponse",
  "CreateManyResponse",
  "UpdateResponse",
  "UpdateManyResponse",
  "GetOneResponse",
  "GetManyResponse",
  "DeleteOneResponse",
  "DeleteManyResponse",
  "IDataContext",
  "IDataContextProvider",
  "defaultDataProvider",
  "DataProvider",
  "DataContext",
  "DataContextProvider",
  "ILiveContext",
  "ILiveContextProvider",
  "LiveContext",
  "LiveContextProvider",
  "defaultNotificationProvider",
  "NotificationContext",
  "NotificationContextProvider",
  "RefineContext",
  "RefineContextProvider",
  "ResourceContext",
  "ResourceContextProvider",
  "IResourceContext",
  "OptionsProps",
  "ResourceProps",
  "IResourceComponentsProps",
  "IResourceComponents",
  "IResourceItem",
  "RouterContext",
  "RouterContextProvider",
  "IRouterProvider",
  "IRouterContext",
  "PromptProps",
  "TranslationContext",
  "TranslationContextProvider",
  "Translate",
  "I18nProvider",
  "ITranslationContext",
  "UnsavedWarnContext",
  "UnsavedWarnContextProvider",
  "IUnsavedWarnContext",
  "importCSVMapper",
  "userFriendlyResourceName",
  "userFriendlySecond",
  "parseTableParams",
  "parseTableParamsFromQuery",
  "stringifyTableParams",
  "compareFilters",
  "unionFilters",
  "setInitialFilters",
  "file2Base64",
  "UseCanProps",
  "useCan",
  "useCanWithoutCache",
  "useAuthenticated",
  "useCheckError",
  "useGetIdentity",
  "useIsAuthenticated",
  "UseLoginReturnType",
  "useLogin",
  "useLogout",
  "usePermissions",
  "useIsExistAuthentication",
  "unionFilters",
  "useApiUrl",
  "UseCreateReturnType",
  "useCreate",
  "UseCreateManyReturnType",
  "useCreateMany",
  "UseCustomProps",
  "useCustom",
  "useDelete",
  "useDeleteMany",
  "UseListProps",
  "useList",
  "UseManyProps",
  "useMany",
  "UseOneProps",
  "useOne",
  "UseUpdateReturnType",
  "useUpdate",
  "useUpdateMany",
  "CSVDownloadProps",
  "LabelKeyObject",
  "useExport",
  "Authenticated",
  "CanAccess",
  "LayoutWrapper",
  "Refine",
  "RouteChangeHandler",
  "UndoableQueue",
  "file2Base64",
  "importCSVMapper",
  "parseTableParams",
  "parseTableParamsFromQuery",
  "setInitialFilters",
  "stringifyTableParams",
  "unionFilters",
  "useApiUrl",
  "useAuthenticated",
  "useCacheQueries",
  "useCan",
  "useCanWithoutCache",
  "useCancelNotification",
  "useCheckError",
  "useCreate",
  "useCreateMany",
  "useCustom",
  "useDelete",
  "useDeleteMany",
  "useExport",
  "useGetIdentity",
  "useGetLocale",
  "useGetManyQueries",
  "useGetOneQueries",
  "useHandleNotification",
  "useIsAuthenticated",
  "useIsExistAuthentication",
  "useList",
  "useListResourceQueries",
  "useLiveMode",
  "useLogin",
  "useLogout",
  "useMany",
  "useMutationMode",
  "useNavigation",
  "useNotification",
  "useOne",
  "usePermissions",
  "usePublish",
  "useRedirectionAfterSubmission",
  "useRefineContext",
  "useResource",
  "useResourceSubscription",
  "useResourceWithRoute",
  "useRouterContext",
  "useSetLocale",
  "useShow",
  "useSubscription",
  "useSyncWithLocation",
  "useTitle",
  "useTranslate",
  "useUpdate",
  "useUpdateMany",
  "useWarnAboutChange",
  "userFriendlyResourceName",
  "AuthenticatedProps",
  "CanAccessProps",
  "RefineProps",
  "LayoutWrapperProps",
  "LiveModeProps",
  "UseResourceSubscriptionProps",
  "PublishType",
  "UseSubscriptionProps",
  "LiveEvent",
  "HistoryType",
  "UseRedirectionAfterSubmissionType",
  "UseWarnAboutChangeType",
  "UseMutationModeType",
  "useRefineContext",
  "UseSyncWithLocationType",
  "TitleProps",
  "UseResourceType",
  "useResourceWithRoute",
  "useShowReturnType",
  "useShowProps",
  "UseGetLocaleType",
  "Fields",
  "NestedField",
  "QueryBuilderOptions",
  "MetaDataQuery",
  "VariableOptions",
  "HttpError",
  "BaseRecord",
  "Option",
  "MapDataFn",
  "MutationMode",
  "IUndoableQueue",
  "RedirectionTypes",
  "ResourceErrorRouterParams",
  "ResourceRouterParams",
  "SuccessErrorNotification",
  "OpenNotificationParams",
  "AuthProvider",
];

function updateRefineImports(j: JSCodeshift, root: Collection<any>) {
  const refineCoreImports = root.find(j.ImportDeclaration, {
    source: {
      value: "@pankod/refine-core",
    },
  });

  if (refineCoreImports.length === 0) {
    const coreImports: ImportSpecifier[] = [];
    const antdImports: ImportSpecifier[] = [];

    const refineImport = root.find(j.ImportDeclaration, {
      source: {
        value: "@pankod/refine",
      },
    });

    refineImport.replaceWith((path) => {
      for (const item of path.node.specifiers) {
        if (availableCoreImports.includes(item.local.name)) {
          coreImports.push(item as ImportSpecifier);
        } else {
          antdImports.push(item as ImportSpecifier);
        }
      }

      path.node.specifiers = path.node.specifiers.filter(
        (p) => !antdImports.includes(p as ImportSpecifier),
      );

      path.node.source.value = "@pankod/refine-core";

      return path.node;
    });

    const refineElement = root.find(j.JSXElement, {
      openingElement: {
        name: {
          name: "Refine",
        },
      },
    });

    if (refineElement.length > 0) {
      const notificationProviderJSXAttribute = root.find(j.JSXAttribute, {
        name: {
          name: "notificationProvider",
        },
      });

      if (notificationProviderJSXAttribute.length === 0) {
        antdImports.push(
          j.importSpecifier(j.identifier("notificationProvider")),
        );
      }
    }

    if (antdImports.length > 0) {
      root
        .find(j.ImportDeclaration, {
          source: {
            value: "@pankod/refine-core",
          },
        })
        .insertAfter(
          j.importDeclaration(antdImports, j.literal("@pankod/refine-antd")),
        );
    }

    if (coreImports.length === 0) {
      refineImport.remove();
    }

    const refineCSSImport = root.find(j.ImportDeclaration, {
      source: {
        value: "@pankod/refine/dist/styles.min.css",
      },
    });

    refineCSSImport.forEach((refineCSSImport) => {
      refineCSSImport.value.source.value =
        "@pankod/refine-antd/dist/styles.min.css";
    });
  } else {
    console.log(
      "WARNING: A refine core package from @pankod/refine-core is already imported. This tool will not make any migration for refine core.",
    );
    return;
  }
}

const moveConfigProvider = (j: JSCodeshift, root: Collection<any>) => {
  const refineElement = root.find(j.JSXElement, {
    openingElement: {
      name: {
        name: "Refine",
      },
    },
  });

  if (refineElement.length === 0) {
    return;
  }

  const notificationProviderJSXAttribute = root.find(j.JSXAttribute, {
    name: {
      name: "notificationProvider",
    },
  });

  if (notificationProviderJSXAttribute.length === 0) {
    refineElement.forEach((path) => {
      path.node.openingElement.attributes.push(
        j.jsxAttribute(
          j.jsxIdentifier("notificationProvider"),
          j.jsxExpressionContainer(j.identifier("notificationProvider")),
        ),
      );
    });
  }

  const configProviderJSXAttribute = root.find(j.JSXAttribute, {
    name: {
      name: "configProviderProps",
    },
  });

  if (configProviderJSXAttribute.length > 0) {
    // Import ConfigProvider from @pankod/refine-antd
    const refineAntdImport = root.find(j.ImportDeclaration, {
      source: {
        value: "@pankod/refine-antd",
      },
    });

    if (refineAntdImport.length > 0) {
      refineAntdImport.forEach((path) => {
        path.node.specifiers.push(
          j.importSpecifier(j.identifier("ConfigProvider")),
        );
      });
    }

    const configProviderValue = (
      (configProviderJSXAttribute.nodes()[0].value as JSXExpressionContainer)
        .expression as ObjectExpression
    ).properties;

    const newConfigProviderElement = j.jsxElement(
      j.jsxOpeningElement(
        j.jsxIdentifier("ConfigProvider"),
        configProviderValue.map((p: ObjectProperty) =>
          j.jsxAttribute(
            j.jsxIdentifier((p.key as Identifier).name),
            j.jsxExpressionContainer(p.value as any),
          ),
        ),
      ),
      j.jsxClosingElement(j.jsxIdentifier("ConfigProvider")),
      refineElement.nodes(),
    );

    refineElement.replaceWith(newConfigProviderElement);

    configProviderJSXAttribute.remove();
  }
};

const defaultLoginPage = (j: JSCodeshift, root: Collection<any>) => {
  const refineElement = root.find(j.JSXElement, {
    openingElement: {
      name: {
        name: "Refine",
      },
    },
  });

  if (refineElement.length === 0) {
    return;
  }

  const authProviderJSXAttribute = root.find(j.JSXAttribute, {
    name: {
      name: "authProvider",
    },
  });

  const loginPageJSXAttribute = root.find(j.JSXAttribute, {
    name: {
      name: "LoginPage",
    },
  });

  if (
    authProviderJSXAttribute.length > 0 &&
    loginPageJSXAttribute.length === 0
  ) {
    refineElement.forEach((path) => {
      path.node.openingElement.attributes.push(
        j.jsxAttribute(
          j.jsxIdentifier("LoginPage"),
          j.jsxExpressionContainer(j.identifier("LoginPage")),
        ),
      );
    });

    const refineAntdImport = root.find(j.ImportDeclaration, {
      source: {
        value: "@pankod/refine-antd",
      },
    });

    if (refineAntdImport.length > 0) {
      refineAntdImport.forEach((path) => {
        path.node.specifiers.push(j.importSpecifier(j.identifier("LoginPage")));
      });
    }
  }
};

const defaultLayout = (j: JSCodeshift, root: Collection<any>) => {
  const refineElement = root.find(j.JSXElement, {
    openingElement: {
      name: {
        name: "Refine",
      },
    },
  });

  if (refineElement.length === 0) {
    return;
  }

  const layoutJSXAttribute = root.find(j.JSXAttribute, {
    name: {
      name: "Layout",
    },
  });

  if (layoutJSXAttribute.length === 0) {
    refineElement.forEach((path) => {
      path.node.openingElement.attributes.push(
        j.jsxAttribute(
          j.jsxIdentifier("Layout"),
          j.jsxExpressionContainer(j.identifier("Layout")),
        ),
      );
    });

    const refineAntdImport = root.find(j.ImportDeclaration, {
      source: {
        value: "@pankod/refine-antd",
      },
    });

    if (refineAntdImport.length > 0) {
      refineAntdImport.forEach((path) => {
        path.node.specifiers.push(j.importSpecifier(j.identifier("Layout")));
      });
    }
  }
};

const defaultCatchAllPage = (j: JSCodeshift, root: Collection<any>) => {
  const refineElement = root.find(j.JSXElement, {
    openingElement: {
      name: {
        name: "Refine",
      },
    },
  });

  if (refineElement.length === 0) {
    return;
  }

  const catchAllJSXAttribute = root.find(j.JSXAttribute, {
    name: {
      name: "catchAll",
    },
  });

  if (catchAllJSXAttribute.length > 0) {
    return;
  }

  refineElement.forEach((path) => {
    path.node.openingElement.attributes.push(
      j.jsxAttribute(
        j.jsxIdentifier("catchAll"),
        j.jsxExpressionContainer(
          j.jsxElement(
            j.jsxOpeningElement(j.jsxIdentifier("ErrorComponent"), [], true),
          ),
        ),
      ),
    );
  });

  const refineAntdImport = root.find(j.ImportDeclaration, {
    source: {
      value: "@pankod/refine-antd",
    },
  });

  if (refineAntdImport.length > 0) {
    refineAntdImport.forEach((path) => {
      path.node.specifiers.push(
        j.importSpecifier(j.identifier("ErrorComponent")),
      );
    });
  }
};

const updateSetEditIdToSetId = (j: JSCodeshift, root: Collection<any>) => {
  const updatedFormHooks = [
    "useEditableTable",
    "useModalForm",
    "useDrawerForm",
  ];

  for (const formHook of updatedFormHooks) {
    const useEditableTableHook = root.find(j.CallExpression, {
      callee: {
        name: formHook,
      },
    });

    useEditableTableHook.forEach((path) => {
      const setEditIdProperty = path.parentPath.node.id.properties.find(
        (p) => p.value.name === "setEditId",
      );

      if (setEditIdProperty) {
        setEditIdProperty.value.name = "setId: setEditId";
      }

      const editIdProperty = path.parentPath.node.id.properties.find(
        (p) => p.value.name === "editId",
      );

      if (editIdProperty) {
        editIdProperty.value.name = "id: editId";
      }
    });
  }
};

const packagesToUpdate = [
  "@pankod/refine-airtable",
  "@pankod/refine-graphql",
  "@pankod/refine-hasura",
  "@pankod/refine-nestjsx-crud",
  "@pankod/refine-nextjs-router",
  "@pankod/refine-react-router",
  "@pankod/refine-simple-rest",
  "@pankod/refine-strapi",
  "@pankod/refine-strapi-graphql",
  "@pankod/refine-supabase",
  "@pankod/refine-appwrite",
  "@pankod/refine-ably",
  "@pankod/@pankod/refine-strapi-v4",
];

export async function postTransform(files: any, flags: any) {
  const rootDir = path.join(process.cwd(), files[0]);
  const packageJsonPath = path.join(rootDir, "package.json");
  const useYarn = checkPackageLock(rootDir) === "yarn.lock";
  let packageJsonData;

  try {
    packageJsonData = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
  } catch (err) {
    console.error(
      `Error: failed to load package.json from ${packageJsonPath}, ensure provided directory is root.`,
    );
  }

  const dependenciesToInstall: Array<{
    name: string;
    version: string;
  }> = [
    {
      name: "@pankod/refine-core",
      version: "3.x.x",
    },
    {
      name: "@pankod/refine-antd",
      version: "3.x.x",
    },
  ];

  for (const key of Object.keys(packageJsonData.dependencies)) {
    if (packagesToUpdate.includes(key)) {
      dependenciesToInstall.push({
        name: key,
        version: "3.x.x",
      });
    }
  }

  if (!flags.dry) {
    await install(
      rootDir,
      dependenciesToInstall.map((dep) => `${dep.name}@${dep.version}`),
      {
        useYarn,
        isOnline: true,
      },
    );

    await remove(rootDir, ["@pankod/refine"], {
      useYarn,
    });
  }
}

export default function transformer(file: FileInfo, api: API): string {
  const j = api.jscodeshift;
  const source = j(file.source);

  const refineImports = source.find(j.ImportDeclaration, {
    source: {
      value: "@pankod/refine",
    },
  });

  if (refineImports.length === 0) {
    return;
  }

  updateRefineImports(j, source);
  moveConfigProvider(j, source);
  updateSetEditIdToSetId(j, source);
  defaultLoginPage(j, source);
  defaultLayout(j, source);
  defaultCatchAllPage(j, source);

  return source.toSource();
}