Skip to content

Commit

Permalink
chore: convert dev command to clack (#6366)
Browse files Browse the repository at this point in the history
* chore: wip convert dev command and stream logs into NetlifyLog - need to not call outro if framework

* chore: format live tunnel changes

* chore: capture all logs from @netlify/build

* chore: format styles

* chore: fix log breaking tests

* chore: log function detection with logger and remove outro

* chore: convert function registration output to clack

* chore: remove old comments from shell file

* chore: add expect-errors back

* chore: add expect-errors back

* chore: remove comment

* chore: handle formatters in console overrides

* chore: remove log

* chore: ensure tests pass where we assert what is printed for child command

* chore: ensure tests pass where we assert what is printed for child command

* chore: remove pnpm added by mistake

* In progress of fixing framework detection tests

* chore: fix snapshot tests from frameworks

* chore: fix lint issues

* chore: fix lint issues

* chore: fix lint issues

* chore: fix prettier errors

* chore: fix error not defined in addons logic

* chore: fix import order

* chore: fix empty line

* chore: fix unpublished

* chore: fix express error

---------

Co-authored-by: Lewis Thorley <lewis.thorley@netlify.com>
  • Loading branch information
lemusthelroy and lemusthelroy authored Mar 19, 2024
1 parent bf0560f commit 3f7e207
Show file tree
Hide file tree
Showing 25 changed files with 5,752 additions and 4,906 deletions.
9,902 changes: 5,326 additions & 4,576 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@
"@netlify/eslint-config-node": "7.0.0",
"@netlify/functions": "2.4.1",
"@sindresorhus/slugify": "2.2.1",
"@types/express": "^4.17.21",
"@types/fs-extra": "11.0.4",
"@types/inquirer": "9.0.7",
"@types/node": "20.9.0",
Expand Down
24 changes: 11 additions & 13 deletions scripts/postinstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import chalk from 'chalk'

import { createMainCommand } from '../dist/commands/index.js'
import { generateAutocompletion } from '../dist/lib/completion/index.js'
import { NetlifyLog, intro, outro } from '../dist/utils/styles/index.js'

const id = (message) => message

Expand All @@ -25,6 +26,7 @@ const format = (message, styles) => {
}

const postInstall = async () => {
intro('Running postinstall script')
// yarn plug and play seems to have an issue with reading an esm file by building up the cache.
// as yarn pnp analyzes everything inside the postinstall
// yarn pnp executes it out of a .yarn folder .yarn/unplugged/netlify-cli-file-fb026a3a6d/node_modules/netlify-cli/scripts/postinstall.js
Expand All @@ -34,26 +36,22 @@ const postInstall = async () => {
generateAutocompletion(program)
}

console.log('')
console.log(await format('Success! Netlify CLI has been installed!', ['greenBright', 'bold', 'underline']))
console.log('')
console.log('Your device is now configured to use Netlify CLI to deploy and manage your Netlify sites.')
console.log('')
console.log('Next steps:')
console.log('')
console.log(
NetlifyLog.success(await format('Success! Netlify CLI has been installed!', ['greenBright', 'bold', 'underline']))
NetlifyLog.info('Your device is now configured to use Netlify CLI to deploy and manage your Netlify sites.')
NetlifyLog.info('Next steps:')
NetlifyLog.info(
` ${await format('netlify init', [
'cyanBright',
'bold',
])} Connect or create a Netlify site from current directory`,
)
console.log(
NetlifyLog.info(
` ${await format('netlify deploy', ['cyanBright', 'bold'])} Deploy the latest changes to your Netlify site`,
)
console.log('')
console.log(`For more information on the CLI run ${await format('netlify help', ['cyanBright', 'bold'])}`)
console.log(`Or visit the docs at ${await format('https://cli.netlify.com', ['cyanBright', 'bold'])}`)
console.log('')
NetlifyLog.info(`For more information on the CLI run ${await format('netlify help', ['cyanBright', 'bold'])}`)
NetlifyLog.info(`Or visit the docs at ${await format('https://cli.netlify.com', ['cyanBright', 'bold'])}`)

outro({ message: 'Postinstall script finished' })
}

await postInstall()
32 changes: 12 additions & 20 deletions src/commands/dev/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,7 @@ import { OptionValues, Option } from 'commander'
import { BLOBS_CONTEXT_VARIABLE, encodeBlobsContext, getBlobsContext } from '../../lib/blobs/blobs.js'
import { promptEditorHelper } from '../../lib/edge-functions/editor-helper.js'
import { startFunctionsServer } from '../../lib/functions/server.js'
import { printBanner } from '../../utils/banner.js'
import {
BANG,
chalk,
log,
NETLIFYDEV,
NETLIFYDEVERR,
NETLIFYDEVLOG,
NETLIFYDEVWARN,
normalizeConfig,
} from '../../utils/command-helpers.js'
import { BANG, chalk, normalizeConfig } from '../../utils/command-helpers.js'
import detectServerSettings, { getConfigWithPlugins } from '../../utils/detect-server-settings.js'
import { getDotEnvVariables, getSiteInformation, injectEnvVariables, UNLINKED_SITE_MOCK_ID } from '../../utils/dev.js'
import { getEnvelopeEnv, normalizeContext } from '../../utils/env/index.js'
Expand All @@ -32,6 +22,7 @@ import BaseCommand from '../base-command.js'

import { createDevExecCommand } from './dev-exec.js'
import { type DevConfig } from './types.js'
import { NetlifyLog, intro, outro } from '../../utils/styles/index.js'

/**
*
Expand All @@ -51,13 +42,13 @@ const handleLiveTunnel = async ({ api, options, settings, site, state }) => {
const customSlug = typeof live === 'string' && live.length !== 0 ? live : undefined
const slug = getLiveTunnelSlug(state, customSlug)

let message = `${NETLIFYDEVWARN} Creating live URL with ID ${chalk.yellow(slug)}`
let message = `Creating live URL with ID ${chalk.yellow(slug)}`

if (!customSlug) {
message += ` (to generate a custom URL, use ${chalk.magenta('--live=<subdomain>')})`
}

log(message)
NetlifyLog.message(message)

const sessionUrl = await startLiveTunnel({
siteId: site.id,
Expand Down Expand Up @@ -89,7 +80,7 @@ const validateShortFlagArgs = (args: string) => {
}

export const dev = async (options: OptionValues, command: BaseCommand) => {
log(`${NETLIFYDEV}`)
!options.isChildCommand && intro('dev')
const { api, cachedConfig, config, repositoryRoot, site, siteInfo, state } = command.netlify
config.dev = { ...config.dev }
config.build = { ...config.build }
Expand Down Expand Up @@ -117,12 +108,12 @@ export const dev = async (options: OptionValues, command: BaseCommand) => {

if (!options.offline && siteInfo.use_envelope) {
env = await getEnvelopeEnv({ api, context: options.context, env, siteInfo })
log(`${NETLIFYDEVLOG} Injecting environment variable values for ${chalk.yellow('all scopes')}`)
NetlifyLog.info(`Injecting environment variable values for ${chalk.yellow('all scopes')}`)
}

env = await getDotEnvVariables({ devConfig, env, site })
injectEnvVariables(env)
await promptEditorHelper({ chalk, config, log, NETLIFYDEVLOG, repositoryRoot, state })
await promptEditorHelper({ config, repositoryRoot, state })

const { accountId, addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({
// inherited from base command --offline
Expand All @@ -139,15 +130,15 @@ export const dev = async (options: OptionValues, command: BaseCommand) => {

if (process.env.NETLIFY_INCLUDE_DEV_SERVER_PLUGIN) {
if (options.debug) {
log(`${NETLIFYDEVLOG} Including dev server plugin: ${process.env.NETLIFY_INCLUDE_DEV_SERVER_PLUGIN}`)
NetlifyLog.info(`Including dev server plugin: ${process.env.NETLIFY_INCLUDE_DEV_SERVER_PLUGIN}`)
}
settings.plugins = [...(settings.plugins || []), process.env.NETLIFY_INCLUDE_DEV_SERVER_PLUGIN]
}

cachedConfig.config = getConfigWithPlugins(cachedConfig.config, settings)
} catch (error_) {
if (error_ && typeof error_ === 'object' && 'message' in error_) {
log(NETLIFYDEVERR, error_.message)
NetlifyLog.error(error_.message)
}
process.exit(1)
}
Expand All @@ -160,7 +151,7 @@ export const dev = async (options: OptionValues, command: BaseCommand) => {
process.env.URL = url
process.env.DEPLOY_URL = url

log(`${NETLIFYDEVWARN} Setting up local development server`)
NetlifyLog.info('Setting up local development server')

const { configMutations, configPath: configPathOverride } = await runDevTimeline({
command,
Expand Down Expand Up @@ -240,7 +231,8 @@ export const dev = async (options: OptionValues, command: BaseCommand) => {
await openBrowser({ url, silentBrowserNoneError: true })
}

printBanner({ url })
// This is a long running process, so we don't want to exit with an outro()
NetlifyLog.success(`🧑‍💻 Server now ready on ${chalk.cyan(url)}`)
}

export const createDevCommand = (program: BaseCommand) => {
Expand Down
2 changes: 2 additions & 0 deletions src/commands/functions/functions-invoke.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import fetch from 'node-fetch'
import { NETLIFYDEVWARN, chalk, error, exit } from '../../utils/command-helpers.js'
import { BACKGROUND, CLOCKWORK_USERAGENT, getFunctions } from '../../utils/functions/index.js'
import BaseCommand from '../base-command.js'
import { NetlifyLog } from '../../utils/styles/index.js'

const require = createRequire(import.meta.url)

Expand Down Expand Up @@ -146,6 +147,7 @@ const getFunctionToTrigger = function (options, argumentName) {
}

export const functionsInvoke = async (nameArgument: string, options: OptionValues, command: BaseCommand) => {
NetlifyLog.message('Invoking function')
const { config, relConfigFilePath } = command.netlify

const functionsDir = options.functions || (config.dev && config.dev.functions) || config.functionsDirectory
Expand Down
52 changes: 52 additions & 0 deletions src/commands/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { createStatusCommand } from './status/index.js'
import { createSwitchCommand } from './switch/index.js'
import { createUnlinkCommand } from './unlink/index.js'
import { createWatchCommand } from './watch/index.js'
import { NetlifyLog } from '../utils/styles/index.js'

const SUGGESTION_TIMEOUT = 1e4

Expand Down Expand Up @@ -179,12 +180,63 @@ const mainCommand = async function (options, command) {
await execa(process.argv[0], [process.argv[1], suggestion], { stdio: 'inherit' })
}

const combineConsoleMessages = (message?: any, optionalParams?: any[]) => {
if (!message) return ''

if (optionalParams && optionalParams.length > 0) {
// Replace all %s, %d, %i, %f, %o with the next optional param
let formattedMessage = message.replace(/%[sdifo]/g, (match: string) => {
if (optionalParams.length > 0) {
const nextParam = optionalParams.shift()
return typeof nextParam !== 'undefined' ? String(nextParam) : match
}
return match
})
// Append remaining optional params if any
if (optionalParams.length > 0) {
formattedMessage += ' ' + optionalParams.join(' ')
}
return formattedMessage
}

if (typeof message === 'number' || typeof message === 'boolean') {
return message.toString()
}

// if message is an object, array or function, we need to use util.inspect
if (typeof message === 'object' || typeof message === 'function') {
return message.toString()
}

return message
}

const transportLogsToNetlifyLog = () => {
// Some tests failed when child processes invoked console.log and the test would
// assert that the output from the terminal contained the expected output.
if (process.env.VITEST) {
return
}
console.log = (message?: any, ...optionalParams: any[]) =>
message && NetlifyLog.info(combineConsoleMessages(message, optionalParams))
console.warn = (message?: any, ...optionalParams: any[]) =>
message && NetlifyLog.warn(combineConsoleMessages(message, optionalParams))
console.error = (message?: any, ...optionalParams: any[]) =>
message && NetlifyLog.error(combineConsoleMessages(message, optionalParams))
console.info = (message?: any, ...optionalParams: any[]) =>
message && NetlifyLog.info(combineConsoleMessages(message, optionalParams))
}
/**
* Creates the `netlify-cli` command
* Promise is needed as the envinfo is a promise
* @returns {import('./base-command.js').default}
*/
export const createMainCommand = () => {
// This below transport of logging has been added to ensure that
// logs are displated using the NetlifyLog style as some packages
// we use are using console.log to display logs
transportLogsToNetlifyLog()

const program = new BaseCommand('netlify')
// register all the commands
createAddonsCommand(program)
Expand Down
2 changes: 1 addition & 1 deletion src/commands/serve/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export const serve = async (options: OptionValues, command: BaseCommand) => {

env = await getDotEnvVariables({ devConfig, env, site })
injectEnvVariables(env)
await promptEditorHelper({ chalk, config, log, NETLIFYDEVLOG, repositoryRoot, state })
await promptEditorHelper({ config, repositoryRoot, state })

const { accountId, addonsUrls, capabilities, siteUrl, timeouts } = await getSiteInformation({
// inherited from base command --offline
Expand Down
19 changes: 7 additions & 12 deletions src/lib/edge-functions/editor-helper.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { env } from 'process'

import inquirer from 'inquirer'

import { runRecipe } from '../../commands/recipes/recipes.js'
import { NetlifyLog, confirm } from '../../utils/styles/index.js'
import { NETLIFYDEVLOG, chalk } from '../../utils/command-helpers.js'

const STATE_PROMPT_PROPERTY = 'promptVSCodeSettings'

// @ts-expect-error TS(7031) FIXME: Binding element 'NETLIFYDEVLOG' implicitly has an ... Remove this comment to see the full error message
export const promptEditorHelper = async ({ NETLIFYDEVLOG, chalk, config, log, repositoryRoot, state }) => {
export const promptEditorHelper = async ({ config, repositoryRoot, state }) => {
// This prevents tests from hanging when running them inside the VS Code
// terminal, as otherwise we'll show the prompt and wait for a response.
if (env.NODE_ENV === 'test') return
Expand All @@ -23,15 +23,10 @@ export const promptEditorHelper = async ({ NETLIFYDEVLOG, chalk, config, log, re
state.set(STATE_PROMPT_PROPERTY, true)

const message = 'Would you like to configure VS Code to use Edge Functions?'
const { confirm } = await inquirer.prompt({
type: 'confirm',
name: 'confirm',
message,
default: true,
})

if (!confirm) {
log(
const confirmVSCodeConfiguration = await confirm({ message, initialValue: true })

if (!confirmVSCodeConfiguration) {
NetlifyLog.info(
`${NETLIFYDEVLOG} You can start this configuration manually by running ${chalk.magenta.bold(
'netlify recipes vscode',
)}.`,
Expand Down
11 changes: 6 additions & 5 deletions src/lib/edge-functions/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { MultiMap } from '../../utils/multimap.js'
import { getPathInProject } from '../settings.js'

import { INTERNAL_EDGE_FUNCTIONS_FOLDER } from './consts.js'
import { NetlifyLog } from '../../utils/styles/index.js'

// TODO: Replace with a proper type for the entire config object.
export interface Config {
Expand Down Expand Up @@ -385,7 +386,7 @@ export class EdgeFunctionsRegistry {
warnings.length === 0 ? '' : ` with warnings:\n${warnings.map((warning) => ` - ${warning}`).join('\n')}`

if (event === 'buildError') {
log(`${NETLIFYDEVERR} ${chalk.red('Failed to load')} ${subject}: ${buildError}`)
NetlifyLog.error(`${NETLIFYDEVERR} ${chalk.red('Failed to load')} ${subject}: ${buildError}`, { exit: true })

return
}
Expand All @@ -394,7 +395,7 @@ export class EdgeFunctionsRegistry {
const icon = warningsText ? NETLIFYDEVWARN : NETLIFYDEVLOG
const color = warningsText ? chalk.yellow : chalk.green

log(`${icon} ${color('Loaded')} ${subject}${warningsText}`)
NetlifyLog.info(`${icon} ${color('Loaded')} ${subject}${warningsText}`)

return
}
Expand All @@ -403,19 +404,19 @@ export class EdgeFunctionsRegistry {
const icon = warningsText ? NETLIFYDEVWARN : NETLIFYDEVLOG
const color = warningsText ? chalk.yellow : chalk.green

log(`${icon} ${color('Reloaded')} ${subject}${warningsText}`)
NetlifyLog.info(`${icon} ${color('Reloaded')} ${subject}${warningsText}`)

return
}

if (event === 'reloading') {
log(`${NETLIFYDEVLOG} ${chalk.magenta('Reloading')} ${subject}...`)
NetlifyLog.info(`${NETLIFYDEVLOG} ${chalk.magenta('Reloading')} ${subject}...`)

return
}

if (event === 'removed') {
log(`${NETLIFYDEVLOG} ${chalk.magenta('Removed')} ${subject}`)
NetlifyLog.info(`${NETLIFYDEVLOG} ${chalk.magenta('Removed')} ${subject}`)
}
}

Expand Down
Loading

0 comments on commit 3f7e207

Please sign in to comment.