Repository: remix-run/remix
Pull request #2733 (2022-04-11 18:11:29)

fix(remix-dev): Prioritize remix templates in CLI create
We've received a few issues where folks would step through the interactive CLI, select one of our templates and end up with an unexpected result, largely because we always prioritize local files when detecting the input type.

Since local files are likely going to be used less frequently than our built-in stacks and templates, this PR checks for those options first and ensures that those inputs are not matched to local files/directories to avoid conflicts. If a local file or forked repo is desired, the user can pass the full path/URL accordingly.

Closes: #2491

- [ ] Docs
- [x] Tests


chaance: Unsure what's going on with the tests but they are hanging indefinitely at the moment. Circling back in a bit when I can get tests to run on my machine.

Patch:
diff --git a/packages/remix-dev/cli/create.ts b/packages/remix-dev/cli/create.ts
index 48e794d41..cae486b99 100644
--- a/packages/remix-dev/cli/create.ts
+++ b/packages/remix-dev/cli/create.ts
@@ -563,14 +563,29 @@ export type TemplateType =
   | "local";
 
 export function detectTemplateType(template: string): TemplateType | null {
-  // 1. Check if the user passed a local file. If they hand us an explicit file
+  // 1. Prioritize Remix templates and stacks first. This ensures that inputs
+  //    like `--template remix` always pull from our templates, which is almost
+  //    always the desired behavior. If users maintain a fork either locally or
+  //    in another repo they can pass the repo shorthand, URL or path instead.
+  //    This also ensures that our interactive CLI always works as expected even
+  //    if the user has another directory with the same name.
+  //    https://github.com/remix-run/remix/issues/2491
+  if (isRemixTemplate(template)) {
+    return "template";
+  }
+
+  if (isRemixStack(template)) {
+    return "repoTemplate";
+  }
+
+  // 2. Check if the user passed a local file. If they hand us an explicit file
   //    URL, we'll validate it first. Otherwise we just ping the filesystem to
   //    see if the string references a filepath and, if not, move on.
   if (template.startsWith("file://")) {
     return "local";
   }
 
-  // 2. Check if it's a path to a local directory.
+  // 3. Check if it's a path to a local directory.
   try {
     if (
       fse.existsSync(
@@ -585,28 +600,17 @@ export function detectTemplateType(template: string): TemplateType | null {
     // ignore FS errors and move on
   }
 
-  // 3. check if it's one of the pre-built remix stacks
-  if (isRemixStack(template)) {
-    return "repoTemplate";
-  }
-
   // 4. examples/<template> will use an example folder in the Remix repo
   if (/^examples?\/[\w-]+$/.test(template)) {
     return "example";
   }
 
-  // 5. If the string contains no slashes, spaces, or special chars, we assume
-  //    it is one of our remix-run/remix/templates.
-  if (/^[\w-]+$/.test(template)) {
-    return "template";
-  }
-
-  // 6. Handle GitHub repos (URLs or :org/:repo shorthand)
+  // 5. Handle GitHub repos (URLs or :org/:repo shorthand)
   if (isValidGithubUrl(template) || isGithubRepoShorthand(template)) {
     return "repo";
   }
 
-  // 7. Any other valid URL should be treated as a tarball.
+  // 6. Any other valid URL should be treated as a tarball.
   if (isUrl(template)) {
     return "remoteTarball";
   }


Test patch:
diff --git a/packages/remix-dev/__tests__/create-test.ts b/packages/remix-dev/__tests__/create-test.ts
index e731d3316..5a0c0bb28 100644
--- a/packages/remix-dev/__tests__/create-test.ts
+++ b/packages/remix-dev/__tests__/create-test.ts
@@ -340,6 +340,35 @@ describe("the create command", () => {
     expect(fse.existsSync(path.join(projectDir, "app/root.tsx"))).toBeTruthy();
   });
 
+  it("prioritizes built-in templates when validating input", async () => {
+    let projectDir = await getProjectDir("built-in-template");
+
+    // create a local directory in our cwd with the same name as our chosen
+    // template and give it a package.json so we can check it against the one in
+    // our template
+    let dupedDir = path.join(process.cwd(), "express");
+    await fse.mkdir(dupedDir);
+    await fse.writeFile(
+      path.join(dupedDir, "package.json"),
+      '{ "name": "dummy" }'
+    );
+
+    await run([
+      "create",
+      projectDir,
+      "--template",
+      "express",
+      "--install",
+      "--typescript",
+    ]);
+
+    expect(fse.existsSync(path.join(projectDir, "package.json"))).toBeTruthy();
+    let pkgJSON = JSON.parse(
+      fse.readFileSync(path.join(projectDir, "package.json"), "utf-8")
+    );
+    expect(pkgJSON.name).not.toBe("dummy");
+  });
+
   it("runs remix.init script when installing dependencies", async () => {
     let projectDir = await getProjectDir("remix-init-auto");
     await run([