fix: handle case of implicit plugins/index.js files during migration (#22501)

* handle case of implicit index.js

* fix test error message

* fix test
This commit is contained in:
Lachlan Miller
2022-06-25 00:48:42 +10:00
committed by GitHub
parent 2f8475cbc0
commit c7f63e1f29
10 changed files with 150 additions and 6 deletions
@@ -1,5 +1,6 @@
/* eslint-disable no-dupe-class-members */
import path from 'path'
import debugLib from 'debug'
import { fork } from 'child_process'
import fs from 'fs-extra'
import semver from 'semver'
@@ -29,11 +30,14 @@ import {
getSpecPattern,
legacyOptions,
legacyIntegrationFolder,
getLegacyPluginsCustomFilePath,
} from '../sources/migration'
import { makeCoreData } from '../data'
import { LegacyPluginsIpc } from '../data/LegacyPluginsIpc'
import { hasTypeScriptInstalled } from '../util'
const debug = debugLib('cypress:data-context:MigrationActions')
const tsNode = require.resolve('@packages/server/lib/plugins/child/register_ts_node')
export function getConfigWithDefaults (legacyConfig: any) {
@@ -71,7 +75,11 @@ export function getDiff (oldConfig: any, newConfig: any) {
}
export async function processConfigViaLegacyPlugins (projectRoot: string, legacyConfig: LegacyCypressConfigJson): Promise<LegacyCypressConfigJson> {
const pluginFile = legacyConfig.pluginsFile ?? await tryGetDefaultLegacyPluginsFile(projectRoot)
const pluginFile = legacyConfig.pluginsFile
? await getLegacyPluginsCustomFilePath(projectRoot, legacyConfig.pluginsFile)
: await tryGetDefaultLegacyPluginsFile(projectRoot)
debug('found legacy pluginsFile at %s', pluginFile)
return new Promise((resolve, reject) => {
// couldn't find a pluginsFile
@@ -121,10 +129,12 @@ export async function processConfigViaLegacyPlugins (projectRoot: string, legacy
const legacyConfigWithDefaults = getConfigWithDefaults(legacyConfig)
ipc.on('ready', () => {
debug('legacyConfigIpc:ready')
ipc.send('loadLegacyPlugins', legacyConfigWithDefaults)
})
ipc.on('loadLegacyPlugins:reply', (modifiedLegacyConfig) => {
debug('loadLegacyPlugins:reply')
const diff = getDiff(legacyConfigWithDefaults, modifiedLegacyConfig)
// if env is updated by plugins, avoid adding it to the config file
@@ -139,6 +149,7 @@ export async function processConfigViaLegacyPlugins (projectRoot: string, legacy
})
ipc.on('loadLegacyPlugins:error', (error) => {
debug('loadLegacyPlugins:error')
error = getError('LEGACY_CONFIG_ERROR_DURING_MIGRATION', cwd, error)
reject(error)
@@ -146,6 +157,7 @@ export async function processConfigViaLegacyPlugins (projectRoot: string, legacy
})
ipc.on('childProcess:unhandledError', (error) => {
debug('childProcess:unhandledError')
reject(error)
ipc.killChildProcess()
})
@@ -51,6 +51,8 @@ export async function createConfigString (cfg: LegacyCypressConfigJson, options:
const newConfig = reduceConfig(cfg, options)
const relativePluginPath = await getPluginRelativePath(cfg, options.projectRoot)
debug('creating cypress.config from newConfig %o relativePluginPath %s options %o', newConfig, relativePluginPath, options)
return createCypressConfig(newConfig, relativePluginPath, options)
}
@@ -188,6 +190,67 @@ function formatObjectForConfig (obj: Record<string, unknown>) {
return JSON.stringify(obj, null, 2).replace(/^[{]|[}]$/g, '') // remove opening and closing {}
}
// Returns path of `pluginsFile` relative to projectRoot
// Considers cases of:
// 1. `pluginsFile` pointing to a directory containing an index file
// 2. `pluginsFile` pointing to a file
//
// Example:
// - projectRoot
// --- cypress
// ----- plugins
// -------- index.js
// Both { "pluginsFile": "cypress/plugins"} and { "pluginsFile": "cypress/plugins/index.js" } are valid.
//
// Will return `cypress/plugins/index.js` for both cases.
export async function getLegacyPluginsCustomFilePath (projectRoot: string, pluginPath: string): Promise<string> {
debug('looking for pluginPath %s in projectRoot %s', pluginPath, projectRoot)
const pluginLoc = path.join(projectRoot, pluginPath)
debug('fs.stats on %s', pluginLoc)
let stats: fs.Stats
try {
stats = await fs.stat(pluginLoc)
} catch (e) {
throw Error(`Looked for pluginsFile at ${pluginPath}, but it was not found.`)
}
if (stats.isFile()) {
debug('found pluginsFile %s', pluginLoc)
return pluginPath
}
if (stats.isDirectory()) {
// Although you are supposed to pass a file to `pluginsFile`, we also supported
// passing a directory containing an `index` file.
// If pluginsFile is a directory, see if there is an index.{js,ts} and grab that.
// {
// "pluginsFile": "plugins"
// }
// Where cypress/plugins contains an `index.{js,ts,coffee...}` but NOT `index.d.ts`.
const ls = await fs.readdir(pluginLoc)
const indexFile = ls.find((file) => file.startsWith('index.') && !file.endsWith('.d.ts'))
debug('pluginsFile was a directory containing %o, looks like we want %s', ls, indexFile)
if (indexFile) {
const pathToIndex = path.join(pluginPath, indexFile)
debug('found pluginsFile %s', pathToIndex)
return pathToIndex
}
}
debug('error, could not find path to pluginsFile!')
throw Error(`Could not find pluginsFile. Received projectRoot ${projectRoot} and pluginPath: ${pluginPath}`)
}
async function createE2ETemplate (pluginPath: string | undefined, createConfigOptions: CreateConfigOptions, options: Record<string, unknown>) {
if (createConfigOptions.shouldAddCustomE2ESpecPattern && !options.specPattern) {
options.specPattern = 'cypress/e2e/**/*.{js,jsx,ts,tsx}'
@@ -201,8 +264,7 @@ async function createE2ETemplate (pluginPath: string | undefined, createConfigOp
`
}
const pluginFile = await fs.readFile(path.join(createConfigOptions.projectRoot, pluginPath), 'utf8')
let relPluginsPath
let relPluginsPath: string
const startsWithDotSlash = new RegExp(/^.\//)
@@ -212,6 +274,9 @@ async function createE2ETemplate (pluginPath: string | undefined, createConfigOp
relPluginsPath = `'./${pluginPath}'`
}
const legacyPluginFileLoc = await getLegacyPluginsCustomFilePath(createConfigOptions.projectRoot, pluginPath)
const pluginFile = await fs.readFile(path.join(createConfigOptions.projectRoot, legacyPluginFileLoc), 'utf8')
const requirePlugins = hasDefaultExport(pluginFile)
? `return require(${relPluginsPath}).default(on, config)`
: `return require(${relPluginsPath})(on, config)`
+19 -2
View File
@@ -1075,6 +1075,22 @@ describe('Full migration flow for each project', { retries: { openMode: 0, runMo
checkOutcome()
})
it('completes journey for migration-e2e-plugins-implicit-index-js', () => {
startMigrationFor('migration-e2e-plugins-implicit-index-js')
// no specs, nothing to rename?
cy.get(renameAutoStep).should('exist')
// no CT
cy.get(renameManualStep).should('not.exist')
cy.get(renameSupportStep).should('exist')
cy.get(setupComponentStep).should('not.exist')
cy.get(configFileStep).should('exist')
runAutoRename()
renameSupport()
migrateAndVerifyConfig()
checkOutcome()
})
it('completes journey for migration-e2e-fully-custom', () => {
startMigrationFor('migration-e2e-fully-custom')
// integration folder and testFiles are custom, cannot rename anything
@@ -1698,7 +1714,8 @@ describe('Migrate custom config files', () => {
it('shows error if plugins file do not exist', () => {
scaffoldAndVisitLaunchpad('migration', ['--config-file', 'erroredConfigFiles/incorrectPluginsFile.json'])
cy.contains(`${getPathForPlatform('foo/bar')} file threw an error.`)
cy.contains('Please ensure your pluginsFile is valid and relaunch the migration tool to migrate to Cypress version 10.0.0.')
const err = `Looked for pluginsFile at foo/bar, but it was not found.`
cy.contains(err)
})
})
+1 -1
View File
@@ -37,7 +37,7 @@ export async function _e2eTestScaffold (cleanupEmpty = true) {
await fs.writeFile(
OUTPUT_PATH,
`/* eslint-disable */
// Auto-generated by ${path.basename(__filename)}
// Auto-generated by ${path.basename(__filename)} (run yarn gulp e2eTestScaffold)
export const fixtureDirs = [
${allDirs
.map((dir) => ` '${path.basename(dir)}'`).join(',\n')}
@@ -0,0 +1,35 @@
## Migration E2E Plugins Implicit index.js
An e2e project with 1 spec file. The purpose of this project is to cover the case where `cypress.json` has `pluginsFile` configured to specify a folder (containing an `index.js`) instead of a file. Technically, `pluginsFile` should point to a file, but Cypress 9 also supported a folder containing an `index.js` file, too.
Reference issue: https://github.com/cypress-io/cypress/issues/22461
The following migration steps will be used during this migration:
- [x] automatic file rename
- [ ] manual file rename
- [x] rename support
- [x] update config file
- [ ] setup component testing
## Automatic Migration
| Before | After|
|---|---|
| `cypress/integration/foo.spec.js` | `cypress/e2e/foo.cy.js` |
## Manual Files
This step is not used.
## Rename supportFile
The project has a default support file, `cypress/support/index.js`. We can rename it for them to `cypress/support/e2e.js`.
| Before | After|
|---|---|
| `cypress/support/index.js` | `cypress/support/e2e.js` |
## Update Config
The expected output is in [`expected-cypress.config.js`](./expected-cypress.config.js).
@@ -0,0 +1,3 @@
{
"pluginsFile": "cypress/plugins"
}
@@ -0,0 +1 @@
module.exports = (on, config) => config
@@ -0,0 +1,11 @@
const { defineConfig } = require('cypress')
module.exports = defineConfig({
e2e: {
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents (on, config) {
return require('./cypress/plugins')(on, config)
},
},
})