From 7061b121b8b0c53c159272b13ec972b9a4b01dae Mon Sep 17 00:00:00 2001 From: mfrachet Date: Mon, 30 Nov 2020 14:57:46 +0100 Subject: [PATCH 1/4] test(create-gatsby): add test for init starter in create-gatsby --- .../src/__tests__/init-starter.ts | 157 ++++++++++++++++++ packages/create-gatsby/src/index.ts | 4 +- packages/create-gatsby/src/init-starter.ts | 58 ++++--- packages/create-gatsby/src/utils.ts | 12 ++ 4 files changed, 199 insertions(+), 32 deletions(-) create mode 100644 packages/create-gatsby/src/__tests__/init-starter.ts create mode 100644 packages/create-gatsby/src/utils.ts diff --git a/packages/create-gatsby/src/__tests__/init-starter.ts b/packages/create-gatsby/src/__tests__/init-starter.ts new file mode 100644 index 0000000000000..cb411652aeff8 --- /dev/null +++ b/packages/create-gatsby/src/__tests__/init-starter.ts @@ -0,0 +1,157 @@ +import { execSync } from "child_process" +import execa from "execa" +import fs from "fs-extra" +import path from "path" +import { initStarter } from "../init-starter" +import { reporter } from "../reporter" + +jest.mock(`../utils`) +jest.mock(`execa`) +jest.mock(`child_process`) +jest.mock(`fs-extra`) +jest.mock(`path`) +jest.mock(`../reporter`) +jest.mock(`../get-config-store`, () => { + return { + getConfigStore: (): unknown => { + return { + items: {}, + set(key: string, value: unknown): void { + this.items[key] = value + }, + get(key: string): unknown { + return this.items[key] + }, + + __reset(): void { + this.items = {} + }, + } + }, + } +}) + +describe(`init-starter`, () => { + beforeEach(() => { + process.chdir = jest.fn() + }) + + afterEach(() => { + jest.resetAllMocks() + }) + + describe(`initStarter / cloning`, () => { + it(`reports an error when it s not possible to clone the repo`, async () => { + ;(path as any).join.mockImplementation(() => `/somewhere-here`) + ;(execa as any).mockImplementation(() => { + throw new Error(`Not possible to clone the repo`) + }) + + try { + await initStarter(`gatsby-starter-hello-world`, `./somewhere`, []) + } catch (e) { + expect(execa).toBeCalledWith(`git`, [ + `clone`, + `gatsby-starter-hello-world`, + `--recursive`, + `--depth=1`, + `--quiet`, + ]) + expect(reporter.panic).toBeCalledWith(`Not possible to clone the repo`) + expect(reporter.success).not.toBeCalledWith( + `Created site from template` + ) + expect(fs.remove).toBeCalledWith(`/somewhere-here`) + } + }) + + it(`reports a success when everything is going ok`, async () => { + ;(path as any).join.mockImplementation(() => `/somewhere-here`) + ;(execa as any).mockImplementation(() => Promise.resolve()) + ;(fs as any).readJSON.mockImplementation(() => { + return { name: `gatsby-project` } + }) + + await initStarter(`gatsby-starter-hello-world`, `./somewhere`, []) + + expect(execa).toBeCalledWith(`git`, [ + `clone`, + `gatsby-starter-hello-world`, + `--recursive`, + `--depth=1`, + `--quiet`, + ]) + expect(reporter.panic).not.toBeCalled() + expect(reporter.success).toBeCalledWith(`Created site from template`) + expect(fs.remove).toBeCalledWith(`/somewhere-here`) + }) + }) + + describe(`initStarter / install`, () => { + it(`process package installation with yarn`, async () => { + process.env.npm_config_user_agent = `yarn` + ;(path as any).join.mockImplementation(() => `/somewhere-here`) + ;(execa as any).mockImplementation(() => Promise.resolve()) + ;(fs as any).readJSON.mockImplementation(() => { + return { name: `gatsby-project` } + }) + + await initStarter(`gatsby-starter-hello-world`, `./somewhere`, []) + + expect(fs.remove).toBeCalledWith(`package-lock.json`) + expect(reporter.success).toBeCalledWith(`Installed plugins`) + expect(reporter.panic).not.toBeCalled() + expect(execa).toBeCalledWith(`yarnpkg`, [`--silent`], { + stderr: `inherit`, + }) + }) + + it(`process package installation with NPM`, async () => { + process.env.npm_config_user_agent = `npm` + ;(path as any).join.mockImplementation(() => `/somewhere-here`) + ;(execa as any).mockImplementation(() => Promise.resolve()) + ;(fs as any).readJSON.mockImplementation(() => { + return { name: `gatsby-project` } + }) + + await initStarter(`gatsby-starter-hello-world`, `./somewhere`, [ + `one-package`, + ]) + + expect(fs.remove).toBeCalledWith(`yarn.lock`) + expect(reporter.success).toBeCalledWith(`Installed Gatsby`) + expect(reporter.success).toBeCalledWith(`Installed plugins`) + expect(reporter.panic).not.toBeCalled() + expect(execa).toBeCalledWith( + `npm`, + [`install`, `--loglevel`, `error`, `--color`, `always`], + { stderr: `inherit` } + ) + expect(execa).toBeCalledWith( + `npm`, + [`install`, `--loglevel`, `error`, `--color`, `always`, `one-package`], + { stderr: `inherit` } + ) + }) + + it(`gently informs the user that yarn is not available when trying to use it`, async () => { + process.env.npm_config_user_agent = `yarn` + ;(execSync as any).mockImplementation(() => { + throw new Error(`Something wrong occured when trying to use yarn`) + }) + ;(path as any).join.mockImplementation(() => `/somewhere-here`) + ;(execa as any).mockImplementation(() => Promise.resolve()) + ;(fs as any).readJSON.mockImplementation(() => { + return { name: `gatsby-project` } + }) + + await initStarter(`gatsby-starter-hello-world`, `./somewhere`, [ + `one-package`, + ]) + + expect(reporter.info).toBeCalledWith( + `Woops! Yarn doesn't seem be installed on your machine. You can install it on https://yarnpkg.com/getting-started/install. As a fallback, we will run the next steps with npm.` + ) + }) + }) +}) diff --git a/packages/create-gatsby/src/index.ts b/packages/create-gatsby/src/index.ts index 6b8456886e8fd..c524a1c70376a 100644 --- a/packages/create-gatsby/src/index.ts +++ b/packages/create-gatsby/src/index.ts @@ -2,7 +2,7 @@ import Enquirer from "enquirer" import cmses from "./cmses.json" import styles from "./styles.json" import features from "./features.json" -import { initStarter, getPackageManager, gitSetup } from "./init-starter" +import { initStarter, packageManager, gitSetup } from "./init-starter" import { installPlugins } from "./install-plugins" import c from "ansi-colors" import path from "path" @@ -312,7 +312,7 @@ ${c.bold(`Thanks! Here's what we'll now do:`)} await gitSetup(data.project) - const pm = await getPackageManager() + const pm = await packageManager() const runCommand = pm === `npm` ? `npm run` : `yarn` reporter.info( diff --git a/packages/create-gatsby/src/init-starter.ts b/packages/create-gatsby/src/init-starter.ts index fa9f4e6c76fdc..1347de1e441b9 100644 --- a/packages/create-gatsby/src/init-starter.ts +++ b/packages/create-gatsby/src/init-starter.ts @@ -7,6 +7,7 @@ import { spin } from "tiny-spin" import { getConfigStore } from "./get-config-store" type PackageManager = "yarn" | "npm" import c from "ansi-colors" +import { clearLine } from "./utils" const packageManagerConfigKey = `cli.packageManager` @@ -16,25 +17,21 @@ const kebabify = (str: string): string => .replace(/[^a-zA-Z]+/g, `-`) .toLowerCase() -export const getPackageManager = (): PackageManager => - getConfigStore().get(packageManagerConfigKey) +export const packageManager = (npmConfigUserAgent?: string): PackageManager => { + const configStore = getConfigStore() + const actualPackageManager = configStore.get(packageManagerConfigKey) -export const setPackageManager = (packageManager: PackageManager): void => { - getConfigStore().set(packageManagerConfigKey, packageManager) -} + if (actualPackageManager) return actualPackageManager -const ESC = `\u001b` + if (npmConfigUserAgent?.includes(`yarn`)) { + configStore.set(packageManagerConfigKey, `yarn`) + return `yarn` + } + + configStore.set(packageManagerConfigKey, `npm`) + return `npm` +} -export const clearLine = (count = 1): Promise => - new Promise(resolve => { - // First move the cursor up one line... - process.stderr.moveCursor(0, -count, () => { - // ... then clear that line. This is the ANSI escape sequence for "clear whole line" - // List of escape sequences: http://ascii-table.com/ansi-escape-sequences.php - process.stderr.write(`${ESC}[2K`) - resolve() - }) - }) // Checks the existence of yarn package // We use yarnpkg instead of yarn to avoid conflict with Hadoop yarn // Refer to https://github.com/yarnpkg/yarn/issues/673 @@ -43,6 +40,9 @@ const checkForYarn = (): boolean => { execSync(`yarnpkg --version`, { stdio: `ignore` }) return true } catch (e) { + reporter.info( + `Woops! Yarn doesn't seem be installed on your machine. You can install it on https://yarnpkg.com/getting-started/install. As a fallback, we will run the next steps with npm.` + ) return false } } @@ -117,36 +117,34 @@ const install = async ( const npmConfigUserAgent = process.env.npm_config_user_agent try { - if (!getPackageManager()) { - if (npmConfigUserAgent?.includes(`yarn`)) { - setPackageManager(`yarn`) - } else { - setPackageManager(`npm`) - } - } + const pm = packageManager(npmConfigUserAgent) + const options: Options = { stderr: `inherit`, } const config = [`--loglevel`, `error`, `--color`, `always`] - if (getPackageManager() === `yarn` && checkForYarn()) { - await fs.remove(`package-lock.json`) + if (pm === `yarn` && checkForYarn()) { const args = packages.length ? [`add`, `--silent`, ...packages] : [`--silent`] + + await fs.remove(`package-lock.json`) await execa(`yarnpkg`, args, options) } else { await fs.remove(`yarn.lock`) - await execa(`npm`, [`install`, ...config], options) await clearLine() + reporter.success(`Installed Gatsby`) reporter.info(`${c.blueBright(c.symbols.pointer)} Installing plugins...`) + await execa(`npm`, [`install`, ...config, ...packages], options) await clearLine() - reporter.success(`Installed plugins`) } + + reporter.success(`Installed plugins`) } catch (e) { reporter.panic(e.message) } finally { @@ -161,9 +159,7 @@ const clone = async ( branch?: string ): Promise => { const branchProps = branch ? [`-b`, branch] : [] - const stop = spin(`Cloning site template`) - const args = [ `clone`, ...branchProps, @@ -176,11 +172,13 @@ const clone = async ( try { await execa(`git`, args) + + reporter.success(`Created site from template`) } catch (err) { reporter.panic(err.message) } + stop() - reporter.success(`Created site from template`) await fs.remove(path.join(rootPath, `.git`)) } diff --git a/packages/create-gatsby/src/utils.ts b/packages/create-gatsby/src/utils.ts new file mode 100644 index 0000000000000..77ea4dc353e77 --- /dev/null +++ b/packages/create-gatsby/src/utils.ts @@ -0,0 +1,12 @@ +const ESC = `\u001b` + +export const clearLine = (count = 1): Promise => + new Promise(resolve => { + // First move the cursor up one line... + process.stderr.moveCursor(0, -count, () => { + // ... then clear that line. This is the ANSI escape sequence for "clear whole line" + // List of escape sequences: http://ascii-table.com/ansi-escape-sequences.php + process.stderr.write(`${ESC}[2K`) + resolve() + }) + }) From 0e37ce43aeae0c20918c7721e3210e2bd3f0b6bc Mon Sep 17 00:00:00 2001 From: Marvin Frachet Date: Tue, 1 Dec 2020 10:55:54 +0100 Subject: [PATCH 2/4] Update packages/create-gatsby/src/init-starter.ts Co-authored-by: Matt Kane --- packages/create-gatsby/src/init-starter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/create-gatsby/src/init-starter.ts b/packages/create-gatsby/src/init-starter.ts index 1347de1e441b9..070949b6d470c 100644 --- a/packages/create-gatsby/src/init-starter.ts +++ b/packages/create-gatsby/src/init-starter.ts @@ -41,7 +41,7 @@ const checkForYarn = (): boolean => { return true } catch (e) { reporter.info( - `Woops! Yarn doesn't seem be installed on your machine. You can install it on https://yarnpkg.com/getting-started/install. As a fallback, we will run the next steps with npm.` + `Woops! You have chosen "yarn" as your package manager, but it doesn't seem be installed on your machine. You can install it from https://yarnpkg.com/getting-started/install or change your preferred package manager with the command "gatsby options set pm npm". As a fallback, we will run the next steps with npm.` ) return false } From dc7adc4256a223019ead5a2388800b057e023825 Mon Sep 17 00:00:00 2001 From: mfrachet Date: Tue, 1 Dec 2020 10:58:46 +0100 Subject: [PATCH 3/4] Fix review comments --- packages/create-gatsby/src/__tests__/init-starter.ts | 2 +- packages/create-gatsby/src/index.ts | 4 ++-- packages/create-gatsby/src/init-starter.ts | 10 +++++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/create-gatsby/src/__tests__/init-starter.ts b/packages/create-gatsby/src/__tests__/init-starter.ts index cb411652aeff8..40436632603d9 100644 --- a/packages/create-gatsby/src/__tests__/init-starter.ts +++ b/packages/create-gatsby/src/__tests__/init-starter.ts @@ -150,7 +150,7 @@ describe(`init-starter`, () => { ]) expect(reporter.info).toBeCalledWith( - `Woops! Yarn doesn't seem be installed on your machine. You can install it on https://yarnpkg.com/getting-started/install. As a fallback, we will run the next steps with npm.` + `Woops! You have chosen "yarn" as your package manager, but it doesn't seem be installed on your machine. You can install it from https://yarnpkg.com/getting-started/install or change your preferred package manager with the command "gatsby options set pm npm". As a fallback, we will run the next steps with npm.` ) }) }) diff --git a/packages/create-gatsby/src/index.ts b/packages/create-gatsby/src/index.ts index c524a1c70376a..6b8456886e8fd 100644 --- a/packages/create-gatsby/src/index.ts +++ b/packages/create-gatsby/src/index.ts @@ -2,7 +2,7 @@ import Enquirer from "enquirer" import cmses from "./cmses.json" import styles from "./styles.json" import features from "./features.json" -import { initStarter, packageManager, gitSetup } from "./init-starter" +import { initStarter, getPackageManager, gitSetup } from "./init-starter" import { installPlugins } from "./install-plugins" import c from "ansi-colors" import path from "path" @@ -312,7 +312,7 @@ ${c.bold(`Thanks! Here's what we'll now do:`)} await gitSetup(data.project) - const pm = await packageManager() + const pm = await getPackageManager() const runCommand = pm === `npm` ? `npm run` : `yarn` reporter.info( diff --git a/packages/create-gatsby/src/init-starter.ts b/packages/create-gatsby/src/init-starter.ts index 070949b6d470c..8e85d14eaa08a 100644 --- a/packages/create-gatsby/src/init-starter.ts +++ b/packages/create-gatsby/src/init-starter.ts @@ -17,11 +17,15 @@ const kebabify = (str: string): string => .replace(/[^a-zA-Z]+/g, `-`) .toLowerCase() -export const packageManager = (npmConfigUserAgent?: string): PackageManager => { +export const getPackageManager = ( + npmConfigUserAgent?: string +): PackageManager => { const configStore = getConfigStore() const actualPackageManager = configStore.get(packageManagerConfigKey) - if (actualPackageManager) return actualPackageManager + if (actualPackageManager) { + return actualPackageManager + } if (npmConfigUserAgent?.includes(`yarn`)) { configStore.set(packageManagerConfigKey, `yarn`) @@ -117,7 +121,7 @@ const install = async ( const npmConfigUserAgent = process.env.npm_config_user_agent try { - const pm = packageManager(npmConfigUserAgent) + const pm = getPackageManager(npmConfigUserAgent) const options: Options = { stderr: `inherit`, From f0dd8aeabd15f47fbd1c330a028701cde2b347bd Mon Sep 17 00:00:00 2001 From: mfrachet Date: Tue, 1 Dec 2020 11:58:20 +0100 Subject: [PATCH 4/4] Prevent side effects in the console --- packages/create-gatsby/src/__tests__/init-starter.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/create-gatsby/src/__tests__/init-starter.ts b/packages/create-gatsby/src/__tests__/init-starter.ts index 40436632603d9..d2ed06a979a0a 100644 --- a/packages/create-gatsby/src/__tests__/init-starter.ts +++ b/packages/create-gatsby/src/__tests__/init-starter.ts @@ -5,6 +5,11 @@ import path from "path" import { initStarter } from "../init-starter" import { reporter } from "../reporter" +jest.mock(`tiny-spin`, () => { + return { + spin: (): (() => void) => jest.fn(), + } +}) jest.mock(`../utils`) jest.mock(`execa`) jest.mock(`child_process`)