fix(launchpad): support default export (#20383)

Co-authored-by: David Munechika <david.munechika@gatech.edu>
Co-authored-by: ElevateBart <ledouxb@gmail.com>
Co-authored-by: Barthélémy Ledoux <bart@cypress.io>
This commit is contained in:
Lachlan Miller
2022-03-02 09:33:44 +10:00
committed by GitHub
parent e9125f4b78
commit 7a029256e8
22 changed files with 223 additions and 144 deletions
@@ -7,7 +7,7 @@ module.exports = defineConfig({
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
return require('./')(on, config)
return require('./cypress/plugins/index.js')(on, config)
},
},
})
@@ -22,7 +22,7 @@ module.exports = defineConfig({
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
return require('./')(on, config)
return require('./cypress/plugins/index.js')(on, config)
},
baseUrl: 'localhost:3000',
},
@@ -38,7 +38,7 @@ module.exports = defineConfig({
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
return require('./')(on, config)
return require('./cypress/plugins/index.js')(on, config)
},
},
})
@@ -54,7 +54,7 @@ module.exports = defineConfig({
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
return require('./')(on, config)
return require('./cypress/plugins/index.js')(on, config)
},
retries: 2,
baseUrl: 'localhost:300',
@@ -71,7 +71,22 @@ module.exports = defineConfig({
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
return require('./')(on, config)
return require('./cypress/plugins/index.js')(on, config)
},
},
})
`
exports['cypress.config.js generation should handle export default in plugins file 1'] = `
import { defineConfig } from 'cypress'
export default 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/index.ts').default(on, config)
},
},
})
@@ -86,7 +101,7 @@ module.exports = defineConfig({
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
return require('./path/to/plugin/file')(on, config)
return require('./cypress/plugins/index.js')(on, config)
},
},
})
@@ -1,94 +0,0 @@
exports['cypress.config.js generation should create a string when passed only a global option 1'] = `
const { defineConfig } = require('cypress')
module.exports = defineConfig({
viewportWidth: 300,
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/index.js')(on, config)
},
},
})
`
exports['cypress.config.js generation should create a string when passed only a e2e options 1'] = `
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/index.js')(on, config)
},
baseUrl: 'localhost:3000',
},
})
`
exports['cypress.config.js generation should create a string when passed only a component options 1'] = `
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/index.js')(on, config)
},
},
})
`
exports['cypress.config.js generation should create a string for a config with global, component, and e2e options 1'] = `
const { defineConfig } = require('cypress')
module.exports = defineConfig({
viewportWidth: 300,
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/index.js')(on, config)
},
retries: 2,
baseUrl: 'localhost:300',
},
})
`
exports['cypress.config.js generation should create a string when passed an empty object 1'] = `
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/index.js')(on, config)
},
},
})
`
exports['cypress.config.js generation should exclude fields that are no longer valid 1'] = `
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('./path/to/plugin/file')(on, config)
},
},
})
`
+2
View File
@@ -14,6 +14,7 @@
"test-unit": "mocha -r @packages/ts/register --config ./test/.mocharc.js"
},
"dependencies": {
"@babel/parser": "7.13.0",
"@storybook/csf-tools": "^6.4.0-alpha.38",
"@urql/core": "2.3.1",
"@urql/exchange-execute": "1.1.0",
@@ -46,6 +47,7 @@
"wonka": "^4.0.15"
},
"devDependencies": {
"@babel/types": "7.17.0",
"@packages/config": "0.0.0-development",
"@packages/errors": "0.0.0-development",
"@packages/example": "0.0.0-development",
@@ -9,8 +9,10 @@ import { substitute } from './autoRename'
import { supportFileRegexps } from './regexps'
import type { MigrationFile } from '../MigrationDataSource'
import { toPosix } from '../../util'
import Debug from 'debug'
import dedent from 'dedent'
import { hasDefaultExport } from './parserUtils'
const debug = Debug('cypress:data-context:sources:migration:codegen')
type ConfigOptions = {
@@ -54,7 +56,10 @@ export interface CreateConfigOptions {
}
export async function createConfigString (cfg: OldCypressConfig, options: CreateConfigOptions) {
return createCypressConfig(reduceConfig(cfg), await getPluginRelativePath(cfg, options.projectRoot), options)
const newConfig = reduceConfig(cfg)
const relativePluginPath = await getPluginRelativePath(cfg, options.projectRoot)
return createCypressConfig(newConfig, relativePluginPath, options)
}
interface FileToBeMigratedManually {
@@ -172,7 +177,7 @@ function createCypressConfig (config: ConfigOptions, pluginPath: string, options
const globalString = Object.keys(config.global).length > 0 ? `${formatObjectForConfig(config.global)},` : ''
const componentString = options.hasComponentTesting ? createComponentTemplate(config.component) : ''
const e2eString = options.hasE2ESpec
? createE2eTemplate(pluginPath, options.hasPluginsFile, config.e2e)
? createE2ETemplate(pluginPath, options, config.e2e)
: ''
if (defineConfigAvailable(options.projectRoot)) {
@@ -202,18 +207,33 @@ function formatObjectForConfig (obj: Record<string, unknown>) {
return JSON.stringify(obj, null, 2).replace(/^[{]|[}]$/g, '') // remove opening and closing {}
}
function createE2eTemplate (pluginPath: string, hasPluginsFile: boolean, options: Record<string, unknown>) {
const requirePlugins = `return require('./${pluginPath}')(on, config)`
function createE2ETemplate (pluginPath: string, createConfigOptions: CreateConfigOptions, options: Record<string, unknown>) {
if (!createConfigOptions.hasPluginsFile) {
return dedent`
e2e: {
setupNodeEvents(on, config) {}
}
`
}
const setupNodeEvents = `// We've imported your old cypress plugins here.
const pluginFile = fs.readFileSync(path.join(createConfigOptions.projectRoot, pluginPath), 'utf8')
const relPluginsPath = path.normalize(`'./${pluginPath}'`)
const requirePlugins = hasDefaultExport(pluginFile)
? `return require(${relPluginsPath}).default(on, config)`
: `return require(${relPluginsPath})(on, config)`
const setupNodeEvents = dedent`
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
${hasPluginsFile ? requirePlugins : ''}
${requirePlugins}
}`
return `e2e: {
${setupNodeEvents},${formatObjectForConfig(options)}
},`
return dedent`
e2e: {
${setupNodeEvents},${formatObjectForConfig(options)}
},`
}
function createComponentTemplate (options: Record<string, unknown>) {
@@ -0,0 +1,47 @@
import { parse, ParserOptions } from '@babel/parser'
import { visit } from 'recast'
import type * as bt from '@babel/types'
const babelParserOptions: ParserOptions = {
sourceType: 'module',
strictMode: false,
tokens: true,
plugins: [
'decorators-legacy',
'doExpressions',
'objectRestSpread',
'classProperties',
'classPrivateProperties',
'classPrivateMethods',
'exportDefaultFrom',
'exportNamespaceFrom',
'asyncGenerators',
'functionBind',
'functionSent',
'dynamicImport',
'numericSeparator',
'optionalChaining',
'importMeta',
'bigInt',
'optionalCatchBinding',
'throwExpressions',
'nullishCoalescingOperator',
'typescript',
],
}
export function hasDefaultExport (src: string): boolean {
const ast = parse(src, babelParserOptions) as bt.File
let hasDefault = false
visit(ast, {
visitExportDefaultDeclaration () {
hasDefault = true
return false
},
})
return hasDefault
}
@@ -1,5 +1,6 @@
// necessary to have mocha types working correctly
import 'mocha'
import path from 'path'
import { e2eProjectDirs } from '@packages/frontend-shared/cypress/e2e/support/e2eProjectDirs'
import Fixtures from '@tooling/system-tests/lib/fixtures'
import { DataContext, DataContextConfig } from '../../src'
@@ -8,6 +9,10 @@ import type { BrowserApiShape } from '../../src/sources/BrowserDataSource'
import type { AppApiShape, AuthApiShape, ElectronApiShape, LocalSettingsApiShape, ProjectApiShape } from '../../src/actions'
import { InjectedConfigApi } from '../../src/data'
export function getSystemTestProject (project: typeof e2eProjectDirs[number]) {
return path.join(__dirname, '..', '..', '..', '..', 'system-tests', 'projects', project)
}
export async function scaffoldMigrationProject (project: typeof e2eProjectDirs[number]) {
Fixtures.removeProject(project)
@@ -1,17 +1,11 @@
import { expect } from 'chai'
import path from 'path'
import { e2eProjectDirs } from '@packages/frontend-shared/cypress/e2e/support/e2eProjectDirs'
import { createTestDataContext } from '../helper'
function getCurrentProject (project: typeof e2eProjectDirs[number]) {
return path.join(__dirname, '..', '..', '..', '..', '..', 'system-tests', 'projects', project)
}
import { createTestDataContext, getSystemTestProject } from '../helper'
describe('packagesToInstall', () => {
it('create-react-app-unconfigured', async () => {
const ctx = createTestDataContext()
ctx.coreData.currentProject = getCurrentProject('create-react-app-unconfigured')
ctx.coreData.currentProject = getSystemTestProject('create-react-app-unconfigured')
ctx.coreData.wizard.chosenFramework = 'crav5'
ctx.coreData.wizard.chosenBundler = 'webpack5'
@@ -23,7 +17,7 @@ describe('packagesToInstall', () => {
it('vueclivue2-unconfigured', async () => {
const ctx = createTestDataContext()
ctx.coreData.currentProject = getCurrentProject('vueclivue2-unconfigured')
ctx.coreData.currentProject = getSystemTestProject('vueclivue2-unconfigured')
ctx.coreData.wizard.chosenFramework = 'vuecli4vue2'
ctx.coreData.wizard.chosenBundler = 'webpack4'
@@ -35,7 +29,7 @@ describe('packagesToInstall', () => {
it('vueclivue3-unconfigured', async () => {
const ctx = createTestDataContext()
ctx.coreData.currentProject = getCurrentProject('vueclivue3-unconfigured')
ctx.coreData.currentProject = getSystemTestProject('vueclivue3-unconfigured')
ctx.coreData.wizard.chosenFramework = 'vuecli4vue3'
ctx.coreData.wizard.chosenBundler = 'webpack4'
@@ -47,7 +41,7 @@ describe('packagesToInstall', () => {
it('vuecli5vue3-unconfigured', async () => {
const ctx = createTestDataContext()
ctx.coreData.currentProject = getCurrentProject('vuecli5vue3-unconfigured')
ctx.coreData.currentProject = getSystemTestProject('vuecli5vue3-unconfigured')
ctx.coreData.wizard.chosenFramework = 'vuecli5vue3'
ctx.coreData.wizard.chosenBundler = 'webpack5'
@@ -59,7 +53,7 @@ describe('packagesToInstall', () => {
it('regular react project with vite', async () => {
const ctx = createTestDataContext()
ctx.coreData.currentProject = getCurrentProject('react-vite-ts-unconfigured')
ctx.coreData.currentProject = getSystemTestProject('react-vite-ts-unconfigured')
ctx.coreData.wizard.chosenFramework = 'react'
ctx.coreData.wizard.chosenBundler = 'vite'
@@ -71,7 +65,7 @@ describe('packagesToInstall', () => {
it('regular vue project with vite', async () => {
const ctx = createTestDataContext()
ctx.coreData.currentProject = getCurrentProject('vue3-vite-ts-unconfigured')
ctx.coreData.currentProject = getSystemTestProject('vue3-vite-ts-unconfigured')
ctx.coreData.wizard.chosenFramework = 'vue3'
ctx.coreData.wizard.chosenBundler = 'vite'
@@ -83,7 +77,7 @@ describe('packagesToInstall', () => {
it('nextjs-unconfigured', async () => {
const ctx = createTestDataContext()
ctx.coreData.currentProject = getCurrentProject('nextjs-unconfigured')
ctx.coreData.currentProject = getSystemTestProject('nextjs-unconfigured')
ctx.coreData.wizard.chosenFramework = 'nextjs'
ctx.coreData.wizard.chosenBundler = 'webpack4'
@@ -95,7 +89,7 @@ describe('packagesToInstall', () => {
it('nuxtjs-vue2-unconfigured', async () => {
const ctx = createTestDataContext()
ctx.coreData.currentProject = getCurrentProject('nuxtjs-vue2-unconfigured')
ctx.coreData.currentProject = getSystemTestProject('nuxtjs-vue2-unconfigured')
ctx.coreData.wizard.chosenFramework = 'nuxtjs'
ctx.coreData.wizard.chosenBundler = 'webpack4'
@@ -107,7 +101,7 @@ describe('packagesToInstall', () => {
it('pristine-with-e2e-testing-and-storybook', async () => {
const ctx = createTestDataContext()
ctx.coreData.currentProject = getCurrentProject('pristine-with-e2e-testing-and-storybook')
ctx.coreData.currentProject = getSystemTestProject('pristine-with-e2e-testing-and-storybook')
ctx.coreData.wizard.chosenFramework = 'react'
ctx.coreData.wizard.chosenBundler = 'webpack4'
@@ -120,7 +114,7 @@ describe('packagesToInstall', () => {
const ctx = createTestDataContext()
// this should never happen!
ctx.coreData.currentProject = getCurrentProject('pristine-with-e2e-testing-and-storybook')
ctx.coreData.currentProject = getSystemTestProject('pristine-with-e2e-testing-and-storybook')
ctx.coreData.wizard.chosenFramework = undefined
ctx.coreData.wizard.chosenBundler = undefined
@@ -13,11 +13,9 @@ import {
} from '../../../../src/sources/migration'
import { expect } from 'chai'
import { MigrationFile } from '../../../../src/sources'
import { scaffoldMigrationProject } from '../../helper'
import { scaffoldMigrationProject, getSystemTestProject } from '../../helper'
const root = path.join(__dirname, '..', '..', '..', '..', '..')
const projectRoot = path.join(root, 'system-tests', 'projects', 'migration-e2e-defaults')
const projectRoot = getSystemTestProject('migration-e2e-defaults')
describe('cypress.config.js generation', () => {
it('should create a string when passed only a global option', async () => {
@@ -112,7 +110,7 @@ describe('cypress.config.js generation', () => {
it('should exclude fields that are no longer valid', async () => {
const config = {
'$schema': 'http://someschema.com',
pluginsFile: 'path/to/plugin/file',
pluginsFile: './cypress/plugins/index.js',
componentFolder: 'path/to/component/folder',
}
@@ -126,6 +124,21 @@ describe('cypress.config.js generation', () => {
snapshot(generatedConfig)
})
it('should handle export default in plugins file', async () => {
const projectRoot = getSystemTestProject('migration-e2e-export-default')
const config = fs.readJsonSync(path.join(projectRoot, 'cypress.json'))
const generatedConfig = await createConfigString(config, {
hasE2ESpec: true,
hasComponentTesting: false,
hasPluginsFile: true,
projectRoot,
hasTypescript: true,
})
snapshot(generatedConfig)
})
})
describe('supportFilesForMigrationGuide', () => {
@@ -44,6 +44,7 @@ export const e2eProjectDirs = [
'migration-e2e-custom-test-files',
'migration-e2e-defaults',
'migration-e2e-defaults-no-specs',
'migration-e2e-export-default',
'migration-e2e-fully-custom',
'migration-e2e-no-plugins-support-file',
'migration-specs-already-migrated',
@@ -505,6 +505,25 @@ describe('Full migration flow for each project', { retries: { openMode: 2, runMo
checkOutcome()
})
it('completes journey for migration-e2e-export-default', () => {
startMigrationFor('migration-e2e-export-default')
// rename integration->e2e
cy.get(renameAutoStep).should('exist')
cy.get(renameManualStep).should('not.exist')
// cypress/support/index.ts -> cypress/support/e2e.ts
cy.get(renameSupportStep).should('exist')
// no component specs
cy.get(setupComponentStep).should('not.exist')
cy.get(configFileStep).should('exist')
runAutoRename()
renameSupport('ts')
migrateAndVerifyConfig('ts')
checkOutcome()
})
it('completes journey for migration-typescript-project', () => {
startMigrationFor('migration-typescript-project')
// defaults, rename all the things
@@ -0,0 +1,31 @@
## Migration E2E Defaults No Specs
An e2e project with TypeScript using `export default` in `cypress/plugins/index.ts`.
The following migration steps will be used during this migration:
- [ ] automatic file rename
- [ ] manual file rename
- [x] rename support
- [x] update config file
- [ ] setup component testing
## Automatic Migration
This step is not used, since there are no spec files to rename.
## 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.ts` | `cypress/support/e2e.ts` |
## Update Config
The expected output is in [`expected-cypress.config.ts`](./expected-cypress.config.ts).
@@ -0,0 +1 @@
{}
@@ -0,0 +1,6 @@
export default function (on, config) {
// eslint-disable-next-line
const foo: number = 123 // to make sure the parser handles TS
return config
}
@@ -0,0 +1,11 @@
import { defineConfig } from 'cypress'
export default 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/index.ts').default(on, config)
},
},
})
@@ -0,0 +1,6 @@
{
"dependencies": {
"typescript": "^4.0.0"
},
"_cySkipYarnInstall": true
}
@@ -0,0 +1 @@
{}
@@ -2,8 +2,6 @@ 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) {},
},
})
+1 -1
View File
@@ -6,7 +6,7 @@
"fixturesFolder": false,
"componentFolder": "src",
"testFiles": "**/*.spec.{tsx,js}",
"pluginsFile": "cypress/plugins/index.ts",
"pluginsFile": "cypress/plugins/index.js",
"e2e": {
"defaultCommandTimeout": 10000,
"slowTestThreshold": 5000
@@ -8,7 +8,7 @@ module.exports = defineConfig({
// 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/index.ts')(on, config)
return require('./cypress/plugins/index.js')(on, config)
},
defaultCommandTimeout: 10000,
slowTestThreshold: 5000,
+11 -8
View File
@@ -2331,6 +2331,14 @@
"@babel/helper-validator-identifier" "^7.14.9"
to-fast-properties "^2.0.0"
"@babel/types@7.17.0", "@babel/types@^7.0.0", "@babel/types@^7.12.10", "@babel/types@^7.12.11", "@babel/types@^7.12.7", "@babel/types@^7.13.0", "@babel/types@^7.15.0", "@babel/types@^7.15.4", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.0", "@babel/types@^7.6.0", "@babel/types@^7.6.1", "@babel/types@^7.7.0", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5", "@babel/types@^7.9.6":
version "7.17.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b"
integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==
dependencies:
"@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
"@babel/types@7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.3.tgz#5a383dffa5416db1b73dedffd311ffd0788fb31c"
@@ -2340,14 +2348,6 @@
lodash "^4.17.13"
to-fast-properties "^2.0.0"
"@babel/types@^7.0.0", "@babel/types@^7.12.10", "@babel/types@^7.12.11", "@babel/types@^7.12.7", "@babel/types@^7.13.0", "@babel/types@^7.15.0", "@babel/types@^7.15.4", "@babel/types@^7.16.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.17.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.0", "@babel/types@^7.6.0", "@babel/types@^7.6.1", "@babel/types@^7.7.0", "@babel/types@^7.8.3", "@babel/types@^7.8.6", "@babel/types@^7.9.0", "@babel/types@^7.9.5", "@babel/types@^7.9.6":
version "7.17.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b"
integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==
dependencies:
"@babel/helper-validator-identifier" "^7.16.7"
to-fast-properties "^2.0.0"
"@bahmutov/all-paths@1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@bahmutov/all-paths/-/all-paths-1.0.2.tgz#9ae0dcdf9022dd6e5e14d7fda3479e6a330d035b"
@@ -29431,6 +29431,7 @@ minipass-fetch@^1.3.0, minipass-fetch@^1.3.2:
resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-1.3.3.tgz#34c7cea038c817a8658461bf35174551dce17a0a"
integrity sha512-akCrLDWfbdAWkMLBxJEeWTdNsjML+dt5YgOI4gJ53vuO0vrmYQkUPxa6j6V65s9CcePIr2SSWqjT2EcrNseryQ==
dependencies:
encoding "^0.1.12"
minipass "^3.1.0"
minipass-sized "^1.0.3"
minizlib "^2.0.0"
@@ -43058,8 +43059,10 @@ watchpack@^1.6.0, watchpack@^1.7.4:
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453"
integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==
dependencies:
chokidar "^3.4.1"
graceful-fs "^4.1.2"
neo-async "^2.5.0"
watchpack-chokidar2 "^2.0.1"
optionalDependencies:
chokidar "^3.4.1"
watchpack-chokidar2 "^2.0.1"