Skip to content

Commit

Permalink
feat: allow providing custom runner per transform file thru Codeshift…
Browse files Browse the repository at this point in the history
…Config

depends on:
- hypermod-io#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:
- hypermod-io#67

Signed-off-by: Kipras Melnikovas <[email protected]>
  • Loading branch information
kiprasmel committed Jan 28, 2022
1 parent a7a01b2 commit bfbf31a
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 15 deletions.
118 changes: 103 additions & 15 deletions packages/cli/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import * as jscodeshift from 'jscodeshift/src/Runner';

import { fetchPackage, fetchRemotePackage } from '@codeshift/fetcher';
import { isValidConfig } from '@codeshift/validator';
import { CodeshiftConfig } from '@codeshift/types';
import {
CodeshiftConfig, //
DefaultRunner,
} from '@codeshift/types';

import { Flags } from './types';
import { InvalidUserInputError } from './errors';
Expand Down Expand Up @@ -175,20 +178,105 @@ Make sure the package name "${pkgName}" is correct and try again.`,
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<void> =>
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) {}

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<unknown>;
} => {
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: 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.
*/
transformInsideFileThatSpecifiesCodeshiftConfig:
transformImported.default,
// resolvedTransformPath
});
} else {
console.info(
'\nusing DEFAULT runner for transform',
resolvedTransformPath,
);

defaultRunner();
}
}

await packageManager.uninstallAll();
Expand Down
24 changes: 24 additions & 0 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,27 @@ export interface CodeshiftConfig {
transforms?: Record<string, string>;
presets?: Record<string, string>;
}

export type DefaultRunner = (
jscodeshiftOptionOverrides?: object,
pathsToModify?: string[], //
transformerPath?: string,
) => Promise<void>;

export interface CustomRunnerCtx<Transform = unknown> {
pathsToModify: string[]; //
defaultRunner: DefaultRunner;
transformInsideFileThatSpecifiesCodeshiftConfig: Transform;
}

export type CustomRunner<
Transform = unknown, //
Return = unknown | Promise<unknown>
> = (ctx: CustomRunnerCtx<Transform>) => Return;

export interface CodeshiftConfig<
Transform = unknown, //
RunnerReturn = unknown | Promise<unknown>
> {
runner: CustomRunner<Transform, RunnerReturn>;
}

0 comments on commit bfbf31a

Please sign in to comment.