From 582322db860d983217ff92b12472366c859340c4 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 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 | 93 +++++++++++++++++++++++++++++++------ packages/types/src/index.ts | 27 +++++++++++ 2 files changed, 106 insertions(+), 14 deletions(-) create mode 100644 packages/types/src/index.ts diff --git a/packages/cli/src/main.ts b/packages/cli/src/main.ts index 73352daa5..0f421372e 100644 --- a/packages/cli/src/main.ts +++ b/packages/cli/src/main.ts @@ -4,6 +4,10 @@ import chalk from 'chalk'; import { PluginManager } from 'live-plugin-manager'; // @ts-ignore Run transform(s) on path https://github.com/facebook/jscodeshift/issues/398 import * as jscodeshift from 'jscodeshift/src/Runner'; +import { + CodeshiftConfig, // + DefaultRunner, +} from '@codeshift/types'; import { Flags } from './types'; import { InvalidUserInputError } from './errors'; @@ -130,20 +134,81 @@ export default async function main(paths: string[], flags: Flags) { 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 = ( + /** + * ideally you'd be able to pass in either the path, + * or the actual transform, + * but jscodeshift doesn't allow this (unless we fork?) + */ + jscodeshiftOptionOverrides = {}, + pathsToModify = paths, + transformerPath: string = resolvedTransformPath, + ): 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 { + // 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)) { + await transformImported.codeshiftConfig.runner(paths, { + 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. + */ + transform: transformImported.default, + // resolvedTransformPath + }); + } else { + defaultRunner(); + } } await packageManager.uninstallAll(); diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts new file mode 100644 index 000000000..42768eee1 --- /dev/null +++ b/packages/types/src/index.ts @@ -0,0 +1,27 @@ +export interface CodeshiftConfig { + target?: string[]; + maintainers?: string[]; + description?: string; + transforms?: Record; + presets?: Record; +} + +export type DefaultRunner = ( + jscodeshiftOptionOverrides?: object, + pathsToModify?: string[], // + transformerPath?: string, +) => Promise; + +export interface CustomRunnerCtx { + transform: Transform; + defaultRunner: DefaultRunner; +} + +export type CustomRunner = ( + pathsToModify: string[], // + options: CustomRunnerCtx, +) => void | Promise; + +export interface CodeshiftConfig { + runner: CustomRunner; +}