From 9ad9ea76f69666e7da22dac9d3c6a12176d7ff1e Mon Sep 17 00:00:00 2001 From: Matt Brophy Date: Mon, 2 May 2022 18:30:57 -0400 Subject: [PATCH] fix: provide clear error messages for create-remix errors (#3064) --- packages/remix-dev/__tests__/create-test.ts | 82 +++++++++++ packages/remix-dev/__tests__/github-mocks.ts | 6 + packages/remix-dev/__tests__/msw.ts | 6 + packages/remix-dev/cli/create.ts | 146 ++++++++++--------- 4 files changed, 170 insertions(+), 70 deletions(-) diff --git a/packages/remix-dev/__tests__/create-test.ts b/packages/remix-dev/__tests__/create-test.ts index 74cd1fa67e6..9e2dda96bf6 100644 --- a/packages/remix-dev/__tests__/create-test.ts +++ b/packages/remix-dev/__tests__/create-test.ts @@ -542,6 +542,88 @@ describe("the create command", () => { expect(output).toContain(getOptOutOfInstallMessage("pnpm exec remix init")); process.env.npm_user_agent = originalUserAgent; }); + + describe("errors", () => { + it("identifies when a github repo is not accessible (403)", async () => { + let projectDir = await getProjectDir("repo"); + await expect(() => + run([ + "create", + projectDir, + "--template", + "error-username/403", + "--no-install", + "--typescript", + ]) + ).rejects.toMatchInlineSnapshot( + `[Error: 🚨 The template could not be verified because you do not have access to the repository. Please double check the access rights of this repo and try again.]` + ); + }); + + it("identifies when a github repo does not exist (404)", async () => { + let projectDir = await getProjectDir("repo"); + await expect(() => + run([ + "create", + projectDir, + "--template", + "error-username/404", + "--no-install", + "--typescript", + ]) + ).rejects.toMatchInlineSnapshot( + `[Error: 🚨 The template could not be verified. Please double check that the template is a valid GitHub repository and try again.]` + ); + }); + + it("identifies when something unknown goes wrong with the repo request (4xx)", async () => { + let projectDir = await getProjectDir("repo"); + await expect(() => + run([ + "create", + projectDir, + "--template", + "error-username/400", + "--no-install", + "--typescript", + ]) + ).rejects.toMatchInlineSnapshot( + `[Error: 🚨 The template could not be verified. The server returned a response with a 400 status. Please double check that the template is a valid GitHub repository and try again.]` + ); + }); + + it("identifies when a remote tarball does not exist (404)", async () => { + let projectDir = await getProjectDir("remote-tarball"); + await expect(() => + run([ + "create", + projectDir, + "--template", + "https://example.com/error/404/remix-stack.tar.gz", + "--no-install", + "--typescript", + ]) + ).rejects.toMatchInlineSnapshot( + `[Error: 🚨 The template file could not be verified. Please double check the URL and try again.]` + ); + }); + + it("identifies when a remote tarball does not exist (4xx)", async () => { + let projectDir = await getProjectDir("remote-tarball"); + await expect(() => + run([ + "create", + projectDir, + "--template", + "https://example.com/error/400/remix-stack.tar.gz", + "--no-install", + "--typescript", + ]) + ).rejects.toMatchInlineSnapshot( + `[Error: 🚨 The template file could not be verified. The server returned a response with a 400 status. Please double check the URL and try again.]` + ); + }); + }); }); function getSuccessMessage(projectDirectory: string) { diff --git a/packages/remix-dev/__tests__/github-mocks.ts b/packages/remix-dev/__tests__/github-mocks.ts index 56718f8c0e3..8e486be7b03 100644 --- a/packages/remix-dev/__tests__/github-mocks.ts +++ b/packages/remix-dev/__tests__/github-mocks.ts @@ -76,6 +76,12 @@ let githubHandlers: Array = [ return res(ctx.status(200)); } ), + rest.head( + `https://github.com/error-username/:status`, + async (req, res, ctx) => { + return res(ctx.status(Number(req.params.status))); + } + ), rest.head(`https://github.com/:owner/:repo`, async (req, res, ctx) => { return res(ctx.status(200)); }), diff --git a/packages/remix-dev/__tests__/msw.ts b/packages/remix-dev/__tests__/msw.ts index 409603c3d17..b73408dd11a 100644 --- a/packages/remix-dev/__tests__/msw.ts +++ b/packages/remix-dev/__tests__/msw.ts @@ -8,6 +8,12 @@ import { githubHandlers } from "./github-mocks"; type RequestHandler = Parameters[0]; let miscHandlers: Array = [ + rest.head( + "https://example.com/error/:status/remix-stack.tar.gz", + async (req, res, ctx) => { + return res(ctx.status(Number(req.params.status))); + } + ), rest.head("https://example.com/remix-stack.tar.gz", async (req, res, ctx) => { return res(ctx.status(200)); }), diff --git a/packages/remix-dev/cli/create.ts b/packages/remix-dev/cli/create.ts index 83a3e8750a6..0264e41e39c 100644 --- a/packages/remix-dev/cli/create.ts +++ b/packages/remix-dev/cli/create.ts @@ -502,69 +502,73 @@ export async function validateTemplate(input: string) { } case "remoteTarball": { let spinner = ora("Validating the template file…").start(); + let response; try { - let response = await fetch(input, { method: "HEAD" }); - spinner.stop(); - switch (response.status) { - case 200: - return; - case 404: - throw Error( - "🚨 The template file could not be verified. Please double check " + - "the URL and try again." - ); - default: - throw Error( - "🚨 The template file could not be verified. The server returned " + - `a response with a ${response.status} status. Please double ` + - "check the URL and try again." - ); - } - } catch (err) { - spinner.stop(); + response = await fetch(input, { method: "HEAD" }); + } catch (_) { throw Error( "🚨 There was a problem verifying the template file. Please ensure " + "you are connected to the internet and try again later." ); + } finally { + spinner.stop(); + } + + switch (response.status) { + case 200: + return; + case 404: + throw Error( + "🚨 The template file could not be verified. Please double check " + + "the URL and try again." + ); + default: + throw Error( + "🚨 The template file could not be verified. The server returned " + + `a response with a ${response.status} status. Please double ` + + "check the URL and try again." + ); } } case "repo": { let spinner = ora("Validating the template repo…").start(); let { url, filePath } = getRepoInfo(input); + let response; try { - let response = await fetch(url, { method: "HEAD" }); - spinner.stop(); - switch (response.status) { - case 200: - return; - case 403: - throw Error( - "🚨 The template could not be verified because you do not have " + - "access to the repository. Please double check the access " + - "rights of this repo and try again." - ); - case 404: - throw Error( - "🚨 The template could not be verified. Please double check that " + - "the template is a valid GitHub repository" + - (filePath && filePath !== "/" - ? " and that the filepath points to a directory in the repo" - : "") + - " and try again." - ); - default: - throw Error( - "🚨 The template could not be verified. The server returned a " + - `response with a ${response.status} status. Please double check ` + - "that the template is a valid GitHub repository and try again." - ); - } + response = await fetch(url, { method: "HEAD" }); } catch (_) { - spinner.stop(); throw Error( - "🚨 There was a problem verifying the template. Please ensure you " + + "🚨 There was a problem fetching the template. Please ensure you " + "are connected to the internet and try again later." ); + } finally { + spinner.stop(); + } + + switch (response.status) { + case 200: + return; + case 403: + throw Error( + "🚨 The template could not be verified because you do not have " + + "access to the repository. Please double check the access " + + "rights of this repo and try again." + ); + case 404: + throw Error( + "🚨 The template could not be verified. Please double check that " + + "the template is a valid GitHub repository" + + (filePath && filePath !== "/" + ? " and that the filepath points to a directory in the repo" + : "") + + " and try again." + ); + default: + throw Error( + "🚨 The template could not be verified. The server returned a " + + `response with a ${response.status} status. Please double check ` + + "that the template is a valid GitHub repository and try again." + ); } } case "example": @@ -576,34 +580,36 @@ export async function validateTemplate(input: string) { } let typeDir = templateType + "s"; let templateUrl = `https://github.com/remix-run/remix/tree/main/${typeDir}/${name}`; + let response; try { - let response = await fetch(templateUrl, { method: "HEAD" }); - spinner.stop(); - switch (response.status) { - case 200: - return; - case 404: - throw Error( - "🚨 The template could not be verified. Please double check that " + - "the template is a valid project directory in " + - `https://github.com/remix-run/remix/tree/main/${typeDir} and ` + - "try again." - ); - default: - throw Error( - "🚨 The template could not be verified. The server returned a " + - `response with a ${response.status} status. Please double ` + - "check that the template is a valid project directory in " + - `https://github.com/remix-run/remix/tree/main/${typeDir} and ` + - "try again." - ); - } + response = await fetch(templateUrl, { method: "HEAD" }); } catch (_) { - spinner.stop(); throw Error( "🚨 There was a problem verifying the template. Please ensure you are " + "connected to the internet and try again later." ); + } finally { + spinner.stop(); + } + + switch (response.status) { + case 200: + return; + case 404: + throw Error( + "🚨 The template could not be verified. Please double check that " + + "the template is a valid project directory in " + + `https://github.com/remix-run/remix/tree/main/${typeDir} and ` + + "try again." + ); + default: + throw Error( + "🚨 The template could not be verified. The server returned a " + + `response with a ${response.status} status. Please double ` + + "check that the template is a valid project directory in " + + `https://github.com/remix-run/remix/tree/main/${typeDir} and ` + + "try again." + ); } } }