diff --git a/.changeset/sharp-months-battle.md b/.changeset/sharp-months-battle.md new file mode 100644 index 000000000..62ab83b47 --- /dev/null +++ b/.changeset/sharp-months-battle.md @@ -0,0 +1,5 @@ +--- +"aws-sdk-js-codemod": patch +--- + +Call JS API of jscodeshift instead of using spawn diff --git a/src/cli.spec.ts b/src/cli.spec.ts deleted file mode 100644 index f528a33e8..000000000 --- a/src/cli.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { spawn } from "child_process"; - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore: package.json will be imported from dist folders -import { version } from "../package.json"; // eslint-disable-line -import { run } from "./cli"; - -jest.mock("child_process"); - -describe("cli", () => { - const verifySpawnCall = (args: string[]) => { - expect(spawn).toHaveBeenCalledWith("npm", ["exec", "jscodeshift", "--", ...args], { - stdio: "inherit", - shell: process.platform == "win32", - }); - }; - - afterEach(() => { - jest.clearAllMocks(); - }); - - it("should print aws-sdk-js-codemod version", async () => { - jest.spyOn(process.stdout, "write"); - - const mockArgs = ["--version"]; - await run(mockArgs); - - expect(process.stdout.write).toHaveBeenCalledWith( - expect.stringMatching(`aws-sdk-js-codemod: ${version}`) - ); - verifySpawnCall(mockArgs); - }); - - it("should pass args to jscodeshift", async () => { - const mockArgs = ["random", "args", "one", "two", "three"]; - await run(mockArgs); - verifySpawnCall(mockArgs); - }); -}); diff --git a/src/cli.ts b/src/cli.ts index 1680440c4..4b6ce5fe2 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -18,39 +18,64 @@ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -import { spawn } from "child_process"; +// Most of the code from here is from bin/jscodeshift.js +// It's kept that way so that users can reuse jscodeshift options. // eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore: package.json will be imported from dist folders -import { version } from "../package.json"; // eslint-disable-line +// @ts-nocheck +import Runner from "jscodeshift/dist/Runner"; +import path from "path"; + import { - getArgsWithUpdatedTransformFile, getHelpParagraph, - getTransformFileFromArgs, + getJsCodeshiftParser, getTransforms, getUpdatedTransformFile, } from "./utils"; -export const run = async (args: string[]): Promise => { - const transforms = getTransforms(); - - if (args[0] === "--version") { - process.stdout.write(`aws-sdk-js-codemod: ${version}\n\n`); - } else if (args[0] === "--help" || args[0] === "-h") { - process.stdout.write(getHelpParagraph(transforms)); - } else if (args.includes("-t") || args.some((arg) => arg.startsWith("--transform="))) { - const transformFile = getTransformFileFromArgs(args); - if (transforms.map(({ name }) => name).includes(transformFile)) { - const updatedTransformFile = getUpdatedTransformFile(transformFile); - args = getArgsWithUpdatedTransformFile(args, updatedTransformFile); - } +const args = process.argv; +const transforms = getTransforms(); + +if (args[2] === "--help" || args[2] === "-h") { + process.stdout.write(getHelpParagraph(transforms)); +} + +const parser = getJsCodeshiftParser(); + +let options, positionalArguments; +try { + ({ options, positionalArguments } = parser.parse()); + if (positionalArguments.length === 0 && !options.stdin) { + process.stderr.write( + "Error: You have to provide at least one file/directory to transform." + + "\n\n---\n\n" + + parser.getHelpText() + ); + process.exit(1); } - spawn("npm", ["exec", "jscodeshift", "--", ...args], { - stdio: "inherit", - shell: process.platform == "win32", - }); -}; +} catch (e) { + const exitCode = e.exitCode === undefined ? 1 : e.exitCode; + (exitCode ? process.stderr : process.stdout).write(e.message); + process.exit(exitCode); +} + +const { transform } = options; +if (transforms.map(({ name }) => name).includes(transform)) { + options.transform = getUpdatedTransformFile(transform); +} -const [, , ...args] = process.argv; +function run(paths, options) { + Runner.run( + /^https?/.test(options.transform) ? options.transform : path.resolve(options.transform), + paths, + options + ); +} -run(args); +if (options.stdin) { + let buffer = ""; + process.stdin.on("data", (data) => (buffer += data)); + process.stdin.on("end", () => run(buffer.split("\n"), options)); +} else { + run(positionalArguments, options); +} diff --git a/src/utils/getArgsWithUpdatedTransformFile.ts b/src/utils/getArgsWithUpdatedTransformFile.ts deleted file mode 100644 index 07d57ac64..000000000 --- a/src/utils/getArgsWithUpdatedTransformFile.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const getArgsWithUpdatedTransformFile = ( - args: string[], - updatedTransformFile: string -): string[] => { - if (args.includes("-t")) { - const transformIndex = args.indexOf("-t"); - args[transformIndex + 1] = updatedTransformFile; - return args; - } - - const transformArg = args.find((arg) => arg.startsWith("--transform=")); - if (!transformArg) { - throw new Error("No transform file specified in -t or --transform."); - } - const transformIndex = args.indexOf(transformArg); - args[transformIndex] = `--transform=${updatedTransformFile}`; - return args; -}; diff --git a/src/utils/getJsCodeshiftParser.ts b/src/utils/getJsCodeshiftParser.ts new file mode 100644 index 000000000..b86672f12 --- /dev/null +++ b/src/utils/getJsCodeshiftParser.ts @@ -0,0 +1,154 @@ +// Most of the code from here is from bin/jscodeshift.js +// It's kept that way so that users can reuse jscodeshift options. + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck + +import { readFileSync } from "fs"; +import argsParser from "jscodeshift/dist/argsParser"; +import { dirname, join } from "path"; + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore: package.json will be imported from dist folders +import { version } from "../../package.json"; + +const requirePackage = (name: string) => { + const entry = require.resolve(name); + let dir = dirname(entry); + while (dir !== "/") { + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const pkg = require(join(dir, "package.json")); + return pkg.name === name ? pkg : {}; + } catch (error) {} // eslint-disable-line no-empty + dir = dirname(dir); + } + return {}; +}; + +/* eslint-disable @typescript-eslint/naming-convention */ +export const getJsCodeshiftParser = () => + argsParser.options({ + transform: { + display_index: 15, + abbr: "t", + default: "./transform.js", + help: "path to the transform file. Can be either a local path or url", + metavar: "FILE", + required: true, + }, + cpus: { + display_index: 1, + abbr: "c", + help: "start at most N child processes to process source files", + defaultHelp: "max(all - 1, 1)", + metavar: "N", + process: Number, + }, + verbose: { + display_index: 16, + abbr: "v", + choices: [0, 1, 2], + default: 0, + help: "show more information about the transform process", + metavar: "N", + process: Number, + }, + dry: { + display_index: 2, + abbr: "d", + flag: true, + default: false, + help: "dry run (no changes are made to files)", + }, + print: { + display_index: 11, + abbr: "p", + flag: true, + default: false, + help: "print transformed files to stdout, useful for development", + }, + babel: { + display_index: 0, + flag: true, + default: true, + help: "apply babeljs to the transform file", + }, + extensions: { + display_index: 3, + default: "js", + help: "transform files with these file extensions (comma separated list)", + metavar: "EXT", + }, + ignorePattern: { + display_index: 7, + full: "ignore-pattern", + list: true, + help: "ignore files that match a provided glob expression", + metavar: "GLOB", + }, + ignoreConfig: { + display_index: 6, + full: "ignore-config", + list: true, + help: "ignore files if they match patterns sourced from a configuration file (e.g. a .gitignore)", + metavar: "FILE", + }, + gitignore: { + display_index: 8, + flag: true, + default: false, + help: "adds entries the current directory's .gitignore file", + }, + runInBand: { + display_index: 12, + flag: true, + default: false, + full: "run-in-band", + help: "run serially in the current process", + }, + silent: { + display_index: 13, + abbr: "s", + flag: true, + default: false, + help: "do not write to stdout or stderr", + }, + parser: { + display_index: 9, + choices: ["babel", "babylon", "flow", "ts", "tsx"], + default: "babel", + help: "the parser to use for parsing the source files", + }, + parserConfig: { + display_index: 10, + full: "parser-config", + help: "path to a JSON file containing a custom parser configuration for flow or babylon", + metavar: "FILE", + process: (file: string) => JSON.parse(readFileSync(file).toString()), + }, + failOnError: { + display_index: 4, + flag: true, + help: "Return a non-zero code when there are errors", + full: "fail-on-error", + default: false, + }, + version: { + display_index: 17, + help: "print version and exit", + callback: function () { + return [ + `aws-sdk-js-codemod: ${version}`, + `- jscodeshift: ${requirePackage("jscodeshift").version}`, + `- recast: ${requirePackage("recast").version}\n`, + ].join("\n"); + }, + }, + stdin: { + display_index: 14, + help: "read file/directory list from stdin", + flag: true, + default: false, + }, + }); diff --git a/src/utils/getTransformFileFromArgs.ts b/src/utils/getTransformFileFromArgs.ts deleted file mode 100644 index d3652f821..000000000 --- a/src/utils/getTransformFileFromArgs.ts +++ /dev/null @@ -1,12 +0,0 @@ -export const getTransformFileFromArgs = (args: string[]): string => { - if (args.includes("-t")) { - const transformIndex = args.indexOf("-t"); - return args[transformIndex + 1]; - } - - const transformArg = args.find((arg) => arg.startsWith("--transform=")); - if (!transformArg) { - throw new Error("No transform file specified in -t or --transform."); - } - return transformArg.split("=")[1]; -}; diff --git a/src/utils/index.ts b/src/utils/index.ts index 333f1f262..7c3b2db54 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,5 +1,4 @@ -export * from "./getArgsWithUpdatedTransformFile"; export * from "./getHelpParagraph"; -export * from "./getTransformFileFromArgs"; +export * from "./getJsCodeshiftParser"; export * from "./getTransforms"; export * from "./getUpdatedTransformFile";