Skip to content

fix: polish and improve deploy command output #7250

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
May 5, 2025
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
7 changes: 7 additions & 0 deletions bin/run.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#!/usr/bin/env node
import { argv } from 'process'
import EventEmitter from 'events'

import { maybeEnableCompileCache } from '../dist/utils/nodejs-compile-cache.js'

Expand All @@ -20,6 +21,12 @@ const UPDATE_BOXEN_OPTIONS = {
}

const main = async () => {
// TODO(serhalp) Investigate and fix this at the root instead.
// This avoids `MaxListenersExceededWarning` warnings during Edge Functions bundling,
// from somewhere around here:
// https://github.com/netlify/build/blob/ca0bb348b3d7437d2418526f49b803a3db4e5ac2/packages/build/src/steps/run_step.ts.
EventEmitter.defaultMaxListeners = 25

const { default: chalk } = await import('chalk')
const { default: updateNotifier } = await import('update-notifier')
const { default: terminalLink } = await import('terminal-link')
Expand Down
97 changes: 52 additions & 45 deletions src/commands/deploy/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { BACKGROUND_FUNCTIONS_WARNING } from '../../lib/log.js'
import { type Spinner, startSpinner, stopSpinner } from '../../lib/spinner.js'
import { detectFrameworkSettings, getDefaultConfig } from '../../utils/build-info.js'
import {
NETLIFY_CYAN_HEX,
NETLIFYDEVERR,
NETLIFYDEVLOG,
chalk,
Expand All @@ -48,6 +49,8 @@ import { sitesCreate } from '../sites/sites-create.js'
import type { $TSFixMe } from '../types.js'
import { SiteInfo } from '../../utils/types.js'
import type { DeployOptionValues } from './option_values.js'
import boxen from 'boxen'
import terminalLink from 'terminal-link'

const triggerDeploy = async ({
api,
Expand Down Expand Up @@ -280,7 +283,6 @@ const prepareProductionDeploy = async ({ api, siteData }) => {
await api.unlockDeploy({ deploy_id: siteData.published_deploy.id })
log(`\n${NETLIFYDEVLOG} "Auto publishing" has been enabled for production context\n`)
}
log('Deploying to main site URL...')
}

// @ts-expect-error TS(7006) FIXME: Parameter 'actual' implicitly has an 'any' type.
Expand Down Expand Up @@ -345,7 +347,7 @@ const deployProgressCb = function () {
return
case 'stop':
default: {
stopSpinner({ spinner: spinnersByType[event.type], text: event.msg })
spinnersByType[event.type].success(event.msg)
delete spinnersByType[event.type]
}
}
Expand Down Expand Up @@ -380,7 +382,9 @@ const uploadDeployBlobs = async ({
const blobsToken = token || undefined
const { success } = await runCoreSteps(['blobs_upload'], {
...options,
quiet: silent,
// We log our own progress so we don't want this as well. Plus, this logs much of the same
// information as the build that (likely) came before this as part of the deploy build.
quiet: options.debug ?? true,
// @ts-expect-error(serhalp) -- Untyped in `@netlify/build`
cachedConfig,
packagePath,
Expand Down Expand Up @@ -458,8 +462,6 @@ const runDeploy = async ({
try {
if (deployToProduction) {
await prepareProductionDeploy({ siteData, api })
} else {
log('Deploying to draft URL...')
}

const draft = !deployToProduction && !alias
Expand Down Expand Up @@ -590,13 +592,7 @@ const handleBuild = async ({
return { newConfig, configMutations }
}

/**
*
* @param {*} options Bundling options
* @returns
*/
// @ts-expect-error TS(7006) FIXME: Parameter 'options' implicitly has an 'any' type.
const bundleEdgeFunctions = async (options, command: BaseCommand) => {
const bundleEdgeFunctions = async (options: DeployOptionValues, command: BaseCommand): Promise<void> => {
const argv = process.argv.slice(2)
const statusCb =
options.silent || argv.includes('--json') || argv.includes('--silent') ? () => {} : deployProgressCb()
Expand All @@ -612,6 +608,10 @@ const bundleEdgeFunctions = async (options, command: BaseCommand) => {
packagePath: command.workspacePackage,
buffer: true,
featureFlags: edgeFunctionsFeatureFlags,
// We log our own progress so we don't want this as well. Plus, this logs much of the same
// information as the build that (likely) came before this as part of the deploy build.
quiet: options.debug ?? true,
// @ts-expect-error FIXME(serhalp): This is missing from the `runCoreSteps` type in @netlify/build
edgeFunctionsBootstrapURL: await getBootstrapURL(),
})

Expand Down Expand Up @@ -645,32 +645,24 @@ interface JsonData {

const printResults = ({
deployToProduction,
isIntegrationDeploy,
json,
results,
runBuildCommand,
}: {
deployToProduction: boolean
isIntegrationDeploy: boolean
json: boolean
results: Awaited<ReturnType<typeof prepAndRunDeploy>>
runBuildCommand: boolean
}): void => {
const msgData: Record<string, string> = {
'Build logs': results.logsUrl,
'Function logs': results.functionLogsUrl,
'Edge function Logs': results.edgeFunctionLogsUrl,
}

if (deployToProduction) {
msgData['Unique deploy URL'] = results.deployUrl
msgData['Website URL'] = results.siteUrl
} else {
msgData['Website draft URL'] = results.deployUrl
'Build logs': terminalLink(results.logsUrl, results.logsUrl),
'Function logs': terminalLink(results.functionLogsUrl, results.functionLogsUrl),
'Edge function Logs': terminalLink(results.edgeFunctionLogsUrl, results.edgeFunctionLogsUrl),
}

// Spacer
log()
log('')
// Note: this is leakily mimicking the @netlify/build heading style
log(chalk.cyanBright.bold(`🚀 Deploy complete\n${'─'.repeat(64)}`))

// Json response for piping commands
if (json) {
Expand All @@ -690,40 +682,52 @@ const printResults = ({
logJson(jsonData)
exit(0)
} else {
const message = deployToProduction
? `Deployed to production URL: ${terminalLink(results.siteUrl, results.siteUrl)}\n
Unique deploy URL: ${terminalLink(results.deployUrl, results.deployUrl)}`
: `Deployed draft to ${terminalLink(results.deployUrl, results.deployUrl)}`

log(
boxen(message, {
padding: 1,
margin: 1,
textAlignment: 'center',
borderStyle: 'round',
borderColor: NETLIFY_CYAN_HEX,
// This is an intentional half-width space to work around a unicode padding math bug in boxen
// eslint-disable-next-line no-irregular-whitespace
title: `⬥ ${deployToProduction ? 'Production deploy' : 'Draft deploy'} is live ⬥ `,
titleAlignment: 'center',
}),
)

log(prettyjson.render(msgData))

if (!deployToProduction) {
log()
log('If everything looks good on your draft URL, deploy it to your main site URL with the --prod flag.')
log(
chalk.cyanBright.bold(
`netlify ${isIntegrationDeploy ? 'integration:' : ''}deploy${runBuildCommand ? ' --build' : ''} --prod`,
),
)
log('If everything looks good on your draft URL, deploy it to your main site URL with the --prod flag:')
log(chalk.cyanBright.bold(`netlify deploy${runBuildCommand ? '' : '--no-build'} --prod`))
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this is unrelated, just a little thing we missed in #7195

log()
}
}
}

const prepAndRunDeploy = async ({
// @ts-expect-error TS(7031) FIXME: Binding element 'api' implicitly has an 'any' type... Remove this comment to see the full error message
api,
// @ts-expect-error TS(7031) FIXME: Binding element 'command' implicitly has an 'any' ... Remove this comment to see the full error message
command,
// @ts-expect-error TS(7031) FIXME: Binding element 'config' implicitly has an 'any' t... Remove this comment to see the full error message
config,
// @ts-expect-error TS(7031) FIXME: Binding element 'deployToProduction' implicitly ha... Remove this comment to see the full error message
deployToProduction,
// @ts-expect-error TS(7031) FIXME: Binding element 'options' implicitly has an 'any' ... Remove this comment to see the full error message
options,
// @ts-expect-error TS(7031) FIXME: Binding element 'site' implicitly has an 'any' typ... Remove this comment to see the full error message
site,
// @ts-expect-error TS(7031) FIXME: Binding element 'siteData' implicitly has an 'any'... Remove this comment to see the full error message
siteData,
// @ts-expect-error TS(7031) FIXME: Binding element 'siteId' implicitly has an 'any' t... Remove this comment to see the full error message
siteId,
// @ts-expect-error TS(7031) FIXME: Binding element 'workingDir' implicitly has an 'an... Remove this comment to see the full error message
workingDir,
}: {
options: DeployOptionValues
command: BaseCommand
workingDir: string
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- FIXME(serhalp)
[key: string]: any
}) => {
const alias = options.alias || options.branch
// if a context is passed besides dev, we need to pull env vars from that specific context
Expand All @@ -747,13 +751,19 @@ const prepAndRunDeploy = async ({
await bundleEdgeFunctions(options, command)
}

log('')
// Note: this is leakily mimicking the @netlify/build heading style
log(chalk.cyanBright.bold(`Deploying to Netlify\n${'─'.repeat(64)}`))

log('')
log(
prettyjson.render({
'Deploy path': deployFolder,
'Functions path': functionsFolder,
'Configuration path': configPath,
}),
)
log()

const { functionsFolderStat } = await validateFolders({
deployFolder,
Expand Down Expand Up @@ -782,7 +792,7 @@ const prepAndRunDeploy = async ({
command,
config,
deployFolder,
deployTimeout: options.timeout * SEC_TO_MILLISEC || DEFAULT_DEPLOY_TIMEOUT,
deployTimeout: options.timeout ? options.timeout * SEC_TO_MILLISEC : DEFAULT_DEPLOY_TIMEOUT,
deployToProduction,
functionsConfig,
// pass undefined functionsFolder if doesn't exist
Expand Down Expand Up @@ -893,11 +903,8 @@ export const deploy = async (options: DeployOptionValues, command: BaseCommand)
deployToProduction,
})
}
const isIntegrationDeploy = command.name() === 'integration:deploy'
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

dead code, integration:deploy was removed in December


printResults({
runBuildCommand: options.build,
isIntegrationDeploy,
json: options.json,
results,
deployToProduction,
Expand Down
4 changes: 2 additions & 2 deletions src/commands/sites/sites-list.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { OptionValues } from 'commander'

import { listSites } from '../../lib/api.js'
import { startSpinner, stopSpinner } from '../../lib/spinner.js'
import { startSpinner } from '../../lib/spinner.js'
import { chalk, log, logJson } from '../../utils/command-helpers.js'
import { SiteInfo } from '../../utils/types.js'
import BaseCommand from '../base-command.js'
Expand All @@ -16,7 +16,7 @@ export const sitesList = async (options: OptionValues, command: BaseCommand) =>

const sites = await listSites({ api, options: { filter: 'all' } })
if (spinner) {
stopSpinner({ spinner })
spinner.success()
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

oops, sorry, another unrelated change — when I was fixing the deploy spinner end state I searched for others with the same pattern and found this one

}

if (sites && sites.length !== 0) {
Expand Down
Loading