From a26a7fc7b66622b5ffdddbf06b522eea458ca9bd Mon Sep 17 00:00:00 2001 From: erezrokah Date: Tue, 2 Feb 2021 19:15:17 +0100 Subject: [PATCH 01/14] feat(command-init): recommend build plugins --- src/utils/init/config-github.js | 11 +++-- src/utils/init/config-manual.js | 11 +++-- src/utils/init/utils.js | 85 +++++++++++++++++++++++++++------ 3 files changed, 87 insertions(+), 20 deletions(-) diff --git a/src/utils/init/config-github.js b/src/utils/init/config-github.js index 5a1ba2339ab..b93b0dafd8a 100644 --- a/src/utils/init/config-github.js +++ b/src/utils/init/config-github.js @@ -184,8 +184,8 @@ module.exports = async function configGithub({ context, siteId, repoOwner, repoN const token = await getGitHubToken({ log, globalConfig }) - const { buildCmd, buildDir, functionsDir } = await getBuildSettings({ siteRoot, config }) - await saveNetlifyToml({ siteRoot, config, buildCmd, buildDir, functionsDir, warn }) + const { buildCmd, buildDir, functionsDir, plugins } = await getBuildSettings({ siteRoot, config }) + await saveNetlifyToml({ siteRoot, config, buildCmd, buildDir, functionsDir, plugins, warn }) const octokit = getGitHubClient({ token }) const [deployKey, githubRepo] = await Promise.all([ @@ -204,7 +204,12 @@ module.exports = async function configGithub({ context, siteId, repoOwner, repoN ...(buildCmd && { cmd: buildCmd }), } - await updateSite({ siteId, api, failAndExit, options: { repo } }) + await updateSite({ + siteId, + api, + failAndExit, + options: { repo, plugins: plugins.map((plugin) => ({ package: plugin })) }, + }) // calling updateSite with { repo } resets the functions dir so we need to sync it const updatedSite = await updateSite({ siteId, diff --git a/src/utils/init/config-manual.js b/src/utils/init/config-manual.js index 7d2b7d6a1eb..331fe2e4146 100644 --- a/src/utils/init/config-manual.js +++ b/src/utils/init/config-manual.js @@ -57,8 +57,8 @@ module.exports = async function configManual({ context, siteId, repoData }) { site: { root: siteRoot }, } = netlify - const { buildCmd, buildDir, functionsDir } = await getBuildSettings({ siteRoot, config }) - await saveNetlifyToml({ siteRoot, config, buildCmd, buildDir, functionsDir, warn }) + const { buildCmd, buildDir, functionsDir, plugins } = await getBuildSettings({ siteRoot, config }) + await saveNetlifyToml({ siteRoot, config, buildCmd, buildDir, functionsDir, plugins, warn }) const deployKey = await createDeployKey({ api, failAndExit }) await addDeployKey({ log, exit, deployKey }) @@ -74,7 +74,12 @@ module.exports = async function configManual({ context, siteId, repoData }) { ...(buildCmd && { cmd: buildCmd }), } - await updateSite({ siteId, api, failAndExit, options: { repo } }) + await updateSite({ + siteId, + api, + failAndExit, + options: { repo, plugins: plugins.map((plugin) => ({ package: plugin })) }, + }) // calling updateSite with { repo } resets the functions dir so we need to sync it const updatedSite = await updateSite({ siteId, diff --git a/src/utils/init/utils.js b/src/utils/init/utils.js index 816be700254..f9499401009 100644 --- a/src/utils/init/utils.js +++ b/src/utils/init/utils.js @@ -1,3 +1,4 @@ +const { EOL } = require('os') const path = require('path') const { listFrameworks } = require('@netlify/framework-info') @@ -23,15 +24,20 @@ const getFrameworkDefaults = async ({ siteRoot }) => { const [ { build: { directory, commands }, + plugins, }, ] = frameworks - return { frameworkBuildCommand: commands[0], frameworkBuildDir: directory } + return { frameworkBuildCommand: commands[0], frameworkBuildDir: directory, frameworkPlugins: plugins } } return {} } +const isPluginInstalled = (configPlugins, plugin) => + configPlugins.some(({ package: configPlugin }) => configPlugin === plugin) + const getDefaultSettings = async ({ siteRoot, config }) => { - const { frameworkBuildCommand, frameworkBuildDir } = await getFrameworkDefaults({ siteRoot }) + const { frameworkBuildCommand, frameworkBuildDir, frameworkPlugins } = await getFrameworkDefaults({ siteRoot }) + const recommendedPlugins = frameworkPlugins.filter((plugin) => !isPluginInstalled(config.plugins, plugin)) const { command: defaultBuildCmd = frameworkBuildCommand, publish: defaultBuildDir = frameworkBuildDir, @@ -42,12 +48,12 @@ const getDefaultSettings = async ({ siteRoot, config }) => { defaultBuildCmd, defaultBuildDir: normalizeDir({ siteRoot, dir: defaultBuildDir, defaultValue: '.' }), defaultFunctionsDir: normalizeDir({ siteRoot, dir: defaultFunctionsDir, defaultValue: 'netlify/functions' }), + recommendedPlugins, } } -const getBuildSettings = async ({ siteRoot, config }) => { - const { defaultBuildCmd, defaultBuildDir, defaultFunctionsDir } = await getDefaultSettings({ siteRoot, config }) - const { buildCmd, buildDir, functionsDir } = await inquirer.prompt([ +const getPromptInputs = ({ defaultBuildCmd, defaultBuildDir, defaultFunctionsDir, recommendedPlugins }) => { + const inputs = [ { type: 'input', name: 'buildCmd', @@ -67,16 +73,58 @@ const getBuildSettings = async ({ siteRoot, config }) => { message: 'Netlify functions folder:', default: defaultFunctionsDir, }, - ]) + ] - return { buildCmd, buildDir, functionsDir } + if (recommendedPlugins.length === 0) { + return inputs + } + + if (recommendedPlugins.length === 1) { + return [ + ...inputs, + { + type: 'confirm', + name: 'installSinglePlugin', + message: `Install ${recommendedPlugins[0]}?`, + default: true, + }, + ] + } + + return [ + ...inputs, + { + type: 'checkbox', + name: 'plugins', + message: 'Which build plugins to install:', + choices: recommendedPlugins, + }, + ] } -const getNetlifyToml = ({ - command = '# no build command', - publish = '.', - functions = 'functions', -}) => `# example netlify.toml +const getPluginsToInstall = ({ plugins, installSinglePlugin, recommendedPlugins }) => { + if (Array.isArray(plugins)) { + return plugins + } + + return installSinglePlugin === true ? [recommendedPlugins[0]] : [] +} + +const getBuildSettings = async ({ siteRoot, config }) => { + const { defaultBuildCmd, defaultBuildDir, defaultFunctionsDir, recommendedPlugins } = await getDefaultSettings({ + siteRoot, + config, + }) + const { buildCmd, buildDir, functionsDir, plugins, installSinglePlugin } = await inquirer.prompt( + getPromptInputs({ defaultBuildCmd, defaultBuildDir, defaultFunctionsDir, recommendedPlugins }), + ) + + const pluginsToInstall = getPluginsToInstall({ plugins, installSinglePlugin, recommendedPlugins }) + return { buildCmd, buildDir, functionsDir, plugins: pluginsToInstall } +} + +const getNetlifyToml = ({ command = '# no build command', publish = '.', functions = 'functions', plugins = [] }) => { + const content = `# example netlify.toml [build] command = "${command}" functions = "${functions}" @@ -98,8 +146,14 @@ const getNetlifyToml = ({ ## more info on configuring this file: https://www.netlify.com/docs/netlify-toml-reference/ ` + if (plugins.length === 0) { + return content + } + + return `${content}${EOL}${plugins.map((plugin) => `[[plugins]]${EOL} package = "${plugin}"`).join(EOL)}` +} -const saveNetlifyToml = async ({ siteRoot, config, buildCmd, buildDir, functionsDir, warn }) => { +const saveNetlifyToml = async ({ siteRoot, config, buildCmd, buildDir, functionsDir, plugins, warn }) => { const tomlPath = path.join(siteRoot, 'netlify.toml') const exists = await fileExistsAsync(tomlPath) const cleanedConfig = cleanDeep(config) @@ -117,7 +171,10 @@ const saveNetlifyToml = async ({ siteRoot, config, buildCmd, buildDir, functions ]) if (makeNetlifyTOML) { try { - await writeFileAsync(tomlPath, getNetlifyToml({ command: buildCmd, publish: buildDir, functions: functionsDir })) + await writeFileAsync( + tomlPath, + getNetlifyToml({ command: buildCmd, publish: buildDir, functions: functionsDir, plugins }), + ) } catch (error) { warn(`Failed saving Netlify toml file: ${error.message}`) } From 29c1975d81079b1d9c3ff61c2e2be489f9e0a3c1 Mon Sep 17 00:00:00 2001 From: erezrokah Date: Tue, 9 Feb 2021 21:22:42 +0100 Subject: [PATCH 02/14] feat: update plugins installation messages --- src/utils/init/utils.js | 47 +++++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/src/utils/init/utils.js b/src/utils/init/utils.js index f9499401009..fdad914e989 100644 --- a/src/utils/init/utils.js +++ b/src/utils/init/utils.js @@ -18,16 +18,22 @@ const normalizeDir = ({ siteRoot, dir, defaultValue }) => { return relativeDir || defaultValue } -const getFrameworkDefaults = async ({ siteRoot }) => { +const getFrameworkInfo = async ({ siteRoot }) => { const frameworks = await listFrameworks({ projectDir: siteRoot }) if (frameworks.length !== 0) { const [ { + name, build: { directory, commands }, plugins, }, ] = frameworks - return { frameworkBuildCommand: commands[0], frameworkBuildDir: directory, frameworkPlugins: plugins } + return { + frameworkTitle: name, + frameworkBuildCommand: commands[0], + frameworkBuildDir: directory, + frameworkPlugins: plugins, + } } return {} } @@ -35,8 +41,7 @@ const getFrameworkDefaults = async ({ siteRoot }) => { const isPluginInstalled = (configPlugins, plugin) => configPlugins.some(({ package: configPlugin }) => configPlugin === plugin) -const getDefaultSettings = async ({ siteRoot, config }) => { - const { frameworkBuildCommand, frameworkBuildDir, frameworkPlugins } = await getFrameworkDefaults({ siteRoot }) +const getDefaultSettings = ({ siteRoot, config, frameworkPlugins, frameworkBuildCommand, frameworkBuildDir }) => { const recommendedPlugins = frameworkPlugins.filter((plugin) => !isPluginInstalled(config.plugins, plugin)) const { command: defaultBuildCmd = frameworkBuildCommand, @@ -52,7 +57,13 @@ const getDefaultSettings = async ({ siteRoot, config }) => { } } -const getPromptInputs = ({ defaultBuildCmd, defaultBuildDir, defaultFunctionsDir, recommendedPlugins }) => { +const getPromptInputs = ({ + defaultBuildCmd, + defaultBuildDir, + defaultFunctionsDir, + recommendedPlugins, + frameworkTitle, +}) => { const inputs = [ { type: 'input', @@ -79,13 +90,16 @@ const getPromptInputs = ({ defaultBuildCmd, defaultBuildDir, defaultFunctionsDir return inputs } + const prefix = `Seems like this is a ${formatTitle(frameworkTitle)} site.${EOL} ` if (recommendedPlugins.length === 1) { return [ ...inputs, { type: 'confirm', name: 'installSinglePlugin', - message: `Install ${recommendedPlugins[0]}?`, + message: `${prefix}Recommended Build Plugin: ${formatTitle(recommendedPlugins[0])}${EOL} Install ${ + recommendedPlugins[0] + }?`, default: true, }, ] @@ -96,7 +110,9 @@ const getPromptInputs = ({ defaultBuildCmd, defaultBuildDir, defaultFunctionsDir { type: 'checkbox', name: 'plugins', - message: 'Which build plugins to install:', + message: `${prefix}Recommended Build Plugins: ${recommendedPlugins + .map(formatTitle) + .join(', ')}${EOL} Which plugins to install?`, choices: recommendedPlugins, }, ] @@ -111,14 +127,25 @@ const getPluginsToInstall = ({ plugins, installSinglePlugin, recommendedPlugins } const getBuildSettings = async ({ siteRoot, config }) => { + const { frameworkTitle, frameworkBuildCommand, frameworkBuildDir, frameworkPlugins } = await getFrameworkInfo({ + siteRoot, + }) const { defaultBuildCmd, defaultBuildDir, defaultFunctionsDir, recommendedPlugins } = await getDefaultSettings({ siteRoot, config, + frameworkBuildCommand, + frameworkBuildDir, + frameworkPlugins, }) const { buildCmd, buildDir, functionsDir, plugins, installSinglePlugin } = await inquirer.prompt( - getPromptInputs({ defaultBuildCmd, defaultBuildDir, defaultFunctionsDir, recommendedPlugins }), + getPromptInputs({ + defaultBuildCmd, + defaultBuildDir, + defaultFunctionsDir, + recommendedPlugins, + frameworkTitle, + }), ) - const pluginsToInstall = getPluginsToInstall({ plugins, installSinglePlugin, recommendedPlugins }) return { buildCmd, buildDir, functionsDir, plugins: pluginsToInstall } } @@ -183,6 +210,8 @@ const saveNetlifyToml = async ({ siteRoot, config, buildCmd, buildDir, functions const formatErrorMessage = ({ message, error }) => `${message} with error: ${chalk.red(error.message)}` +const formatTitle = (title) => chalk.cyan(title) + const createDeployKey = async ({ api, failAndExit }) => { try { const deployKey = await api.createDeployKey() From 687ba29000fb40ed7b71f813ba5805de032c6347 Mon Sep 17 00:00:00 2001 From: erezrokah Date: Wed, 10 Feb 2021 15:31:28 +0100 Subject: [PATCH 03/14] feat: use framework titles --- src/utils/init/utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/init/utils.js b/src/utils/init/utils.js index fdad914e989..e79c2fd249a 100644 --- a/src/utils/init/utils.js +++ b/src/utils/init/utils.js @@ -23,13 +23,13 @@ const getFrameworkInfo = async ({ siteRoot }) => { if (frameworks.length !== 0) { const [ { - name, + title, build: { directory, commands }, plugins, }, ] = frameworks return { - frameworkTitle: name, + frameworkTitle: title, frameworkBuildCommand: commands[0], frameworkBuildDir: directory, frameworkPlugins: plugins, From e3338e932a2c2c391fb1f00737360cd35314b743 Mon Sep 17 00:00:00 2001 From: erezrokah Date: Wed, 10 Feb 2021 16:47:18 +0100 Subject: [PATCH 04/14] feat: pass nodeVersion to framework info --- package-lock.json | 1139 ++++++++++++++++++++++++++++++- package.json | 1 + src/utils/init/config-github.js | 3 +- src/utils/init/config-manual.js | 3 +- src/utils/init/utils.js | 37 +- 5 files changed, 1150 insertions(+), 33 deletions(-) diff --git a/package-lock.json b/package-lock.json index dc4cf39098a..495dc761959 100644 --- a/package-lock.json +++ b/package-lock.json @@ -79,6 +79,7 @@ "netlify-redirect-parser": "^3.0.7", "netlify-redirector": "^0.2.1", "node-fetch": "^2.6.0", + "node-version-alias": "^1.0.1", "open": "^7.0.0", "ora": "^4.1.1", "p-filter": "^2.1.0", @@ -4533,7 +4534,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", - "dev": true, "dependencies": { "defer-to-connect": "^2.0.0" }, @@ -4545,7 +4545,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", - "dev": true, "dependencies": { "@types/http-cache-semantics": "*", "@types/keyv": "*", @@ -4610,8 +4609,7 @@ "node_modules/@types/http-cache-semantics": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", - "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", - "dev": true + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==" }, "node_modules/@types/http-proxy": { "version": "1.17.5", @@ -4653,7 +4651,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -4738,7 +4735,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -4898,6 +4894,146 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/all-node-versions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/all-node-versions/-/all-node-versions-8.0.0.tgz", + "integrity": "sha512-cF8ibgj23U7ai4qjSFzpeccwDXUlPFMzKe0Z6qf6gChR+9S0JMyzYz6oYz4n0nHi/FLH9BJIefsONsMH/WDM2w==", + "dependencies": { + "fetch-node-website": "^5.0.3", + "filter-obj": "^2.0.1", + "get-stream": "^5.1.0", + "global-cache-dir": "^2.0.0", + "jest-validate": "^25.3.0", + "path-exists": "^4.0.0", + "semver": "^7.3.2", + "write-file-atomic": "^3.0.3" + }, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/all-node-versions/node_modules/@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/all-node-versions/node_modules/@types/yargs": { + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/all-node-versions/node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/all-node-versions/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/all-node-versions/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/all-node-versions/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/all-node-versions/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/all-node-versions/node_modules/global-cache-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-cache-dir/-/global-cache-dir-2.0.0.tgz", + "integrity": "sha512-30pvU3e8muclEhc9tt+jRMaywOS3QfNdURflJ5Zv0bohjhcVQpBe5bwRHghGSJORLOKW81/n+3iJvHRHs+/S1Q==", + "dependencies": { + "cachedir": "^2.3.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/all-node-versions/node_modules/jest-get-type": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", + "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/all-node-versions/node_modules/jest-validate": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.5.0.tgz", + "integrity": "sha512-okUFKqhZIpo3jDdtUXUZ2LxGUZJIlfdYBvZb1aczzxrlyMlqdnnws9MOxezoLGhSaFc2XYaHNReNQfj5zPIWyQ==", + "dependencies": { + "@jest/types": "^25.5.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "jest-get-type": "^25.2.6", + "leven": "^3.1.0", + "pretty-format": "^25.5.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/all-node-versions/node_modules/pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dependencies": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + }, + "engines": { + "node": ">= 8.3" + } + }, "node_modules/ansi-align": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", @@ -6265,7 +6401,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", - "dev": true, "dependencies": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", @@ -8808,7 +8943,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true, "engines": { "node": ">=10" } @@ -11224,6 +11358,243 @@ "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz", "integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==" }, + "node_modules/fetch-node-website": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/fetch-node-website/-/fetch-node-website-5.0.3.tgz", + "integrity": "sha512-O86T46FUWSOq4AWON39oaT8H90QFKAbmjfOVBhgaS87AFfeW00txz73KTv7QopPWtHBbGdI1S8cIT1VK1OQYLg==", + "dependencies": { + "chalk": "^4.0.0", + "cli-progress": "^3.7.0", + "figures": "^3.2.0", + "filter-obj": "^2.0.1", + "got": "^10.7.0", + "jest-validate": "^25.3.0" + }, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/fetch-node-website/node_modules/@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/fetch-node-website/node_modules/@jest/types/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fetch-node-website/node_modules/@sindresorhus/is": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz", + "integrity": "sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/fetch-node-website/node_modules/@types/yargs": { + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/fetch-node-website/node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/fetch-node-website/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/fetch-node-website/node_modules/cacheable-lookup": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-2.0.1.tgz", + "integrity": "sha512-EMMbsiOTcdngM/K6gV/OxF2x0t07+vMOWxZNSCRQMjO2MY2nhZQ6OYhOOpyQrbhqsgtvKGI7hcq6xjnA92USjg==", + "dependencies": { + "@types/keyv": "^3.1.1", + "keyv": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fetch-node-website/node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/fetch-node-website/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/fetch-node-website/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/fetch-node-website/node_modules/decompress-response": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-5.0.0.tgz", + "integrity": "sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw==", + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fetch-node-website/node_modules/got": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/got/-/got-10.7.0.tgz", + "integrity": "sha512-aWTDeNw9g+XqEZNcTjMMZSy7B7yE9toWOFYip7ofFTLleJhvZwUxxTxkTpKvF+p1SAA4VHmuEy7PiHTHyq8tJg==", + "dependencies": { + "@sindresorhus/is": "^2.0.0", + "@szmarczak/http-timer": "^4.0.0", + "@types/cacheable-request": "^6.0.1", + "cacheable-lookup": "^2.0.0", + "cacheable-request": "^7.0.1", + "decompress-response": "^5.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^5.0.0", + "lowercase-keys": "^2.0.0", + "mimic-response": "^2.1.0", + "p-cancelable": "^2.0.0", + "p-event": "^4.0.0", + "responselike": "^2.0.0", + "to-readable-stream": "^2.0.0", + "type-fest": "^0.10.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/fetch-node-website/node_modules/jest-get-type": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", + "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/fetch-node-website/node_modules/jest-validate": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.5.0.tgz", + "integrity": "sha512-okUFKqhZIpo3jDdtUXUZ2LxGUZJIlfdYBvZb1aczzxrlyMlqdnnws9MOxezoLGhSaFc2XYaHNReNQfj5zPIWyQ==", + "dependencies": { + "@jest/types": "^25.5.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "jest-get-type": "^25.2.6", + "leven": "^3.1.0", + "pretty-format": "^25.5.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/fetch-node-website/node_modules/jest-validate/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fetch-node-website/node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fetch-node-website/node_modules/pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dependencies": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/fetch-node-website/node_modules/type-fest": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.10.0.tgz", + "integrity": "sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -14171,8 +14542,7 @@ "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, "node_modules/json-parse-better-errors": { "version": "1.0.2", @@ -14356,7 +14726,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", - "dev": true, "dependencies": { "json-buffer": "3.0.1" } @@ -16101,11 +16470,261 @@ "node": ">=6.0" } }, + "node_modules/node-version-alias": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/node-version-alias/-/node-version-alias-1.0.1.tgz", + "integrity": "sha512-E9EhoJkpIIZyYplB298W8ZfhcojQrnKnUPcaOgJqVqICUZwPZkuj10nTzEscwdziOOj545v4tGPvNBG3ieUbSw==", + "dependencies": { + "all-node-versions": "^8.0.0", + "filter-obj": "^2.0.1", + "jest-validate": "^25.3.0", + "normalize-node-version": "^10.0.0", + "path-exists": "^4.0.0", + "semver": "^7.3.2" + }, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/node-version-alias/node_modules/@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/node-version-alias/node_modules/@types/yargs": { + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/node-version-alias/node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/node-version-alias/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/node-version-alias/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-version-alias/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/node-version-alias/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/node-version-alias/node_modules/jest-get-type": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", + "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/node-version-alias/node_modules/jest-validate": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.5.0.tgz", + "integrity": "sha512-okUFKqhZIpo3jDdtUXUZ2LxGUZJIlfdYBvZb1aczzxrlyMlqdnnws9MOxezoLGhSaFc2XYaHNReNQfj5zPIWyQ==", + "dependencies": { + "@jest/types": "^25.5.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "jest-get-type": "^25.2.6", + "leven": "^3.1.0", + "pretty-format": "^25.5.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/node-version-alias/node_modules/pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dependencies": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + }, + "engines": { + "node": ">= 8.3" + } + }, "node_modules/noop2": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/noop2/-/noop2-2.0.0.tgz", "integrity": "sha1-S2NgFemIK1R4PAK0EvaZ2MXNCls=" }, + "node_modules/normalize-node-version": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/normalize-node-version/-/normalize-node-version-10.0.0.tgz", + "integrity": "sha512-/gVbS/qAnowVxr2fJy3F0MxmCvx8QdXJDl8XUE7HT3vsDeDjQfZkX9OiPahF+51Hgy93cKG1hP6uyBjQsMCvWQ==", + "dependencies": { + "all-node-versions": "^8.0.0", + "filter-obj": "^2.0.1", + "jest-validate": "^25.3.0", + "semver": "^7.3.2" + }, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/normalize-node-version/node_modules/@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/normalize-node-version/node_modules/@types/yargs": { + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/normalize-node-version/node_modules/ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/normalize-node-version/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/normalize-node-version/node_modules/chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/normalize-node-version/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/normalize-node-version/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/normalize-node-version/node_modules/jest-get-type": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", + "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/normalize-node-version/node_modules/jest-validate": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.5.0.tgz", + "integrity": "sha512-okUFKqhZIpo3jDdtUXUZ2LxGUZJIlfdYBvZb1aczzxrlyMlqdnnws9MOxezoLGhSaFc2XYaHNReNQfj5zPIWyQ==", + "dependencies": { + "@jest/types": "^25.5.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "jest-get-type": "^25.2.6", + "leven": "^3.1.0", + "pretty-format": "^25.5.0" + }, + "engines": { + "node": ">= 8.3" + } + }, + "node_modules/normalize-node-version/node_modules/pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dependencies": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + }, + "engines": { + "node": ">= 8.3" + } + }, "node_modules/normalize-package-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.1.tgz", @@ -16902,7 +17521,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.0.tgz", "integrity": "sha512-HAZyB3ZodPo+BDpb4/Iu7Jv4P6cSazBz9ZM0ChhEXp70scx834aWCEjQRwgt41UzzejUAPdbqqONfRWTPYrPAQ==", - "dev": true, "engines": { "node": ">=8" } @@ -18860,7 +19478,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", - "dev": true, "dependencies": { "lowercase-keys": "^2.0.0" } @@ -25766,7 +26383,6 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz", "integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==", - "dev": true, "requires": { "defer-to-connect": "^2.0.0" } @@ -25775,7 +26391,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", "integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==", - "dev": true, "requires": { "@types/http-cache-semantics": "*", "@types/keyv": "*", @@ -25840,8 +26455,7 @@ "@types/http-cache-semantics": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz", - "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==", - "dev": true + "integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A==" }, "@types/http-proxy": { "version": "1.17.5", @@ -25883,7 +26497,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz", "integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==", - "dev": true, "requires": { "@types/node": "*" } @@ -25967,7 +26580,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", - "dev": true, "requires": { "@types/node": "*" } @@ -26091,6 +26703,115 @@ "uri-js": "^4.2.2" } }, + "all-node-versions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/all-node-versions/-/all-node-versions-8.0.0.tgz", + "integrity": "sha512-cF8ibgj23U7ai4qjSFzpeccwDXUlPFMzKe0Z6qf6gChR+9S0JMyzYz6oYz4n0nHi/FLH9BJIefsONsMH/WDM2w==", + "requires": { + "fetch-node-website": "^5.0.3", + "filter-obj": "^2.0.1", + "get-stream": "^5.1.0", + "global-cache-dir": "^2.0.0", + "jest-validate": "^25.3.0", + "path-exists": "^4.0.0", + "semver": "^7.3.2", + "write-file-atomic": "^3.0.3" + }, + "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "global-cache-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-cache-dir/-/global-cache-dir-2.0.0.tgz", + "integrity": "sha512-30pvU3e8muclEhc9tt+jRMaywOS3QfNdURflJ5Zv0bohjhcVQpBe5bwRHghGSJORLOKW81/n+3iJvHRHs+/S1Q==", + "requires": { + "cachedir": "^2.3.0", + "path-exists": "^4.0.0" + } + }, + "jest-get-type": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", + "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==" + }, + "jest-validate": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.5.0.tgz", + "integrity": "sha512-okUFKqhZIpo3jDdtUXUZ2LxGUZJIlfdYBvZb1aczzxrlyMlqdnnws9MOxezoLGhSaFc2XYaHNReNQfj5zPIWyQ==", + "requires": { + "@jest/types": "^25.5.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "jest-get-type": "^25.2.6", + "leven": "^3.1.0", + "pretty-format": "^25.5.0" + } + }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + } + } + }, "ansi-align": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", @@ -27121,7 +27842,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz", "integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==", - "dev": true, "requires": { "clone-response": "^1.0.2", "get-stream": "^5.1.0", @@ -29129,8 +29849,7 @@ "defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" }, "define-properties": { "version": "1.1.3", @@ -30985,6 +31704,180 @@ "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.0.tgz", "integrity": "sha512-aN3pcx/DSmtyoovUudctc8+6Hl4T+hI9GBBHLjA76jdZl7+b1sgh5g4k+u/GL3dTy1/pnYzKp69FpJ0OicE3Wg==" }, + "fetch-node-website": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/fetch-node-website/-/fetch-node-website-5.0.3.tgz", + "integrity": "sha512-O86T46FUWSOq4AWON39oaT8H90QFKAbmjfOVBhgaS87AFfeW00txz73KTv7QopPWtHBbGdI1S8cIT1VK1OQYLg==", + "requires": { + "chalk": "^4.0.0", + "cli-progress": "^3.7.0", + "figures": "^3.2.0", + "filter-obj": "^2.0.1", + "got": "^10.7.0", + "jest-validate": "^25.3.0" + }, + "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "@sindresorhus/is": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz", + "integrity": "sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==" + }, + "@types/yargs": { + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "cacheable-lookup": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-2.0.1.tgz", + "integrity": "sha512-EMMbsiOTcdngM/K6gV/OxF2x0t07+vMOWxZNSCRQMjO2MY2nhZQ6OYhOOpyQrbhqsgtvKGI7hcq6xjnA92USjg==", + "requires": { + "@types/keyv": "^3.1.1", + "keyv": "^4.0.0" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "decompress-response": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-5.0.0.tgz", + "integrity": "sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw==", + "requires": { + "mimic-response": "^2.0.0" + } + }, + "got": { + "version": "10.7.0", + "resolved": "https://registry.npmjs.org/got/-/got-10.7.0.tgz", + "integrity": "sha512-aWTDeNw9g+XqEZNcTjMMZSy7B7yE9toWOFYip7ofFTLleJhvZwUxxTxkTpKvF+p1SAA4VHmuEy7PiHTHyq8tJg==", + "requires": { + "@sindresorhus/is": "^2.0.0", + "@szmarczak/http-timer": "^4.0.0", + "@types/cacheable-request": "^6.0.1", + "cacheable-lookup": "^2.0.0", + "cacheable-request": "^7.0.1", + "decompress-response": "^5.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^5.0.0", + "lowercase-keys": "^2.0.0", + "mimic-response": "^2.1.0", + "p-cancelable": "^2.0.0", + "p-event": "^4.0.0", + "responselike": "^2.0.0", + "to-readable-stream": "^2.0.0", + "type-fest": "^0.10.0" + } + }, + "jest-get-type": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", + "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==" + }, + "jest-validate": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.5.0.tgz", + "integrity": "sha512-okUFKqhZIpo3jDdtUXUZ2LxGUZJIlfdYBvZb1aczzxrlyMlqdnnws9MOxezoLGhSaFc2XYaHNReNQfj5zPIWyQ==", + "requires": { + "@jest/types": "^25.5.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "jest-get-type": "^25.2.6", + "leven": "^3.1.0", + "pretty-format": "^25.5.0" + }, + "dependencies": { + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" + }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, + "type-fest": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.10.0.tgz", + "integrity": "sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw==" + } + } + }, "figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -33162,8 +34055,7 @@ "json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, "json-parse-better-errors": { "version": "1.0.2", @@ -33316,7 +34208,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz", "integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==", - "dev": true, "requires": { "json-buffer": "3.0.1" } @@ -34706,11 +35597,205 @@ "@babel/parser": "^7.0.0" } }, + "node-version-alias": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/node-version-alias/-/node-version-alias-1.0.1.tgz", + "integrity": "sha512-E9EhoJkpIIZyYplB298W8ZfhcojQrnKnUPcaOgJqVqICUZwPZkuj10nTzEscwdziOOj545v4tGPvNBG3ieUbSw==", + "requires": { + "all-node-versions": "^8.0.0", + "filter-obj": "^2.0.1", + "jest-validate": "^25.3.0", + "normalize-node-version": "^10.0.0", + "path-exists": "^4.0.0", + "semver": "^7.3.2" + }, + "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "jest-get-type": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", + "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==" + }, + "jest-validate": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.5.0.tgz", + "integrity": "sha512-okUFKqhZIpo3jDdtUXUZ2LxGUZJIlfdYBvZb1aczzxrlyMlqdnnws9MOxezoLGhSaFc2XYaHNReNQfj5zPIWyQ==", + "requires": { + "@jest/types": "^25.5.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "jest-get-type": "^25.2.6", + "leven": "^3.1.0", + "pretty-format": "^25.5.0" + } + }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + } + } + }, "noop2": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/noop2/-/noop2-2.0.0.tgz", "integrity": "sha1-S2NgFemIK1R4PAK0EvaZ2MXNCls=" }, + "normalize-node-version": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/normalize-node-version/-/normalize-node-version-10.0.0.tgz", + "integrity": "sha512-/gVbS/qAnowVxr2fJy3F0MxmCvx8QdXJDl8XUE7HT3vsDeDjQfZkX9OiPahF+51Hgy93cKG1hP6uyBjQsMCvWQ==", + "requires": { + "all-node-versions": "^8.0.0", + "filter-obj": "^2.0.1", + "jest-validate": "^25.3.0", + "semver": "^7.3.2" + }, + "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "@types/yargs": { + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "jest-get-type": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", + "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==" + }, + "jest-validate": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.5.0.tgz", + "integrity": "sha512-okUFKqhZIpo3jDdtUXUZ2LxGUZJIlfdYBvZb1aczzxrlyMlqdnnws9MOxezoLGhSaFc2XYaHNReNQfj5zPIWyQ==", + "requires": { + "@jest/types": "^25.5.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "jest-get-type": "^25.2.6", + "leven": "^3.1.0", + "pretty-format": "^25.5.0" + } + }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + } + } + }, "normalize-package-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.1.tgz", @@ -35319,8 +36404,7 @@ "p-cancelable": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.0.tgz", - "integrity": "sha512-HAZyB3ZodPo+BDpb4/Iu7Jv4P6cSazBz9ZM0ChhEXp70scx834aWCEjQRwgt41UzzejUAPdbqqONfRWTPYrPAQ==", - "dev": true + "integrity": "sha512-HAZyB3ZodPo+BDpb4/Iu7Jv4P6cSazBz9ZM0ChhEXp70scx834aWCEjQRwgt41UzzejUAPdbqqONfRWTPYrPAQ==" }, "p-defer": { "version": "1.0.0", @@ -36801,7 +37885,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", - "dev": true, "requires": { "lowercase-keys": "^2.0.0" } diff --git a/package.json b/package.json index d296615f7a7..136771effef 100644 --- a/package.json +++ b/package.json @@ -143,6 +143,7 @@ "netlify-redirect-parser": "^3.0.7", "netlify-redirector": "^0.2.1", "node-fetch": "^2.6.0", + "node-version-alias": "^1.0.1", "open": "^7.0.0", "ora": "^4.1.1", "p-filter": "^2.1.0", diff --git a/src/utils/init/config-github.js b/src/utils/init/config-github.js index b93b0dafd8a..e3f4115f5fc 100644 --- a/src/utils/init/config-github.js +++ b/src/utils/init/config-github.js @@ -180,11 +180,12 @@ module.exports = async function configGithub({ context, siteId, repoOwner, repoN globalConfig, config, site: { root: siteRoot }, + cachedConfig: { env }, } = netlify const token = await getGitHubToken({ log, globalConfig }) - const { buildCmd, buildDir, functionsDir, plugins } = await getBuildSettings({ siteRoot, config }) + const { buildCmd, buildDir, functionsDir, plugins } = await getBuildSettings({ siteRoot, config, env, warn }) await saveNetlifyToml({ siteRoot, config, buildCmd, buildDir, functionsDir, plugins, warn }) const octokit = getGitHubClient({ token }) diff --git a/src/utils/init/config-manual.js b/src/utils/init/config-manual.js index 331fe2e4146..6cf49826e1f 100644 --- a/src/utils/init/config-manual.js +++ b/src/utils/init/config-manual.js @@ -55,9 +55,10 @@ module.exports = async function configManual({ context, siteId, repoData }) { api, config, site: { root: siteRoot }, + cachedConfig: { env }, } = netlify - const { buildCmd, buildDir, functionsDir, plugins } = await getBuildSettings({ siteRoot, config }) + const { buildCmd, buildDir, functionsDir, plugins } = await getBuildSettings({ siteRoot, config, env, warn }) await saveNetlifyToml({ siteRoot, config, buildCmd, buildDir, functionsDir, plugins, warn }) const deployKey = await createDeployKey({ api, failAndExit }) diff --git a/src/utils/init/utils.js b/src/utils/init/utils.js index e79c2fd249a..70f7a445dc7 100644 --- a/src/utils/init/utils.js +++ b/src/utils/init/utils.js @@ -4,9 +4,13 @@ const path = require('path') const { listFrameworks } = require('@netlify/framework-info') const chalk = require('chalk') const cleanDeep = require('clean-deep') +const { get } = require('dot-prop') const inquirer = require('inquirer') +const locatePath = require('locate-path') const isEmpty = require('lodash/isEmpty') +const nodeVersionAlias = require('node-version-alias') +const { readFileAsync } = require('../../lib/fs') const { fileExistsAsync, writeFileAsync } = require('../../lib/fs') const normalizeDir = ({ siteRoot, dir, defaultValue }) => { @@ -18,8 +22,33 @@ const normalizeDir = ({ siteRoot, dir, defaultValue }) => { return relativeDir || defaultValue } -const getFrameworkInfo = async ({ siteRoot }) => { - const frameworks = await listFrameworks({ projectDir: siteRoot }) +const DEFAULT_NODE_VERSION = '12.18.0' +const NVM_FLAG_PREFIX = '--' + +// to support NODE_VERSION=--lts, etc. +const normalizeConfiguredVersion = (version) => + version.startsWith(NVM_FLAG_PREFIX) ? version.slice(NVM_FLAG_PREFIX.length) : version + +const detectNodeVersion = async ({ siteRoot, env, warn }) => { + try { + const nodeVersionFile = await locatePath(['.nvmrc', '.node-version'], { cwd: siteRoot }) + const configuredVersion = + nodeVersionFile === undefined ? get(env, 'NODE_VERSION.value') : await readFileAsync(nodeVersionFile, 'utf8') + + const version = + configuredVersion === undefined + ? DEFAULT_NODE_VERSION + : await nodeVersionAlias(normalizeConfiguredVersion(configuredVersion)) + + return version + } catch (error) { + warn(`Failed detecting Node.js version: ${error.message}`) + return DEFAULT_NODE_VERSION + } +} + +const getFrameworkInfo = async ({ siteRoot, nodeVersion }) => { + const frameworks = await listFrameworks({ projectDir: siteRoot, nodeVersion }) if (frameworks.length !== 0) { const [ { @@ -126,9 +155,11 @@ const getPluginsToInstall = ({ plugins, installSinglePlugin, recommendedPlugins return installSinglePlugin === true ? [recommendedPlugins[0]] : [] } -const getBuildSettings = async ({ siteRoot, config }) => { +const getBuildSettings = async ({ siteRoot, config, env, warn }) => { + const nodeVersion = await detectNodeVersion({ siteRoot, env, warn }) const { frameworkTitle, frameworkBuildCommand, frameworkBuildDir, frameworkPlugins } = await getFrameworkInfo({ siteRoot, + nodeVersion, }) const { defaultBuildCmd, defaultBuildDir, defaultFunctionsDir, recommendedPlugins } = await getDefaultSettings({ siteRoot, From 9c5e66a2185ff66bdf5a4a33ac3cb37a7e69cba3 Mon Sep 17 00:00:00 2001 From: erezrokah Date: Thu, 11 Feb 2021 12:49:07 +0100 Subject: [PATCH 05/14] feat: use plugins and framework names --- package-lock.json | 15 +++--- package.json | 3 +- src/utils/init/frameworks.js | 23 ++++++++++ src/utils/init/node-version.js | 32 +++++++++++++ src/utils/init/plugins.js | 25 ++++++++++ src/utils/init/utils.js | 84 ++++++++-------------------------- 6 files changed, 109 insertions(+), 73 deletions(-) create mode 100644 src/utils/init/frameworks.js create mode 100644 src/utils/init/node-version.js create mode 100644 src/utils/init/plugins.js diff --git a/package-lock.json b/package-lock.json index 495dc761959..4c74ca03fe2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,9 @@ "dependencies": { "@netlify/build": "^9.9.7", "@netlify/config": "^4.1.3", - "@netlify/framework-info": "^2.3.0", + "@netlify/framework-info": "^3.1.0", "@netlify/plugin-edge-handlers": "^1.11.5", + "@netlify/plugins-list": "^2.2.0", "@netlify/traffic-mesh-agent": "^0.27.10", "@netlify/zip-it-and-ship-it": "^2.7.1", "@oclif/command": "^1.6.1", @@ -2584,9 +2585,9 @@ } }, "node_modules/@netlify/framework-info": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@netlify/framework-info/-/framework-info-2.3.0.tgz", - "integrity": "sha512-vqy9wbBRP8qWnkzA/OQsThr1+cfqapMrORJ4hWcrjhIPRmXIJtwB6OWuLIUalMeSGCwqZjYpKfudc4BLuxxvjw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@netlify/framework-info/-/framework-info-3.1.3.tgz", + "integrity": "sha512-yRrndnWZfugKzHEEdL3m9tRgA4hLS3dZM9RruvpvN5CMML52KvVbSH4xdwjNG7CbCfukrUv4uZ9EABOAoxNhTg==", "dependencies": { "ajv": "^7.0.0", "filter-obj": "^2.0.1", @@ -24826,9 +24827,9 @@ } }, "@netlify/framework-info": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@netlify/framework-info/-/framework-info-2.3.0.tgz", - "integrity": "sha512-vqy9wbBRP8qWnkzA/OQsThr1+cfqapMrORJ4hWcrjhIPRmXIJtwB6OWuLIUalMeSGCwqZjYpKfudc4BLuxxvjw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@netlify/framework-info/-/framework-info-3.1.3.tgz", + "integrity": "sha512-yRrndnWZfugKzHEEdL3m9tRgA4hLS3dZM9RruvpvN5CMML52KvVbSH4xdwjNG7CbCfukrUv4uZ9EABOAoxNhTg==", "requires": { "ajv": "^7.0.0", "filter-obj": "^2.0.1", diff --git a/package.json b/package.json index 136771effef..5ee1290e0f5 100644 --- a/package.json +++ b/package.json @@ -76,8 +76,9 @@ "dependencies": { "@netlify/build": "^9.9.7", "@netlify/config": "^4.1.3", - "@netlify/framework-info": "^2.3.0", + "@netlify/framework-info": "^3.1.0", "@netlify/plugin-edge-handlers": "^1.11.5", + "@netlify/plugins-list": "^2.2.0", "@netlify/traffic-mesh-agent": "^0.27.10", "@netlify/zip-it-and-ship-it": "^2.7.1", "@oclif/command": "^1.6.1", diff --git a/src/utils/init/frameworks.js b/src/utils/init/frameworks.js new file mode 100644 index 00000000000..d8bfe6546de --- /dev/null +++ b/src/utils/init/frameworks.js @@ -0,0 +1,23 @@ +const { listFrameworks } = require('@netlify/framework-info') + +const getFrameworkInfo = async ({ siteRoot, nodeVersion }) => { + const frameworks = await listFrameworks({ projectDir: siteRoot, nodeVersion }) + if (frameworks.length !== 0) { + const [ + { + name, + build: { directory, commands }, + plugins, + }, + ] = frameworks + return { + frameworkName: name, + frameworkBuildCommand: commands[0], + frameworkBuildDir: directory, + frameworkPlugins: plugins, + } + } + return {} +} + +module.exports = { getFrameworkInfo } diff --git a/src/utils/init/node-version.js b/src/utils/init/node-version.js new file mode 100644 index 00000000000..55ea2c85421 --- /dev/null +++ b/src/utils/init/node-version.js @@ -0,0 +1,32 @@ +const { get } = require('dot-prop') +const locatePath = require('locate-path') +const nodeVersionAlias = require('node-version-alias') + +const { readFileAsync } = require('../../lib/fs') + +const DEFAULT_NODE_VERSION = '12.18.0' +const NVM_FLAG_PREFIX = '--' + +// to support NODE_VERSION=--lts, etc. +const normalizeConfiguredVersion = (version) => + version.startsWith(NVM_FLAG_PREFIX) ? version.slice(NVM_FLAG_PREFIX.length) : version + +const detectNodeVersion = async ({ siteRoot, env, warn }) => { + try { + const nodeVersionFile = await locatePath(['.nvmrc', '.node-version'], { cwd: siteRoot }) + const configuredVersion = + nodeVersionFile === undefined ? get(env, 'NODE_VERSION.value') : await readFileAsync(nodeVersionFile, 'utf8') + + const version = + configuredVersion === undefined + ? DEFAULT_NODE_VERSION + : await nodeVersionAlias(normalizeConfiguredVersion(configuredVersion)) + + return version + } catch (error) { + warn(`Failed detecting Node.js version: ${error.message}`) + return DEFAULT_NODE_VERSION + } +} + +module.exports = { detectNodeVersion } diff --git a/src/utils/init/plugins.js b/src/utils/init/plugins.js new file mode 100644 index 00000000000..477159f9ce2 --- /dev/null +++ b/src/utils/init/plugins.js @@ -0,0 +1,25 @@ +const pluginsList = require('@netlify/plugins-list') +const fetch = require('node-fetch') + +const PLUGINS_LIST_URL = 'https://netlify-plugins.netlify.app/plugins.json' +// 1 minute +const PLUGINS_LIST_TIMEOUT = 6e4 + +const getPluginsList = async () => { + try { + const response = await fetch(PLUGINS_LIST_URL, { timeout: PLUGINS_LIST_TIMEOUT }) + return await response.json() + } catch { + return pluginsList + } +} + +const getPluginInfo = (list, packageName) => list.find(({ package }) => package === packageName) + +const isPluginInstalled = (configPlugins, plugin) => + configPlugins.some(({ package: configPlugin }) => configPlugin === plugin) + +const getRecommendPlugins = (frameworkPlugins, config) => + frameworkPlugins.filter((plugin) => !isPluginInstalled(config.plugins, plugin)) + +module.exports = { getPluginsList, getPluginInfo, getRecommendPlugins } diff --git a/src/utils/init/utils.js b/src/utils/init/utils.js index 70f7a445dc7..80b804a81e6 100644 --- a/src/utils/init/utils.js +++ b/src/utils/init/utils.js @@ -1,18 +1,17 @@ const { EOL } = require('os') const path = require('path') -const { listFrameworks } = require('@netlify/framework-info') const chalk = require('chalk') const cleanDeep = require('clean-deep') -const { get } = require('dot-prop') const inquirer = require('inquirer') -const locatePath = require('locate-path') const isEmpty = require('lodash/isEmpty') -const nodeVersionAlias = require('node-version-alias') -const { readFileAsync } = require('../../lib/fs') const { fileExistsAsync, writeFileAsync } = require('../../lib/fs') +const { getFrameworkInfo } = require('./frameworks') +const { detectNodeVersion } = require('./node-version') +const { getPluginsList, getPluginInfo, getRecommendPlugins } = require('./plugins') + const normalizeDir = ({ siteRoot, dir, defaultValue }) => { if (dir === undefined) { return defaultValue @@ -22,56 +21,8 @@ const normalizeDir = ({ siteRoot, dir, defaultValue }) => { return relativeDir || defaultValue } -const DEFAULT_NODE_VERSION = '12.18.0' -const NVM_FLAG_PREFIX = '--' - -// to support NODE_VERSION=--lts, etc. -const normalizeConfiguredVersion = (version) => - version.startsWith(NVM_FLAG_PREFIX) ? version.slice(NVM_FLAG_PREFIX.length) : version - -const detectNodeVersion = async ({ siteRoot, env, warn }) => { - try { - const nodeVersionFile = await locatePath(['.nvmrc', '.node-version'], { cwd: siteRoot }) - const configuredVersion = - nodeVersionFile === undefined ? get(env, 'NODE_VERSION.value') : await readFileAsync(nodeVersionFile, 'utf8') - - const version = - configuredVersion === undefined - ? DEFAULT_NODE_VERSION - : await nodeVersionAlias(normalizeConfiguredVersion(configuredVersion)) - - return version - } catch (error) { - warn(`Failed detecting Node.js version: ${error.message}`) - return DEFAULT_NODE_VERSION - } -} - -const getFrameworkInfo = async ({ siteRoot, nodeVersion }) => { - const frameworks = await listFrameworks({ projectDir: siteRoot, nodeVersion }) - if (frameworks.length !== 0) { - const [ - { - title, - build: { directory, commands }, - plugins, - }, - ] = frameworks - return { - frameworkTitle: title, - frameworkBuildCommand: commands[0], - frameworkBuildDir: directory, - frameworkPlugins: plugins, - } - } - return {} -} - -const isPluginInstalled = (configPlugins, plugin) => - configPlugins.some(({ package: configPlugin }) => configPlugin === plugin) - const getDefaultSettings = ({ siteRoot, config, frameworkPlugins, frameworkBuildCommand, frameworkBuildDir }) => { - const recommendedPlugins = frameworkPlugins.filter((plugin) => !isPluginInstalled(config.plugins, plugin)) + const recommendedPlugins = getRecommendPlugins(frameworkPlugins, config) const { command: defaultBuildCmd = frameworkBuildCommand, publish: defaultBuildDir = frameworkBuildDir, @@ -86,12 +37,12 @@ const getDefaultSettings = ({ siteRoot, config, frameworkPlugins, frameworkBuild } } -const getPromptInputs = ({ +const getPromptInputs = async ({ defaultBuildCmd, defaultBuildDir, defaultFunctionsDir, recommendedPlugins, - frameworkTitle, + frameworkName, }) => { const inputs = [ { @@ -119,30 +70,33 @@ const getPromptInputs = ({ return inputs } - const prefix = `Seems like this is a ${formatTitle(frameworkTitle)} site.${EOL} ` + const pluginsList = await getPluginsList() + + const prefix = `Seems like this is a ${formatTitle(frameworkName)} site.${EOL} ` if (recommendedPlugins.length === 1) { + const { name } = getPluginInfo(pluginsList, recommendedPlugins[0]) return [ ...inputs, { type: 'confirm', name: 'installSinglePlugin', - message: `${prefix}Recommended Build Plugin: ${formatTitle(recommendedPlugins[0])}${EOL} Install ${ - recommendedPlugins[0] - }?`, + message: `${prefix}Recommended Build Plugin: ${formatTitle(`${name} plugin`)}${EOL} Install ${name} plugin?`, default: true, }, ] } + const infos = recommendedPlugins.map((packageName) => getPluginInfo(pluginsList, packageName)) return [ ...inputs, { type: 'checkbox', name: 'plugins', - message: `${prefix}Recommended Build Plugins: ${recommendedPlugins + message: `${prefix}Recommended Build Plugins: ${infos + .map(({ name }) => `${name} plugin`) .map(formatTitle) .join(', ')}${EOL} Which plugins to install?`, - choices: recommendedPlugins, + choices: infos.map(({ name, package }) => ({ name: `${name} plugin`, value: package })), }, ] } @@ -157,7 +111,7 @@ const getPluginsToInstall = ({ plugins, installSinglePlugin, recommendedPlugins const getBuildSettings = async ({ siteRoot, config, env, warn }) => { const nodeVersion = await detectNodeVersion({ siteRoot, env, warn }) - const { frameworkTitle, frameworkBuildCommand, frameworkBuildDir, frameworkPlugins } = await getFrameworkInfo({ + const { frameworkName, frameworkBuildCommand, frameworkBuildDir, frameworkPlugins } = await getFrameworkInfo({ siteRoot, nodeVersion, }) @@ -169,12 +123,12 @@ const getBuildSettings = async ({ siteRoot, config, env, warn }) => { frameworkPlugins, }) const { buildCmd, buildDir, functionsDir, plugins, installSinglePlugin } = await inquirer.prompt( - getPromptInputs({ + await getPromptInputs({ defaultBuildCmd, defaultBuildDir, defaultFunctionsDir, recommendedPlugins, - frameworkTitle, + frameworkName, }), ) const pluginsToInstall = getPluginsToInstall({ plugins, installSinglePlugin, recommendedPlugins }) From 381773d5b3502b739cf3520039f434afe5651cfb Mon Sep 17 00:00:00 2001 From: erezrokah Date: Wed, 17 Feb 2021 14:59:12 +0100 Subject: [PATCH 06/14] test(command-init): add tests --- package-lock.json | 1 + package.json | 1 + src/utils/init/utils.js | 2 +- tests/command.init.test.js | 283 ++++++++++++++++++++++++++++++++++++ tests/command.lm.test.js | 6 +- tests/utils/mock-api.js | 28 +++- tests/utils/site-builder.js | 12 +- 7 files changed, 325 insertions(+), 8 deletions(-) create mode 100644 tests/command.init.test.js diff --git a/package-lock.json b/package-lock.json index 4c74ca03fe2..7058c19195e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -137,6 +137,7 @@ "standard-version": "^9.0.0", "strip-ansi": "^6.0.0", "temp-dir": "^2.0.0", + "toml": "^3.0.0", "tomlify-j0.4": "^3.0.0" }, "engines": { diff --git a/package.json b/package.json index 5ee1290e0f5..1b2a6bd00ba 100644 --- a/package.json +++ b/package.json @@ -197,6 +197,7 @@ "standard-version": "^9.0.0", "strip-ansi": "^6.0.0", "temp-dir": "^2.0.0", + "toml": "^3.0.0", "tomlify-j0.4": "^3.0.0" }, "ava": { diff --git a/src/utils/init/utils.js b/src/utils/init/utils.js index 80b804a81e6..56cc5b94587 100644 --- a/src/utils/init/utils.js +++ b/src/utils/init/utils.js @@ -111,7 +111,7 @@ const getPluginsToInstall = ({ plugins, installSinglePlugin, recommendedPlugins const getBuildSettings = async ({ siteRoot, config, env, warn }) => { const nodeVersion = await detectNodeVersion({ siteRoot, env, warn }) - const { frameworkName, frameworkBuildCommand, frameworkBuildDir, frameworkPlugins } = await getFrameworkInfo({ + const { frameworkName, frameworkBuildCommand, frameworkBuildDir, frameworkPlugins = [] } = await getFrameworkInfo({ siteRoot, nodeVersion, }) diff --git a/tests/command.init.test.js b/tests/command.init.test.js new file mode 100644 index 00000000000..95a8f46cff2 --- /dev/null +++ b/tests/command.init.test.js @@ -0,0 +1,283 @@ +const { Buffer } = require('buffer') + +const test = require('ava') +const execa = require('execa') +const toml = require('toml') + +const { readFileAsync } = require('../src/lib/fs') + +const cliPath = require('./utils/cli-path') +const { withMockApi } = require('./utils/mock-api') +const { withSiteBuilder } = require('./utils/site-builder') + +const handleQuestions = (process, questions) => { + const remainingQuestions = [...questions] + let buffer = '' + process.stdout.on('data', (data) => { + buffer += data + const index = remainingQuestions.findIndex(({ question }) => buffer.includes(question)) + if (index >= 0) { + buffer = '' + process.stdin.write(Buffer.from(remainingQuestions[index].answer)) + remainingQuestions.splice(index, 1) + } + }) +} + +const CONFIRM = '\n' +const DOWN = '\u001B[B' +const answerWithValue = (value) => `${value}${CONFIRM}` + +const assertSiteInit = async ( + t, + builder, + requests, + { command = 'custom-build-command', functions = 'custom-functions', publish = 'custom-publish', plugins = [] } = {}, +) => { + // assert netlify.toml was created with user inputs + const netlifyToml = toml.parse(await readFileAsync(`${builder.directory}/netlify.toml`, 'utf8')) + t.deepEqual(netlifyToml, { + build: { command, functions, publish }, + ...(plugins.length === 0 ? {} : { plugins }), + }) + + // assert updateSite was called with user inputs + const siteUpdateRequests = requests.filter(({ path }) => path === '/api/v1/sites/site_id').map(({ body }) => body) + t.deepEqual(siteUpdateRequests, [ + { + plugins, + repo: { + allowed_branches: ['master'], + cmd: command, + dir: publish, + provider: 'manual', + repo_branch: 'master', + repo_path: 'git@github.com:owner/repo.git', + }, + }, + { + build_settings: { + functions_dir: functions, + }, + }, + ]) +} + +test('netlify init existing site', async (t) => { + const initQuestions = [ + { + question: 'Create & configure a new site', + answer: CONFIRM, + }, + { + question: 'How do you want to link this folder to a site', + answer: CONFIRM, + }, + { + question: 'Your build command (hugo build/yarn run build/etc)', + answer: answerWithValue('custom-build-command'), + }, + { + question: 'Directory to deploy (blank for current dir)', + answer: answerWithValue('custom-publish'), + }, + { + question: 'Netlify functions folder', + answer: answerWithValue('custom-functions'), + }, + { + question: 'No netlify.toml detected', + answer: CONFIRM, + }, + { question: 'Give this Netlify SSH public key access to your repository', answer: CONFIRM }, + { question: 'The SSH URL of the remote git repo', answer: CONFIRM }, + { question: 'Configure the following webhook for your repository', answer: CONFIRM }, + ] + + const routes = [ + { + path: 'sites', + response: [ + { + admin_url: 'https://app.netlify.com/sites/site-name/overview', + ssl_url: 'https://site-name.netlify.app/', + id: 'site_id', + name: 'site-name', + build_settings: { repo_url: 'https://github.com/owner/repo' }, + }, + ], + }, + { path: 'deploy_keys', method: 'post', response: { public_key: 'public_key' } }, + { path: 'sites/site_id', method: 'patch', response: { deploy_hook: 'deploy_hook' } }, + ] + + await withSiteBuilder('new-site', async (builder) => { + builder.withGit({ repoUrl: 'git@github.com:owner/repo.git' }) + + await builder.buildAsync() + await withMockApi(routes, async ({ apiUrl, requests }) => { + // --force is required since we we return an existing site in the `sites` route + // --manual is used to avoid the config-github flow that uses GitHub API + const childProcess = execa(cliPath, ['init', '--force', '--manual'], { + cwd: builder.directory, + env: { NETLIFY_API_URL: apiUrl }, + }) + + handleQuestions(childProcess, initQuestions) + + await childProcess + + await assertSiteInit(t, builder, requests) + }) + }) +}) + +test('netlify init new site', async (t) => { + const initQuestions = [ + { + question: 'Create & configure a new site', + answer: answerWithValue(DOWN), + }, + { question: 'Team: (Use arrow keys)', answer: CONFIRM }, + { question: 'Site name (optional)', answer: answerWithValue('test-site-name') }, + { + question: 'Your build command (hugo build/yarn run build/etc)', + answer: answerWithValue('custom-build-command'), + }, + { + question: 'Directory to deploy (blank for current dir)', + answer: answerWithValue('custom-publish'), + }, + { + question: 'Netlify functions folder', + answer: answerWithValue('custom-functions'), + }, + { + question: 'No netlify.toml detected', + answer: CONFIRM, + }, + { question: 'Give this Netlify SSH public key access to your repository', answer: CONFIRM }, + { question: 'The SSH URL of the remote git repo', answer: CONFIRM }, + { question: 'Configure the following webhook for your repository', answer: CONFIRM }, + ] + + const routes = [ + { + path: 'accounts', + response: [{ slug: 'test-account' }], + }, + { + path: 'sites', + response: [], + }, + { + path: 'user', + response: { name: 'test user', slug: 'test-user', email: 'user@test.com' }, + }, + { + path: 'test-account/sites', + method: 'post', + response: { id: 'site_id', name: 'test-site-name' }, + }, + { path: 'deploy_keys', method: 'post', response: { public_key: 'public_key' } }, + { path: 'sites/site_id', method: 'patch', response: { deploy_hook: 'deploy_hook' } }, + ] + + await withSiteBuilder('new-site', async (builder) => { + builder.withGit({ repoUrl: 'git@github.com:owner/repo.git' }) + + await builder.buildAsync() + await withMockApi(routes, async ({ apiUrl, requests }) => { + // --manual is used to avoid the config-github flow that uses GitHub API + const childProcess = execa(cliPath, ['init', '--manual'], { + cwd: builder.directory, + env: { NETLIFY_API_URL: apiUrl }, + encoding: 'utf8', + }) + + handleQuestions(childProcess, initQuestions) + + await childProcess + + await assertSiteInit(t, builder, requests) + }) + }) +}) + +test('netlify init new Next.js site', async (t) => { + const initQuestions = [ + { + question: 'Create & configure a new site', + answer: answerWithValue(DOWN), + }, + { question: 'Team: (Use arrow keys)', answer: CONFIRM }, + { question: 'Site name (optional)', answer: answerWithValue('test-site-name') }, + { + question: 'Your build command (hugo build/yarn run build/etc)', + answer: answerWithValue('custom-build-command'), + }, + { + question: 'Directory to deploy (blank for current dir)', + answer: answerWithValue('custom-publish'), + }, + { + question: 'Netlify functions folder', + answer: answerWithValue('custom-functions'), + }, + { + question: 'Install Next on Netlify plugin', + answer: CONFIRM, + }, + { + question: 'No netlify.toml detected', + answer: CONFIRM, + }, + { question: 'Give this Netlify SSH public key access to your repository', answer: CONFIRM }, + { question: 'The SSH URL of the remote git repo', answer: CONFIRM }, + { question: 'Configure the following webhook for your repository', answer: CONFIRM }, + ] + + const routes = [ + { + path: 'accounts', + response: [{ slug: 'test-account' }], + }, + + { + path: 'sites', + response: [], + }, + { + path: 'user', + response: { name: 'test user', slug: 'test-user', email: 'user@test.com' }, + }, + { + path: 'test-account/sites', + method: 'post', + response: { id: 'site_id', name: 'test-site-name' }, + }, + { path: 'deploy_keys', method: 'post', response: { public_key: 'public_key' } }, + { path: 'sites/site_id', method: 'patch', response: { deploy_hook: 'deploy_hook' } }, + ] + + await withSiteBuilder('new-site', async (builder) => { + builder + .withGit({ repoUrl: 'git@github.com:owner/repo.git' }) + .withPackageJson({ packageJson: { dependencies: { next: '^10.0.0' } } }) + + await builder.buildAsync() + await withMockApi(routes, async ({ apiUrl, requests }) => { + // --manual is used to avoid the config-github flow that uses GitHub API + const childProcess = execa(cliPath, ['init', '--manual'], { + cwd: builder.directory, + env: { NETLIFY_API_URL: apiUrl }, + }) + + handleQuestions(childProcess, initQuestions) + + await childProcess + + await assertSiteInit(t, builder, requests, { plugins: [{ package: '@netlify/plugin-nextjs' }] }) + }) + }) +}) diff --git a/tests/command.lm.test.js b/tests/command.lm.test.js index 0ebc63be544..6bfea026b4e 100644 --- a/tests/command.lm.test.js +++ b/tests/command.lm.test.js @@ -21,7 +21,7 @@ if (process.env.IS_FORK !== 'true') { const builder = createSiteBuilder({ siteName: 'site-with-lm' }) await builder.buildAsync() - const mockApi = startMockApi({ + const { server } = startMockApi({ routes: [ { method: 'post', path: 'sites/site_id/services/large-media/instances', status: 201 }, { path: 'sites/site_id', response: { id_domain: 'localhost' } }, @@ -33,8 +33,8 @@ if (process.env.IS_FORK !== 'true') { env: { NETLIFY_SITE_ID: siteId, SHELL: process.env.SHELL || 'bash' }, } t.context.builder = builder - t.context.mockApi = mockApi - t.context.apiUrl = `http://localhost:${mockApi.address().port}/api/v1` + t.context.mockApi = server + t.context.apiUrl = `http://localhost:${server.address().port}/api/v1` await callCli(['lm:uninstall'], t.context.execOptions) }) diff --git a/tests/utils/mock-api.js b/tests/utils/mock-api.js index c08c01cbafe..d9e5ecf8b6d 100644 --- a/tests/utils/mock-api.js +++ b/tests/utils/mock-api.js @@ -1,24 +1,48 @@ const bodyParser = require('body-parser') const express = require('express') +const addRequest = (requests, request) => { + requests.push({ + path: request.path, + body: request.body, + method: request.method, + }) +} + const startMockApi = ({ routes }) => { + const requests = [] const app = express() app.use(bodyParser.urlencoded({ extended: true })) + app.use(bodyParser.json()) + app.use(bodyParser.raw()) routes.forEach(({ method = 'get', path, response = {}, status = 200 }) => { app[method.toLowerCase()](`/api/v1/${path}`, function onRequest(req, res) { + addRequest(requests, req) res.status(status) res.json(response) }) }) app.all('*', function onRequest(req, res) { + addRequest(requests, req) console.warn(`Route not found: ${req.url}`) res.status(404) res.json({ message: 'Not found' }) }) - return app.listen() + return { server: app.listen(), requests } +} + +const withMockApi = async (routes, testHandler) => { + let mockApi + try { + mockApi = startMockApi({ routes }) + const apiUrl = `http://localhost:${mockApi.server.address().port}/api/v1` + return await testHandler({ apiUrl, requests: mockApi.requests }) + } finally { + mockApi.server.close() + } } -module.exports = { startMockApi } +module.exports = { withMockApi, startMockApi } diff --git a/tests/utils/site-builder.js b/tests/utils/site-builder.js index 4831055a4b9..2c202e9ab34 100644 --- a/tests/utils/site-builder.js +++ b/tests/utils/site-builder.js @@ -2,6 +2,7 @@ const os = require('os') const path = require('path') const process = require('process') +const execa = require('execa') const tempDirectory = require('temp-dir') const { toToml } = require('tomlify-j0.4') const { v4: uuidv4 } = require('uuid') @@ -44,10 +45,10 @@ const createSiteBuilder = ({ siteName }) => { }) return builder }, - withPackageJson: ({ object, pathPrefix = '' }) => { + withPackageJson: ({ packageJson, pathPrefix = '' }) => { const dest = path.join(directory, pathPrefix, 'package.json') tasks.push(async () => { - const content = JSON.stringify(object, null, 2) + const content = JSON.stringify(packageJson, null, 2) await ensureDir(path.dirname(dest)) await fs.writeFileAsync(dest, `${content}\n`) }) @@ -122,6 +123,13 @@ const createSiteBuilder = ({ siteName }) => { }) return builder }, + withGit: ({ repoUrl }) => { + tasks.push(async () => { + await execa('git', ['init'], { cwd: directory }) + await execa('git', ['remote', 'add', 'origin', repoUrl], { cwd: directory }) + }) + return builder + }, buildAsync: async () => { for (const task of tasks) { // eslint-disable-next-line no-await-in-loop From 5e6c013b7ede6fb7f3bc91c8628e14339df57e4f Mon Sep 17 00:00:00 2001 From: erezrokah Date: Wed, 17 Feb 2021 18:48:54 +0100 Subject: [PATCH 07/14] refactor: add comment explaining chosing first framework --- src/utils/init/frameworks.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/init/frameworks.js b/src/utils/init/frameworks.js index d8bfe6546de..53a7126f941 100644 --- a/src/utils/init/frameworks.js +++ b/src/utils/init/frameworks.js @@ -2,6 +2,7 @@ const { listFrameworks } = require('@netlify/framework-info') const getFrameworkInfo = async ({ siteRoot, nodeVersion }) => { const frameworks = await listFrameworks({ projectDir: siteRoot, nodeVersion }) + // several frameworks can be detected - first one has highest priority if (frameworks.length !== 0) { const [ { From d6cd8720f7c40749e4b57ed8927d5188ae34a95d Mon Sep 17 00:00:00 2001 From: erezrokah Date: Sun, 21 Feb 2021 13:59:40 +0100 Subject: [PATCH 08/14] fix: don't remove existing plugins --- src/utils/init/config-github.js | 21 +++-- src/utils/init/config-manual.js | 20 ++--- src/utils/init/plugins.js | 13 ++- src/utils/init/utils.js | 56 +++++++++---- tests/command.init.test.js | 136 +++++++++++++++++++++++++++----- 5 files changed, 184 insertions(+), 62 deletions(-) diff --git a/src/utils/init/config-github.js b/src/utils/init/config-github.js index e3f4115f5fc..62035d43368 100644 --- a/src/utils/init/config-github.js +++ b/src/utils/init/config-github.js @@ -3,7 +3,7 @@ const chalk = require('chalk') const ghauth = require('../gh-auth') -const { getBuildSettings, saveNetlifyToml, formatErrorMessage, createDeployKey, updateSite } = require('./utils') +const { getBuildSettings, saveNetlifyToml, formatErrorMessage, createDeployKey, setupSite } = require('./utils') const formatRepoAndOwner = ({ repoOwner, repoName }) => ({ name: chalk.magenta(repoName), @@ -185,8 +185,8 @@ module.exports = async function configGithub({ context, siteId, repoOwner, repoN const token = await getGitHubToken({ log, globalConfig }) - const { buildCmd, buildDir, functionsDir, plugins } = await getBuildSettings({ siteRoot, config, env, warn }) - await saveNetlifyToml({ siteRoot, config, buildCmd, buildDir, functionsDir, plugins, warn }) + const { buildCmd, buildDir, functionsDir, pluginsToInstall } = await getBuildSettings({ siteRoot, config, env, warn }) + await saveNetlifyToml({ siteRoot, config, buildCmd, buildDir, functionsDir, pluginsToInstall, warn }) const octokit = getGitHubClient({ token }) const [deployKey, githubRepo] = await Promise.all([ @@ -205,18 +205,15 @@ module.exports = async function configGithub({ context, siteId, repoOwner, repoN ...(buildCmd && { cmd: buildCmd }), } - await updateSite({ - siteId, - api, - failAndExit, - options: { repo, plugins: plugins.map((plugin) => ({ package: plugin })) }, - }) // calling updateSite with { repo } resets the functions dir so we need to sync it - const updatedSite = await updateSite({ - siteId, + const updatedSite = await setupSite({ api, failAndExit, - options: { build_settings: { functions_dir: functionsDir } }, + siteId, + repo, + functionsDir, + configPlugins: config.plugins, + pluginsToInstall, }) await addDeployHook({ deployHook: updatedSite.deploy_hook, octokit, repoOwner, repoName, failAndExit }) log() diff --git a/src/utils/init/config-manual.js b/src/utils/init/config-manual.js index 6cf49826e1f..86c387bd3ba 100644 --- a/src/utils/init/config-manual.js +++ b/src/utils/init/config-manual.js @@ -1,6 +1,6 @@ const inquirer = require('inquirer') -const { getBuildSettings, saveNetlifyToml, createDeployKey, updateSite } = require('./utils') +const { getBuildSettings, saveNetlifyToml, createDeployKey, setupSite } = require('./utils') const addDeployKey = async ({ log, exit, deployKey }) => { log('\nGive this Netlify SSH public key access to your repository:\n') @@ -58,8 +58,8 @@ module.exports = async function configManual({ context, siteId, repoData }) { cachedConfig: { env }, } = netlify - const { buildCmd, buildDir, functionsDir, plugins } = await getBuildSettings({ siteRoot, config, env, warn }) - await saveNetlifyToml({ siteRoot, config, buildCmd, buildDir, functionsDir, plugins, warn }) + const { buildCmd, buildDir, functionsDir, pluginsToInstall } = await getBuildSettings({ siteRoot, config, env, warn }) + await saveNetlifyToml({ siteRoot, config, buildCmd, buildDir, functionsDir, pluginsToInstall, warn }) const deployKey = await createDeployKey({ api, failAndExit }) await addDeployKey({ log, exit, deployKey }) @@ -75,18 +75,14 @@ module.exports = async function configManual({ context, siteId, repoData }) { ...(buildCmd && { cmd: buildCmd }), } - await updateSite({ - siteId, + const updatedSite = await setupSite({ api, failAndExit, - options: { repo, plugins: plugins.map((plugin) => ({ package: plugin })) }, - }) - // calling updateSite with { repo } resets the functions dir so we need to sync it - const updatedSite = await updateSite({ siteId, - api, - failAndExit, - options: { build_settings: { functions_dir: functionsDir } }, + repo, + functionsDir, + configPlugins: config.plugins, + pluginsToInstall, }) const deployHookAdded = await addDeployHook({ log, deployHook: updatedSite.deploy_hook }) if (!deployHookAdded) { diff --git a/src/utils/init/plugins.js b/src/utils/init/plugins.js index 477159f9ce2..36ca3152af6 100644 --- a/src/utils/init/plugins.js +++ b/src/utils/init/plugins.js @@ -22,4 +22,15 @@ const isPluginInstalled = (configPlugins, plugin) => const getRecommendPlugins = (frameworkPlugins, config) => frameworkPlugins.filter((plugin) => !isPluginInstalled(config.plugins, plugin)) -module.exports = { getPluginsList, getPluginInfo, getRecommendPlugins } +const getPluginsToInstall = ({ plugins, installSinglePlugin, recommendedPlugins }) => { + if (Array.isArray(plugins)) { + return plugins.map((plugin) => ({ package: plugin })) + } + + return installSinglePlugin === true ? [{ package: recommendedPlugins[0] }] : [] +} + +const getUIPlugins = (configPlugins) => + configPlugins.filter(({ origin }) => origin === 'ui').map(({ package }) => ({ package })) + +module.exports = { getPluginsList, getPluginInfo, getRecommendPlugins, getPluginsToInstall, getUIPlugins } diff --git a/src/utils/init/utils.js b/src/utils/init/utils.js index 56cc5b94587..6119808df6e 100644 --- a/src/utils/init/utils.js +++ b/src/utils/init/utils.js @@ -10,7 +10,7 @@ const { fileExistsAsync, writeFileAsync } = require('../../lib/fs') const { getFrameworkInfo } = require('./frameworks') const { detectNodeVersion } = require('./node-version') -const { getPluginsList, getPluginInfo, getRecommendPlugins } = require('./plugins') +const { getPluginsList, getPluginInfo, getRecommendPlugins, getPluginsToInstall, getUIPlugins } = require('./plugins') const normalizeDir = ({ siteRoot, dir, defaultValue }) => { if (dir === undefined) { @@ -101,14 +101,6 @@ const getPromptInputs = async ({ ] } -const getPluginsToInstall = ({ plugins, installSinglePlugin, recommendedPlugins }) => { - if (Array.isArray(plugins)) { - return plugins - } - - return installSinglePlugin === true ? [recommendedPlugins[0]] : [] -} - const getBuildSettings = async ({ siteRoot, config, env, warn }) => { const nodeVersion = await detectNodeVersion({ siteRoot, env, warn }) const { frameworkName, frameworkBuildCommand, frameworkBuildDir, frameworkPlugins = [] } = await getFrameworkInfo({ @@ -131,11 +123,20 @@ const getBuildSettings = async ({ siteRoot, config, env, warn }) => { frameworkName, }), ) - const pluginsToInstall = getPluginsToInstall({ plugins, installSinglePlugin, recommendedPlugins }) - return { buildCmd, buildDir, functionsDir, plugins: pluginsToInstall } + const pluginsToInstall = getPluginsToInstall({ + plugins, + installSinglePlugin, + recommendedPlugins, + }) + return { buildCmd, buildDir, functionsDir, pluginsToInstall } } -const getNetlifyToml = ({ command = '# no build command', publish = '.', functions = 'functions', plugins = [] }) => { +const getNetlifyToml = ({ + command = '# no build command', + publish = '.', + functions = 'functions', + pluginsToInstall = [], +}) => { const content = `# example netlify.toml [build] command = "${command}" @@ -158,14 +159,16 @@ const getNetlifyToml = ({ command = '# no build command', publish = '.', functio ## more info on configuring this file: https://www.netlify.com/docs/netlify-toml-reference/ ` - if (plugins.length === 0) { + if (pluginsToInstall.length === 0) { return content } - return `${content}${EOL}${plugins.map((plugin) => `[[plugins]]${EOL} package = "${plugin}"`).join(EOL)}` + return `${content}${EOL}${pluginsToInstall + .map(({ package }) => `[[plugins]]${EOL} package = "${package}"`) + .join(EOL)}` } -const saveNetlifyToml = async ({ siteRoot, config, buildCmd, buildDir, functionsDir, plugins, warn }) => { +const saveNetlifyToml = async ({ siteRoot, config, buildCmd, buildDir, functionsDir, pluginsToInstall, warn }) => { const tomlPath = path.join(siteRoot, 'netlify.toml') const exists = await fileExistsAsync(tomlPath) const cleanedConfig = cleanDeep(config) @@ -185,7 +188,7 @@ const saveNetlifyToml = async ({ siteRoot, config, buildCmd, buildDir, functions try { await writeFileAsync( tomlPath, - getNetlifyToml({ command: buildCmd, publish: buildDir, functions: functionsDir, plugins }), + getNetlifyToml({ command: buildCmd, publish: buildDir, functions: functionsDir, pluginsToInstall }), ) } catch (error) { warn(`Failed saving Netlify toml file: ${error.message}`) @@ -217,4 +220,23 @@ const updateSite = async ({ siteId, api, failAndExit, options }) => { } } -module.exports = { getBuildSettings, saveNetlifyToml, formatErrorMessage, createDeployKey, updateSite } +const setupSite = async ({ api, failAndExit, siteId, repo, functionsDir, configPlugins, pluginsToInstall }) => { + await updateSite({ + siteId, + api, + failAndExit, + // merge existing plugins with new ones + options: { repo, plugins: [...getUIPlugins(configPlugins), ...pluginsToInstall] }, + }) + // calling updateSite with { repo } resets the functions dir so we need to sync it + const updatedSite = await updateSite({ + siteId, + api, + failAndExit, + options: { build_settings: { functions_dir: functionsDir } }, + }) + + return updatedSite +} + +module.exports = { getBuildSettings, saveNetlifyToml, formatErrorMessage, createDeployKey, updateSite, setupSite } diff --git a/tests/command.init.test.js b/tests/command.init.test.js index 95a8f46cff2..591a1470f67 100644 --- a/tests/command.init.test.js +++ b/tests/command.init.test.js @@ -28,21 +28,15 @@ const CONFIRM = '\n' const DOWN = '\u001B[B' const answerWithValue = (value) => `${value}${CONFIRM}` -const assertSiteInit = async ( +const assetSiteRequests = ( t, - builder, requests, { command = 'custom-build-command', functions = 'custom-functions', publish = 'custom-publish', plugins = [] } = {}, ) => { - // assert netlify.toml was created with user inputs - const netlifyToml = toml.parse(await readFileAsync(`${builder.directory}/netlify.toml`, 'utf8')) - t.deepEqual(netlifyToml, { - build: { command, functions, publish }, - ...(plugins.length === 0 ? {} : { plugins }), - }) - // assert updateSite was called with user inputs - const siteUpdateRequests = requests.filter(({ path }) => path === '/api/v1/sites/site_id').map(({ body }) => body) + const siteUpdateRequests = requests + .filter(({ path, method }) => path === '/api/v1/sites/site_id' && method === 'PATCH') + .map(({ body }) => body) t.deepEqual(siteUpdateRequests, [ { plugins, @@ -63,6 +57,22 @@ const assertSiteInit = async ( ]) } +const assertSiteInit = async ( + t, + builder, + requests, + { command = 'custom-build-command', functions = 'custom-functions', publish = 'custom-publish', plugins = [] } = {}, +) => { + // assert netlify.toml was created with user inputs + const netlifyToml = toml.parse(await readFileAsync(`${builder.directory}/netlify.toml`, 'utf8')) + t.deepEqual(netlifyToml, { + build: { command, functions, publish }, + ...(plugins.length === 0 ? {} : { plugins }), + }) + + assetSiteRequests(t, requests, { command, functions, publish, plugins }) +} + test('netlify init existing site', async (t) => { const initQuestions = [ { @@ -94,18 +104,23 @@ test('netlify init existing site', async (t) => { { question: 'Configure the following webhook for your repository', answer: CONFIRM }, ] + const siteInfo = { + admin_url: 'https://app.netlify.com/sites/site-name/overview', + ssl_url: 'https://site-name.netlify.app/', + id: 'site_id', + name: 'site-name', + build_settings: { repo_url: 'https://github.com/owner/repo' }, + } const routes = [ + { + path: 'accounts', + response: [{ slug: 'test-account' }], + }, + { path: 'sites/site_id/service-instances', response: [] }, + { path: 'sites/site_id', response: siteInfo }, { path: 'sites', - response: [ - { - admin_url: 'https://app.netlify.com/sites/site-name/overview', - ssl_url: 'https://site-name.netlify.app/', - id: 'site_id', - name: 'site-name', - build_settings: { repo_url: 'https://github.com/owner/repo' }, - }, - ], + response: [siteInfo], }, { path: 'deploy_keys', method: 'post', response: { public_key: 'public_key' } }, { path: 'sites/site_id', method: 'patch', response: { deploy_hook: 'deploy_hook' } }, @@ -120,7 +135,8 @@ test('netlify init existing site', async (t) => { // --manual is used to avoid the config-github flow that uses GitHub API const childProcess = execa(cliPath, ['init', '--force', '--manual'], { cwd: builder.directory, - env: { NETLIFY_API_URL: apiUrl }, + // NETLIFY_SITE_ID and NETLIFY_AUTH_TOKEN are required for @netlify/config to retrieve site info + env: { NETLIFY_API_URL: apiUrl, NETLIFY_SITE_ID: 'site_id', NETLIFY_AUTH_TOKEN: 'fake-token' }, }) handleQuestions(childProcess, initQuestions) @@ -281,3 +297,83 @@ test('netlify init new Next.js site', async (t) => { }) }) }) + +test('netlify init existing Next.js site with existing plugins', async (t) => { + const initQuestions = [ + { + question: 'Create & configure a new site', + answer: CONFIRM, + }, + { + question: 'How do you want to link this folder to a site', + answer: CONFIRM, + }, + { + question: 'Your build command (hugo build/yarn run build/etc)', + answer: answerWithValue('custom-build-command'), + }, + { + question: 'Directory to deploy (blank for current dir)', + answer: answerWithValue('custom-publish'), + }, + { + question: 'Netlify functions folder', + answer: answerWithValue('custom-functions'), + }, + { + question: 'Install Next on Netlify plugin', + answer: CONFIRM, + }, + { question: 'Give this Netlify SSH public key access to your repository', answer: CONFIRM }, + { question: 'The SSH URL of the remote git repo', answer: CONFIRM }, + { question: 'Configure the following webhook for your repository', answer: CONFIRM }, + ] + + const siteInfo = { + admin_url: 'https://app.netlify.com/sites/site-name/overview', + ssl_url: 'https://site-name.netlify.app/', + id: 'site_id', + name: 'site-name', + build_settings: { repo_url: 'https://github.com/owner/repo' }, + plugins: [{ package: '@netlify/plugin-lighthouse' }], + } + const routes = [ + { + path: 'accounts', + response: [{ slug: 'test-account' }], + }, + { path: 'sites/site_id/service-instances', response: [] }, + { path: 'sites/site_id', response: siteInfo }, + { + path: 'sites', + response: [siteInfo], + }, + { path: 'deploy_keys', method: 'post', response: { public_key: 'public_key' } }, + { path: 'sites/site_id', method: 'patch', response: { deploy_hook: 'deploy_hook' } }, + ] + + await withSiteBuilder('new-site', async (builder) => { + builder + .withGit({ repoUrl: 'git@github.com:owner/repo.git' }) + .withPackageJson({ packageJson: { dependencies: { next: '^10.0.0' } } }) + + await builder.buildAsync() + await withMockApi(routes, async ({ apiUrl, requests }) => { + // --force is required since we we return an existing site in the `sites` route + // --manual is used to avoid the config-github flow that uses GitHub API + const childProcess = execa(cliPath, ['init', '--force', '--manual'], { + cwd: builder.directory, + // NETLIFY_SITE_ID and NETLIFY_AUTH_TOKEN are required for @netlify/config to retrieve site info + env: { NETLIFY_API_URL: apiUrl, NETLIFY_SITE_ID: 'site_id', NETLIFY_AUTH_TOKEN: 'fake-token' }, + }) + + handleQuestions(childProcess, initQuestions) + + await childProcess + + assetSiteRequests(t, requests, { + plugins: [{ package: '@netlify/plugin-lighthouse' }, { package: '@netlify/plugin-nextjs' }], + }) + }) + }) +}) From c7aa88bbe2e3bddb19f72b0e2a2c46a051d9180c Mon Sep 17 00:00:00 2001 From: erezrokah Date: Mon, 22 Feb 2021 15:18:04 +0100 Subject: [PATCH 09/14] fix: add icons --- src/utils/init/utils.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/utils/init/utils.js b/src/utils/init/utils.js index 6119808df6e..652ec8abb10 100644 --- a/src/utils/init/utils.js +++ b/src/utils/init/utils.js @@ -72,7 +72,7 @@ const getPromptInputs = async ({ const pluginsList = await getPluginsList() - const prefix = `Seems like this is a ${formatTitle(frameworkName)} site.${EOL} ` + const prefix = `Seems like this is a ${formatTitle(frameworkName)} site.${EOL}❇️ ` if (recommendedPlugins.length === 1) { const { name } = getPluginInfo(pluginsList, recommendedPlugins[0]) return [ @@ -80,7 +80,7 @@ const getPromptInputs = async ({ { type: 'confirm', name: 'installSinglePlugin', - message: `${prefix}Recommended Build Plugin: ${formatTitle(`${name} plugin`)}${EOL} Install ${name} plugin?`, + message: `${prefix}Recommended Build Plugin: ${formatTitle(`${name} plugin`)}${EOL}➡️ Install ${name} plugin?`, default: true, }, ] @@ -95,7 +95,7 @@ const getPromptInputs = async ({ message: `${prefix}Recommended Build Plugins: ${infos .map(({ name }) => `${name} plugin`) .map(formatTitle) - .join(', ')}${EOL} Which plugins to install?`, + .join(', ')}${EOL}➡️ Which plugins to install?`, choices: infos.map(({ name, package }) => ({ name: `${name} plugin`, value: package })), }, ] From 697cb19c08c50d57e8e9810e50f095b9c456bd91 Mon Sep 17 00:00:00 2001 From: erezrokah Date: Tue, 23 Feb 2021 19:06:31 +0100 Subject: [PATCH 10/14] fix: don't add plugins to netlify.toml --- src/utils/init/config-github.js | 2 +- src/utils/init/config-manual.js | 2 +- src/utils/init/utils.js | 19 +++---------------- tests/command.init.test.js | 1 - 4 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/utils/init/config-github.js b/src/utils/init/config-github.js index 62035d43368..a858ec32e79 100644 --- a/src/utils/init/config-github.js +++ b/src/utils/init/config-github.js @@ -186,7 +186,7 @@ module.exports = async function configGithub({ context, siteId, repoOwner, repoN const token = await getGitHubToken({ log, globalConfig }) const { buildCmd, buildDir, functionsDir, pluginsToInstall } = await getBuildSettings({ siteRoot, config, env, warn }) - await saveNetlifyToml({ siteRoot, config, buildCmd, buildDir, functionsDir, pluginsToInstall, warn }) + await saveNetlifyToml({ siteRoot, config, buildCmd, buildDir, functionsDir, warn }) const octokit = getGitHubClient({ token }) const [deployKey, githubRepo] = await Promise.all([ diff --git a/src/utils/init/config-manual.js b/src/utils/init/config-manual.js index 86c387bd3ba..93aea2918a7 100644 --- a/src/utils/init/config-manual.js +++ b/src/utils/init/config-manual.js @@ -59,7 +59,7 @@ module.exports = async function configManual({ context, siteId, repoData }) { } = netlify const { buildCmd, buildDir, functionsDir, pluginsToInstall } = await getBuildSettings({ siteRoot, config, env, warn }) - await saveNetlifyToml({ siteRoot, config, buildCmd, buildDir, functionsDir, pluginsToInstall, warn }) + await saveNetlifyToml({ siteRoot, config, buildCmd, buildDir, functionsDir, warn }) const deployKey = await createDeployKey({ api, failAndExit }) await addDeployKey({ log, exit, deployKey }) diff --git a/src/utils/init/utils.js b/src/utils/init/utils.js index 652ec8abb10..07ff6737b8e 100644 --- a/src/utils/init/utils.js +++ b/src/utils/init/utils.js @@ -135,9 +135,7 @@ const getNetlifyToml = ({ command = '# no build command', publish = '.', functions = 'functions', - pluginsToInstall = [], -}) => { - const content = `# example netlify.toml +}) => `# example netlify.toml [build] command = "${command}" functions = "${functions}" @@ -159,16 +157,8 @@ const getNetlifyToml = ({ ## more info on configuring this file: https://www.netlify.com/docs/netlify-toml-reference/ ` - if (pluginsToInstall.length === 0) { - return content - } - - return `${content}${EOL}${pluginsToInstall - .map(({ package }) => `[[plugins]]${EOL} package = "${package}"`) - .join(EOL)}` -} -const saveNetlifyToml = async ({ siteRoot, config, buildCmd, buildDir, functionsDir, pluginsToInstall, warn }) => { +const saveNetlifyToml = async ({ siteRoot, config, buildCmd, buildDir, functionsDir, warn }) => { const tomlPath = path.join(siteRoot, 'netlify.toml') const exists = await fileExistsAsync(tomlPath) const cleanedConfig = cleanDeep(config) @@ -186,10 +176,7 @@ const saveNetlifyToml = async ({ siteRoot, config, buildCmd, buildDir, functions ]) if (makeNetlifyTOML) { try { - await writeFileAsync( - tomlPath, - getNetlifyToml({ command: buildCmd, publish: buildDir, functions: functionsDir, pluginsToInstall }), - ) + await writeFileAsync(tomlPath, getNetlifyToml({ command: buildCmd, publish: buildDir, functions: functionsDir })) } catch (error) { warn(`Failed saving Netlify toml file: ${error.message}`) } diff --git a/tests/command.init.test.js b/tests/command.init.test.js index 591a1470f67..a998d1cbd9d 100644 --- a/tests/command.init.test.js +++ b/tests/command.init.test.js @@ -67,7 +67,6 @@ const assertSiteInit = async ( const netlifyToml = toml.parse(await readFileAsync(`${builder.directory}/netlify.toml`, 'utf8')) t.deepEqual(netlifyToml, { build: { command, functions, publish }, - ...(plugins.length === 0 ? {} : { plugins }), }) assetSiteRequests(t, requests, { command, functions, publish, plugins }) From 23ddd6793d9bc05b43c346072f55da50b4a0a867 Mon Sep 17 00:00:00 2001 From: erezrokah Date: Tue, 2 Mar 2021 12:04:39 +0100 Subject: [PATCH 11/14] fix: set defaut for multiple plugins list --- src/utils/init/utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/init/utils.js b/src/utils/init/utils.js index 07ff6737b8e..30806b7eb60 100644 --- a/src/utils/init/utils.js +++ b/src/utils/init/utils.js @@ -97,6 +97,7 @@ const getPromptInputs = async ({ .map(formatTitle) .join(', ')}${EOL}➡️ Which plugins to install?`, choices: infos.map(({ name, package }) => ({ name: `${name} plugin`, value: package })), + default: infos.map(({ package }) => package), }, ] } From 9fa0fd8e8908e5380b6fff263632d21787350d12 Mon Sep 17 00:00:00 2001 From: erezrokah Date: Tue, 9 Mar 2021 14:25:36 +0100 Subject: [PATCH 12/14] chore: sync deps --- package-lock.json | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7058c19195e..9f37fedd474 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,9 @@ "dependencies": { "@netlify/build": "^9.9.7", "@netlify/config": "^4.1.3", - "@netlify/framework-info": "^3.1.0", + "@netlify/framework-info": "^3.1.3", "@netlify/plugin-edge-handlers": "^1.11.5", - "@netlify/plugins-list": "^2.2.0", + "@netlify/plugins-list": "^2.4.0", "@netlify/traffic-mesh-agent": "^0.27.10", "@netlify/zip-it-and-ship-it": "^2.7.1", "@oclif/command": "^1.6.1", diff --git a/package.json b/package.json index 1b2a6bd00ba..6b2dc826567 100644 --- a/package.json +++ b/package.json @@ -76,9 +76,9 @@ "dependencies": { "@netlify/build": "^9.9.7", "@netlify/config": "^4.1.3", - "@netlify/framework-info": "^3.1.0", + "@netlify/framework-info": "^3.1.3", "@netlify/plugin-edge-handlers": "^1.11.5", - "@netlify/plugins-list": "^2.2.0", + "@netlify/plugins-list": "^2.4.0", "@netlify/traffic-mesh-agent": "^0.27.10", "@netlify/zip-it-and-ship-it": "^2.7.1", "@oclif/command": "^1.6.1", From 7232b1136c166e35824ab2ebc0d8b5da3cf7b4a4 Mon Sep 17 00:00:00 2001 From: erezrokah Date: Tue, 9 Mar 2021 15:00:59 +0100 Subject: [PATCH 13/14] fix: update messages and fix tests --- src/utils/command.js | 2 +- src/utils/init/utils.js | 8 +++++--- tests/command.init.test.js | 8 ++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/utils/command.js b/src/utils/command.js index e4129e3d0e1..04d58fa06ab 100644 --- a/src/utils/command.js +++ b/src/utils/command.js @@ -147,7 +147,7 @@ class BaseCommand extends Command { siteId: argv.siteId || (typeof argv.site === 'string' && argv.site) || state.get('siteId'), token, mode: 'cli', - host, + apiHost: host, pathPrefix, scheme, offline, diff --git a/src/utils/init/utils.js b/src/utils/init/utils.js index 30806b7eb60..ea52940033c 100644 --- a/src/utils/init/utils.js +++ b/src/utils/init/utils.js @@ -80,7 +80,9 @@ const getPromptInputs = async ({ { type: 'confirm', name: 'installSinglePlugin', - message: `${prefix}Recommended Build Plugin: ${formatTitle(`${name} plugin`)}${EOL}➡️ Install ${name} plugin?`, + message: `${prefix}We're going to install this Build Plugin: ${formatTitle( + `${name} plugin`, + )}${EOL}➡️ OK to install?`, default: true, }, ] @@ -92,10 +94,10 @@ const getPromptInputs = async ({ { type: 'checkbox', name: 'plugins', - message: `${prefix}Recommended Build Plugins: ${infos + message: `${prefix}We're going to install these plugins: ${infos .map(({ name }) => `${name} plugin`) .map(formatTitle) - .join(', ')}${EOL}➡️ Which plugins to install?`, + .join(', ')}${EOL}➡️ OK to install??`, choices: infos.map(({ name, package }) => ({ name: `${name} plugin`, value: package })), default: infos.map(({ package }) => package), }, diff --git a/tests/command.init.test.js b/tests/command.init.test.js index a998d1cbd9d..2bf82580bd6 100644 --- a/tests/command.init.test.js +++ b/tests/command.init.test.js @@ -130,7 +130,7 @@ test('netlify init existing site', async (t) => { await builder.buildAsync() await withMockApi(routes, async ({ apiUrl, requests }) => { - // --force is required since we we return an existing site in the `sites` route + // --force is required since we return an existing site in the `sites` route // --manual is used to avoid the config-github flow that uses GitHub API const childProcess = execa(cliPath, ['init', '--force', '--manual'], { cwd: builder.directory, @@ -240,7 +240,7 @@ test('netlify init new Next.js site', async (t) => { answer: answerWithValue('custom-functions'), }, { - question: 'Install Next on Netlify plugin', + question: 'OK to install', answer: CONFIRM, }, { @@ -320,7 +320,7 @@ test('netlify init existing Next.js site with existing plugins', async (t) => { answer: answerWithValue('custom-functions'), }, { - question: 'Install Next on Netlify plugin', + question: 'OK to install', answer: CONFIRM, }, { question: 'Give this Netlify SSH public key access to your repository', answer: CONFIRM }, @@ -358,7 +358,7 @@ test('netlify init existing Next.js site with existing plugins', async (t) => { await builder.buildAsync() await withMockApi(routes, async ({ apiUrl, requests }) => { - // --force is required since we we return an existing site in the `sites` route + // --force is required since we return an existing site in the `sites` route // --manual is used to avoid the config-github flow that uses GitHub API const childProcess = execa(cliPath, ['init', '--force', '--manual'], { cwd: builder.directory, From d69403bb9c0adda976480b9871a3ce4da49514a4 Mon Sep 17 00:00:00 2001 From: erezrokah Date: Wed, 10 Mar 2021 11:25:40 +0100 Subject: [PATCH 14/14] fix: revert command custom api host workaround --- src/utils/command.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/command.js b/src/utils/command.js index 04d58fa06ab..e4129e3d0e1 100644 --- a/src/utils/command.js +++ b/src/utils/command.js @@ -147,7 +147,7 @@ class BaseCommand extends Command { siteId: argv.siteId || (typeof argv.site === 'string' && argv.site) || state.get('siteId'), token, mode: 'cli', - apiHost: host, + host, pathPrefix, scheme, offline,