Skip to content

Commit 795a451

Browse files
authored
fix: polish and improve deploy command output (#7250)
* refactor: remove dead integration:deploy code * fix: show checkmark on spinner success This regressed recently, oops. * fix: work around `MaxListenersExceededWarning` on build/deploy * fix(deploy): suppress redundant blobs upload output * fix(deploy): remove redundant output * refactor: add some missing `prepAndRunDeploy` types * fix(deploy): add spacing between output sections * fix(deploy): polish final success output Move deploy URL(s) to its own primary section in a callout box - It's the primary call-to-action, so it should be the primary focus - It's a little more celebratory/satisfying - I moved the "draft" vs. "production" mention to this box - Use OSC 8 "terminal links" when supported - Use Netlify cyan and distinctive diamond * fix(deploy): remove leftover reference to `--build` See #7195 * fix(deploy): polish final deploy output a bit more * fix(deploy): suppress redundant EF bundling output * refactor: use shared Netlify cyan color
1 parent 758fd88 commit 795a451

File tree

3 files changed

+61
-47
lines changed

3 files changed

+61
-47
lines changed

bin/run.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#!/usr/bin/env node
22
import { argv } from 'process'
3+
import EventEmitter from 'events'
34

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

@@ -20,6 +21,12 @@ const UPDATE_BOXEN_OPTIONS = {
2021
}
2122

2223
const main = async () => {
24+
// TODO(serhalp) Investigate and fix this at the root instead.
25+
// This avoids `MaxListenersExceededWarning` warnings during Edge Functions bundling,
26+
// from somewhere around here:
27+
// https://github.com/netlify/build/blob/ca0bb348b3d7437d2418526f49b803a3db4e5ac2/packages/build/src/steps/run_step.ts.
28+
EventEmitter.defaultMaxListeners = 25
29+
2330
const { default: chalk } = await import('chalk')
2431
const { default: updateNotifier } = await import('update-notifier')
2532
const { default: terminalLink } = await import('terminal-link')

src/commands/deploy/deploy.ts

Lines changed: 52 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { BACKGROUND_FUNCTIONS_WARNING } from '../../lib/log.js'
2626
import { type Spinner, startSpinner, stopSpinner } from '../../lib/spinner.js'
2727
import { detectFrameworkSettings, getDefaultConfig } from '../../utils/build-info.js'
2828
import {
29+
NETLIFY_CYAN_HEX,
2930
NETLIFYDEVERR,
3031
NETLIFYDEVLOG,
3132
chalk,
@@ -48,6 +49,8 @@ import { sitesCreate } from '../sites/sites-create.js'
4849
import type { $TSFixMe } from '../types.js'
4950
import { SiteInfo } from '../../utils/types.js'
5051
import type { DeployOptionValues } from './option_values.js'
52+
import boxen from 'boxen'
53+
import terminalLink from 'terminal-link'
5154

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

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

465467
const draft = !deployToProduction && !alias
@@ -590,13 +592,7 @@ const handleBuild = async ({
590592
return { newConfig, configMutations }
591593
}
592594

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

@@ -645,32 +645,24 @@ interface JsonData {
645645

646646
const printResults = ({
647647
deployToProduction,
648-
isIntegrationDeploy,
649648
json,
650649
results,
651650
runBuildCommand,
652651
}: {
653652
deployToProduction: boolean
654-
isIntegrationDeploy: boolean
655653
json: boolean
656654
results: Awaited<ReturnType<typeof prepAndRunDeploy>>
657655
runBuildCommand: boolean
658656
}): void => {
659657
const msgData: Record<string, string> = {
660-
'Build logs': results.logsUrl,
661-
'Function logs': results.functionLogsUrl,
662-
'Edge function Logs': results.edgeFunctionLogsUrl,
663-
}
664-
665-
if (deployToProduction) {
666-
msgData['Unique deploy URL'] = results.deployUrl
667-
msgData['Website URL'] = results.siteUrl
668-
} else {
669-
msgData['Website draft URL'] = results.deployUrl
658+
'Build logs': terminalLink(results.logsUrl, results.logsUrl),
659+
'Function logs': terminalLink(results.functionLogsUrl, results.functionLogsUrl),
660+
'Edge function Logs': terminalLink(results.edgeFunctionLogsUrl, results.edgeFunctionLogsUrl),
670661
}
671662

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

675667
// Json response for piping commands
676668
if (json) {
@@ -690,40 +682,52 @@ const printResults = ({
690682
logJson(jsonData)
691683
exit(0)
692684
} else {
685+
const message = deployToProduction
686+
? `Deployed to production URL: ${terminalLink(results.siteUrl, results.siteUrl)}\n
687+
Unique deploy URL: ${terminalLink(results.deployUrl, results.deployUrl)}`
688+
: `Deployed draft to ${terminalLink(results.deployUrl, results.deployUrl)}`
689+
690+
log(
691+
boxen(message, {
692+
padding: 1,
693+
margin: 1,
694+
textAlignment: 'center',
695+
borderStyle: 'round',
696+
borderColor: NETLIFY_CYAN_HEX,
697+
// This is an intentional half-width space to work around a unicode padding math bug in boxen
698+
// eslint-disable-next-line no-irregular-whitespace
699+
title: `⬥ ${deployToProduction ? 'Production deploy' : 'Draft deploy'} is live ⬥ `,
700+
titleAlignment: 'center',
701+
}),
702+
)
703+
693704
log(prettyjson.render(msgData))
694705

695706
if (!deployToProduction) {
696707
log()
697-
log('If everything looks good on your draft URL, deploy it to your main site URL with the --prod flag.')
698-
log(
699-
chalk.cyanBright.bold(
700-
`netlify ${isIntegrationDeploy ? 'integration:' : ''}deploy${runBuildCommand ? ' --build' : ''} --prod`,
701-
),
702-
)
708+
log('If everything looks good on your draft URL, deploy it to your main site URL with the --prod flag:')
709+
log(chalk.cyanBright.bold(`netlify deploy${runBuildCommand ? '' : '--no-build'} --prod`))
703710
log()
704711
}
705712
}
706713
}
707714

708715
const prepAndRunDeploy = async ({
709-
// @ts-expect-error TS(7031) FIXME: Binding element 'api' implicitly has an 'any' type... Remove this comment to see the full error message
710716
api,
711-
// @ts-expect-error TS(7031) FIXME: Binding element 'command' implicitly has an 'any' ... Remove this comment to see the full error message
712717
command,
713-
// @ts-expect-error TS(7031) FIXME: Binding element 'config' implicitly has an 'any' t... Remove this comment to see the full error message
714718
config,
715-
// @ts-expect-error TS(7031) FIXME: Binding element 'deployToProduction' implicitly ha... Remove this comment to see the full error message
716719
deployToProduction,
717-
// @ts-expect-error TS(7031) FIXME: Binding element 'options' implicitly has an 'any' ... Remove this comment to see the full error message
718720
options,
719-
// @ts-expect-error TS(7031) FIXME: Binding element 'site' implicitly has an 'any' typ... Remove this comment to see the full error message
720721
site,
721-
// @ts-expect-error TS(7031) FIXME: Binding element 'siteData' implicitly has an 'any'... Remove this comment to see the full error message
722722
siteData,
723-
// @ts-expect-error TS(7031) FIXME: Binding element 'siteId' implicitly has an 'any' t... Remove this comment to see the full error message
724723
siteId,
725-
// @ts-expect-error TS(7031) FIXME: Binding element 'workingDir' implicitly has an 'an... Remove this comment to see the full error message
726724
workingDir,
725+
}: {
726+
options: DeployOptionValues
727+
command: BaseCommand
728+
workingDir: string
729+
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- FIXME(serhalp)
730+
[key: string]: any
727731
}) => {
728732
const alias = options.alias || options.branch
729733
// if a context is passed besides dev, we need to pull env vars from that specific context
@@ -747,13 +751,19 @@ const prepAndRunDeploy = async ({
747751
await bundleEdgeFunctions(options, command)
748752
}
749753

754+
log('')
755+
// Note: this is leakily mimicking the @netlify/build heading style
756+
log(chalk.cyanBright.bold(`Deploying to Netlify\n${'─'.repeat(64)}`))
757+
758+
log('')
750759
log(
751760
prettyjson.render({
752761
'Deploy path': deployFolder,
753762
'Functions path': functionsFolder,
754763
'Configuration path': configPath,
755764
}),
756765
)
766+
log()
757767

758768
const { functionsFolderStat } = await validateFolders({
759769
deployFolder,
@@ -782,7 +792,7 @@ const prepAndRunDeploy = async ({
782792
command,
783793
config,
784794
deployFolder,
785-
deployTimeout: options.timeout * SEC_TO_MILLISEC || DEFAULT_DEPLOY_TIMEOUT,
795+
deployTimeout: options.timeout ? options.timeout * SEC_TO_MILLISEC : DEFAULT_DEPLOY_TIMEOUT,
786796
deployToProduction,
787797
functionsConfig,
788798
// pass undefined functionsFolder if doesn't exist
@@ -893,11 +903,8 @@ export const deploy = async (options: DeployOptionValues, command: BaseCommand)
893903
deployToProduction,
894904
})
895905
}
896-
const isIntegrationDeploy = command.name() === 'integration:deploy'
897-
898906
printResults({
899907
runBuildCommand: options.build,
900-
isIntegrationDeploy,
901908
json: options.json,
902909
results,
903910
deployToProduction,

src/commands/sites/sites-list.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { OptionValues } from 'commander'
22

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

1717
const sites = await listSites({ api, options: { filter: 'all' } })
1818
if (spinner) {
19-
stopSpinner({ spinner })
19+
spinner.success()
2020
}
2121

2222
if (sites && sites.length !== 0) {

0 commit comments

Comments
 (0)