Skip to content

Commit

Permalink
chore: enhance types for base-command.ts (#6261)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasholzer authored Dec 12, 2023
1 parent 3f37b8e commit d5fcdc5
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 221 deletions.
221 changes: 79 additions & 142 deletions src/commands/base-command.ts

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions src/commands/dev/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { getGeoCountryArgParser } from '../../utils/validation.js'
import BaseCommand from '../base-command.js'

import { createDevExecCommand } from './dev-exec.js'
import { type DevConfig } from './types.js'

/**
*
Expand Down Expand Up @@ -90,7 +91,6 @@ export const dev = async (options: OptionValues, command: BaseCommand) => {
const { api, cachedConfig, config, repositoryRoot, site, siteInfo, state } = command.netlify
config.dev = { ...config.dev }
config.build = { ...config.build }
/** @type {import('./types.js').DevConfig} */
const devConfig = {
framework: '#auto',
autoLaunch: Boolean(options.open),
Expand All @@ -99,7 +99,7 @@ export const dev = async (options: OptionValues, command: BaseCommand) => {
...(config.build.base && { base: config.build.base }),
...config.dev,
...options,
}
} as DevConfig

let { env } = cachedConfig

Expand Down
5 changes: 0 additions & 5 deletions src/commands/link/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,6 @@ or run ${chalk.cyanBright('netlify sites:create')} to create a site.`)
} catch (error_) {
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
if (error_.status === 404) {
// @ts-expect-error TS(2345) FIXME: Argument of type 'Error' is not assignable to para... Remove this comment to see the full error message
error(new Error(`Site ID '${siteId}' not found`))
} else {
// @ts-expect-error TS(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message
Expand All @@ -225,7 +224,6 @@ or run ${chalk.cyanBright('netlify sites:create')} to create a site.`)
}

if (!site) {
// @ts-expect-error TS(2345) FIXME: Argument of type 'Error' is not assignable to para... Remove this comment to see the full error message
error(new Error(`No site found`))
}

Expand Down Expand Up @@ -285,7 +283,6 @@ export const link = async (options: OptionValues, command: BaseCommand) => {
} catch (error_) {
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
if (error_.status === 404) {
// @ts-expect-error TS(2345) FIXME: Argument of type 'Error' is not assignable to para... Remove this comment to see the full error message
error(new Error(`Site id ${options.id} not found`))
} else {
// @ts-expect-error TS(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message
Expand Down Expand Up @@ -315,7 +312,6 @@ export const link = async (options: OptionValues, command: BaseCommand) => {
} catch (error_) {
// @ts-expect-error TS(2571) FIXME: Object is of type 'unknown'.
if (error_.status === 404) {
// @ts-expect-error TS(2345) FIXME: Argument of type 'Error' is not assignable to para... Remove this comment to see the full error message
error(new Error(`${options.name} not found`))
} else {
// @ts-expect-error TS(2345) FIXME: Argument of type 'unknown' is not assignable to pa... Remove this comment to see the full error message
Expand All @@ -324,7 +320,6 @@ export const link = async (options: OptionValues, command: BaseCommand) => {
}

if (results.length === 0) {
// @ts-expect-error TS(2345) FIXME: Argument of type 'Error' is not assignable to para... Remove this comment to see the full error message
error(new Error(`No sites found named ${options.name}`))
}
const [firstSiteData] = results
Expand Down
4 changes: 2 additions & 2 deletions src/commands/serve/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ import { generateInspectSettings, startProxyServer } from '../../utils/proxy-ser
import { runBuildTimeline } from '../../utils/run-build.js'
import type { ServerSettings } from '../../utils/types.js'
import BaseCommand from '../base-command.js'
import { type DevConfig } from '../dev/types.js'

export const serve = async (options: OptionValues, command: BaseCommand) => {
const { api, cachedConfig, config, repositoryRoot, site, siteInfo, state } = command.netlify
config.dev = { ...config.dev }
config.build = { ...config.build }
/** @type {import('../dev/types').DevConfig} */
const devConfig = {
...(config.functionsDirectory && { functions: config.functionsDirectory }),
...(config.build.publish && { publish: config.build.publish }),
Expand All @@ -40,7 +40,7 @@ export const serve = async (options: OptionValues, command: BaseCommand) => {
// Override the `framework` value so that we start a static server and not
// the framework's development server.
framework: '#static',
}
} as DevConfig

let { env } = cachedConfig

Expand Down
26 changes: 20 additions & 6 deletions src/commands/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import type { NetlifyAPI } from 'netlify'

import StateConfig from '../utils/state-config.js'

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type $TSFixMe = any;

export type NetlifySite = {
root?: string
configPath?: string
Expand All @@ -11,21 +14,32 @@ export type NetlifySite = {
set id(id: string): void
}

type PatchedConfig = NetlifyTOML & {
functionsDirectory?: string
build: NetlifyTOML['build'] & {
functionsSource?: string
}
dev: NetlifyTOML['dev'] & {
functions?: string
}
}

/**
* The netlify object inside each command with the state
*/
export type NetlifyOptions = {
api: NetlifyAPI
apiOpts: unknown
// poorly duck type the missing api functions
api: NetlifyAPI & Record<string, (...args: $TSFixMe) => Promise<$TSFixMe>>
apiOpts: $TSFixMe
repositoryRoot: string
/** Absolute path of the netlify configuration file */
configFilePath: string
/** Relative path of the netlify configuration file */
relConfigFilePath: string
site: NetlifySite
siteInfo: unknown
config: NetlifyTOML
cachedConfig: Record<string, unknown>
globalConfig: unknown
siteInfo: $TSFixMe
config: PatchedConfig
cachedConfig: Record<string, $TSFixMe>
globalConfig: $TSFixMe
state: StateConfig
}
13 changes: 3 additions & 10 deletions src/utils/command-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,23 +184,16 @@ export const warn = (message = '') => {
log(` ${bang} Warning: ${message}`)
}

/**
* throws an error or log it
* @param {unknown} message
* @param {object} [options]
* @param {boolean} [options.exit]
*/
export const error = (message = '', options = {}) => {
/** Throws an error or logs it */
export const error = (message: Error | string = '', options: { exit?: boolean } = {}) => {
const err =
// @ts-expect-error TS(2358) FIXME: The left-hand side of an 'instanceof' expression m... Remove this comment to see the full error message
message instanceof Error
? message
: // eslint-disable-next-line unicorn/no-nested-ternary
typeof message === 'string'
? new Error(message)
: /** @type {Error} */ { message, stack: undefined, name: 'Error' }
: { message, stack: undefined, name: 'Error' }

// @ts-expect-error TS(2339) FIXME: Property 'exit' does not exist on type '{}'.
if (options.exit === false) {
const bang = chalk.red(BANG)
if (process.env.DEBUG) {
Expand Down
52 changes: 26 additions & 26 deletions src/utils/detect-server-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@ import { readFile } from 'fs/promises'
import { EOL } from 'os'
import { dirname, relative, resolve } from 'path'

import { getFramework, getSettings } from '@netlify/build-info'
import { Project, Settings, getFramework, getSettings } from '@netlify/build-info'
import type { OptionValues } from 'commander'
import getPort from 'get-port'

import BaseCommand from '../commands/base-command.js'
import { type DevConfig } from '../commands/dev/types.js'

import { detectFrameworkSettings } from './build-info.js'
import { NETLIFYDEVWARN, chalk, log } from './command-helpers.js'
import { acquirePort } from './dev.js'
import { getInternalFunctionsDir } from './functions/functions.js'
import { getPluginsToAutoInstall } from './init/utils.js'
import { BaseServerSettings, ServerSettings } from './types.js'

/** @param {string} str */
// @ts-expect-error TS(7006) FIXME: Parameter 'str' implicitly has an 'any' type.
Expand Down Expand Up @@ -180,11 +185,8 @@ const handleStaticServer = async ({ devConfig, flags, workingDir }) => {

/**
* Retrieves the settings from a framework
* @param {import('@netlify/build-info').Settings} [settings]
* @returns {import('./types.js').BaseServerSettings | undefined}
*/
// @ts-expect-error TS(7006) FIXME: Parameter 'settings' implicitly has an 'any' type.
const getSettingsFromDetectedSettings = (settings) => {
const getSettingsFromDetectedSettings = (command: BaseCommand, settings?: Settings) => {
if (!settings) {
return
}
Expand All @@ -196,7 +198,7 @@ const getSettingsFromDetectedSettings = (settings) => {
framework: settings.framework.name,
env: settings.env,
pollingStrategies: settings.pollingStrategies,
plugins: getPluginsToAutoInstall(settings.plugins_from_config_file, settings.plugins_recommended),
plugins: getPluginsToAutoInstall(command, settings.plugins_from_config_file, settings.plugins_recommended),
}
}

Expand Down Expand Up @@ -265,32 +267,29 @@ const mergeSettings = async ({ devConfig, frameworkSettings = {}, workingDir })

/**
* Handles a forced framework and retrieves the settings for it
* @param {object} config
* @param {import('../commands/dev/types.js').DevConfig} config.devConfig
* @param {import('@netlify/build-info').Project} config.project
* @param {string} config.workingDir
* @param {string=} config.workspacePackage
* @returns {Promise<import('./types.js').BaseServerSettings>}
*/
// @ts-expect-error TS(7031) FIXME: Binding element 'devConfig' implicitly has an 'any... Remove this comment to see the full error message
const handleForcedFramework = async ({ devConfig, project, workingDir, workspacePackage }) => {
const handleForcedFramework = async (options: {
command: BaseCommand
devConfig: DevConfig
project: Project
workingDir: string
workspacePackage?: string
}): Promise<BaseServerSettings> => {
// this throws if `devConfig.framework` is not a supported framework
const framework = await getFramework(devConfig.framework, project)
const settings = await getSettings(framework, project, workspacePackage || '')
const frameworkSettings = getSettingsFromDetectedSettings(settings)
return mergeSettings({ devConfig, workingDir, frameworkSettings })
const framework = await getFramework(options.devConfig.framework, options.project)
const settings = await getSettings(framework, options.project, options.workspacePackage || '')
const frameworkSettings = getSettingsFromDetectedSettings(options.command, settings)
return mergeSettings({ devConfig: options.devConfig, workingDir: options.workingDir, frameworkSettings })
}

/**
* Get the server settings based on the flags and the devConfig
* @param {import('../commands/dev/types.js').DevConfig} devConfig
* @param {import('commander').OptionValues} flags
* @param {import('../commands/base-command.js').default} command
* @returns {Promise<import('./types.js').ServerSettings>}
*/

// @ts-expect-error TS(7006) FIXME: Parameter 'devConfig' implicitly has an 'any' type... Remove this comment to see the full error message
const detectServerSettings = async (devConfig, flags, command) => {
const detectServerSettings = async (
devConfig: DevConfig,
flags: OptionValues,
command: BaseCommand,
): Promise<ServerSettings> => {
validateProperty(devConfig, 'framework', 'string')

/** @type {Partial<import('./types.js').BaseServerSettings>} */
Expand All @@ -304,7 +303,7 @@ const detectServerSettings = async (devConfig, flags, command) => {

const runDetection = !hasCommandAndTargetPort(devConfig)
const frameworkSettings = runDetection
? getSettingsFromDetectedSettings(await detectFrameworkSettings(command, 'dev'))
? getSettingsFromDetectedSettings(command, await detectFrameworkSettings(command, 'dev'))
: undefined
if (frameworkSettings === undefined && runDetection) {
log(`${NETLIFYDEVWARN} No app server detected. Using simple static server`)
Expand All @@ -325,6 +324,7 @@ const detectServerSettings = async (devConfig, flags, command) => {
validateFrameworkConfig({ devConfig })
// this is when the user explicitly configures a framework, e.g. `framework = "gatsby"`
settings = await handleForcedFramework({
command,
devConfig,
project: command.project,
workingDir: command.workingDir,
Expand Down
54 changes: 26 additions & 28 deletions src/utils/init/utils.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,57 @@
import { writeFile } from 'fs/promises'
import path from 'path'

import { NetlifyConfig } from '@netlify/build'
import { Settings } from '@netlify/build-info'
import cleanDeep from 'clean-deep'
import inquirer from 'inquirer'

import BaseCommand from '../../commands/base-command.js'
import { fileExistsAsync } from '../../lib/fs.js'
import { normalizeBackslash } from '../../lib/path.js'
import { detectBuildSettings } from '../build-info.js'
import { chalk, error as failAndExit, log, warn } from '../command-helpers.js'

import { getRecommendPlugins, getUIPlugins } from './plugins.js'

// these plugins represent runtimes that are
// expected to be "automatically" installed. Even though
// they can be installed on package/toml, we always
// want them installed in the site settings. When installed
// there our build will automatically install the latest without
// user management of the versioning.
const pluginsToAlwaysInstall = new Set(['@netlify/plugin-nextjs'])
const formatTitle = (title: string) => chalk.cyan(title)

/**
* Retrieve a list of plugins to auto install
* @param {string[]=} pluginsInstalled
* @param {string[]=} pluginsRecommended
* @param pluginsToAlwaysInstall these plugins represent runtimes that are
* expected to be "automatically" installed. Even though
* they can be installed on package/toml, we always
* want them installed in the site settings. When installed
* there our build will automatically install the latest without
* user management of the versioning.
* @param pluginsInstalled
* @param pluginsRecommended
* @returns
*/
export const getPluginsToAutoInstall = (pluginsInstalled = [], pluginsRecommended = []) =>
pluginsRecommended.reduce(
export const getPluginsToAutoInstall = (
command: BaseCommand,
pluginsInstalled: string[] = [],
pluginsRecommended: string[] = [],
) => {
const nextRuntime = '@netlify/plugin-nextjs'
const pluginsToAlwaysInstall = new Set([nextRuntime])
return pluginsRecommended.reduce(
(acc, plugin) =>
pluginsInstalled.includes(plugin) && !pluginsToAlwaysInstall.has(plugin) ? acc : [...acc, plugin],

/** @type {string[]} */ [],
[] as string[],
)

}
/**
*
* @param {Partial<import('@netlify/build-info').Settings>} settings
* @param {*} config
* @param {import('../../commands/base-command.js').default} command
*/
// @ts-expect-error TS(7006) FIXME: Parameter 'settings' implicitly has an 'any' type.
const normalizeSettings = (settings, config, command) => {
const plugins = getPluginsToAutoInstall(settings.plugins_from_config_file, settings.plugins_recommended)
const normalizeSettings = (settings: Settings, config: NetlifyConfig, command: BaseCommand) => {
const plugins = getPluginsToAutoInstall(command, settings.plugins_from_config_file, settings.plugins_recommended)
const recommendedPlugins = getRecommendPlugins(plugins, config)

return {
defaultBaseDir: settings.baseDirectory ?? command.project.relativeBaseDirectory ?? '',
defaultBuildCmd: config.build.command || settings.buildCommand,
defaultBuildDir: settings.dist,
// @ts-expect-error types need to be fixed on @netlify/build
defaultFunctionsDir: config.build.functions || 'netlify/functions',
recommendedPlugins,
}
Expand Down Expand Up @@ -106,7 +110,7 @@ export const getBuildSettings = async ({ command, config }) => {
await normalizeSettings(setting, config, command)

if (recommendedPlugins.length !== 0 && setting.framework?.name) {
log(`Configuring ${formatTitle(setting.framework?.name)} runtime...`)
log(`Configuring ${formatTitle(setting.framework.name)} runtime...`)
log()
}

Expand Down Expand Up @@ -210,12 +214,6 @@ export const formatErrorMessage = ({ error, message }) => {
return `${message} with error: ${chalk.red(errorMessage)}`
}

/**
* @param {string} title
*/
// @ts-expect-error TS(7006) FIXME: Parameter 'title' implicitly has an 'any' type.
const formatTitle = (title) => chalk.cyan(title)

// @ts-expect-error TS(7031) FIXME: Binding element 'api' implicitly has an 'any' type... Remove this comment to see the full error message
export const createDeployKey = async ({ api }) => {
try {
Expand Down

1 comment on commit d5fcdc5

@github-actions
Copy link

Choose a reason for hiding this comment

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

📊 Benchmark results

  • Dependency count: 1,399
  • Package size: 405 MB
  • Number of ts-expect-error directives: 1,292

Please sign in to comment.