Skip to content

Commit

Permalink
chore: rework component onboarding in launchpad (#25713)
Browse files Browse the repository at this point in the history
* chore: refactoring and types

* rework source of frameworks

* revert rename

* fix tests

* fix more tests

* types

* update code

* use same public API internally

* rename interfaces

* rename

* work on dev server api

* fix types

* fix test

* attempt to support getDevServerConfig

* tests

* add function to define framework [skip ci]

* rework a lot of types

* fix test

* update tests and types

* refactor

* revert changes

* lint

* fix test

* revert

* remove

* add "community" label [skip ci]

* refactor

* types

* lint

* fix bug

* update function name

* address feedback

* improve types with Pick

* refactor using type guard

* correct label

---------

Co-authored-by: Zachary Williams <[email protected]>
  • Loading branch information
lmiller1990 and ZachJW34 authored Feb 13, 2023
1 parent 536c905 commit 3ad71ea
Show file tree
Hide file tree
Showing 42 changed files with 744 additions and 261 deletions.
2 changes: 2 additions & 0 deletions cli/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export default cypress

export const defineConfig = cypress.defineConfig

export const defineComponentFramework = cypress.defineComponentFramework

export const run = cypress.run

export const open = cypress.open
Expand Down
18 changes: 18 additions & 0 deletions cli/lib/cypress.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,24 @@ const cypressModuleApi = {
defineConfig (config) {
return config
},

/**
* Provides automatic code completion for Component Frameworks Definitions.
* While it's not strictly necessary for Cypress to parse your configuration, we
* recommend wrapping your Component Framework Definition object with `defineComponentFramework()`
* @example
* module.exports = defineComponentFramework({
* type: 'cypress-ct-solid-js'
* // ...
* })
*
* @see ../types/cypress-npm-api.d.ts
* @param {Cypress.ThirdPartyComponentFrameworkDefinition} config
* @returns {Cypress.ThirdPartyComponentFrameworkDefinition} the configuration passed in parameter
*/
defineComponentFramework (config) {
return config
},
}

module.exports = cypressModuleApi
16 changes: 16 additions & 0 deletions cli/types/cypress-npm-api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,22 @@ declare module 'cypress' {
* @returns {Cypress.ConfigOptions} the configuration passed in parameter
*/
defineConfig<ComponentDevServerOpts = any>(config: Cypress.ConfigOptions<ComponentDevServerOpts>): Cypress.ConfigOptions

/**
* Provides automatic code completion for Component Frameworks Definitions.
* While it's not strictly necessary for Cypress to parse your configuration, we
* recommend wrapping your Component Framework Definition object with `defineComponentFramework()`
* @example
* module.exports = defineComponentFramework({
* type: 'cypress-ct-solid-js'
* // ...
* })
*
* @see ../types/cypress-npm-api.d.ts
* @param {Cypress.ThirdPartyComponentFrameworkDefinition} config
* @returns {Cypress.ThirdPartyComponentFrameworkDefinition} the configuration passed in parameter
*/
defineComponentFramework(config: Cypress.ThirdPartyComponentFrameworkDefinition): Cypress.ThirdPartyComponentFrameworkDefinition
}

// export Cypress NPM module interface
Expand Down
171 changes: 171 additions & 0 deletions cli/types/cypress.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3244,6 +3244,177 @@ declare namespace Cypress {

type PickConfigOpt<T> = T extends keyof DefineDevServerConfig ? DefineDevServerConfig[T] : any

interface DependencyToInstall {
dependency: CypressComponentDependency
satisfied: boolean
loc: string | null
detectedVersion: string | null
}

interface CypressComponentDependency {
/**
* Unique idenitifer.
* @example 'reactscripts'
*/
type: string

/**
* Name to display in the user interface.
* @example "React Scripts"
*/
name: string

/**
* Package name on npm.
* @example react-scripts
*/
package: string

/**
* Code to run when installing. Version is optional.
*
* Should be <package_name>@<version>.
*
* @example `react`
* @example `react@18`
* @example `react-scripts`
*/
installer: string

/**
* Description shown in UI. It is recommended to use the same one the package uses on npm.
* @example 'Create React apps with no build configuration'
*/
description: string

/**
* Minimum version supported. Should conform to Semantic Versioning as used in `package.json`.
* @see https://docs.npmjs.com/cli/v9/configuring-npm/package-json#dependencies
* @example '^=4.0.0 || ^=5.0.0'
* @example '^2.0.0'
*/
minVersion: string
}

interface ResolvedComponentFrameworkDefinition {
/**
* A semantic, unique identifier. Must begin with `cypress-ct-` for third party implementations.
* @example: 'reactscripts', 'nextjs', 'cypress-ct-solid-js'
*/
type: string

/**
* Used as the flag for `getPreset` for meta framworks, such as finding the webpack config for CRA, Angular, etc.
*
* configFramwork: () => {
* return getSolidJsMetaFrameworkBundlerConfig()
* }
* It is also the name of the string added to `cypress.config`
*
* @example
*
* export default {
* component: {
* devServer: {
* framework: 'create-react-app' // can be 'next', 'create-react-app', etc etc.
* }
* }
* }
*/
configFramework: string

/**
* Library (React, Vue) or template (aka "meta framework") (CRA, Next.js, Angular)
*/
category: 'library' | 'template'

/**
* Name displayed in Launchpad when doing initial setup.
* @example 'Solid.js', 'Create React App'
*/
name: string

/**
* Supported bundlers.
*/
supportedBundlers: Array<'webpack' | 'vite'>

/**
* Used to attempt to automatically select the correct framework/bundler from the dropdown.
* @example
*
* const SOLID_DETECTOR: Dependency = {
* type: 'solid',
* name: 'Solid.js',
* package: 'solid-js',
* installer: 'solid-js',
* description: 'Solid is a declarative JavaScript library for creating user interfaces',
* minVersion: '^1.0.0',
* }
*/
detectors: CypressComponentDependency[]

/**
* Array of required dependencies. This could be the bundler and JavaScript library.
* It's the same type as `detectors`.
*/
dependencies: (bundler: 'webpack' | 'vite', projectPath: string) => Promise<DependencyToInstall[]>

/**
* @internal
* This is used interally by Cypress for the "Create From Component" feature.
*/
codeGenFramework?: 'react' | 'vue' | 'svelte' | 'angular'

/**
* @internal
* This is used interally by Cypress for the "Create From Component" feature.
* @example '*.{js,jsx,tsx}'
*/
glob?: string

/**
* This is the path to get mount, eg `import { mount } from <mount_module>,
* @example: `cypress-ct-solidjs/src/mount`
*/
mountModule: (projectPath: string) => Promise<string>

/**
* Support status. Internally alpha | beta | full.
* Community integrations are "community".
* @internal
*/
supportStatus: 'alpha' | 'beta' | 'full' | 'community'

/**
* Function returning string for used for the component-index.html file.
* Cypress provides a default if one isn't specified for third party integrations.
*/
componentIndexHtml?: () => string

/**
* Used for the Create From Comopnent feature.
* This is currently not supported for third party frameworks.
* @internal
*/
specPattern?: '**/*.cy.ts'
}

type ComponentFrameworkDefinition = Omit<ResolvedComponentFrameworkDefinition, 'dependencies'> & {
dependencies: (bundler: 'webpack' | 'vite') => CypressComponentDependency[]
}

// Certain properties are not supported for third party frameworks right now, such as ones related to the "Create From" feature.
type ThirdPartyComponentFrameworkDefinition = Pick<ComponentFrameworkDefinition, 'type' | 'name' | 'supportedBundlers' | 'detectors' | 'dependencies'> & {
type: `cypress-ct-${string}` | string

/**
* Only `library` is supported for third party definitions.
* `template` will be supported in the future.
*/
category: 'library'
}

interface AngularDevServerProjectConfig {
root: string
sourceRoot: string
Expand Down
30 changes: 29 additions & 1 deletion cli/types/tests/cypress-npm-api-test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// type tests for Cypress NPM module
// https://on.cypress.io/module-api
import cypress, { defineConfig } from 'cypress'
import cypress, { defineComponentFramework, defineConfig } from 'cypress'

cypress.run // $ExpectType (options?: Partial<CypressRunOptions> | undefined) => Promise<CypressRunResult | CypressFailedRunResult>
cypress.open // $ExpectType (options?: Partial<CypressOpenOptions> | undefined) => Promise<void>
Expand Down Expand Up @@ -55,6 +55,34 @@ const config = defineConfig({
modifyObstructiveCode: true
})

const solid = {
type: 'solid-js',
name: 'Solid.js',
package: 'solid-js',
installer: 'solid-js',
description: 'Solid is a declarative JavaScript library for creating user interfaces',
minVersion: '^1.0.0'
}

const thirdPartyFrameworkDefinition = defineComponentFramework({
type: 'cypress-ct-third-party',
name: 'Third Party',
category: 'library',
dependencies: (bundler) => [solid],
detectors: [solid],
supportedBundlers: ['vite', 'webpack']
})

const thirdPartyFrameworkDefinitionInvalidStrings = defineComponentFramework({
type: 'cypress-ct-third-party',
name: 'Third Party',
// only library supported for third party definitions
category: 'template', // $ExpectError
dependencies: (bundler) => [],
detectors: [{}], // $ExpectError
supportedBundlers: ['metro', 'webpack'] // $ExpectError
})

// component options
const componentConfigNextWebpack: Cypress.ConfigOptions = {
component: {
Expand Down
9 changes: 8 additions & 1 deletion npm/webpack-dev-server/src/devServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ export type PresetHandlerResult = { frameworkConfig: Configuration, sourceWebpac
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>

async function getPreset (devServerConfig: WebpackDevServerConfig): Promise<Optional<PresetHandlerResult, 'frameworkConfig'>> {
const defaultWebpackModules = () => ({ sourceWebpackModulesResult: sourceDefaultWebpackDependencies(devServerConfig) })

// Third party library (eg solid-js, lit, etc)
if (devServerConfig.framework?.startsWith('cypress-ct-')) {
return defaultWebpackModules()
}

switch (devServerConfig.framework) {
case 'create-react-app':
return createReactAppHandler(devServerConfig)
Expand All @@ -134,7 +141,7 @@ async function getPreset (devServerConfig: WebpackDevServerConfig): Promise<Opti
case 'vue':
case 'svelte':
case undefined:
return { sourceWebpackModulesResult: sourceDefaultWebpackDependencies(devServerConfig) }
return defaultWebpackModules()

default:
throw new Error(`Unexpected framework ${(devServerConfig as any).framework}, please visit https://on.cypress.io/component-framework-configuration to see a list of supported frameworks`)
Expand Down
5 changes: 2 additions & 3 deletions packages/data-context/src/actions/CodegenActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import type { DataContext } from '..'
import { SpecOptions, codeGenerator } from '../codegen'
import templates from '../codegen/templates'
import type { CodeGenType } from '../gen/graphcache-config.gen'
import { WizardFrontendFramework, WIZARD_FRAMEWORKS } from '@packages/scaffold-config'
import { parse as parseReactComponent, resolver as reactDocgenResolvers } from 'react-docgen'
import { visit } from 'ast-types'

Expand Down Expand Up @@ -152,7 +151,7 @@ export class CodegenActions {
})
}

getWizardFrameworkFromConfig (): WizardFrontendFramework | undefined {
getWizardFrameworkFromConfig (): Cypress.ResolvedComponentFrameworkDefinition | undefined {
const config = this.ctx.lifecycleManager.loadedConfigFile

// If devServer is a function, they are using a custom dev server.
Expand All @@ -161,7 +160,7 @@ export class CodegenActions {
}

// @ts-ignore - because of the conditional above, we know that devServer isn't a function
return WIZARD_FRAMEWORKS.find((framework) => framework.configFramework === config?.component?.devServer.framework)
return this.ctx.coreData.wizard.frameworks.find((framework) => framework.configFramework === config?.component?.devServer.framework)
}
}

Expand Down
Loading

0 comments on commit 3ad71ea

Please sign in to comment.