Skip to content

Commit 7a5b7c0

Browse files
authored
fix(dev): polish and improve dev command output (#7249)
* fix: fix framework server loading spinner rendering This is a regression in the loading spinner shown before "Waiting for framework port", introduced in 19.0.3. * fix: fix framework server spinner See: 1. #7026 2. #7131 3. #7124 Oops, I was really overthinking this. All this needs to do is, every time a chunk comes in from the underlying framework dev server, clear the spinner line, write the chunk, then resume the spinner. This wasn't set up right at all, which was obfuscated by the erroneous use of `isSpinning` (see previous commit). * fix: remove "Netlify Dev" prefix from deploy log * fix(dev): polish and improve dev command - Reworded messages for clarity and consistency - I think "Netlify Dev" was a vestige of Netlify CLI's predecessor...? In any case, I dropped this wording. - e.g. Starting Netlify Dev with Astro → Starting Astro dev server - e.g. Waiting for framework port 4321 → Waiting for Astro dev server to be ready on port 4321 - Removed the "🔸 Netlify Dev 🔸" first line (doesn't seem to add anything) - Attempted to address the common confusion around the two printed URLs (our dev server + framework server): - more distinctive box - 'inverted' colour on the URL (cyan background) to make it stand out the most - changed `Server now ready on http...` to `Local dev server ready: http...` - Attempted to address the common confusion around this message: `✔ Waiting for framework port 4321. This can be configured using the 'targetPort' property in the netlify.toml` - (Users often think this is an error message. I don't blame them.) - I changed this to `Waiting for Astro dev server to be ready on port 4321` (where Astro is the framework name) - I added logic so that after 5 seconds of waiting the message changes to `Still waiting for server on port 4321 to be ready. Are you sure this is the correct port for your Astro site? Change this with the targetPort option in your netlify.toml.`. - Collapse "Injected env var" messages into a single line per source - ◈ → ⬥ * fix: revert the framework server log prefix for now
1 parent d9523c7 commit 7a5b7c0

File tree

17 files changed

+194
-185
lines changed

17 files changed

+194
-185
lines changed

src/commands/deploy/deploy.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ 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-
NETLIFYDEV,
3029
NETLIFYDEVERR,
3130
NETLIFYDEVLOG,
3231
chalk,
@@ -72,7 +71,7 @@ const triggerDeploy = async ({
7271
})
7372
} else {
7473
log(
75-
`${NETLIFYDEV} A new deployment was triggered successfully. Visit https://app.netlify.com/sites/${siteData.name}/deploys/${siteBuild.deploy_id} to see the logs.`,
74+
`${NETLIFYDEVLOG} A new deployment was triggered successfully. Visit https://app.netlify.com/sites/${siteData.name}/deploys/${siteBuild.deploy_id} to see the logs.`,
7675
)
7776
}
7877
} catch (error_) {

src/commands/dev-exec/dev-exec.ts

-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ export const devExec = async (cmd: string, options: OptionValues, command: BaseC
1010

1111
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1212
const withEnvelopeEnvVars = await getEnvelopeEnv({ api, context: options.context, env: cachedConfig.env, siteInfo })
13-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
1413
const withDotEnvVars = await getDotEnvVariables({ devConfig: { ...config.dev }, env: withEnvelopeEnvVars, site })
1514

1615
injectEnvVariables(withDotEnvVars)

src/commands/dev/dev.ts

+2-5
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ import { OptionValues } from 'commander'
77
import { BLOBS_CONTEXT_VARIABLE, encodeBlobsContext, getBlobsContextWithEdgeAccess } from '../../lib/blobs/blobs.js'
88
import { promptEditorHelper } from '../../lib/edge-functions/editor-helper.js'
99
import { startFunctionsServer } from '../../lib/functions/server.js'
10-
import { printBanner } from '../../utils/banner.js'
10+
import { printBanner } from '../../utils/dev-server-banner.js'
1111
import {
12-
NETLIFYDEV,
1312
NETLIFYDEVERR,
1413
NETLIFYDEVLOG,
1514
NETLIFYDEVWARN,
@@ -74,7 +73,6 @@ const handleLiveTunnel = async ({
7473
}
7574

7675
export const dev = async (options: OptionValues, command: BaseCommand) => {
77-
log(NETLIFYDEV)
7876
const { api, cachedConfig, config, repositoryRoot, site, siteInfo, state } = command.netlify
7977
config.dev = config.dev != null ? { ...config.dev } : undefined
8078
config.build = { ...config.build }
@@ -156,7 +154,7 @@ export const dev = async (options: OptionValues, command: BaseCommand) => {
156154
process.env.URL = url
157155
process.env.DEPLOY_URL = url
158156

159-
log(`${NETLIFYDEVWARN} Setting up local development server`)
157+
log(`${NETLIFYDEVLOG} Setting up local dev server`)
160158

161159
const { configMutations, configPath: configPathOverride } = await runDevTimeline({
162160
command,
@@ -169,7 +167,6 @@ export const dev = async (options: OptionValues, command: BaseCommand) => {
169167
})
170168

171169
// FIXME(serhalp): `applyMutations` is `(any, any) => any)`. Add types in `@netlify/config`.
172-
173170
const mutatedConfig: typeof config = applyMutations(config, configMutations)
174171

175172
const functionsRegistry = await startFunctionsServer({

src/commands/functions/functions-serve.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { OptionValues } from 'commander'
44

55
import { getBlobsContextWithEdgeAccess } from '../../lib/blobs/blobs.js'
66
import { startFunctionsServer } from '../../lib/functions/server.js'
7-
import { printBanner } from '../../utils/banner.js'
7+
import { printBanner } from '../../utils/dev-server-banner.js'
88
import {
99
UNLINKED_SITE_MOCK_ID,
1010
acquirePort,

src/commands/serve/serve.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
} from '../../lib/blobs/blobs.js'
1111
import { promptEditorHelper } from '../../lib/edge-functions/editor-helper.js'
1212
import { startFunctionsServer } from '../../lib/functions/server.js'
13-
import { printBanner } from '../../utils/banner.js'
13+
import { printBanner } from '../../utils/dev-server-banner.js'
1414
import {
1515
NETLIFYDEVERR,
1616
NETLIFYDEVLOG,

src/utils/banner.ts

-16
This file was deleted.

src/utils/command-helpers.ts

+6-4
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,13 @@ export const USER_AGENT = `${name}/${version} ${platform}-${arch} node-${process
6161
const BASE_FLAGS = new Set(['--debug', '--http-proxy', '--http-proxy-certificate-filename'])
6262

6363
export const NETLIFY_CYAN = chalk.rgb(40, 180, 170)
64+
export const NETLIFY_CYAN_HEX = '#28b5ac'
6465

65-
export const NETLIFYDEV = `${chalk.greenBright('◈')} ${NETLIFY_CYAN('Netlify Dev')} ${chalk.greenBright('◈')}`
66-
export const NETLIFYDEVLOG = chalk.greenBright('◈')
67-
export const NETLIFYDEVWARN = chalk.yellowBright('◈')
68-
export const NETLIFYDEVERR = chalk.redBright('◈')
66+
// TODO(serhalp) I *think* this "dev" naming is a vestige of the predecessor of the CLI? Rename to avoid
67+
// confusion with `netlify dev` command?
68+
export const NETLIFYDEVLOG = chalk.greenBright('⬥')
69+
export const NETLIFYDEVWARN = chalk.yellowBright('⬥')
70+
export const NETLIFYDEVERR = chalk.redBright('⬥')
6971

7072
export const BANG = process.platform === 'win32' ? '»' : '›'
7173

src/utils/dev-server-banner.ts

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import boxen from 'boxen'
2+
3+
import { chalk, log, NETLIFY_CYAN_HEX } from './command-helpers.js'
4+
5+
export const printBanner = (options: { url: string }): void => {
6+
log(
7+
boxen(`Local dev server ready: ${chalk.inverse.cyan(options.url)}`, {
8+
padding: 1,
9+
margin: 1,
10+
textAlignment: 'center',
11+
borderStyle: 'round',
12+
borderColor: NETLIFY_CYAN_HEX,
13+
// This is an intentional half-width space to work around a unicode padding math bug in boxen
14+
title: '⬥ ',
15+
titleAlignment: 'center',
16+
}),
17+
)
18+
}

src/utils/dev.ts

+11-12
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { supportsBackgroundFunctions } from '../lib/account.js'
88

99
import { NETLIFYDEVLOG, chalk, logAndThrowError, log, warn, APIError } from './command-helpers.js'
1010
import { loadDotEnvFiles } from './dot-env.js'
11-
import type { SiteInfo } from './types.js'
11+
import type { EnvironmentVariables, SiteInfo } from './types.js'
1212

1313
// Possible sources of environment variables. For the purpose of printing log messages only. Order does not matter.
1414
const ENV_VAR_SOURCES = {
@@ -167,10 +167,9 @@ const getEnvSourceName = (source) => {
167167

168168
/**
169169
* @param {{devConfig: any, env: Record<string, { sources: string[], value: string}>, site: any}} param0
170-
* @returns {Promise<Record<string, { sources: string[], value: string}>>}
171170
*/
172171
// @ts-expect-error TS(7031) FIXME: Binding element 'devConfig' implicitly has an 'any... Remove this comment to see the full error message
173-
export const getDotEnvVariables = async ({ devConfig, env, site }) => {
172+
export const getDotEnvVariables = async ({ devConfig, env, site }): Promise<EnvironmentVariables> => {
174173
const dotEnvFiles = await loadDotEnvFiles({ envFiles: devConfig.envFiles, projectDir: site.root })
175174
// @ts-expect-error TS(2339) FIXME: Property 'env' does not exist on type '{ warning: ... Remove this comment to see the full error message
176175
dotEnvFiles.forEach(({ env: fileEnv, file }) => {
@@ -195,20 +194,15 @@ export const getDotEnvVariables = async ({ devConfig, env, site }) => {
195194

196195
/**
197196
* Takes a set of environment variables in the format provided by @netlify/config and injects them into `process.env`
198-
* @param {Record<string, { sources: string[], value: string}>} env
199-
* @return {void}
200197
*/
201-
// @ts-expect-error TS(7006) FIXME: Parameter 'env' implicitly has an 'any' type.
202-
export const injectEnvVariables = (env) => {
198+
export const injectEnvVariables = (env: EnvironmentVariables): void => {
199+
const envVarsToLogByUsedSource: Record<string, string[]> = {}
203200
for (const [key, variable] of Object.entries(env)) {
204201
const existsInProcess = process.env[key] !== undefined
205-
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
206202
const [usedSource, ...overriddenSources] = existsInProcess ? ['process', ...variable.sources] : variable.sources
207203
const usedSourceName = getEnvSourceName(usedSource)
208-
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
209204
const isInternal = variable.sources.includes('internal')
210205

211-
// @ts-expect-error TS(7006) FIXME: Parameter 'source' implicitly has an 'any' type.
212206
overriddenSources.forEach((source) => {
213207
const sourceName = getEnvSourceName(source)
214208

@@ -224,13 +218,18 @@ export const injectEnvVariables = (env) => {
224218
if (!existsInProcess || isInternal) {
225219
// Omitting `general` and `internal` env vars to reduce noise in the logs.
226220
if (usedSource !== 'general' && !isInternal) {
227-
log(`${NETLIFYDEVLOG} Injected ${usedSourceName} env var: ${chalk.yellow(key)}`)
221+
envVarsToLogByUsedSource[usedSource] ??= []
222+
envVarsToLogByUsedSource[usedSource].push(key)
228223
}
229224

230-
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
231225
process.env[key] = variable.value
232226
}
233227
}
228+
229+
for (const [source, keys] of Object.entries(envVarsToLogByUsedSource)) {
230+
const sourceName = getEnvSourceName(source)
231+
log(`${NETLIFYDEVLOG} Injected ${sourceName} env vars: ${keys.map((key) => chalk.yellow(key)).join(', ')}`)
232+
}
234233
}
235234

236235
export const acquirePort = async ({

src/utils/framework-server.ts

+30-8
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import waitPort from 'wait-port'
44

55
import { startSpinner, stopSpinner } from '../lib/spinner.js'
66

7-
import { logAndThrowError, log, NETLIFYDEVERR, NETLIFYDEVLOG } from './command-helpers.js'
7+
import { logAndThrowError, log, NETLIFYDEVERR, NETLIFYDEVLOG, chalk } from './command-helpers.js'
88
import { runCommand } from './shell.js'
99
import { startStaticServer } from './static-server.js'
1010
import type { ServerSettings } from './types.js'
1111

12-
// 10 minutes
13-
const FRAMEWORK_PORT_TIMEOUT = 6e5
12+
const FRAMEWORK_PORT_TIMEOUT_MS = 10 * 60 * 1000
13+
const FRAMEWORK_PORT_WARN_TIMEOUT_MS = 5 * 1000
1414

1515
interface StartReturnObject {
1616
ipVersion?: 4 | 6
@@ -34,10 +34,11 @@ export const startFrameworkServer = async function ({
3434
return { ipVersion: family === 'IPv6' ? 6 : 4 }
3535
}
3636

37-
log(`${NETLIFYDEVLOG} Starting Netlify Dev with ${settings.framework || 'custom config'}`)
37+
log('')
38+
log(`${NETLIFYDEVLOG} Starting ${settings.framework || 'framework'} dev server`)
3839

3940
const spinner = startSpinner({
40-
text: `Waiting for framework port ${settings.frameworkPort}. This can be configured using the 'targetPort' property in the netlify.toml`,
41+
text: `Waiting for ${settings.framework || 'framework'} dev server to be ready on port ${settings.frameworkPort}`,
4142
})
4243

4344
if (settings.clearPublishDirectory && settings.dist) {
@@ -55,24 +56,45 @@ export const startFrameworkServer = async function ({
5556
const ipVersion = parseInt(process.versions.node.split('.')[0]) >= 18 ? 6 : 4
5657
port = { open: true, ipVersion }
5758
} else {
58-
port = await waitPort({
59+
const waitPortPromise = waitPort({
5960
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
6061
port: settings.frameworkPort!,
6162
host: 'localhost',
6263
output: 'silent',
63-
timeout: FRAMEWORK_PORT_TIMEOUT,
64+
timeout: FRAMEWORK_PORT_TIMEOUT_MS,
6465
...(settings.pollingStrategies?.includes('HTTP') && { protocol: 'http' }),
6566
})
6667

68+
const timerId = setTimeout(() => {
69+
if (!port?.open) {
70+
spinner.update({
71+
text: `Still waiting for server on port ${
72+
settings.frameworkPort
73+
} to be ready. Are you sure this is the correct port${
74+
settings.framework ? ` for ${settings.framework}` : ''
75+
}? Change this with the ${chalk.yellow('targetPort')} option in your ${chalk.yellow('netlify.toml')}.`,
76+
})
77+
}
78+
}, FRAMEWORK_PORT_WARN_TIMEOUT_MS)
79+
80+
port = await waitPortPromise
81+
clearTimeout(timerId)
82+
6783
if (!port.open) {
6884
throw new Error(`Timed out waiting for port '${settings.frameworkPort}' to be open`)
6985
}
7086
}
71-
spinner.success()
87+
spinner.success(`${settings.framework || 'framework'} dev server ready on port ${settings.frameworkPort}`)
7288
} catch (error_) {
7389
stopSpinner({ error: true, spinner })
7490
log(NETLIFYDEVERR, `Netlify Dev could not start or connect to localhost:${settings.frameworkPort}.`)
7591
log(NETLIFYDEVERR, `Please make sure your framework server is running on port ${settings.frameworkPort}`)
92+
log(
93+
NETLIFYDEVERR,
94+
`If not, you can configure it using the ${chalk.yellow('targetPort')} option in your ${chalk.yellow(
95+
'netlify.toml',
96+
)}.`,
97+
)
7698
return logAndThrowError(error_)
7799
}
78100

src/utils/shell.ts

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ const cleanupBeforeExit = async ({ exitCode }: { exitCode?: number | undefined }
3636
}
3737
}
3838

39+
// TODO(serhalp): Move (or at least rename). This sounds like a generic shell util but it's specific
40+
// to `netlify dev`...
3941
export const runCommand = (
4042
command: string,
4143
options: {

0 commit comments

Comments
 (0)