Repository: remix-run/remix
Pull request #6918 (2023-07-21 15:37:58)

feat!(v2): remove codemod
<!--

👋 Hey, thanks for your interest in contributing to Remix!

If this is a simple docs change, go ahead and delete all this.

**Please ask first before starting work on any significant new features.**

It's never a fun experience to have your pull request declined after investing a
lot of time and effort into a new feature. To avoid this from happening, we
request that contributors create a
[Feature Request discussion](https://github.com/remix-run/remix/discussions/new?category=ideas)
to first discuss any significant new features.

https://github.com/remix-run/remix/blob/main/CONTRIBUTING.md

Please fill in or delete each item below:

-->

Closes: #

- [ ] Docs
- [ ] Tests

Testing Strategy:

<!--
Please explain how you tested this. For example:

> This test covers this code: <link to test>

Or

> I opened up my windows machine and ran this script:
>
> ```
> npx create-remix@0.0.0-experimental-7e420ee3 --template remix my-test
> cd my-test
> npm run dev
> ```
-->


changeset-bot[bot]: ###  🦋  Changeset detected

Latest commit: f7ef6ca97aeaf39639294ceba2532c161e917486

**The changes in this PR will be included in the next version bump.**

<details><summary>This PR includes changesets to release 18 packages</summary>

  | Name                          | Type  |
| ----------------------------- | ----- |
| @remix-run/dev                | Major |
| create-remix                  | Major |
| remix                         | Major |
| @remix-run/architect          | Major |
| @remix-run/cloudflare         | Major |
| @remix-run/cloudflare-pages   | Major |
| @remix-run/cloudflare-workers | Major |
| @remix-run/css-bundle         | Major |
| @remix-run/deno               | Major |
| @remix-run/eslint-config      | Major |
| @remix-run/express            | Major |
| @remix-run/netlify            | Major |
| @remix-run/node               | Major |
| @remix-run/react              | Major |
| @remix-run/serve              | Major |
| @remix-run/server-runtime     | Major |
| @remix-run/testing            | Major |
| @remix-run/vercel             | Major |

</details>

Not sure what this means? [Click here  to learn what changesets are](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add another changeset to this PR](https://github.com/remix-run/remix/new/pedro/remove-codemod?filename=.changeset/five-tools-dance.md&value=---%0A%22remix%22%3A%20patch%0A%22%40remix-run%2Fdev%22%3A%20patch%0A---%0A%0Afeat!(v2)%3A%20remove%20codemod%0A)



Patch:
diff --git a/.changeset/stupid-emus-repair.md b/.changeset/stupid-emus-repair.md
new file mode 100644
index 000000000..b050e38aa
--- /dev/null
+++ b/.changeset/stupid-emus-repair.md
@@ -0,0 +1,5 @@
+---
+"@remix-run/dev": major
+---
+
+remove codemod command
diff --git a/packages/remix-dev/cli/commands.ts b/packages/remix-dev/cli/commands.ts
index be8f4ba9b..64bb840a1 100644
--- a/packages/remix-dev/cli/commands.ts
+++ b/packages/remix-dev/cli/commands.ts
@@ -17,9 +17,6 @@ import { readConfig } from "../config";
 import { formatRoutes, RoutesFormat, isRoutesFormat } from "../config/format";
 import { detectPackageManager } from "./detectPackageManager";
 import { setupRemix, isSetupPlatform, SetupPlatform } from "./setup";
-import runCodemod from "../codemod";
-import { CodemodError } from "../codemod/utils/error";
-import { TaskError } from "../codemod/utils/task";
 import { transpile as convertFileToJS } from "./useJavascript";
 import type { Options } from "../compiler/options";
 import { createFileWatchCache } from "../compiler/fileWatchCache";
@@ -206,41 +203,6 @@ export async function dev(
   await devServer_unstable.serve(config, resolved);
 }
 
-export async function codemod(
-  codemodName?: string,
-  projectDir?: string,
-  { dry = false, force = false } = {}
-) {
-  if (!codemodName) {
-    console.error(colors.red("Error: Missing codemod name"));
-    console.log(
-      "Usage: " +
-        colors.gray(
-          `remix codemod <${colors.arg("codemod")}> [${colors.arg(
-            "projectDir"
-          )}]`
-        )
-    );
-    process.exit(1);
-  }
-  try {
-    await runCodemod(projectDir ?? process.cwd(), codemodName, {
-      dry,
-      force,
-    });
-  } catch (error: unknown) {
-    if (error instanceof CodemodError) {
-      console.error(`${colors.red("Error:")} ${error.message}`);
-      if (error.additionalInfo) console.info(colors.gray(error.additionalInfo));
-      process.exit(1);
-    }
-    if (error instanceof TaskError) {
-      process.exit(1);
-    }
-    throw error;
-  }
-}
-
 let clientEntries = ["entry.client.tsx", "entry.client.js", "entry.client.jsx"];
 let serverEntries = ["entry.server.tsx", "entry.server.js", "entry.server.jsx"];
 let entries = ["entry.client", "entry.server"];
diff --git a/packages/remix-dev/cli/run.ts b/packages/remix-dev/cli/run.ts
index 334af6790..72b0bc274 100644
--- a/packages/remix-dev/cli/run.ts
+++ b/packages/remix-dev/cli/run.ts
@@ -17,7 +17,6 @@ ${colors.logoBlue("R")} ${colors.logoGreen("E")} ${colors.logoYellow(
     $ remix routes [${colors.arg("projectDir")}]
     $ remix watch [${colors.arg("projectDir")}]
     $ remix setup [${colors.arg("remixPlatform")}]
-    $ remix codemod <${colors.arg("codemod")}> [${colors.arg("projectDir")}]
 
   ${colors.heading("Options")}:
     --help, -h          Print this help message and exit
@@ -39,9 +38,6 @@ ${colors.logoBlue("R")} ${colors.logoGreen("E")} ${colors.logoYellow(
     --no-delete         Skip deleting the \`remix.init\` script
   \`routes\` Options:
     --json              Print the routes as JSON
-  \`codemod\` Options:
-    --dry               Dry run (no changes are made to files)
-    --force             Bypass Git safety checks
 
   ${colors.heading("Values")}:
     - ${colors.arg("projectDir")}        The Remix project directory
@@ -225,10 +221,6 @@ export async function run(argv: string[] = process.argv.slice(2)) {
     case "setup":
       await commands.setup(input[1]);
       break;
-    case "codemod": {
-      await commands.codemod(input[1], input[2]);
-      break;
-    }
     case "reveal": {
       // TODO: simplify getting started guide
       await commands.generateEntry(input[1], input[2], flags.typescript);
diff --git a/packages/remix-dev/codemod/codemod.ts b/packages/remix-dev/codemod/codemod.ts
deleted file mode 100644
index 3e22a7780..000000000
--- a/packages/remix-dev/codemod/codemod.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-export type Options = {
-  dry: boolean;
-  force: boolean;
-};
-
-export type Codemod = (projectDir: string, options: Options) => Promise<void>;
diff --git a/packages/remix-dev/codemod/index.ts b/packages/remix-dev/codemod/index.ts
deleted file mode 100644
index d83e3a73e..000000000
--- a/packages/remix-dev/codemod/index.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import * as path from "path";
-
-import { blue, yellow } from "../colors";
-import * as git from "./utils/git";
-import * as log from "./utils/log";
-import { task } from "./utils/task";
-import type { Codemod, Options } from "./codemod";
-import { CodemodError } from "./utils/error";
-
-const codemods = {} as const;
-const codemodNames = Object.keys(codemods);
-
-type CodemodName = keyof typeof codemods;
-
-const isCodemodName = (maybe: string): maybe is CodemodName =>
-  codemodNames.includes(maybe);
-
-const availableCodemodsText =
-  codemodNames.length === 0
-    ? "We currently don't have any codemods. Check back soon!"
-    : [
-        "Available codemods:",
-        ...codemodNames.sort().map((name) => `- ${name}`),
-      ].join("\n");
-
-export default async (
-  projectDir: string,
-  codemodName: string,
-  { dry = false, force = false }: Partial<Options> = {}
-) => {
-  if (dry) log.info(`${yellow("! Dry mode")}: files will not be overwritten`);
-  if (force)
-    log.info(`${yellow("! Force mode")}: uncommitted changes may be lost`);
-
-  let gitStatus = await git.status(projectDir);
-  if (!dry && !force && gitStatus === "not a git repository") {
-    throw new CodemodError(
-      `${path.resolve(projectDir)} is not a git repository`,
-      "To override this safety check, use the --force flag"
-    );
-  }
-  if (!dry && !force && gitStatus === "dirty") {
-    throw new CodemodError(
-      `${path.resolve(projectDir)} has uncommitted changes`,
-      [
-        "Stash or commit your changes before running codemods",
-        "To override this safety check, use the --force flag",
-      ].join("\n")
-    );
-  }
-
-  let codemod = await task(
-    `Finding codemod: ${blue(codemodName)}`,
-    async () => {
-      if (!isCodemodName(codemodName)) {
-        throw new CodemodError(
-          `Unrecognized codemod: ${blue(codemodName)}`,
-          availableCodemodsText
-        );
-      }
-      return codemods[codemodName] as Codemod;
-    },
-    `Found codemod: ${blue(codemodName)}`
-  );
-
-  await codemod(projectDir, { dry, force });
-};
diff --git a/packages/remix-dev/codemod/utils/error.ts b/packages/remix-dev/codemod/utils/error.ts
deleted file mode 100644
index 2b64e0707..000000000
--- a/packages/remix-dev/codemod/utils/error.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-export class CodemodError extends Error {
-  additionalInfo?: string;
-
-  constructor(message: string, additionalInfo?: string) {
-    super(message);
-
-    // Show up in console as `CodemodError`, not just `Error`
-    this.name = this.constructor.name;
-
-    Error.captureStackTrace(this, this.constructor);
-
-    this.additionalInfo = additionalInfo;
-  }
-}
diff --git a/packages/remix-dev/codemod/utils/git.ts b/packages/remix-dev/codemod/utils/git.ts
deleted file mode 100644
index 0d9a5f505..000000000
--- a/packages/remix-dev/codemod/utils/git.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-import execa from "execa";
-
-type GitStatus = "not a git repository" | "clean" | "dirty";
-
-const TEN_MEBIBYTE = 1024 * 1024 * 10;
-export const status = async (
-  dir: string = process.cwd()
-): Promise<GitStatus> => {
-  // Simplified version of `sync` method of `is-git-clean` package
-  try {
-    let gitStatus = await execa("git", ["status", "--porcelain"], {
-      cwd: dir,
-      encoding: "utf8",
-      maxBuffer: TEN_MEBIBYTE,
-    });
-
-    return gitStatus.stdout === "" ? "clean" : "dirty";
-  } catch (error: unknown) {
-    if (
-      error instanceof Error &&
-      error.message.includes(
-        "fatal: not a git repository (or any of the parent directories): .git"
-      )
-    ) {
-      return "not a git repository";
-    }
-    throw error;
-  }
-};
diff --git a/packages/remix-dev/codemod/utils/log.ts b/packages/remix-dev/codemod/utils/log.ts
deleted file mode 100644
index 9212bd471..000000000
--- a/packages/remix-dev/codemod/utils/log.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { gray, red } from "../../colors";
-
-export const info = (message: string) => console.info(gray(message));
-export const error = (message: string) => console.error(red(message));
diff --git a/packages/remix-dev/codemod/utils/task.ts b/packages/remix-dev/codemod/utils/task.ts
deleted file mode 100644
index 4589421fb..000000000
--- a/packages/remix-dev/codemod/utils/task.ts
+++ /dev/null
@@ -1,26 +0,0 @@
-import ora from "ora";
-
-import * as log from "./log";
-import { CodemodError } from "./error";
-
-export class TaskError extends Error {}
-
-export const task = async <Result>(
-  start: string,
-  callback: (spinner: ora.Ora) => Promise<Result>,
-  succeed: string | ((result: Result) => string) = start
-): Promise<Result> => {
-  let spinner = ora(start).start();
-  try {
-    let result = await callback(spinner);
-    spinner.succeed(typeof succeed === "string" ? succeed : succeed(result));
-    return result;
-  } catch (error: unknown) {
-    if (error instanceof CodemodError) {
-      spinner.fail(error.message);
-      if (error.additionalInfo) log.info(error.additionalInfo);
-      throw new TaskError(error.message);
-    }
-    throw error;
-  }
-};
diff --git a/packages/remix-dev/compiler/plugins/deprecatedRemixPackage.ts b/packages/remix-dev/compiler/plugins/deprecatedRemixPackage.ts
index 1f12b9a91..cc031411c 100644
--- a/packages/remix-dev/compiler/plugins/deprecatedRemixPackage.ts
+++ b/packages/remix-dev/compiler/plugins/deprecatedRemixPackage.ts
@@ -17,9 +17,6 @@ export function deprecatedRemixPackagePlugin(ctx: Context): Plugin {
           ctx.logger.warn(`deprecated \`remix\` import in ${relativePath}`, {
             details: [
               "Imports from the `remix` package were deprecated in v1.3.3.",
-              "Change your code to import from the appropriate `@remix-run/*` package instead.",
-              "You can run the following codemod to autofix this issue:",
-              "-> `npx @remix-run/dev@latest codemod replace-remix-magic-imports`",
             ],
             key: importer,
           });


Test patch:
diff --git a/packages/remix-dev/__tests__/cli-test.ts b/packages/remix-dev/__tests__/cli-test.ts
index 71385829f..acecba62d 100644
--- a/packages/remix-dev/__tests__/cli-test.ts
+++ b/packages/remix-dev/__tests__/cli-test.ts
@@ -89,7 +89,6 @@ describe("remix CLI", () => {
             $ remix routes [projectDir]
             $ remix watch [projectDir]
             $ remix setup [remixPlatform]
-            $ remix codemod <codemod> [projectDir]
 
           Options:
             --help, -h          Print this help message and exit
@@ -111,9 +110,6 @@ describe("remix CLI", () => {
             --no-delete         Skip deleting the \`remix.init\` script
           \`routes\` Options:
             --json              Print the routes as JSON
-          \`codemod\` Options:
-            --dry               Dry run (no changes are made to files)
-            --force             Bypass Git safety checks
 
           Values:
             - projectDir        The Remix project directory