-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: discover CT community definitions in monorepos with hoisted dependencies #26066
Changes from all commits
00fa9f4
6b4ff05
1b7f578
4c60b60
5bffe16
8dcef02
c2dbdce
21c8f71
d95d3e5
ac054d1
da65e43
98a10d6
59f89ca
a9ca93c
7403bd2
5897943
2fcde94
ac1373e
c43348e
bd15692
9449b10
eba120d
e669582
d1ea474
541098c
371efb2
43792f5
b6acb62
b172a09
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -235,8 +235,10 @@ function openGlobalMode (options: OpenGlobalModeOptions = {}) { | |
}) | ||
} | ||
|
||
function openProject (projectName: ProjectFixtureDir, argv: string[] = []) { | ||
if (!fixtureDirs.includes(projectName)) { | ||
type WithPrefix<T extends string> = `${T}${string}`; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here, this allows us to open Cypress from within a project to support monorepo testing |
||
|
||
function openProject (projectName: WithPrefix<ProjectFixtureDir>, argv: string[] = []) { | ||
if (!fixtureDirs.some((dir) => projectName.startsWith(dir))) { | ||
throw new Error(`Unknown project ${projectName}`) | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -232,5 +232,25 @@ describe('scaffolding component testing', { | |
|
||
verifyConfigFile('cypress.config.js') | ||
}) | ||
|
||
it('Scaffolds component testing for monorepos with hoisted dependencies', () => { | ||
cy.scaffoldProject('ct-monorepo-unconfigured') | ||
cy.openProject('ct-monorepo-unconfigured/packages/foo') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Random idea to keep the autocompletion on the project name: cy.openProject('ct-monorepo-unconfigured', { package: 'packages/foo' }) |
||
|
||
cy.withCtx(async (ctx) => { | ||
await ctx.actions.file.removeFileInProject(ctx.path.join('..', '..', 'node_modules', 'cypress-ct-qwik')) | ||
await ctx.actions.file.moveFileInProject( | ||
ctx.path.join('..', '..', 'cypress-ct-qwik'), | ||
ctx.path.join('..', '..', 'node_modules', 'cypress-ct-qwik'), | ||
) | ||
}) | ||
|
||
cy.visitLaunchpad() | ||
cy.skipWelcome() | ||
|
||
cy.contains('Component Testing').click() | ||
cy.get(`[data-testid="select-framework"]`).click() | ||
cy.contains('Qwik').should('be.visible') | ||
}) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ import globby from 'globby' | |
import { z } from 'zod' | ||
import fs from 'fs-extra' | ||
import Debug from 'debug' | ||
import findUp from 'find-up' | ||
|
||
const debug = Debug('cypress:scaffold-config:ct-detect-third-party') | ||
|
||
|
@@ -25,6 +26,46 @@ const thirdPartyDefinitionPrefixes = { | |
globalPrefix: 'cypress-ct-', | ||
} | ||
|
||
const ROOT_PATHS = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit... should this be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I used the term paths because some of them are directories and some are files, for example |
||
'.git', | ||
|
||
// https://pnpm.io/workspaces | ||
'pnpm-workspace.yaml', | ||
|
||
// https://rushjs.io/pages/advanced/config_files/ | ||
'rush.json', | ||
|
||
// https://nx.dev/deprecated/workspace-json#workspace.json | ||
// https://nx.dev/reference/nx-json#nx.json | ||
'workspace.json', | ||
'nx.json', | ||
|
||
// https://lerna.js.org/docs/api-reference/configuration | ||
'lerna.json', | ||
] | ||
|
||
async function hasWorkspacePackageJson (directory: string) { | ||
try { | ||
const pkg = await fs.readJson(path.join(directory, 'package.json')) | ||
|
||
debug('package file for %s: %o', directory, pkg) | ||
|
||
return !!pkg.workspaces | ||
} catch (e) { | ||
debug('error reading package.json in %s. this is not the repository root', directory) | ||
|
||
return false | ||
} | ||
} | ||
|
||
export async function isRepositoryRoot (directory: string) { | ||
if (ROOT_PATHS.some((rootPath) => fs.existsSync(path.join(directory, rootPath)))) { | ||
return true | ||
} | ||
|
||
return hasWorkspacePackageJson(directory) | ||
} | ||
|
||
export function isThirdPartyDefinition (definition: Cypress.ComponentFrameworkDefinition | Cypress.ThirdPartyComponentFrameworkDefinition): boolean { | ||
return definition.type.startsWith(thirdPartyDefinitionPrefixes.globalPrefix) || | ||
thirdPartyDefinitionPrefixes.namespacedPrefixRe.test(definition.type) | ||
|
@@ -46,14 +87,46 @@ export async function detectThirdPartyCTFrameworks ( | |
projectRoot: string, | ||
): Promise<Cypress.ThirdPartyComponentFrameworkDefinition[]> { | ||
try { | ||
const fullPathGlobs = [ | ||
path.join(projectRoot, CT_FRAMEWORK_GLOBAL_GLOB), | ||
path.join(projectRoot, CT_FRAMEWORK_NAMESPACED_GLOB), | ||
].map((x) => x.replaceAll('\\', '/')) | ||
let fullPathGlobs | ||
let packageJsonPaths: string[] = [] | ||
|
||
// Start at the project root and check each directory above it until we see | ||
// an indication that the current directory is the root of the repository. | ||
await findUp(async (directory: string) => { | ||
fullPathGlobs = [ | ||
path.join(directory, CT_FRAMEWORK_GLOBAL_GLOB), | ||
path.join(directory, CT_FRAMEWORK_NAMESPACED_GLOB), | ||
].map((x) => x.replaceAll('\\', '/')) | ||
|
||
debug('searching for third-party dependencies with globs %o', fullPathGlobs) | ||
|
||
const newPackagePaths = await globby(fullPathGlobs) | ||
|
||
if (newPackagePaths.length > 0) { | ||
debug('found third-party dependencies %o', newPackagePaths) | ||
} | ||
|
||
packageJsonPaths = [...packageJsonPaths, ...newPackagePaths] | ||
|
||
const isCurrentRepositoryRoot = await isRepositoryRoot(directory) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can give this PR a test later, but definitely will be good to see some tests around this full functionality (whether it's Cy-in-Cy, or just an elaborate unit/integration test in this package). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a unit test in this package, let me know what you think |
||
|
||
if (isCurrentRepositoryRoot) { | ||
debug('stopping search at %s because it is believed to be the repository root', directory) | ||
|
||
return findUp.stop | ||
} | ||
|
||
// Return undefined to keep searching | ||
return undefined | ||
}, { cwd: projectRoot }) | ||
|
||
if (packageJsonPaths.length === 0) { | ||
debug('no third-party dependencies detected') | ||
|
||
const packageJsonPaths = await globby(fullPathGlobs) | ||
return [] | ||
} | ||
|
||
debug('Found packages matching %s glob: %o', fullPathGlobs, packageJsonPaths) | ||
debug('found third-party dependencies %o', packageJsonPaths) | ||
|
||
const modules = await Promise.all( | ||
packageJsonPaths.map(async (packageJsonPath) => { | ||
|
@@ -96,7 +169,7 @@ export async function detectThirdPartyCTFrameworks ( | |
|
||
return defaultEntry | ||
} catch (e) { | ||
debug('Ignoring %s due to error resolving: %o', e) | ||
debug('Ignoring %s due to error resolving module', e) | ||
} | ||
}), | ||
).then((modules) => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
const qwikDep = { | ||
type: 'qwik', | ||
name: 'Qwik', | ||
package: '@builder.io/qwik', | ||
installer: '@builder.io/qwik', | ||
description: | ||
'An Open-Source sub-framework designed with a focus on server-side-rendering, lazy-loading, and styling/animation.', | ||
minVersion: '^0.17.5', | ||
} | ||
|
||
module.exports = { | ||
type: 'cypress-ct-qwik', | ||
|
||
category: 'library', | ||
|
||
name: 'Qwik', | ||
|
||
supportedBundlers: ['vite'], | ||
|
||
detectors: [qwikDep], | ||
|
||
// Cypress will include the bundler dependency here, if they selected one. | ||
dependencies: () => { | ||
return [qwikDep] | ||
}, | ||
|
||
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 256 274"><defs><linearGradient id="logosQwik0" x1="22.347%" x2="77.517%" y1="49.545%" y2="50.388%"><stop offset="0%" stop-color="#4340C4"/><stop offset="12%" stop-color="#4642C8"/><stop offset="100%" stop-color="#594EE4"/></linearGradient><linearGradient id="logosQwik1" x1="38.874%" x2="60.879%" y1="49.845%" y2="50.385%"><stop offset="0%" stop-color="#4340C4"/><stop offset="74%" stop-color="#534ADB"/><stop offset="100%" stop-color="#594EE4"/></linearGradient><linearGradient id="logosQwik2" x1="-.004%" x2="100.123%" y1="49.529%" y2="50.223%"><stop offset="0%" stop-color="#4340C4"/><stop offset="23%" stop-color="#4340C4"/><stop offset="60%" stop-color="#4F48D5"/><stop offset="100%" stop-color="#594EE4"/></linearGradient><linearGradient id="logosQwik3" x1="35.4%" x2="64.895%" y1="49.459%" y2="50.085%"><stop offset="0%" stop-color="#0080FF"/><stop offset="100%" stop-color="#00B9FF"/></linearGradient><linearGradient id="logosQwik4" x1="-.243%" x2="100.411%" y1="49.366%" y2="50.467%"><stop offset="0%" stop-color="#0080FF"/><stop offset="17%" stop-color="#008BFF"/><stop offset="47%" stop-color="#00A7FF"/><stop offset="63%" stop-color="#00B9FF"/><stop offset="100%" stop-color="#00B9FF"/></linearGradient><linearGradient id="logosQwik5" x1="-.125%" x2="100.225%" y1="49.627%" y2="50.101%"><stop offset="0%" stop-color="#00B9FF"/><stop offset="30%" stop-color="#0080FF"/><stop offset="60%" stop-color="#2D67F1"/><stop offset="86%" stop-color="#4D55E8"/><stop offset="100%" stop-color="#594EE4"/></linearGradient><linearGradient id="logosQwik6" x1="4.557%" x2="99.354%" y1="50.184%" y2="51.298%"><stop offset="0%" stop-color="#4340C4"/><stop offset="12%" stop-color="#4642C8"/><stop offset="100%" stop-color="#594EE4"/></linearGradient></defs><path fill="url(#logosQwik0)" d="m175.051 236.859l25.162-15.071l49.298-86.929l-76.287 89.097z"/><path fill="url(#logosQwik1)" d="m242.337 80.408l-4.926-9.4l-1.932-3.663l-.2.196l-25.818-47.015C202.984 8.65 190.631 1.231 177.01 1.074l-25.074.206L188.15 114.8l-23.958 23.331l8.924 86.245l73.769-84.021c10.005-11.587 11.97-28.09 4.92-41.646l-9.466-18.302h-.002Z"/><path fill="url(#logosQwik2)" d="m201.113 72.256l-43.18-70.907L83.41.003C70.165-.15 57.83 6.573 50.88 17.87L7.01 101.747l34.443-33.334L84.701 8.356l97.894 112.153l18.3-18.626c8.397-8.142 5.54-19.558.22-29.625l-.002-.002Z"/><path fill="url(#logosQwik3)" d="M97.784 95.26L84.522 8.796l-73.148 88.03c-12.328 11.935-14.897 30.662-6.419 45.49l42.98 74.727c6.553 11.464 18.755 18.577 32.024 18.729l42.945.49L71.46 119.607L97.784 95.26Z"/><path fill="url(#logosQwik4)" d="M173.227 223.9L71.38 119.022l-13.196 12.59c-10.812 10.248-11.106 27.332-.728 37.921l43.99 66.384l70.65.907l1.127-12.926h.003Z"/><path fill="url(#logosQwik5)" d="m101.584 235.903l72.292-11.599l47.704 49.464z"/><path fill="url(#logosQwik6)" d="m173.111 224.483l27.168-3.457l24.096 49.915c1.06 2.06-1.719 3.977-3.373 2.302l-47.89-48.76Z"/><path fill="#FFF" d="M182.708 120.058L84.681 8.601l12.502 85.958L71.16 118.78l101.772 105.372l-7.595-85.905l17.371-18.192z"/></svg>', | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export default function mount () { | ||
return 'Legit mount function' | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"name": "cypress-ct-qwik", | ||
"version": "1.0.0", | ||
"main": "index.js", | ||
"exports": { | ||
"node": "./definition.cjs", | ||
"default": "./index.mjs" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"name": "ct-monorepo-unconfigured", | ||
"version": "1.0.0", | ||
"private": true, | ||
"main": "index.js", | ||
"license": "MIT", | ||
"workspaces": [ | ||
"packages/*" | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"name": "@packages/bar", | ||
"version": "1.0.0", | ||
"private": true, | ||
"main": "index.js", | ||
"license": "MIT" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"name": "@packages/foo", | ||
"version": "1.0.0", | ||
"private": true, | ||
"main": "index.js", | ||
"license": "MIT" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had to re-work this in order to call
cy.openProject
with a directory inside of a project like<projectName>/packages/foo
to test monoreposThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shame we lose the auto completion :(
Let's do it for now, but I wonder if we can find a way to keep the completion 🤔