From 4d0f11b57655a9bf87f8b05cb3af41cfc76e081d Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Thu, 20 Jan 2022 15:14:28 +0200 Subject: [PATCH] feat: allow providing custom runner per transform file thru CodeshiftConfig depends on: - https://github.com/CodeshiftCommunity/CodeshiftCommunity/pull/63 example on how it could be used: - https://github.com/pipedrive/CodeshiftCommunity/pull/22 - though note we might refactor into separate PRs, idk, preferably would use directly from upstream (you). fixes a lot of cases for us: 1. we have a postcss codemod that we want to run, while still utilizing the @codeshift/cli. though, i don't know if these changes will work if we're using a remote package, will they? 2. we'll want to do some global pre-processing on files before running our codemod. though, there's still no way to provide the codemod as a __function__ instead of an __import path__ to jscodeshift, which will force us to do dependency injection instead of just passing the pre-processed results as an argument to a function. this is where the considerations to fork jscodeshift come into play again: - https://github.com/CodeshiftCommunity/CodeshiftCommunity/issues/67 Signed-off-by: Kipras Melnikovas --- packages/cli/src/main.ts | 119 +++++++++++++++++++++++++++++++----- packages/types/src/index.ts | 20 ++++++ 2 files changed, 124 insertions(+), 15 deletions(-) diff --git a/packages/cli/src/main.ts b/packages/cli/src/main.ts index 6c4d4b58e..5d0f72bef 100644 --- a/packages/cli/src/main.ts +++ b/packages/cli/src/main.ts @@ -6,7 +6,10 @@ import merge from 'lodash/merge'; // @ts-ignore Run transform(s) on path https://github.com/facebook/jscodeshift/issues/398 import * as jscodeshift from 'jscodeshift/src/Runner'; import { isValidConfig } from '@codeshift/validator'; -import { CodeshiftConfig } from '@codeshift/types'; +import { + CodeshiftConfig, // + DefaultRunner, +} from '@codeshift/types'; import { Flags } from './types'; import { InvalidUserInputError } from './errors'; @@ -218,20 +221,106 @@ Make sure the package name ${pkgName} has been spelled correctly and exists befo const resolvedTransformPath = path.resolve(transform); console.log(chalk.green('Running transform:'), resolvedTransformPath); - await jscodeshift.run(resolvedTransformPath, paths, { - verbose: 0, - dry: flags.dry, - print: true, - babel: true, - extensions: flags.extensions, - ignorePattern: flags.ignorePattern, - cpus: flags.cpus, - ignoreConfig: [], - runInBand: flags.runInBand, - silent: false, - parser: flags.parser, - stdin: false, - }); + const defaultRunner: DefaultRunner = ( + jscodeshiftOptionOverrides = {}, + pathsToModify = paths, + /** + * ideally you'd be able to pass in either the path, + * or the actual transform, + * but jscodeshift doesn't allow this (unless we fork?) + */ + transformerPath: string = resolvedTransformPath, + /** + * i think the jscodeshift.run is synchronous + * so the promise is not needed, + * but if we want to change it in the future, + * making it's return type a promise will help + * to avoid breaking changes for consumers who + * use the defaultRunner. + */ + ): Promise => + jscodeshift.run(transformerPath, pathsToModify, { + verbose: 0, + dry: flags.dry, + print: true, + babel: true, + extensions: flags.extensions, + ignorePattern: flags.ignorePattern, + cpus: flags.cpus, + ignoreConfig: [], + runInBand: flags.runInBand, + silent: false, + parser: flags.parser, + stdin: false, + ...jscodeshiftOptionOverrides, + }); + + let transformImported: any; + try { + /** + * TODO MAINTAINER -- i am not confident that this will work + * if the transform was provided thru an npm package. + */ + + // eslint-disable-next-line @typescript-eslint/no-var-requires + transformImported = require(resolvedTransformPath); + } catch (_e) {} + console.log({ transformImported }); + + const transformHasCustomRunner = ( + ti: any, + ): ti is { + /** + * ideally, `default` would be the type of the transformer, + * which would be identical to the type of the argument to + * `CustomTransformerConfig`, + * + * but unless we put the transformer itself into the config, + * we cannot ensure that the type is correct. + * + */ + default: unknown; // + codeshiftConfig: CodeshiftConfig; + } => { + if (ti && 'codeshiftConfig' in ti) { + return 'runner' in transformImported['codeshiftConfig']; + } + return false; + }; + + if (transformHasCustomRunner(transformImported)) { + console.info( + '\nusing CUSTOM runner for transform', + resolvedTransformPath, + ); + + await transformImported.codeshiftConfig.runner({ + pathsToModify, + defaultRunner, + /** + * providing the `transform`, `resolvedTransformPath`, etc. here + * is quite useless, because it's file-based, + * so in whichever file the config is in, + * that default export will be the transform, + * and the file's path will be the resolved path. + * + * ...unless you have a custom runner defined in a separate file, + * and want it to be able to access the transform, + * esp. if that runner does not take in a path, + * but rather the transform function. + */ + transformInsideFileThatSpecifiesCodeshiftConfig: + transformImported.default, + // resolvedTransformPath + }); + } else { + console.info( + '\nusing DEFAULT runner for transform', + resolvedTransformPath, + ); + + defaultRunner(); + } } await packageManager.uninstallAll(); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 25616f495..e7e154f70 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -5,3 +5,23 @@ export interface CodeshiftConfig { transforms?: Record; presets?: Record; } + +export type DefaultRunner = ( + jscodeshiftOptionOverrides?: object, + pathsToModify?: string[], // + transformerPath?: string, +) => Promise; + +export interface CustomRunnerCtx { + pathsToModify: string[]; // + defaultRunner: DefaultRunner; + transformInsideFileThatSpecifiesCodeshiftConfig: Transform; +} + +export type CustomRunner = ( + ctx: CustomRunnerCtx, +) => void | Promise; + +export interface CodeshiftConfig { + runner: CustomRunner; +}