Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,155 changes: 1,120 additions & 35 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.3",
"@netlify/plugin-edge-handlers": "^1.11.5",
"@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",
Expand Down Expand Up @@ -143,6 +144,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",
Expand Down Expand Up @@ -195,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": {
Expand Down
15 changes: 9 additions & 6 deletions src/utils/init/config-github.js
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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 } = await getBuildSettings({ siteRoot, config })
const { buildCmd, buildDir, functionsDir, pluginsToInstall } = await getBuildSettings({ siteRoot, config, env, warn })
await saveNetlifyToml({ siteRoot, config, buildCmd, buildDir, functionsDir, warn })

const octokit = getGitHubClient({ token })
Expand All @@ -204,13 +205,15 @@ module.exports = async function configGithub({ context, siteId, repoOwner, repoN
...(buildCmd && { cmd: buildCmd }),
}

await updateSite({ siteId, api, failAndExit, options: { repo } })
// 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()
Expand Down
16 changes: 9 additions & 7 deletions src/utils/init/config-manual.js
Original file line number Diff line number Diff line change
@@ -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')
Expand Down Expand Up @@ -55,9 +55,10 @@ module.exports = async function configManual({ context, siteId, repoData }) {
api,
config,
site: { root: siteRoot },
cachedConfig: { env },
} = netlify

const { buildCmd, buildDir, functionsDir } = await getBuildSettings({ siteRoot, config })
const { buildCmd, buildDir, functionsDir, pluginsToInstall } = await getBuildSettings({ siteRoot, config, env, warn })
await saveNetlifyToml({ siteRoot, config, buildCmd, buildDir, functionsDir, warn })

const deployKey = await createDeployKey({ api, failAndExit })
Expand All @@ -74,13 +75,14 @@ module.exports = async function configManual({ context, siteId, repoData }) {
...(buildCmd && { cmd: buildCmd }),
}

await updateSite({ siteId, api, failAndExit, options: { repo } })
// 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,
})
const deployHookAdded = await addDeployHook({ log, deployHook: updatedSite.deploy_hook })
if (!deployHookAdded) {
Expand Down
24 changes: 24 additions & 0 deletions src/utils/init/frameworks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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 [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we only considering the first element of the array on purpose (i.e. the framework-info lib is only returning one framework for the time being)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might detect a few frameworks - the first one has higher priority based on the order in https://github.com/netlify/framework-info/blob/32c58bb8de2f86874a61bdc7abab059c4c638f6c/src/frameworks/main.js#L4.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't mind having a comment here just highlighting that (we pick the first as its the highest priority). Not critical though, happy to move forward if you feel like it doesn't make sense @erezrokah 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. I'll add it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in e1a410e

{
name,
build: { directory, commands },
plugins,
},
] = frameworks
return {
frameworkName: name,
frameworkBuildCommand: commands[0],
frameworkBuildDir: directory,
frameworkPlugins: plugins,
}
}
return {}
}

module.exports = { getFrameworkInfo }
32 changes: 32 additions & 0 deletions src/utils/init/node-version.js
Original file line number Diff line number Diff line change
@@ -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 }
36 changes: 36 additions & 0 deletions src/utils/init/plugins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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))

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 }
127 changes: 105 additions & 22 deletions src/utils/init/utils.js
Original file line number Diff line number Diff line change
@@ -1,13 +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 inquirer = require('inquirer')
const isEmpty = require('lodash/isEmpty')

const { fileExistsAsync, writeFileAsync } = require('../../lib/fs')

const { getFrameworkInfo } = require('./frameworks')
const { detectNodeVersion } = require('./node-version')
const { getPluginsList, getPluginInfo, getRecommendPlugins, getPluginsToInstall, getUIPlugins } = require('./plugins')

const normalizeDir = ({ siteRoot, dir, defaultValue }) => {
if (dir === undefined) {
return defaultValue
Expand All @@ -17,21 +21,8 @@ const normalizeDir = ({ siteRoot, dir, defaultValue }) => {
return relativeDir || defaultValue
}

const getFrameworkDefaults = async ({ siteRoot }) => {
const frameworks = await listFrameworks({ projectDir: siteRoot })
if (frameworks.length !== 0) {
const [
{
build: { directory, commands },
},
] = frameworks
return { frameworkBuildCommand: commands[0], frameworkBuildDir: directory }
}
return {}
}

const getDefaultSettings = async ({ siteRoot, config }) => {
const { frameworkBuildCommand, frameworkBuildDir } = await getFrameworkDefaults({ siteRoot })
const getDefaultSettings = ({ siteRoot, config, frameworkPlugins, frameworkBuildCommand, frameworkBuildDir }) => {
const recommendedPlugins = getRecommendPlugins(frameworkPlugins, config)
const {
command: defaultBuildCmd = frameworkBuildCommand,
publish: defaultBuildDir = frameworkBuildDir,
Expand All @@ -42,12 +33,18 @@ 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 = async ({
defaultBuildCmd,
defaultBuildDir,
defaultFunctionsDir,
recommendedPlugins,
frameworkName,
}) => {
const inputs = [
{
type: 'input',
name: 'buildCmd',
Expand All @@ -67,9 +64,74 @@ const getBuildSettings = async ({ siteRoot, config }) => {
message: 'Netlify functions folder:',
default: defaultFunctionsDir,
},
])
]

if (recommendedPlugins.length === 0) {
return inputs
}

return { buildCmd, buildDir, functionsDir }
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}We're going to install this Build Plugin: ${formatTitle(
`${name} plugin`,
)}${EOL}➡️ OK to install?`,
default: true,
},
]
}

const infos = recommendedPlugins.map((packageName) => getPluginInfo(pluginsList, packageName))
return [
...inputs,
{
type: 'checkbox',
name: 'plugins',
message: `${prefix}We're going to install these plugins: ${infos
.map(({ name }) => `${name} plugin`)
.map(formatTitle)
.join(', ')}${EOL}➡️ OK to install??`,
choices: infos.map(({ name, package }) => ({ name: `${name} plugin`, value: package })),
default: infos.map(({ package }) => package),
},
]
}

const getBuildSettings = async ({ siteRoot, config, env, warn }) => {
const nodeVersion = await detectNodeVersion({ siteRoot, env, warn })
const { frameworkName, frameworkBuildCommand, frameworkBuildDir, frameworkPlugins = [] } = await getFrameworkInfo({
siteRoot,
nodeVersion,
})
const { defaultBuildCmd, defaultBuildDir, defaultFunctionsDir, recommendedPlugins } = await getDefaultSettings({
siteRoot,
config,
frameworkBuildCommand,
frameworkBuildDir,
frameworkPlugins,
})
const { buildCmd, buildDir, functionsDir, plugins, installSinglePlugin } = await inquirer.prompt(
await getPromptInputs({
defaultBuildCmd,
defaultBuildDir,
defaultFunctionsDir,
recommendedPlugins,
frameworkName,
}),
)
const pluginsToInstall = getPluginsToInstall({
plugins,
installSinglePlugin,
recommendedPlugins,
})
return { buildCmd, buildDir, functionsDir, pluginsToInstall }
}

const getNetlifyToml = ({
Expand Down Expand Up @@ -126,6 +188,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()
Expand All @@ -146,4 +210,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 }
Loading