Skip to content

Commit

Permalink
fix: handle case of implicit plugins/index.js files during migration (#…
Browse files Browse the repository at this point in the history
…22501)

* handle case of implicit index.js

* fix test error message

* fix test
  • Loading branch information
lmiller1990 authored Jun 24, 2022
1 parent 2f8475c commit c7f63e1
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 6 deletions.
14 changes: 13 additions & 1 deletion packages/data-context/src/actions/MigrationActions.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -139,13 +149,15 @@ 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)
ipc.killChildProcess()
})

ipc.on('childProcess:unhandledError', (error) => {
debug('childProcess:unhandledError')
reject(error)
ipc.killChildProcess()
})
Expand Down
69 changes: 67 additions & 2 deletions packages/data-context/src/sources/migration/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down Expand Up @@ -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}'
Expand All @@ -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(/^.\//)

Expand All @@ -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)`
Expand Down
21 changes: 19 additions & 2 deletions packages/launchpad/cypress/e2e/migration.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
})
})
2 changes: 1 addition & 1 deletion scripts/gulp/tasks/gulpE2ETestScaffold.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')}
Expand Down
Original file line number Diff line number Diff line change
@@ -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).
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"pluginsFile": "cypress/plugins"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = (on, config) => config
Empty file.
Original file line number Diff line number Diff line change
@@ -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)
},
},
})

9 comments on commit c7f63e1

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on c7f63e1 Jun 24, 2022

Choose a reason for hiding this comment

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

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.3.0/linux-x64/develop-c7f63e1f2973b2de6478e1fd73262ee4da627273/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on c7f63e1 Jun 24, 2022

Choose a reason for hiding this comment

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

Circle has built the linux arm64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.3.0/linux-arm64/develop-c7f63e1f2973b2de6478e1fd73262ee4da627273/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on c7f63e1 Jun 24, 2022

Choose a reason for hiding this comment

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

Circle has built the darwin arm64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.3.0/darwin-arm64/develop-c7f63e1f2973b2de6478e1fd73262ee4da627273/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on c7f63e1 Jun 24, 2022

Choose a reason for hiding this comment

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

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.3.0/darwin-x64/develop-c7f63e1f2973b2de6478e1fd73262ee4da627273/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on c7f63e1 Jun 24, 2022

Choose a reason for hiding this comment

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

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.3.0/linux-x64/develop-c7f63e1f2973b2de6478e1fd73262ee4da627273/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on c7f63e1 Jun 24, 2022

Choose a reason for hiding this comment

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

Circle has built the linux arm64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.3.0/linux-arm64/develop-c7f63e1f2973b2de6478e1fd73262ee4da627273/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on c7f63e1 Jun 24, 2022

Choose a reason for hiding this comment

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

Circle has built the darwin arm64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.3.0/darwin-arm64/develop-c7f63e1f2973b2de6478e1fd73262ee4da627273/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on c7f63e1 Jun 24, 2022

Choose a reason for hiding this comment

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

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.3.0/darwin-x64/develop-c7f63e1f2973b2de6478e1fd73262ee4da627273/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on c7f63e1 Jun 24, 2022

Choose a reason for hiding this comment

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

Circle has built the win32 x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/10.3.0/win32-x64/develop-c7f63e1f2973b2de6478e1fd73262ee4da627273/cypress.tgz

Please sign in to comment.