mirror of
https://github.com/cypress-io/cypress.git
synced 2026-05-12 18:29:53 -05:00
chore: Check project dependencies for CT compatibility (#26497)
* chore: Check project dependencies for CT compatibility * Cleanup
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import fetch from 'cross-fetch'
|
||||
import type { DataContext } from '../DataContext'
|
||||
import { isDependencyInstalled } from '@packages/scaffold-config'
|
||||
|
||||
// Require rather than import since data-context is stricter than network and there are a fair amount of errors in agent.
|
||||
const { agent } = require('@packages/network')
|
||||
@@ -18,4 +19,8 @@ export class UtilDataSource {
|
||||
// which is what will be used here
|
||||
return fetch(input, { agent, ...init })
|
||||
}
|
||||
|
||||
isDependencyInstalled (dependency: Cypress.CypressComponentDependency, projectPath: string) {
|
||||
return isDependencyInstalled(dependency, projectPath)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import type { DataContext } from '..'
|
||||
import type { TestingType } from '@packages/types'
|
||||
import { CYPRESS_REMOTE_MANIFEST_URL, NPM_CYPRESS_REGISTRY_URL } from '@packages/types'
|
||||
import Debug from 'debug'
|
||||
import { WIZARD_DEPENDENCIES } from '@packages/scaffold-config'
|
||||
import semver from 'semver'
|
||||
|
||||
const debug = Debug('cypress:data-context:sources:VersionsDataSource')
|
||||
|
||||
@@ -160,6 +162,47 @@ export class VersionsDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const projectPath = this.ctx.currentProject
|
||||
|
||||
if (projectPath) {
|
||||
const dependenciesToCheck = WIZARD_DEPENDENCIES
|
||||
|
||||
debug('Checking %d dependencies in project', dependenciesToCheck.length)
|
||||
// Check all dependencies of interest in parallel
|
||||
const dependencyResults = await Promise.allSettled(
|
||||
dependenciesToCheck.map(async (dependency) => {
|
||||
const result = await this.ctx.util.isDependencyInstalled(dependency, projectPath)
|
||||
|
||||
// If a dependency isn't satisfied then we are no longer interested in it,
|
||||
// exclude from further processing by rejecting promise
|
||||
if (!result.satisfied) {
|
||||
throw new Error('Unsatisfied dependency')
|
||||
}
|
||||
|
||||
// We only want major version, fallback to `-1` if we couldn't detect version
|
||||
const majorVersion = result.detectedVersion ? semver.major(result.detectedVersion) : -1
|
||||
|
||||
// For any satisfied dependencies, build a `package@version` string
|
||||
return `${result.dependency.package}@${majorVersion}`
|
||||
}),
|
||||
)
|
||||
// Take any dependencies that were found and combine into comma-separated string
|
||||
const headerValue = dependencyResults
|
||||
.filter(this.isFulfilled)
|
||||
.map((result) => result.value)
|
||||
.join(',')
|
||||
|
||||
if (headerValue) {
|
||||
manifestHeaders['x-dependencies'] = headerValue
|
||||
}
|
||||
} else {
|
||||
debug('No project path, skipping dependency check')
|
||||
}
|
||||
} catch (err) {
|
||||
debug('Failed to detect project dependencies', err)
|
||||
}
|
||||
|
||||
try {
|
||||
const manifestResponse = await this.ctx.util.fetch(CYPRESS_REMOTE_MANIFEST_URL, {
|
||||
headers: manifestHeaders,
|
||||
@@ -190,4 +233,8 @@ export class VersionsDataSource {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
private isFulfilled<R> (item: PromiseSettledResult<R>): item is PromiseFulfilledResult<R> {
|
||||
return item.status === 'fulfilled'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ describe('VersionsDataSource', () => {
|
||||
let ctx: DataContext
|
||||
let nmiStub: sinon.SinonStub
|
||||
let fetchStub: sinon.SinonStub
|
||||
let isDependencyInstalledStub: sinon.SinonStub
|
||||
let mockNow: Date = new Date()
|
||||
let versionsDataSource: VersionsDataSource
|
||||
let currentCypressVersion: string = pkg.version
|
||||
@@ -23,14 +24,26 @@ describe('VersionsDataSource', () => {
|
||||
before(() => {
|
||||
ctx = createTestDataContext('open')
|
||||
|
||||
;(ctx.lifecycleManager as any)._cachedInitialConfig = {
|
||||
component: {
|
||||
devServer: {
|
||||
framework: 'react',
|
||||
bundler: 'vite',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ctx.coreData.currentProject = '/abc'
|
||||
ctx.coreData.currentTestingType = 'e2e'
|
||||
|
||||
fetchStub = sinon.stub()
|
||||
isDependencyInstalledStub = sinon.stub()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
nmiStub = sinon.stub(nmi, 'machineId')
|
||||
sinon.stub(ctx.util, 'fetch').callsFake(fetchStub)
|
||||
sinon.stub(ctx.util, 'isDependencyInstalled').callsFake(isDependencyInstalledStub)
|
||||
sinon.stub(os, 'platform').returns('darwin')
|
||||
sinon.stub(os, 'arch').returns('x64')
|
||||
sinon.useFakeTimers({ now: mockNow })
|
||||
@@ -45,7 +58,7 @@ describe('VersionsDataSource', () => {
|
||||
|
||||
fetchStub
|
||||
.withArgs(CYPRESS_REMOTE_MANIFEST_URL, {
|
||||
headers: {
|
||||
headers: sinon.match({
|
||||
'Content-Type': 'application/json',
|
||||
'x-cypress-version': currentCypressVersion,
|
||||
'x-os-name': 'darwin',
|
||||
@@ -54,7 +67,7 @@ describe('VersionsDataSource', () => {
|
||||
'x-machine-id': 'abcd123',
|
||||
'x-testing-type': 'e2e',
|
||||
'x-logged-in': 'false',
|
||||
},
|
||||
}),
|
||||
}).resolves({
|
||||
json: sinon.stub().resolves({
|
||||
name: 'Cypress',
|
||||
@@ -99,7 +112,7 @@ describe('VersionsDataSource', () => {
|
||||
|
||||
fetchStub
|
||||
.withArgs(CYPRESS_REMOTE_MANIFEST_URL, {
|
||||
headers: {
|
||||
headers: sinon.match({
|
||||
'Content-Type': 'application/json',
|
||||
'x-cypress-version': currentCypressVersion,
|
||||
'x-os-name': 'darwin',
|
||||
@@ -107,7 +120,7 @@ describe('VersionsDataSource', () => {
|
||||
'x-initial-launch': String(false),
|
||||
'x-testing-type': 'component',
|
||||
'x-logged-in': 'false',
|
||||
},
|
||||
}),
|
||||
}).resolves({
|
||||
json: sinon.stub().resolves({
|
||||
name: 'Cypress',
|
||||
@@ -121,7 +134,7 @@ describe('VersionsDataSource', () => {
|
||||
|
||||
versionsDataSource.resetLatestVersionTelemetry()
|
||||
|
||||
const latestVersion = await ctx.coreData.versionData.latestVersion
|
||||
const latestVersion = await ctx.coreData.versionData?.latestVersion
|
||||
|
||||
expect(latestVersion).to.eql('16.0.0')
|
||||
})
|
||||
@@ -131,7 +144,7 @@ describe('VersionsDataSource', () => {
|
||||
|
||||
fetchStub
|
||||
.withArgs(CYPRESS_REMOTE_MANIFEST_URL, {
|
||||
headers: {
|
||||
headers: sinon.match({
|
||||
'Content-Type': 'application/json',
|
||||
'x-cypress-version': currentCypressVersion,
|
||||
'x-os-name': 'darwin',
|
||||
@@ -140,7 +153,7 @@ describe('VersionsDataSource', () => {
|
||||
'x-machine-id': 'abcd123',
|
||||
'x-testing-type': 'e2e',
|
||||
'x-logged-in': 'false',
|
||||
},
|
||||
}),
|
||||
})
|
||||
.rejects()
|
||||
.withArgs(NPM_CYPRESS_REGISTRY_URL)
|
||||
@@ -158,7 +171,7 @@ describe('VersionsDataSource', () => {
|
||||
|
||||
fetchStub
|
||||
.withArgs(CYPRESS_REMOTE_MANIFEST_URL, {
|
||||
headers: {
|
||||
headers: sinon.match({
|
||||
'Content-Type': 'application/json',
|
||||
'x-cypress-version': currentCypressVersion,
|
||||
'x-os-name': 'darwin',
|
||||
@@ -167,7 +180,7 @@ describe('VersionsDataSource', () => {
|
||||
'x-machine-id': 'abcd123',
|
||||
'x-testing-type': 'e2e',
|
||||
'x-logged-in': 'false',
|
||||
},
|
||||
}),
|
||||
})
|
||||
.callsFake(async () => new Response('Error'))
|
||||
.withArgs(NPM_CYPRESS_REGISTRY_URL)
|
||||
@@ -183,9 +196,60 @@ describe('VersionsDataSource', () => {
|
||||
|
||||
versionsDataSource.resetLatestVersionTelemetry()
|
||||
|
||||
await ctx.coreData.versionData.latestVersion
|
||||
await ctx.coreData.versionData?.latestVersion
|
||||
|
||||
expect(versionInfo.current.version).to.eql(currentCypressVersion)
|
||||
})
|
||||
|
||||
it('generates x-framework, x-bundler, and x-dependencies headers', async () => {
|
||||
isDependencyInstalledStub.callsFake(async (dependency) => {
|
||||
// Should include any resolved dependency with a valid version
|
||||
if (dependency.package === 'react') {
|
||||
return {
|
||||
dependency,
|
||||
detectedVersion: '1.2.3',
|
||||
satisfied: true,
|
||||
} as Cypress.DependencyToInstall
|
||||
}
|
||||
|
||||
// Not satisfied dependency should be excluded
|
||||
if (dependency.package === 'vue') {
|
||||
return {
|
||||
dependency,
|
||||
detectedVersion: '4.5.6',
|
||||
satisfied: false,
|
||||
}
|
||||
}
|
||||
|
||||
// Satisfied dependency without resolved version should result in -1
|
||||
if (dependency.package === 'typescript') {
|
||||
return {
|
||||
dependency,
|
||||
detectedVersion: null,
|
||||
satisfied: true,
|
||||
}
|
||||
}
|
||||
|
||||
// Any dependencies that error while resolving should be excluded
|
||||
throw new Error('Failed check')
|
||||
})
|
||||
|
||||
ctx.coreData.currentTestingType = 'component'
|
||||
versionsDataSource = new VersionsDataSource(ctx)
|
||||
ctx.coreData.currentTestingType = 'e2e'
|
||||
versionsDataSource.resetLatestVersionTelemetry()
|
||||
await versionsDataSource.versionData()
|
||||
|
||||
expect(fetchStub).to.have.been.calledWith(
|
||||
CYPRESS_REMOTE_MANIFEST_URL,
|
||||
{
|
||||
headers: sinon.match({
|
||||
'x-framework': 'react',
|
||||
'x-dev-server': 'vite',
|
||||
'x-dependencies': 'typescript@-1,react@1',
|
||||
}),
|
||||
},
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -65,7 +65,7 @@ describe('Launchpad: Open Mode', () => {
|
||||
cy.openProject('todos', ['--e2e'])
|
||||
})
|
||||
|
||||
it('includes x-framework and x-dev-server, even when launched in e2e mode', () => {
|
||||
it('includes `x-framework`, `x-dev-server`, and `x-dependencies` headers, even when launched in e2e mode', () => {
|
||||
cy.visitLaunchpad()
|
||||
cy.skipWelcome()
|
||||
cy.get('h1').should('contain', 'Choose a browser')
|
||||
@@ -74,6 +74,7 @@ describe('Launchpad: Open Mode', () => {
|
||||
headers: {
|
||||
'x-framework': 'react',
|
||||
'x-dev-server': 'webpack',
|
||||
'x-dependencies': 'typescript@4',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user