diff --git a/CHANGELOG.md b/CHANGELOG.md index 7efc47be4df..277f62a51b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,13 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +## [17.10.1](https://github.com/netlify/cli/compare/v17.10.0...v17.10.1) (2023-12-08) + + +### Bug Fixes + +* use correct file extensions for transpilation ([#6256](https://github.com/netlify/cli/issues/6256)) ([8308097](https://github.com/netlify/cli/commit/8308097334fc76117cee4798772abf18ff5a7e4d)) + ## [17.10.0](https://github.com/netlify/cli/compare/v17.9.0...v17.10.0) (2023-12-06) diff --git a/package-lock.json b/package-lock.json index 899c453f3d4..ee3ede96d94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "netlify-cli", - "version": "17.10.0", + "version": "17.10.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "netlify-cli", - "version": "17.10.0", + "version": "17.10.1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index e1e18e7ac58..3fbe9eaffe0 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "netlify-cli", "description": "Netlify command line tool", - "version": "17.10.0", + "version": "17.10.1", "author": "Netlify Inc.", "type": "module", "engines": { diff --git a/src/commands/base-command.ts b/src/commands/base-command.ts index 7a73b601ddc..91832c5a99a 100644 --- a/src/commands/base-command.ts +++ b/src/commands/base-command.ts @@ -38,6 +38,13 @@ import openBrowser from '../utils/open-browser.js' import StateConfig from '../utils/state-config.js' import { identify, reportError, track } from '../utils/telemetry/index.js' +import { type NetlifyOptions } from './types.js' + +type Analytics = { + startTime: bigint + payload?: Record +} + // load the autocomplete plugin inquirer.registerPrompt('autocomplete', inquirerAutocompletePrompt) /** Netlify CLI client id. Lives in bot@netlify.com */ @@ -61,21 +68,11 @@ const HELP_SEPARATOR_WIDTH = 5 */ const COMMANDS_WITHOUT_WORKSPACE_OPTIONS = new Set(['api', 'recipes', 'completion', 'status', 'switch', 'login', 'lm']) -/** - * Formats a help list correctly with the correct indent - * @param {string[]} textArray - * @returns - */ -// @ts-expect-error TS(7006) FIXME: Parameter 'textArray' implicitly has an 'any' type... Remove this comment to see the full error message -const formatHelpList = (textArray) => textArray.join('\n').replace(/^/gm, ' '.repeat(HELP_INDENT_WIDTH)) +/** Formats a help list correctly with the correct indent */ +const formatHelpList = (textArray: string[]) => textArray.join('\n').replace(/^/gm, ' '.repeat(HELP_INDENT_WIDTH)) -/** - * Get the duration between a start time and the current time - * @param {bigint} startTime - * @returns - */ -// @ts-expect-error TS(7006) FIXME: Parameter 'startTime' implicitly has an 'any' type... Remove this comment to see the full error message -const getDuration = function (startTime) { +/** Get the duration between a start time and the current time */ +const getDuration = (startTime: bigint) => { const durationNs = process.hrtime.bigint() - startTime return Math.round(Number(durationNs / BigInt(NANO_SECS_TO_MSECS))) } @@ -83,13 +80,8 @@ const getDuration = function (startTime) { /** * Retrieves a workspace package based of the filter flag that is provided. * If the filter flag does not match a workspace package or is not defined then it will prompt with an autocomplete to select a package - * @param {Project} project - * @param {string=} filter - * @returns {Promise} */ -// @ts-expect-error TS(7006) FIXME: Parameter 'project' implicitly has an 'any' type. -async function selectWorkspace(project, filter) { - // @ts-expect-error TS(7006) FIXME: Parameter 'pkg' implicitly has an 'any' type. +async function selectWorkspace(project: Project, filter?: string): Promise { const selected = project.workspace?.packages.find((pkg) => { if ( project.relativeBaseDirectory && @@ -113,9 +105,7 @@ async function selectWorkspace(project, filter) { // @ts-expect-error TS(7006) FIXME: Parameter '_' implicitly has an 'any' type. source: (/** @type {string} */ _, input = '') => (project.workspace?.packages || []) - // @ts-expect-error TS(7006) FIXME: Parameter 'pkg' implicitly has an 'any' type. .filter((pkg) => pkg.path.includes(input)) - // @ts-expect-error TS(7006) FIXME: Parameter 'pkg' implicitly has an 'any' type. .map((pkg) => ({ name: `${pkg.name ? `${chalk.bold(pkg.name)} ` : ''}${pkg.path} ${chalk.dim( `--filter ${pkg.name || pkg.path}`, @@ -138,19 +128,12 @@ async function getRepositoryRoot(cwd?: string): Promise { /** Base command class that provides tracking and config initialization */ export default class BaseCommand extends Command { - /** - * The netlify object inside each command with the state - * @type {import('./types.js').NetlifyOptions} - */ - // @ts-expect-error TS(7008) FIXME: Member 'netlify' implicitly has an 'any' type. - netlify - - /** @type {{ startTime: bigint, payload?: any}} */ - analytics = { startTime: process.hrtime.bigint() } - - /** @type {Project} */ - // @ts-expect-error TS(7008) FIXME: Member 'project' implicitly has an 'any' type. - project + /** The netlify object inside each command with the state */ + // @ts-expect-error This will be set for each command, TypeScript is just not able to infer it + netlify: NetlifyOptions + analytics: Analytics = { startTime: process.hrtime.bigint() } + // @ts-expect-error This will be set for each command, TypeScript is just not able to infer it + project: Project /** * The working directory that is used for reading the `netlify.toml` file and storing the state. @@ -164,25 +147,16 @@ export default class BaseCommand extends Command { /** * The workspace root if inside a mono repository. * Must not be the repository root! - * @type {string|undefined} */ - // @ts-expect-error TS(7008) FIXME: Member 'jsWorkspaceRoot' implicitly has an 'any' t... Remove this comment to see the full error message - jsWorkspaceRoot - /** - * The current workspace package we should execute the commands in - * @type {string|undefined} - */ - // @ts-expect-error TS(7008) FIXME: Member 'workspacePackage' implicitly has an 'any' ... Remove this comment to see the full error message - workspacePackage + jsWorkspaceRoot?: string + /** The current workspace package we should execute the commands in */ + workspacePackage?: string /** * IMPORTANT this function will be called for each command! * Don't do anything expensive in there. - * @param {string} name The command name - * @returns */ - // @ts-expect-error TS(7006) FIXME: Parameter 'name' implicitly has an 'any' type. - createCommand(name) { + createCommand(name: string): BaseCommand { const base = new BaseCommand(name) // If --silent or --json flag passed disable logger .addOption(new Option('--json', 'Output return values as JSON').hideHelp(true)) @@ -229,38 +203,28 @@ export default class BaseCommand extends Command { } debug(`${name}:preAction`)('start') this.analytics = { startTime: process.hrtime.bigint() } - await this.init(actionCommand) + await this.init(actionCommand as BaseCommand) debug(`${name}:preAction`)('end') }) } - /** @private */ - noBaseOptions = false - + #noBaseOptions = false /** don't show help options on command overview (mostly used on top commands like `addons` where options only apply on children) */ noHelpOptions() { - this.noBaseOptions = true + this.#noBaseOptions = true return this } - /** @type {string[]} The examples list for the command (used inside doc generation and help page) */ - examples = [] - - /** - * Set examples for the command - * @param {string[]} examples - */ - // @ts-expect-error TS(7006) FIXME: Parameter 'examples' implicitly has an 'any' type. - addExamples(examples) { + /** The examples list for the command (used inside doc generation and help page) */ + examples: string[] = [] + /** Set examples for the command */ + addExamples(examples: string[]) { this.examples = examples return this } - /** - * Overrides the help output of commander with custom styling - * @returns {import('commander').Help} - */ - createHelp() { + /** Overrides the help output of commander with custom styling */ + createHelp(): Help { const help = super.createHelp() help.commandUsage = (command) => { @@ -294,13 +258,8 @@ export default class BaseCommand extends Command { help.longestSubcommandTermLength = (command: BaseCommand): number => getCommands(command).reduce((max, cmd) => Math.max(max, cmd.name().length), 0) - /** - * override the longestOptionTermLength to react on hide options flag - * @param {BaseCommand} command - * @param {import('commander').Help} helper - * @returns {number} - */ - help.longestOptionTermLength = (command, helper) => + /** override the longestOptionTermLength to react on hide options flag */ + help.longestOptionTermLength = (command: BaseCommand, helper: Help): number => // @ts-expect-error TS(2551) FIXME: Property 'noBaseOptions' does not exist on type 'C... Remove this comment to see the full error message (command.noBaseOptions === false && helper.visibleOptions(command).reduce((max, option) => Math.max(max, helper.optionTerm(option).length), 0)) || @@ -310,15 +269,8 @@ export default class BaseCommand extends Command { const parentCommand = this.name() === 'netlify' ? command : command.parent const termWidth = helper.padWidth(command, helper) const helpWidth = helper.helpWidth || FALLBACK_HELP_CMD_WIDTH - /** - * formats a term correctly - * @param {string} term - * @param {string} [description] - * @param {boolean} [isCommand] - * @returns {string} - */ - // @ts-expect-error TS(7006) FIXME: Parameter 'term' implicitly has an 'any' type. - const formatItem = (term, description, isCommand = false) => { + // formats a term correctly + const formatItem = (term: string, description?: string, isCommand = false): string => { const bang = isCommand ? `${HELP_$} ` : '' if (description) { @@ -330,25 +282,20 @@ export default class BaseCommand extends Command { return `${bang}${term}` } - /** @type {string[]} */ - // @ts-expect-error TS(7034) FIXME: Variable 'output' implicitly has type 'any[]' in s... Remove this comment to see the full error message - let output = [] + let output: string[] = [] // Description const [topDescription, ...commandDescription] = (helper.commandDescription(command) || '').split('\n') if (topDescription.length !== 0) { - // @ts-expect-error TS(7005) FIXME: Variable 'output' implicitly has an 'any[]' type. output = [...output, topDescription, ''] } // on the parent help command the version should be displayed if (this.name() === 'netlify') { - // @ts-expect-error TS(7005) FIXME: Variable 'output' implicitly has an 'any[]' type. output = [...output, chalk.bold('VERSION'), formatHelpList([formatItem(USER_AGENT)]), ''] } // Usage - // @ts-expect-error TS(7005) FIXME: Variable 'output' implicitly has an 'any[]' type. output = [...output, chalk.bold('USAGE'), helper.commandUsage(command), ''] // Arguments @@ -359,7 +306,7 @@ export default class BaseCommand extends Command { output = [...output, chalk.bold('ARGUMENTS'), formatHelpList(argumentList), ''] } - if (command.noBaseOptions === false) { + if (command.#noBaseOptions === false) { // Options const optionList = helper .visibleOptions(command) @@ -405,14 +352,9 @@ export default class BaseCommand extends Command { return help } - /** - * Will be called on the end of an action to track the metrics - * @param {*} [error_] - */ - // @ts-expect-error TS(7006) FIXME: Parameter 'error_' implicitly has an 'any' type. - async onEnd(error_) { - // @ts-expect-error TS(2339) FIXME: Property 'payload' does not exist on type '{ start... Remove this comment to see the full error message - const { payload, startTime } = this.analytics + /** Will be called on the end of an action to track the metrics */ + async onEnd(error_?: unknown) { + const { payload = {}, startTime } = this.analytics const duration = getDuration(startTime) const status = error_ === undefined ? 'success' : 'error' @@ -430,7 +372,6 @@ export default class BaseCommand extends Command { } catch {} if (error_ !== undefined) { - // @ts-expect-error TS(2345) FIXME: Argument of type 'string | Error' is not assignabl... Remove this comment to see the full error message error(error_ instanceof Error ? error_ : format(error_), { exit: false }) exit(1) } @@ -504,25 +445,18 @@ export default class BaseCommand extends Command { return accessToken } - /** - * Adds some data to the analytics payload - * @param {Record} payload - */ - // @ts-expect-error TS(7006) FIXME: Parameter 'payload' implicitly has an 'any' type. - setAnalyticsPayload(payload) { - // @ts-expect-error TS(2339) FIXME: Property 'payload' does not exist on type '{ start... Remove this comment to see the full error message - const newPayload = { ...this.analytics.payload, ...payload } - // @ts-expect-error TS(2322) FIXME: Type '{ payload: any; startTime: bigint; }' is not... Remove this comment to see the full error message - this.analytics = { ...this.analytics, payload: newPayload } + /** Adds some data to the analytics payload */ + setAnalyticsPayload(payload: Record) { + this.analytics = { + ...this.analytics, + payload: { ...this.analytics.payload, ...payload }, + } } /** * Initializes the options and parses the configuration needs to be called on start of a command function - * @param {BaseCommand} actionCommand The command of the action that is run (`this.` gets the parent command) - * @private */ - // @ts-expect-error TS(7006) FIXME: Parameter 'actionCommand' implicitly has an 'any' ... Remove this comment to see the full error message - async init(actionCommand) { + private async init(actionCommand: BaseCommand) { debug(`${actionCommand.name()}:init`)('start') const flags = actionCommand.opts() // here we actually want to use the process.cwd as we are setting the workingDir @@ -551,8 +485,7 @@ export default class BaseCommand extends Command { }) }) const frameworks = await this.project.detectFrameworks() - /** @type { string|undefined} */ - let packageConfig = flags.config ? resolve(flags.config) : undefined + let packageConfig: string | undefined = flags.config ? resolve(flags.config) : undefined // check if we have detected multiple projects inside which one we have to perform our operations. // only ask to select one if on the workspace root if ( @@ -577,17 +510,19 @@ export default class BaseCommand extends Command { const state = new StateConfig(this.workingDir) const [token] = await getToken(flags.auth) - const apiUrlOpts = { + const apiUrlOpts: { + userAgent: string + scheme?: string + host?: string + pathPrefix?: string + } = { userAgent: USER_AGENT, } if (process.env.NETLIFY_API_URL) { const apiUrl = new URL(process.env.NETLIFY_API_URL) - // @ts-expect-error TS(2339) FIXME: Property 'scheme' does not exist on type '{ userAg... Remove this comment to see the full error message apiUrlOpts.scheme = apiUrl.protocol.slice(0, -1) - // @ts-expect-error TS(2339) FIXME: Property 'host' does not exist on type '{ userAgen... Remove this comment to see the full error message apiUrlOpts.host = apiUrl.host - // @ts-expect-error TS(2339) FIXME: Property 'pathPrefix' does not exist on type '{ us... Remove this comment to see the full error message apiUrlOpts.pathPrefix = process.env.NETLIFY_API_URL === `${apiUrl.protocol}//${apiUrl.host}` ? '/api/v1' : apiUrl.pathname } @@ -613,7 +548,8 @@ export default class BaseCommand extends Command { certificateFile: flags.httpProxyCertificateFilename, }) const apiOpts = { ...apiUrlOpts, agent } - const api = new NetlifyAPI(token || '', apiOpts) + // TODO: remove typecast once we have proper types for the API + const api = new NetlifyAPI(token || '', apiOpts) as NetlifyOptions['api'] // If a user passes a site name as an option instead of a site ID to options.site, the siteInfo object // will only have the property siteInfo.id. Checking for one of the other properties ensures that we can do @@ -631,7 +567,6 @@ export default class BaseCommand extends Command { // ================================================== // Perform analytics reporting // ================================================== - // @ts-expect-error TS(7006) FIXME: Parameter 'framework' implicitly has an 'any' type... Remove this comment to see the full error message const frameworkIDs = frameworks?.map((framework) => framework.id) if (frameworkIDs?.length !== 0) { this.setAnalyticsPayload({ frameworks: frameworkIDs }) @@ -639,7 +574,6 @@ export default class BaseCommand extends Command { this.setAnalyticsPayload({ monorepo: Boolean(this.project.workspace), packageManager: this.project.packageManager?.name, - // @ts-expect-error TS(7031) FIXME: Binding element 'id' implicitly has an 'any' type. buildSystem: this.project.buildSystems.map(({ id }) => id), }) @@ -687,23 +621,21 @@ export default class BaseCommand extends Command { debug(`${this.name()}:init`)('end') } - /** - * Find and resolve the Netlify configuration - * @param {object} config - * @param {string} config.cwd - * @param {string|null=} config.token - * @param {*} config.state - * @param {boolean=} config.offline - * @param {string=} config.configFilePath An optional path to the netlify configuration file e.g. netlify.toml - * @param {string=} config.packagePath - * @param {string=} config.repositoryRoot - * @param {string=} config.host - * @param {string=} config.pathPrefix - * @param {string=} config.scheme - * @returns {ReturnType} - */ - // @ts-expect-error TS(7023) FIXME: 'getConfig' implicitly has return type 'any' becau... Remove this comment to see the full error message - async getConfig(config) { + /** Find and resolve the Netlify configuration */ + async getConfig(config: { + cwd: string + token?: string | null + // eslint-disable-next-line @typescript-eslint/no-explicit-any + state?: any + offline?: boolean + /** An optional path to the netlify configuration file e.g. netlify.toml */ + configFilePath?: string + packagePath?: string + repositoryRoot?: string + host?: string + pathPrefix?: string + scheme?: string + }): ReturnType { // the flags that are passed to the command like `--debug` or `--offline` const flags = this.opts() @@ -754,10 +686,15 @@ export default class BaseCommand extends Command { * Returns the context that should be used in case one hasn't been explicitly * set. The default context is `dev` most of the time, but some commands may * wish to override that. - * - * @returns {'production' | 'dev'} */ - getDefaultContext() { + getDefaultContext(): 'production' | 'dev' { return this.name() === 'serve' ? 'production' : 'dev' } + + /** + * Retrieve feature flags for this site + */ + getFeatureFlag(flagName: string): T { + return this.netlify.siteInfo.feature_flags?.[flagName] || null + } } diff --git a/src/commands/dev/dev.ts b/src/commands/dev/dev.ts index 62eda3e7a43..50f1da5a745 100644 --- a/src/commands/dev/dev.ts +++ b/src/commands/dev/dev.ts @@ -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' /** * @@ -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), @@ -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 diff --git a/src/commands/link/link.ts b/src/commands/link/link.ts index cce6a46a7bb..30889cb2525 100644 --- a/src/commands/link/link.ts +++ b/src/commands/link/link.ts @@ -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 @@ -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`)) } @@ -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 @@ -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 @@ -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 diff --git a/src/commands/serve/serve.ts b/src/commands/serve/serve.ts index 762ab246662..d136d55c4bf 100644 --- a/src/commands/serve/serve.ts +++ b/src/commands/serve/serve.ts @@ -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 }), @@ -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 diff --git a/src/commands/types.d.ts b/src/commands/types.d.ts index dcb4c643b85..f776e22570e 100644 --- a/src/commands/types.d.ts +++ b/src/commands/types.d.ts @@ -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 @@ -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 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 - globalConfig: unknown + siteInfo: $TSFixMe + config: PatchedConfig + cachedConfig: Record + globalConfig: $TSFixMe state: StateConfig } diff --git a/src/lib/functions/netlify-function.ts b/src/lib/functions/netlify-function.ts index 8acd316709c..f4f55501771 100644 --- a/src/lib/functions/netlify-function.ts +++ b/src/lib/functions/netlify-function.ts @@ -121,7 +121,7 @@ export default class NetlifyFunction { } if (extension === '.js') { - return '.js' + return '.mjs' } } diff --git a/src/lib/functions/runtimes/js/builders/zisi.ts b/src/lib/functions/runtimes/js/builders/zisi.ts index e85de6f0429..1d08ab35112 100644 --- a/src/lib/functions/runtimes/js/builders/zisi.ts +++ b/src/lib/functions/runtimes/js/builders/zisi.ts @@ -175,7 +175,7 @@ export default async function handler({ config, directory, errorExit, func, meta featureFlags.zisi_pure_esm_mjs = true } else { // We must use esbuild for certain file extensions. - const mustTranspile = ['.js', '.ts', '.mts', '.cts'].includes(path.extname(func.mainFile)) + const mustTranspile = ['.mjs', '.ts', '.mts', '.cts'].includes(path.extname(func.mainFile)) const mustUseEsbuild = hasTypeModule || mustTranspile if (mustUseEsbuild && !functionsConfig['*'].nodeBundler) { diff --git a/src/utils/command-helpers.ts b/src/utils/command-helpers.ts index ee53421d7f8..5c884e28b51 100644 --- a/src/utils/command-helpers.ts +++ b/src/utils/command-helpers.ts @@ -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) { diff --git a/src/utils/detect-server-settings.ts b/src/utils/detect-server-settings.ts index 18bc5a1904e..5fe75bea9cb 100644 --- a/src/utils/detect-server-settings.ts +++ b/src/utils/detect-server-settings.ts @@ -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. @@ -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 } @@ -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), } } @@ -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} */ -// @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 => { // 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} */ - -// @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 => { validateProperty(devConfig, 'framework', 'string') /** @type {Partial} */ @@ -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`) @@ -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, diff --git a/src/utils/init/utils.ts b/src/utils/init/utils.ts index 0e8bfb3f1f0..f349693f0d6 100644 --- a/src/utils/init/utils.ts +++ b/src/utils/init/utils.ts @@ -1,9 +1,12 @@ 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' @@ -11,43 +14,44 @@ 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} 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, } @@ -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() } @@ -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 { diff --git a/tests/integration/commands/deploy/deploy.test.js b/tests/integration/commands/deploy/deploy.test.js index 4460d6eef80..0bdfbd110c4 100644 --- a/tests/integration/commands/deploy/deploy.test.js +++ b/tests/integration/commands/deploy/deploy.test.js @@ -495,7 +495,7 @@ describe.skipIf(process.env.NETLIFY_TEST_DISABLE_LIVE === 'true').concurrent('co export default async () => new Response("Internal V2 API") export const config = { path: "/internal-v2-func" } `, - path: '.netlify/functions-internal/func-4.js', + path: '.netlify/functions-internal/func-4.mjs', }) .buildAsync() diff --git a/tests/integration/commands/functions-with-args/functions-with-args.test.js b/tests/integration/commands/functions-with-args/functions-with-args.test.js index bb63fd0e1ee..181c89b459e 100644 --- a/tests/integration/commands/functions-with-args/functions-with-args.test.js +++ b/tests/integration/commands/functions-with-args/functions-with-args.test.js @@ -597,7 +597,7 @@ exports.handler = async () => ({ }) }) - test('Serves functions with a `.js` extension', async (t) => { + test('Serves functions with a `.mjs` extension', async (t) => { await withSiteBuilder('function-mjs', async (builder) => { const bundlerConfig = args.includes('esbuild') ? { node_bundler: 'esbuild' } : {} @@ -610,7 +610,7 @@ exports.handler = async () => ({ }, }) .withContentFile({ - path: 'functions/hello.js', + path: 'functions/hello.mjs', content: ` const handler = async () => { return { diff --git a/tests/integration/frameworks/hugo.test.ts b/tests/integration/frameworks/hugo.test.ts index 9990ea3060b..61ec0d9dec7 100644 --- a/tests/integration/frameworks/hugo.test.ts +++ b/tests/integration/frameworks/hugo.test.ts @@ -1,11 +1,11 @@ import { expect, test } from 'vitest' import { FixtureTestContext, setupFixtureTests } from '../utils/fixture.js' -import got from '../utils/got.js' +import fetch from 'node-fetch' setupFixtureTests('hugo-site', { devServer: true }, () => { test('should not infinite redirect when -d flag is passed', async ({ devServer: { url } }) => { - const response = await got(`${url}/`).text() + const response = await fetch(`${url}/`).then((res) => res.text()) expect(response).toContain('Home page!') })