fix: types for config changes

This commit is contained in:
Tim Griesser
2022-02-16 16:14:27 -05:00
parent 025f5cdda3
commit 9307dc01ec
9 changed files with 108 additions and 59 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ const issuedWarnings = new Set()
const validateNoBreakingOptions = (breakingCfgOptions, cfg, onWarning, onErr) => {
breakingCfgOptions.forEach(({ name, errorKey, newName, isWarning, value }) => {
if (cfg.hasOwnProperty(name)) {
if (_.has(cfg, name)) {
if (value && cfg[name] !== value) {
// Bail if a value is specified but the config does not have that value.
return
+5 -5
View File
@@ -538,22 +538,22 @@ export const breakingOptions: Array<BreakingOption> = [
export const breakingRootOptions: Array<BreakingOption> = [
{
name: 'supportFile',
errorKey: 'SUPPORT_FILE_ROOT_NOT_SUPPORTED',
errorKey: 'REMOVED_ROOT_CONFIG_OPTION',
isWarning: false,
},
{
name: 'specPattern',
errorKey: 'SPEC_PATTERN_ROOT_NOT_SUPPORTED',
errorKey: 'REMOVED_ROOT_CONFIG_OPTION',
isWarning: false,
},
{
name: 'excludeSpecPattern',
errorKey: 'SPEC_EXCLUDE_PATTERN_ROOT_NOT_SUPPORTED',
errorKey: 'REMOVED_ROOT_CONFIG_OPTION',
isWarning: false,
},
{
name: 'baseUrl',
errorKey: 'BASE_URL_ROOT_NOT_SUPPORTED',
errorKey: 'REMOVED_ROOT_CONFIG_OPTION',
isWarning: false,
},
]
@@ -563,7 +563,7 @@ export const testingTypeBreakingOptions: { e2e: Array<BreakingOption>, component
component: [
{
name: 'baseUrl',
errorKey: 'BASE_URL_CT_NOT_SUPPORTED',
errorKey: 'CT_CONFIG_NOT_SUPPORTED',
isWarning: false,
},
],
+20 -13
View File
@@ -1,29 +1,36 @@
// TODO: Remove this file when we land type-safe @packages/config
type ErrResult = {
key: string
value: any
type: string
}
export default {
isValidClientCertificatesSet(_key: string, certsForUrls: any[]): string | true {},
isValidClientCertificatesSet(_key: string, certsForUrls: any[]): ErrResult | true {},
isValidBrowser(browser: any): string | true {},
isValidBrowser(browser: any): ErrResult | true {},
isValidBrowserList(key: string, browsers: any[]): string | true {},
isValidBrowserList(key: string, browsers: any[]): ErrResult | true {},
isValidRetriesConfig(key: string, value: any): string | true {},
isValidRetriesConfig(key: string, value: any): ErrResult | true {},
isPlainObject(key: string, value: any): string | true {},
isPlainObject(key: string, value: any): ErrResult | true {},
isNumber(key: string, value: any): string | true {},
isNumber(key: string, value: any): ErrResult | true {},
isNumberOrFalse(key: string, value: any): string | true {},
isNumberOrFalse(key: string, value: any): ErrResult | true {},
isFullyQualifiedUrl(key: string, value: string): string | true {},
isFullyQualifiedUrl(key: string, value: string): ErrResult | true {},
isBoolean(key: string, value: any): string | true {},
isBoolean(key: string, value: any): ErrResult | true {},
isString(key: string, value: any): string | true {},
isString(key: string, value: any): ErrResult | true {},
isArray(key: string, value: any): string | true {},
isArray(key: string, value: any): ErrResult | true {},
isStringOrFalse(key: string, value: any): string | true {},
isStringOrFalse(key: string, value: any): ErrResult | true {},
isStringOrArrayOfStrings(key: string, value: any): string | true {},
isStringOrArrayOfStrings(key: string, value: any): ErrResult | true {},
isOneOf(...any: any[]): (key: any, value: any) => boolean {},
}
@@ -80,7 +80,7 @@ export class ProjectConfigIpc extends EventEmitter {
* sourcing the config (script error, etc.)
*/
once(evt: 'loadConfig:reply', listener: (payload: SerializedLoadConfigReply) => void): this
once(evt: 'loadConfig:error', listener: (realErrorCode: string, requiredFile: string, message: string) => void): this
once(evt: 'loadConfig:error', listener: (err: SerializedError) => void): this
/**
* When
@@ -16,11 +16,11 @@ import debugLib from 'debug'
import pDefer from 'p-defer'
import fs from 'fs'
import { getError, AllCypressErrors, AllCypressErrorNames, CypressError } from '@packages/errors'
import { getError, CypressError, ConfigValidationError } from '@packages/errors'
import type { DataContext } from '..'
import { LoadConfigReply, SetupNodeEventsReply, ProjectConfigIpc, IpcHandler } from './ProjectConfigIpc'
import assert from 'assert'
import type { AllModeOptions, FoundBrowser, FullConfig, TestingType } from '@packages/types'
import type { AllModeOptions, BreakingErrResult, BreakingOption, FoundBrowser, FullConfig, TestingType } from '@packages/types'
import type { BaseErrorDataShape, WarningError } from '.'
import { autoBindDebug } from '../util/autoBindDebug'
@@ -39,7 +39,7 @@ export interface SetupFullConfigOptions {
options: Partial<AllModeOptions>
}
type ConifigErrHandler<T> = <Name extends AllCypressErrorNames>(msg: Name, ...params: Parameters<typeof AllCypressErrors[Name]>) => T
type BreakingValidationFn<T> = (type: BreakingOption, val: BreakingErrResult) => T
/**
* All of the APIs injected from @packages/server & @packages/config
@@ -48,12 +48,12 @@ type ConifigErrHandler<T> = <Name extends AllCypressErrorNames>(msg: Name, ...pa
export interface InjectedConfigApi {
cypressVersion: string
getServerPluginHandlers: () => IpcHandler[]
validateConfig<T extends Cypress.ConfigOptions>(config: Partial<T>, onErr: (errMsg: string) => never): T
validateConfig<T extends Cypress.ConfigOptions>(config: Partial<T>, onErr: (errMsg: ConfigValidationError | string) => never): T
allowedConfig(config: Cypress.ConfigOptions): Cypress.ConfigOptions
updateWithPluginValues(config: FullConfig, modifiedConfig: Partial<Cypress.ConfigOptions>): FullConfig
setupFullConfigWithDefaults(config: SetupFullConfigOptions): Promise<FullConfig>
validateRootConfigBreakingChanges<T extends Cypress.ConfigOptions>(config: Partial<T>, onWarning: ConifigErrHandler<CypressError>, onErr: ConifigErrHandler<never>): T
validateTestingTypeConfigBreakingChanges<T extends Cypress.ConfigOptions>(config: Partial<T>, testingType: Cypress.TestingType, onWarning: ConifigErrHandler<CypressError>, onErr: ConifigErrHandler<never>): T
validateRootConfigBreakingChanges<T extends Cypress.ConfigOptions>(config: Partial<T>, onWarning: BreakingValidationFn<CypressError>, onErr: BreakingValidationFn<never>): T
validateTestingTypeConfigBreakingChanges<T extends Cypress.ConfigOptions>(config: Partial<T>, testingType: Cypress.TestingType, onWarning: BreakingValidationFn<CypressError>, onErr: BreakingValidationFn<never>): T
}
type State<S, V = undefined> = V extends undefined ? {state: S, value?: V } : {state: S, value: V}
@@ -573,11 +573,11 @@ export class ProjectLifecycleManager {
return this.ctx._apis.configApi.validateTestingTypeConfigBreakingChanges(
config,
this._currentTestingType,
(warning, ...args) => {
return getError(warning, ...args)
(type, ...args) => {
return getError(type, ...args)
},
(err, ...args) => {
throw getError(err, ...args)
(type, ...args) => {
throw getError(type, ...args)
},
)
}
@@ -585,26 +585,22 @@ export class ProjectLifecycleManager {
private validateConfigRoot (config: Cypress.ConfigOptions) {
return this.ctx._apis.configApi.validateRootConfigBreakingChanges(
config,
(warning, ...args) => {
return getError(warning, ...args)
(type, obj) => {
return getError(type, obj)
},
(err, ...args) => {
throw getError(err, ...args)
(type, obj) => {
throw getError(type, obj)
},
)
}
private validateConfigFile (file: string | false, config: Cypress.ConfigOptions) {
this.ctx._apis.configApi.validateConfig(config, (errMsg) => {
if (!file) {
// This should never happen, b/c if the config file is false, the config
// should be the default one
throw getError('CONFIG_VALIDATION_ERROR', errMsg)
if (_.isString(errMsg)) {
throw getError('CONFIG_VALIDATION_MSG_ERROR', 'configFile', file || null, errMsg)
}
const base = path.basename(file)
throw getError('SETTINGS_VALIDATION_ERROR', base, errMsg)
throw getError('CONFIG_VALIDATION_ERROR', 'configFile', file || null, errMsg)
})
}
@@ -1034,21 +1030,13 @@ export class ProjectLifecycleManager {
dfd.resolve({ ...val, initialConfig: JSON.parse(val.initialConfig) })
})
ipc.once('loadConfig:error', (type, ...args) => {
debug('loadConfig:error %s, rejecting', type)
ipc.once('loadConfig:error', (err) => {
this.killChildProcess(child)
const err = getError(type, ...args)
// if it's a non-cypress error, restore the initial error
if (!(err.message?.length)) {
err.isCypressErr = false
err.message = args[1]
err.code = type
err.name = type
if (err.isCypressErr) {
dfd.reject(err)
} else {
dfd.reject(getError('CHILD_PROCESS_UNEXPECTED_ERROR', this.configFilePath, err))
}
dfd.reject(err)
})
debug('trigger the load of the file')
+1 -1
View File
@@ -62,7 +62,7 @@ export interface SerializedError {
errorType?: string
stack?: string
annotated?: string
message?: string
message: string
name: string
isCypressErr?: boolean
}
+26 -4
View File
@@ -4,11 +4,13 @@ import chalk from 'chalk'
import _ from 'lodash'
import path from 'path'
import stripAnsi from 'strip-ansi'
import type { BreakingErrResult } from '@packages/types'
import { humanTime, logError, parseResolvedPattern, pluralize } from './errorUtils'
import { errPartial, errTemplate, fmt, theme, PartialErr } from './errTemplate'
import { stackWithoutMessage } from './stackUtils'
import type { ClonedError, ConfigValidationError, CypressError, ErrorLike, ErrTemplateResult } from './errorTypes'
import type { ClonedError, ConfigValidationError, CypressError, ErrTemplateResult, ErrorLike } from './errorTypes'
const ansi_up = new AU()
@@ -636,7 +638,7 @@ export const AllCypressErrors = {
${fmt.stackTrace(err)}`
},
CHILD_PROCESS_UNEXPECTED_ERROR: (configFilePath: string, err: Error) => {
CHILD_PROCESS_UNEXPECTED_ERROR: (configFilePath: string, err: ErrorLike) => {
return errTemplate`
We stopped running your tests because a plugin.
@@ -1198,6 +1200,24 @@ export const AllCypressErrors = {
`
},
REMOVED_ROOT_CONFIG_OPTION: (errShape: BreakingErrResult) => {
return errTemplate`\
The ${fmt.highlight(errShape.name)} configuration option was removed from the root of the Cypress config object version 10.0.0.
Please update this option under each testing type property.
https://on.cypress.io/migration-guide`
},
CT_CONFIG_NOT_SUPPORTED: (errShape: BreakingErrResult) => {
return errTemplate`\
The ${fmt.highlight(errShape.name)} configuration option is not valid in Component testing.
Please remove or add this option under e2e testing type property.
https://on.cypress.io/migration-guide`
},
} as const
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -1222,12 +1242,14 @@ export function getMsgByType<Type extends keyof AllCypressErrorObj> (type: Type,
* @param args
* @returns
*/
export const getError = function <Type extends keyof AllCypressErrorObj> (type: Type, ...args: Parameters<AllCypressErrorObj[Type]>) {
export const getError = function <Type extends keyof AllCypressErrorObj> (type: Type, ...args: Parameters<AllCypressErrorObj[Type]>): CypressError {
// If we don't know this "type" of error, return as a non-cypress error
if (!AllCypressErrors[type]) {
const err = new Error(args[1] || type) as ErrorLike
const err = new Error(args[1] || type) as CypressError
err.isCypressErr = false
err.type = type
err.messageMarkdown = err.message
return err
}
@@ -1077,5 +1077,15 @@ describe('visual error templates', () => {
default: ['spec.{ts,js}', 'spec.ts,spec.js'],
}
},
REMOVED_ROOT_CONFIG_OPTION: () => {
return {
default: [{ name: 'specPattern', configFile: '/path/to/configFile.ts' }],
}
},
CT_CONFIG_NOT_SUPPORTED: () => {
return {
default: [{ name: 'baseUrl', configFile: '/path/to/configFile.ts' }],
}
},
})
})
+22
View File
@@ -44,3 +44,25 @@ export interface SettingsOptions {
testingType?: 'component' |'e2e'
args?: AllModeOptions
}
// Todo, move to @packages/config when it becomes type-safe
export type BreakingOption =
| 'RENAMED_CONFIG_OPTION'
| 'EXPERIMENTAL_COMPONENT_TESTING_REMOVED'
| 'EXPERIMENTAL_SAMESITE_REMOVED'
| 'EXPERIMENTAL_NETWORK_STUBBING_REMOVED'
| 'EXPERIMENTAL_RUN_EVENTS_REMOVED'
| 'EXPERIMENTAL_SHADOW_DOM_REMOVED'
| 'FIREFOX_GC_INTERVAL_REMOVED'
| 'NODE_VERSION_DEPRECATION_SYSTEM'
| 'NODE_VERSION_DEPRECATION_BUNDLED'
| 'REMOVED_ROOT_CONFIG_OPTION'
| 'CT_CONFIG_NOT_SUPPORTED'
export type BreakingErrResult = {
name: string
newName?: string
value?: any
configFile: string
}