From e625766c6753ff0451ee431d51a73f405fce602b Mon Sep 17 00:00:00 2001 From: Bill Glesias Date: Fri, 3 Oct 2025 13:31:07 -0400 Subject: [PATCH 1/3] chore: refactor network into two packages, network and network-tools. network-tools is expected to be used in simple environments, where network is intended to be used in the node context. additionally, makes these packages bundable and removes the ts-node entrypoint to make ESM possible. --- .circleci/src/pipeline/@pipeline.yml | 4 ++ guides/esm-migration.md | 4 +- packages/config/package.json | 3 +- packages/config/src/utils.ts | 4 +- packages/driver/package.json | 2 +- .../src/cy/commands/origin/validator.ts | 5 +- packages/driver/src/cypress.ts | 2 +- packages/driver/src/cypress/location.ts | 6 +- packages/network-tools/.eslintignore | 1 + packages/network-tools/.gitignore | 2 + packages/network-tools/README.md | 33 ++++++++++ .../{network => network-tools}/lib/cors.ts | 10 +-- .../lib/document-domain-injection.ts | 0 packages/network-tools/lib/index.ts | 7 ++ .../{network => network-tools}/lib/types.ts | 0 .../{network => network-tools}/lib/uri.ts | 0 packages/network-tools/package.json | 39 +++++++++++ .../test/unit/cors.spec.ts | 66 +++++++++---------- .../unit/document_domain_injection.spec.ts | 0 .../test/unit/uri.spec.ts | 32 ++++----- packages/network-tools/tsconfig.base.json | 18 +++++ packages/network-tools/tsconfig.cjs.json | 10 +++ packages/network-tools/tsconfig.esm.json | 11 ++++ packages/network-tools/vitest.config.ts | 9 +++ packages/network/.gitignore | 2 + packages/network/README.md | 6 +- packages/network/index.js | 5 -- packages/network/lib/agent.ts | 46 +++++++------ packages/network/lib/allow-destroy.ts | 2 +- packages/network/lib/blocked.ts | 14 ++-- packages/network/lib/ca.ts | 52 ++++++++------- packages/network/lib/client-certificates.ts | 43 ++++++++---- packages/network/lib/connect.ts | 66 ++++++++++--------- packages/network/lib/index.ts | 6 +- packages/network/package.json | 26 +++++--- packages/network/test/support/servers.ts | 22 +++++-- packages/network/test/unit/agent.spec.ts | 18 ++--- packages/network/tsconfig.base.json | 21 ++++++ packages/network/tsconfig.cjs.json | 10 +++ packages/network/tsconfig.esm.json | 11 ++++ packages/network/tsconfig.json | 11 ---- packages/proxy/lib/http/request-middleware.ts | 7 +- .../proxy/lib/http/response-middleware.ts | 5 +- packages/proxy/lib/http/util/buffers.ts | 4 +- packages/proxy/lib/http/util/cookies.ts | 8 +-- packages/proxy/package.json | 1 + .../test/integration/net-stubbing.spec.ts | 3 +- .../test/unit/http/request-middleware.spec.ts | 2 +- .../unit/http/response-middleware.spec.ts | 2 +- packages/server/lib/automation/cookies.ts | 65 +++++++++--------- .../server/lib/browsers/cdp_automation.ts | 6 +- packages/server/lib/cache.ts | 2 +- .../server/lib/cloud/api/cloud_request.ts | 2 +- packages/server/lib/cloud/api/index.ts | 26 ++++---- packages/server/lib/cloud/machine_id.ts | 8 +-- packages/server/lib/cloud/protocol.ts | 3 +- packages/server/lib/cloud/user.ts | 6 +- packages/server/lib/controllers/files.ts | 2 +- packages/server/lib/remote_states.ts | 11 ++-- packages/server/lib/routes.ts | 4 +- packages/server/lib/saved_state.ts | 2 + packages/server/lib/server-base.ts | 11 ++-- packages/server/lib/util/ensure-url.ts | 9 ++- packages/server/lib/util/server_destroy.ts | 4 +- packages/server/package.json | 1 + .../server/test/unit/cloud/api/api_spec.js | 18 +++-- .../test/unit/cloud/api/cloud_request_spec.ts | 2 +- .../server/test/unit/remote_states.spec.ts | 2 +- tooling/v8-snapshot/package.json | 3 +- yarn.lock | 7 ++ 70 files changed, 543 insertions(+), 312 deletions(-) create mode 100644 packages/network-tools/.eslintignore create mode 100644 packages/network-tools/.gitignore create mode 100644 packages/network-tools/README.md rename packages/{network => network-tools}/lib/cors.ts (95%) rename packages/{network => network-tools}/lib/document-domain-injection.ts (100%) create mode 100644 packages/network-tools/lib/index.ts rename packages/{network => network-tools}/lib/types.ts (100%) rename packages/{network => network-tools}/lib/uri.ts (100%) create mode 100644 packages/network-tools/package.json rename packages/{network => network-tools}/test/unit/cors.spec.ts (84%) rename packages/{network => network-tools}/test/unit/document_domain_injection.spec.ts (100%) rename packages/{network => network-tools}/test/unit/uri.spec.ts (50%) create mode 100644 packages/network-tools/tsconfig.base.json create mode 100644 packages/network-tools/tsconfig.cjs.json create mode 100644 packages/network-tools/tsconfig.esm.json create mode 100644 packages/network-tools/vitest.config.ts create mode 100644 packages/network/.gitignore delete mode 100644 packages/network/index.js create mode 100644 packages/network/tsconfig.base.json create mode 100644 packages/network/tsconfig.cjs.json create mode 100644 packages/network/tsconfig.esm.json delete mode 100644 packages/network/tsconfig.json diff --git a/.circleci/src/pipeline/@pipeline.yml b/.circleci/src/pipeline/@pipeline.yml index 5420f375817..ac4500fd1f4 100644 --- a/.circleci/src/pipeline/@pipeline.yml +++ b/.circleci/src/pipeline/@pipeline.yml @@ -198,6 +198,8 @@ commands: name: Sync Cloud Validations command: | source ./scripts/ensure-node.sh + yarn workspace @packages/network-tools build + yarn workspace @packages/network build yarn gulp syncCloudValidations - run: name: Build packages @@ -1234,6 +1236,8 @@ commands: name: Check if binary exists, exit if it does command: | source ./scripts/ensure-node.sh + yarn workspace @packages/network-tools build + yarn workspace @packages/network build yarn gulp e2eTestScaffold yarn check-binary-on-cdn --version $(node ./scripts/get-next-version.js) --type binary --file cypress.zip diff --git a/guides/esm-migration.md b/guides/esm-migration.md index f3fbae0fd9a..b0e6d7df3d8 100644 --- a/guides/esm-migration.md +++ b/guides/esm-migration.md @@ -53,7 +53,8 @@ When migrating some of these projects away from the `ts-node` entry [see `@packa - [x] packages/launcher ✅ **COMPLETED** - [x] packages/launchpad ✅ **COMPLETED** - [x] packages/net-stubbing ✅ **COMPLETED** -- [ ] packages/network **PARTIAL** - entry point is JS +- [x] packages/network ✅ **COMPLETED** +- [x] packages/network-tools ✅ **COMPLETED** - [x] packages/packherd-require ✅ **COMPLETED** - [ ] packages/proxy **PARTIAL** - entry point is JS - [x] packages/reporter ✅ **COMPLETED** @@ -103,6 +104,7 @@ When migrating some of these projects away from the `ts-node` entry [see `@packa - [ ] packages/launcher - [ ] packages/net-stubbing - [x] packages/network ✅ **COMPLETED** +- [x] packages/network-tools ✅ **COMPLETED** - [ ] packages/packherd-require - [ ] packages/proxy - [x] packages/rewriter ✅ **COMPLETED** diff --git a/packages/config/package.json b/packages/config/package.json index 78e5dc87023..28c218fb24a 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -40,12 +40,13 @@ }, "devDependencies": { "@packages/errors": "0.0.0-development", - "@packages/network": "0.0.0-development", + "@packages/network-tools": "0.0.0-development", "@packages/root": "0.0.0-development", "@packages/ts": "0.0.0-development", "@packages/types": "0.0.0-development", "babel-plugin-tester": "^10.1.0", "rimraf": "6.0.1", + "typescript": "~5.4.5", "vitest": "^3.2.4" }, "files": [ diff --git a/packages/config/src/utils.ts b/packages/config/src/utils.ts index 49aa790b2f8..341b1db8d23 100644 --- a/packages/config/src/utils.ts +++ b/packages/config/src/utils.ts @@ -1,6 +1,6 @@ import _ from 'lodash' import { toBoolean } from 'underscore.string' -import * as uri from '@packages/network/lib/uri' +import { origin } from '@packages/network-tools' export const hideKeys = (token?: string | number | boolean) => { if (!token) { @@ -26,7 +26,7 @@ export function setUrls (obj: any) { const proxyUrl = `http://localhost:${obj.port}` const rootUrl = obj.baseUrl - ? uri.origin(obj.baseUrl) + ? origin(obj.baseUrl) : proxyUrl return { diff --git a/packages/driver/package.json b/packages/driver/package.json index 10d5bca6c26..21fe1eb47a5 100644 --- a/packages/driver/package.json +++ b/packages/driver/package.json @@ -26,7 +26,7 @@ "@packages/config": "0.0.0-development", "@packages/errors": "0.0.0-development", "@packages/net-stubbing": "0.0.0-development", - "@packages/network": "0.0.0-development", + "@packages/network-tools": "0.0.0-development", "@packages/proxy": "0.0.0-development", "@packages/rewriter": "0.0.0-development", "@packages/runner": "0.0.0-development", diff --git a/packages/driver/src/cy/commands/origin/validator.ts b/packages/driver/src/cy/commands/origin/validator.ts index d10f3c429b0..08b13297978 100644 --- a/packages/driver/src/cy/commands/origin/validator.ts +++ b/packages/driver/src/cy/commands/origin/validator.ts @@ -2,8 +2,7 @@ import $utils from '../../../cypress/utils' import $errUtils from '../../../cypress/error_utils' import { difference, isPlainObject, isString, isFunction } from 'lodash' import type { LocationObject } from '../../../cypress/location' -import * as cors from '@packages/network/lib/cors' -import { DocumentDomainInjection } from '@packages/network/lib/document-domain-injection' +import { policyFromConfig, DocumentDomainInjection } from '@packages/network-tools' const validOptionKeys = Object.freeze(['args']) @@ -87,7 +86,7 @@ export class Validator { const injector = DocumentDomainInjection.InjectionBehavior(Cypress.config()) - const policy = cors.policyFromConfig({ injectDocumentDomain: Cypress.config('injectDocumentDomain') }) + const policy = policyFromConfig({ injectDocumentDomain: Cypress.config('injectDocumentDomain') }) if (injector.urlsMatch(originLocation.href, specHref)) { $errUtils.throwErrByPath('origin.invalid_url_argument_same_origin', { diff --git a/packages/driver/src/cypress.ts b/packages/driver/src/cypress.ts index fc84aafe8ed..3a4882bfa62 100644 --- a/packages/driver/src/cypress.ts +++ b/packages/driver/src/cypress.ts @@ -45,7 +45,7 @@ import { PrimaryOriginCommunicator, SpecBridgeCommunicator } from './cross-origi import { setupAutEventHandlers } from './cypress/aut_event_handlers' import type { CachedTestState, ReporterRunState, RunState } from '@packages/types' -import { DocumentDomainInjection } from '@packages/network/lib/document-domain-injection' +import { DocumentDomainInjection } from '@packages/network-tools' import { setSpecContentSecurityPolicy } from './util/privileged_channel' import { telemetry } from '@packages/telemetry/src/browser' diff --git a/packages/driver/src/cypress/location.ts b/packages/driver/src/cypress/location.ts index 41fbf61761d..53bccb4ffe3 100644 --- a/packages/driver/src/cypress/location.ts +++ b/packages/driver/src/cypress/location.ts @@ -8,7 +8,7 @@ import _ from 'lodash' import UrlParse from 'url-parse' -import * as cors from '@packages/network/lib/cors' +import { getSuperDomain, getSuperDomainOrigin } from '@packages/network-tools' const reHttp = /^https?:\/\// const reWww = /^www/ @@ -106,11 +106,11 @@ export class $Location { } getSuperDomainOrigin () { - return cors.getSuperDomainOrigin(this.remote.href) + return getSuperDomainOrigin(this.remote.href) } getSuperDomain () { - return cors.getSuperDomain(this.remote.href) + return getSuperDomain(this.remote.href) } getToString () { diff --git a/packages/network-tools/.eslintignore b/packages/network-tools/.eslintignore new file mode 100644 index 00000000000..d5857ea71fb --- /dev/null +++ b/packages/network-tools/.eslintignore @@ -0,0 +1 @@ +**/tsconfig.json diff --git a/packages/network-tools/.gitignore b/packages/network-tools/.gitignore new file mode 100644 index 00000000000..929bed061c1 --- /dev/null +++ b/packages/network-tools/.gitignore @@ -0,0 +1,2 @@ +cjs/ +esm/ \ No newline at end of file diff --git a/packages/network-tools/README.md b/packages/network-tools/README.md new file mode 100644 index 00000000000..857c7178bee --- /dev/null +++ b/packages/network-tools/README.md @@ -0,0 +1,33 @@ +# network-tools + +A low-level package that contains networking-related classes and utilities to be used in the browser and Node.js environment. + +## Exports + +You can see a list of the modules exported from this package in [./lib/index.ts](./lib/index.ts). Here is a brief description of what's available: + +* `cors` contains utilities for Cross-Origin Resource Sharing +* `document-domain-injection` contains utilities related to document.domain injection of the Cypress driver +* `uri` contains utilities for URL parsing and formatting + +See the individual class files in [`./lib`](./lib) for more information. + +## Building + +We currently build a CommonJS and an ESM version of this package. However, since this package is only consumed via CommonJS, we currently only build the CommonJS variant of the package. + +```shell +yarn workspace @packages/network-tools build-prod +``` + +## Testing + +Tests are located in [`./test`](./test) + +To run tests: + +```shell +yarn workspace @packages/network-tools test +yarn workspace @packages/network-tools test-watch +yarn workspace @packages/network-tools test-debug +``` diff --git a/packages/network/lib/cors.ts b/packages/network-tools/lib/cors.ts similarity index 95% rename from packages/network/lib/cors.ts rename to packages/network-tools/lib/cors.ts index c784e820ada..c7d661c2d8a 100644 --- a/packages/network/lib/cors.ts +++ b/packages/network-tools/lib/cors.ts @@ -1,5 +1,5 @@ import _ from 'lodash' -import * as uri from './uri' +import { addDefaultPort, parse as parseUrl } from './uri' import debugModule from 'debug' import _parseDomain from '@cypress/parse-domain' import type { ParsedHost, ParsedHostWithProtocolAndHost } from './types' @@ -25,7 +25,7 @@ export function parseDomain (domain: string, options = {}) { } export function parseUrlIntoHostProtocolDomainTldPort (str: string) { - let { hostname, port, protocol } = uri.parse(str) + let { hostname, port, protocol } = parseUrl(str) if (!hostname) { hostname = '' @@ -200,9 +200,9 @@ declare module 'url' { } } -export function urlMatchesOriginProtectionSpace (urlStr, origin) { - const normalizedUrl = uri.addDefaultPort(urlStr).format() - const normalizedOrigin = uri.addDefaultPort(origin).format() +export function urlMatchesOriginProtectionSpace (urlStr: string, origin: string) { + const normalizedUrl = addDefaultPort(urlStr).format() + const normalizedOrigin = addDefaultPort(origin).format() return _.startsWith(normalizedUrl, normalizedOrigin) } diff --git a/packages/network/lib/document-domain-injection.ts b/packages/network-tools/lib/document-domain-injection.ts similarity index 100% rename from packages/network/lib/document-domain-injection.ts rename to packages/network-tools/lib/document-domain-injection.ts diff --git a/packages/network-tools/lib/index.ts b/packages/network-tools/lib/index.ts new file mode 100644 index 00000000000..4f514a8a94b --- /dev/null +++ b/packages/network-tools/lib/index.ts @@ -0,0 +1,7 @@ +export * from './cors' + +export * from './uri' + +export * from './types' + +export * from './document-domain-injection' diff --git a/packages/network/lib/types.ts b/packages/network-tools/lib/types.ts similarity index 100% rename from packages/network/lib/types.ts rename to packages/network-tools/lib/types.ts diff --git a/packages/network/lib/uri.ts b/packages/network-tools/lib/uri.ts similarity index 100% rename from packages/network/lib/uri.ts rename to packages/network-tools/lib/uri.ts diff --git a/packages/network-tools/package.json b/packages/network-tools/package.json new file mode 100644 index 00000000000..b77f0c3f90f --- /dev/null +++ b/packages/network-tools/package.json @@ -0,0 +1,39 @@ +{ + "name": "@packages/network-tools", + "version": "0.0.0-development", + "private": true, + "main": "cjs/index.js", + "scripts": { + "build": "yarn build:esm && yarn build:cjs", + "build-prod": "yarn build", + "build:cjs": "rimraf cjs && tsc -p tsconfig.cjs.json", + "build:esm": "rimraf esm && tsc -p tsconfig.esm.json", + "check-ts": "tsc -p tsconfig.cjs.json --noEmit && yarn -s tslint -p tsconfig.cjs.json", + "clean": "rimraf cjs esm", + "clean-deps": "rimraf node_modules", + "lint": "eslint --ext .js,.jsx,.ts,.tsx,.json, .", + "test": "yarn test-unit", + "test-debug": "npx vitest --inspect-brk --no-file-parallelism --test-timeout=0 --hook-timeout=0", + "test-unit": "vitest run", + "test-watch": "yarn test-unit --watch", + "tslint": "tslint --config ../ts/tslint.json --project .", + "watch": "tsc -p tsconfig.cjs.json --watch" + }, + "dependencies": { + "@cypress/parse-domain": "2.4.0", + "debug": "^4.3.4", + "lodash": "^4.17.21" + }, + "devDependencies": { + "rimraf": "6.0.1", + "typescript": "~5.4.5", + "vitest": "^3.2.4" + }, + "files": [ + "cjs", + "esm" + ], + "types": "./cjs/index.d.ts", + "module": "esm/index.js", + "nx": {} +} diff --git a/packages/network/test/unit/cors.spec.ts b/packages/network-tools/test/unit/cors.spec.ts similarity index 84% rename from packages/network/test/unit/cors.spec.ts rename to packages/network-tools/test/unit/cors.spec.ts index b915fc17757..e17a3d50ae1 100644 --- a/packages/network/test/unit/cors.spec.ts +++ b/packages/network-tools/test/unit/cors.spec.ts @@ -1,12 +1,12 @@ import { describe, it, expect, beforeEach } from 'vitest' -import { cors } from '../../lib' +import { parseUrlIntoHostProtocolDomainTldPort, urlOriginsMatch, urlSameSiteMatch, urlMatchesPolicyProps, urlMatchesOriginProtectionSpace, getSuperDomainOrigin, policyFromConfig } from '../../lib' import { Policy } from '../../lib/cors' import type { ParsedHostWithProtocolAndHost } from '../../lib/types' describe('lib/cors', () => { describe('.parseUrlIntoHostProtocolDomainTldPort', () => { const expectUrlToBeParsedCorrectly = (url, obj) => { - expect(cors.parseUrlIntoHostProtocolDomainTldPort(url)).toEqual(obj) + expect(parseUrlIntoHostProtocolDomainTldPort(url)).toEqual(obj) } it('parses https://www.google.com', function () { @@ -113,11 +113,11 @@ describe('lib/cors', () => { describe('.urlOriginsMatch', () => { const assertOriginsDoNotMatch = (url1: string, url2: string) => { - expect(cors.urlOriginsMatch(url1, url2)).toBe(false) + expect(urlOriginsMatch(url1, url2)).toBe(false) } const assertOriginsDoMatch = (url1: string, url2: string) => { - expect(cors.urlOriginsMatch(url1, url2)).toBe(true) + expect(urlOriginsMatch(url1, url2)).toBe(true) } describe('domain + subdomain', () => { @@ -217,11 +217,11 @@ describe('lib/cors', () => { describe('.urlSameSiteMatch', () => { const assertsUrlsAreNotSameSite = (url1: string, url2: string) => { - expect(cors.urlSameSiteMatch(url1, url2)).toBe(false) + expect(urlSameSiteMatch(url1, url2)).toBe(false) } const assertsUrlsAreSameSite = (url1: string, url2: string) => { - expect(cors.urlSameSiteMatch(url1, url2)).toBe(true) + expect(urlSameSiteMatch(url1, url2)).toBe(true) } describe('domain + subdomain', () => { @@ -336,22 +336,22 @@ describe('lib/cors', () => { describe('and origin matches', () => { beforeEach(() => { frameUrl = 'http://www.foo.com' - topProps = cors.parseUrlIntoHostProtocolDomainTldPort(frameUrl) + topProps = parseUrlIntoHostProtocolDomainTldPort(frameUrl) }) it('matches', () => { - expect(cors.urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(true) + expect(urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(true) }) }) describe('and origin does not match', () => { beforeEach(() => { frameUrl = 'http://www.foo.com' - topProps = cors.parseUrlIntoHostProtocolDomainTldPort('http://www.bar.com') + topProps = parseUrlIntoHostProtocolDomainTldPort('http://www.bar.com') }) it('does not match', () => { - expect(cors.urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(false) + expect(urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(false) }) }) }) @@ -364,11 +364,11 @@ describe('lib/cors', () => { describe('and origin matches', () => { beforeEach(() => { frameUrl = 'http://www.foo.com' - topProps = cors.parseUrlIntoHostProtocolDomainTldPort(frameUrl) + topProps = parseUrlIntoHostProtocolDomainTldPort(frameUrl) }) it('matches', () => { - expect(cors.urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(true) + expect(urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(true) }) }) @@ -378,12 +378,12 @@ describe('lib/cors', () => { beforeEach(() => { frameUrl = `http://www.${superdomain}` - topProps = cors.parseUrlIntoHostProtocolDomainTldPort(`http://docs.${superdomain}:${port}`) + topProps = parseUrlIntoHostProtocolDomainTldPort(`http://docs.${superdomain}:${port}`) }) describe('and the ports are not strictly equal', () => { it('does not match', () => { - expect(cors.urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(false) + expect(urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(false) }) }) @@ -394,7 +394,7 @@ describe('lib/cors', () => { }) it('does match', () => { - expect(cors.urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(true) + expect(urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(true) }) }) }) @@ -402,11 +402,11 @@ describe('lib/cors', () => { describe('and superdomains do not match', () => { beforeEach(() => { frameUrl = 'http://www.foo.com' - topProps = cors.parseUrlIntoHostProtocolDomainTldPort('http://www.bar.com') + topProps = parseUrlIntoHostProtocolDomainTldPort('http://www.bar.com') }) it('does not match', () => { - expect(cors.urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(false) + expect(urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(false) }) }) }) @@ -419,18 +419,18 @@ describe('lib/cors', () => { describe('and origin matches', () => { beforeEach(() => { frameUrl = 'http://www.foo.com' - topProps = cors.parseUrlIntoHostProtocolDomainTldPort('http://www.foo.com') + topProps = parseUrlIntoHostProtocolDomainTldPort('http://www.foo.com') }) it('matches', () => { - expect(cors.urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(true) + expect(urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(true) }) }) describe('and superdomains match', () => { beforeEach(() => { frameUrl = 'http://www.foo.com' - topProps = cors.parseUrlIntoHostProtocolDomainTldPort('http://docs.foo.com') + topProps = parseUrlIntoHostProtocolDomainTldPort('http://docs.foo.com') }) describe('and neither ports match with neither being 443', () => { @@ -440,7 +440,7 @@ describe('lib/cors', () => { }) it('matches', () => { - expect(cors.urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(true) + expect(urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(true) }) }) @@ -451,18 +451,18 @@ describe('lib/cors', () => { }) it('does not match', () => { - expect(cors.urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(false) + expect(urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(false) }) }) describe('and neither ports match but topProps is 443 / https', () => { beforeEach(() => { frameUrl = `${frameUrl}:8080` - topProps = cors.parseUrlIntoHostProtocolDomainTldPort('https://www.foo.com') + topProps = parseUrlIntoHostProtocolDomainTldPort('https://www.foo.com') }) it('does not match', () => { - expect(cors.urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(false) + expect(urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(false) }) }) @@ -473,7 +473,7 @@ describe('lib/cors', () => { }) it('matches', () => { - expect(cors.urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(true) + expect(urlMatchesPolicyProps({ policy, frameUrl, topProps })).toBe(true) }) }) }) @@ -482,11 +482,11 @@ describe('lib/cors', () => { describe('.urlMatchesOriginProtectionSpace', () => { const assertMatchesOriginProtectionSpace = (urlStr: string, origin: string) => { - expect(cors.urlMatchesOriginProtectionSpace(urlStr, origin), `the url: '${urlStr}' did not match origin protection space '${origin}' when it should have`).toBe(true) + expect(urlMatchesOriginProtectionSpace(urlStr, origin), `the url: '${urlStr}' did not match origin protection space '${origin}' when it should have`).toBe(true) } const assertDoesNotMatchOriginProtectionSpace = (urlStr, origin) => { - expect(cors.urlMatchesOriginProtectionSpace(urlStr, origin), `the url: '${urlStr}' matched origin protection space '${origin}' when it should not have`).toBe(false) + expect(urlMatchesOriginProtectionSpace(urlStr, origin), `the url: '${urlStr}' matched origin protection space '${origin}' when it should not have`).toBe(false) } it('ports', () => { @@ -528,23 +528,23 @@ describe('lib/cors', () => { describe('.getSuperDomainOrigin', () => { it('ports', () => { - expect(cors.getSuperDomainOrigin('https://example.com')).toEqual('https://example.com') - expect(cors.getSuperDomainOrigin('http://example.com:8080')).toEqual('http://example.com:8080') + expect(getSuperDomainOrigin('https://example.com')).toEqual('https://example.com') + expect(getSuperDomainOrigin('http://example.com:8080')).toEqual('http://example.com:8080') }) it('subdomain', () => { - expect(cors.getSuperDomainOrigin('http://www.example.com')).toEqual('http://example.com') - expect(cors.getSuperDomainOrigin('http://www.app.herokuapp.com:8080')).toEqual('http://app.herokuapp.com:8080') + expect(getSuperDomainOrigin('http://www.example.com')).toEqual('http://example.com') + expect(getSuperDomainOrigin('http://www.app.herokuapp.com:8080')).toEqual('http://app.herokuapp.com:8080') }) }) describe('.policyFromConfig', () => { it('returns \'same-origin\' when injectDocumentDomain is false', () => { - expect(cors.policyFromConfig({ injectDocumentDomain: false })).toEqual('same-origin') + expect(policyFromConfig({ injectDocumentDomain: false })).toEqual('same-origin') }) it('returns \'same-super-domain-origin\' when injectDocumentDomain is true', () => { - expect(cors.policyFromConfig({ injectDocumentDomain: true })).toEqual('same-super-domain-origin') + expect(policyFromConfig({ injectDocumentDomain: true })).toEqual('same-super-domain-origin') }) }) }) diff --git a/packages/network/test/unit/document_domain_injection.spec.ts b/packages/network-tools/test/unit/document_domain_injection.spec.ts similarity index 100% rename from packages/network/test/unit/document_domain_injection.spec.ts rename to packages/network-tools/test/unit/document_domain_injection.spec.ts diff --git a/packages/network/test/unit/uri.spec.ts b/packages/network-tools/test/unit/uri.spec.ts similarity index 50% rename from packages/network/test/unit/uri.spec.ts rename to packages/network-tools/test/unit/uri.spec.ts index 19ffd9dda0c..d7c5ca85aa7 100644 --- a/packages/network/test/unit/uri.spec.ts +++ b/packages/network-tools/test/unit/uri.spec.ts @@ -1,73 +1,73 @@ import { describe, it, expect } from 'vitest' import { URL } from 'url' -import { uri } from '../../lib' +import { getPath, isLocalhost, origin } from '../../lib' describe('lib/uri', () => { describe('.getPath', () => { it('returns the pathname and search', () => { - expect(uri.getPath('http://localhost:9999/foo/bar?baz=quux#/index.html')).toEqual('/foo/bar?baz=quux') + expect(getPath('http://localhost:9999/foo/bar?baz=quux#/index.html')).toEqual('/foo/bar?baz=quux') }) it('supports encoded characters', () => { - expect(uri.getPath('http://localhost:9999?foo=0%3C1')).toEqual('/?foo=0%3C1') + expect(getPath('http://localhost:9999?foo=0%3C1')).toEqual('/?foo=0%3C1') }) it('does not encode the "|" character', () => { - expect(uri.getPath('http://localhost:9999?foo=bar|baz')).toEqual('/?foo=bar|baz') + expect(getPath('http://localhost:9999?foo=bar|baz')).toEqual('/?foo=bar|baz') }) it('works with relative urls', () => { - expect(uri.getPath('/foo/bar?foo=bar|baz')).toEqual('/foo/bar?foo=bar|baz') + expect(getPath('/foo/bar?foo=bar|baz')).toEqual('/foo/bar?foo=bar|baz') }) }) describe('.isLocalhost', () => { it('http://localhost is localhost', () => { - expect(uri.isLocalhost(new URL('http://localhost'))).toBe(true) + expect(isLocalhost(new URL('http://localhost'))).toBe(true) }) it('https://localhost is localhost', () => { - expect(uri.isLocalhost(new URL('https://localhost'))).toBe(true) + expect(isLocalhost(new URL('https://localhost'))).toBe(true) }) it('http://127.0.0.1 is localhost', () => { - expect(uri.isLocalhost(new URL('http://127.0.0.1'))).toBe(true) + expect(isLocalhost(new URL('http://127.0.0.1'))).toBe(true) }) it('http://127.0.0.9 is localhost', () => { - expect(uri.isLocalhost(new URL('http://127.0.0.9'))).toBe(true) + expect(isLocalhost(new URL('http://127.0.0.9'))).toBe(true) }) it('http://[::1] is localhost', () => { - expect(uri.isLocalhost(new URL('http://[::1]'))).toBe(true) + expect(isLocalhost(new URL('http://[::1]'))).toBe(true) }) it('http://128.0.0.1 is NOT localhost', () => { - expect(uri.isLocalhost(new URL('http://128.0.0.1'))).toBe(false) + expect(isLocalhost(new URL('http://128.0.0.1'))).toBe(false) }) it('http:foobar.com is NOT localhost', () => { - expect(uri.isLocalhost(new URL('http:foobar.com'))).toBe(false) + expect(isLocalhost(new URL('http:foobar.com'))).toBe(false) }) it('https:foobar.com is NOT localhost', () => { - expect(uri.isLocalhost(new URL('https:foobar.com'))).toBe(false) + expect(isLocalhost(new URL('https:foobar.com'))).toBe(false) }) }) describe('.origin', () => { it('strips everything but the remote origin', () => { expect( - uri.origin('http://localhost:9999/foo/bar?baz=quux#/index.html'), + origin('http://localhost:9999/foo/bar?baz=quux#/index.html'), ).toEqual('http://localhost:9999') expect( - uri.origin('https://www.google.com/'), + origin('https://www.google.com/'), ).toEqual('https://www.google.com') expect( - uri.origin('https://app.foobar.co.uk:1234/a=b'), + origin('https://app.foobar.co.uk:1234/a=b'), ).toEqual('https://app.foobar.co.uk:1234') }) }) diff --git a/packages/network-tools/tsconfig.base.json b/packages/network-tools/tsconfig.base.json new file mode 100644 index 00000000000..14a8ae6a7bd --- /dev/null +++ b/packages/network-tools/tsconfig.base.json @@ -0,0 +1,18 @@ +{ + "include": [ + "lib/*.ts" + ], + "compilerOptions": { + "types": [ + "node" + ], + "skipLibCheck": true, + "esModuleInterop": true, + "strict": true, + "noImplicitAny": true, + "useUnknownInCatchVariables": true, + "noUnusedLocals": true, + "noImplicitReturns": true, + "declaration": true + } +} \ No newline at end of file diff --git a/packages/network-tools/tsconfig.cjs.json b/packages/network-tools/tsconfig.cjs.json new file mode 100644 index 00000000000..709d6e27fca --- /dev/null +++ b/packages/network-tools/tsconfig.cjs.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "rootDir": "./lib", + "outDir": "./cjs", + "target": "ES2022", + "module": "CommonJS", + "moduleResolution": "node" + } +} \ No newline at end of file diff --git a/packages/network-tools/tsconfig.esm.json b/packages/network-tools/tsconfig.esm.json new file mode 100644 index 00000000000..970560cc72e --- /dev/null +++ b/packages/network-tools/tsconfig.esm.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "rootDir": "./lib", + "outDir": "./esm", + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "node", + "noEmit": true + } +} \ No newline at end of file diff --git a/packages/network-tools/vitest.config.ts b/packages/network-tools/vitest.config.ts new file mode 100644 index 00000000000..1a9a321880f --- /dev/null +++ b/packages/network-tools/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + include: ['test/**/*.spec.ts'], + globals: true, + environment: 'node', + }, +}) diff --git a/packages/network/.gitignore b/packages/network/.gitignore new file mode 100644 index 00000000000..929bed061c1 --- /dev/null +++ b/packages/network/.gitignore @@ -0,0 +1,2 @@ +cjs/ +esm/ \ No newline at end of file diff --git a/packages/network/README.md b/packages/network/README.md index 5263862e391..80f8e4d1b16 100644 --- a/packages/network/README.md +++ b/packages/network/README.md @@ -1,6 +1,6 @@ # network -This package contains networking-related classes and utilities. +This package contains networking-related classes and utilities pertaining to the Node.js execution context. ## Exports @@ -11,14 +11,12 @@ You can see a list of the modules exported from this package in [./lib/index.ts] * `blocked` is a utility for matching blocked globs * `concatStream` is a wrapper around [`concat-stream@1.6.2`][2] that makes it always yield a `Buffer` * `connect` contains utilities for making network connections, including `createRetryingSocket` -* `cors` contains utilities for Cross-Origin Resource Sharing -* `uri` contains utilities for URL parsing and formatting See the individual class files in [`./lib`](./lib) for more information. ## Building -Note: you should not ever need to build the .js files manually. `@packages/ts` provides require-time transpilation when in development. +We currently build a CommonJS and an ESM version of this package. However, since this package is only consumed via CommonJS, we currently only build the CommonJS variant of the package. ```shell yarn workspace @packages/network build-prod diff --git a/packages/network/index.js b/packages/network/index.js deleted file mode 100644 index bf66abc3bf7..00000000000 --- a/packages/network/index.js +++ /dev/null @@ -1,5 +0,0 @@ -if (process.env.CYPRESS_INTERNAL_ENV !== 'production') { - require('@packages/ts/register') -} - -module.exports = require('./lib') diff --git a/packages/network/lib/agent.ts b/packages/network/lib/agent.ts index ff776cf323d..7fdc9ffb964 100644 --- a/packages/network/lib/agent.ts +++ b/packages/network/lib/agent.ts @@ -15,23 +15,27 @@ const CRLF = '\r\n' const statusCodeRe = /^HTTP\/1.[01] (\d*)/ let baseCaOptions: CaOptions | undefined -const getCaOptionsPromise = (): Promise => { - return getCaOptions().then((options: CaOptions) => { +const getCaOptionsAsync = async (): Promise => { + try { + const options = await getCaOptions() + baseCaOptions = options return options - }).catch(() => { + } catch (error) { + debug('Error getting CA options', error) + // Errors reading the config are treated as warnings by npm and node and handled by those processes separately // from what we're doing here. - return {} - }) + return {} as CaOptions + } } -let baseCaOptionsPromise: Promise = getCaOptionsPromise() +let baseCaOptionsPromise: Promise = getCaOptionsAsync() // This is for testing purposes only export const _resetBaseCaOptionsPromise = () => { baseCaOptions = undefined - baseCaOptionsPromise = getCaOptionsPromise() + baseCaOptionsPromise = getCaOptionsAsync() } const mergeCAOptions = (options: https.RequestOptions, caOptions: CaOptions): https.RequestOptions => { @@ -146,6 +150,7 @@ export const regenerateRequestHead = (req: http.ClientRequest) => { } } +// this function has to be sync via callback because it is called by the Agent.addRequest method, which expect a sync function export const getFirstWorkingFamily = ( { port, host }: Pick, familyCache: FamilyCache, @@ -196,6 +201,7 @@ export class CombinedAgent { } // called by Node.js whenever a new request is made internally + // NOTE: this function has to be sync via callback because it is called by the Agent.addRequest method, which expect a sync function addRequest (req: http.ClientRequest, options: http.RequestOptions, port?: number, localAddress?: string) { _.merge(req, lenientOptions) @@ -262,7 +268,7 @@ export class CombinedAgent { } } -const getProxyOrTargetOverrideForUrl = (href) => { +const getProxyOrTargetOverrideForUrl = (href: string) => { // HTTP_PROXY_TARGET_FOR_ORIGIN_REQUESTS is used for Cypress in Cypress E2E testing and will // force the parent Cypress server to treat the child Cypress server like a proxy without // having HTTP_PROXY set and will force traffic ONLY bound to that origin to behave @@ -395,7 +401,7 @@ class HttpsAgent extends https.Agent { const port = options.uri?.port || '443' const hostname = options.uri?.hostname || 'localhost' - createProxySock({ proxy, shouldRetry: options.shouldRetry }, (originalErr?, proxySocket?, triggerRetry?) => { + createProxySock({ proxy, shouldRetry: options.shouldRetry }, (originalErr?: Error, proxySocket?: net.Socket, triggerRetry?: (err: Error) => void) => { if (originalErr) { const err: any = new Error(`A connection to the upstream proxy could not be established: ${originalErr.message}`) @@ -406,12 +412,12 @@ class HttpsAgent extends https.Agent { } const onClose = () => { - triggerRetry(new Error('ERR_EMPTY_RESPONSE: The upstream proxy closed the socket after connecting but before sending a response.')) + triggerRetry?.(new Error('ERR_EMPTY_RESPONSE: The upstream proxy closed the socket after connecting but before sending a response.')) } const onError = (err: Error) => { - triggerRetry(err) - proxySocket.destroy() + triggerRetry?.(err) + proxySocket?.destroy() } let buffer = '' @@ -423,15 +429,15 @@ class HttpsAgent extends https.Agent { if (!_.includes(buffer, _.repeat(CRLF, 2))) { // haven't received end of headers yet, keep buffering - proxySocket.once('data', onData) + proxySocket?.once('data', onData) return } // we've now gotten enough of a response not to retry // connecting to the proxy - proxySocket.removeListener('error', onError) - proxySocket.removeListener('close', onClose) + proxySocket?.removeListener('error', onError) + proxySocket?.removeListener('close', onClose) if (!isResponseStatusCode200(buffer)) { return cb(new Error(`Error establishing proxy connection. Response from server was: ${buffer}`), undefined) @@ -453,14 +459,14 @@ class HttpsAgent extends https.Agent { cb(undefined, proxySocket) } - proxySocket.once('close', onClose) - proxySocket.once('error', onError) - proxySocket.once('data', onData) + proxySocket?.once('close', onClose) + proxySocket?.once('error', onError) + proxySocket?.once('data', onData) const connectReq = buildConnectReqHead(hostname, port, proxy) - proxySocket.setNoDelay(true) - proxySocket.write(connectReq) + proxySocket?.setNoDelay(true) + proxySocket?.write(connectReq) }) } } diff --git a/packages/network/lib/allow-destroy.ts b/packages/network/lib/allow-destroy.ts index 9dfc186cacc..0e0e9710136 100644 --- a/packages/network/lib/allow-destroy.ts +++ b/packages/network/lib/allow-destroy.ts @@ -10,7 +10,7 @@ import type net from 'net' export function allowDestroy (server: net.Server) { let connections: net.Socket[] = [] - function trackConn (conn) { + function trackConn (conn: net.Socket) { connections.push(conn) conn.on('close', () => { diff --git a/packages/network/lib/blocked.ts b/packages/network/lib/blocked.ts index 1e59bbda5b9..c00ed6b87fd 100644 --- a/packages/network/lib/blocked.ts +++ b/packages/network/lib/blocked.ts @@ -1,18 +1,18 @@ import _ from 'lodash' import minimatch from 'minimatch' -import { stripProtocolAndDefaultPorts } from './uri' +import { stripProtocolAndDefaultPorts } from '@packages/network-tools' -export function matches (urlToCheck, blockHosts) { +export function matches (urlToCheck: string, blockHosts: string[] | string) { // normalize into flat array - blockHosts = [].concat(blockHosts) + const blockHostsNormalized: string[] = ([] as string[]).concat(blockHosts) - urlToCheck = stripProtocolAndDefaultPorts(urlToCheck) + const urlToCheckStripped = stripProtocolAndDefaultPorts(urlToCheck) as string // use minimatch against the url // to see if any match - const matchUrl = (hostMatcher) => { - return minimatch(urlToCheck, hostMatcher) + const matchUrl = (hostMatcher: string) => { + return minimatch(urlToCheckStripped, hostMatcher) } - return _.find(blockHosts, matchUrl) + return _.find(blockHostsNormalized, matchUrl) } diff --git a/packages/network/lib/ca.ts b/packages/network/lib/ca.ts index 136ffe4ea62..8d90c4acf5d 100644 --- a/packages/network/lib/ca.ts +++ b/packages/network/lib/ca.ts @@ -3,50 +3,54 @@ import tls from 'tls' export type CaOptions = { ca?: string[] } -const getNpmConfigCAFileValue = (): Promise => { +const getNpmConfigCAFileValue = async (): Promise => { if (process.env.npm_config_cafile) { - return fs.readFile(process.env.npm_config_cafile, 'utf8').then((ca) => { + try { + const ca = await fs.readFile(process.env.npm_config_cafile, 'utf8') + return ca - }).catch(() => { + } catch (error) { return undefined - }) + } } return Promise.resolve(undefined) } -const getNodeExtraCACertsFileValue = (): Promise => { +const getNodeExtraCACertsFileValue = async (): Promise => { if (process.env.NODE_EXTRA_CA_CERTS) { - return fs.readFile(process.env.NODE_EXTRA_CA_CERTS, 'utf8').then((ca) => { + try { + const ca = await fs.readFile(process.env.NODE_EXTRA_CA_CERTS, 'utf8') + return ca - }).catch(() => { + } catch (error) { return undefined - }) + } } return Promise.resolve(undefined) } -const getCaOptions = (): Promise => { +const getCaOptions = async (): Promise => { // Load the contents of process.env.npm_config_cafile and process.env.NODE_EXTRA_CA_CERTS // They will be cached so we don't have to actually read them on every call - return Promise.all([getNpmConfigCAFileValue(), getNodeExtraCACertsFileValue()]).then(([npm_config_cafile, NODE_EXTRA_CA_CERTS]) => { - // Merge the contents of ca with the npm config options. These options are meant to be replacements, but we want to keep the tls client certificate - // config that our consumers provide - if (npm_config_cafile) { - return { ca: [npm_config_cafile] } - } + const [npm_config_cafile, NODE_EXTRA_CA_CERTS] = await Promise.all([getNpmConfigCAFileValue(), getNodeExtraCACertsFileValue()]) - if (process.env.npm_config_ca) { - return { ca: [process.env.npm_config_ca] } - } + // Merge the contents of ca with the npm config options. These options are meant to be replacements, but we want to keep the tls client certificate + // config that our consumers provide + if (npm_config_cafile) { + return { ca: [npm_config_cafile] } + } - // Merge the contents of ca with the NODE_EXTRA_CA_CERTS options. This option is additive to the tls root certificates - if (NODE_EXTRA_CA_CERTS) { - return { ca: tls.rootCertificates.concat(NODE_EXTRA_CA_CERTS) } - } + if (process.env.npm_config_ca) { + return { ca: [process.env.npm_config_ca] } + } + + // Merge the contents of ca with the NODE_EXTRA_CA_CERTS options. This option is additive to the tls root certificates + if (NODE_EXTRA_CA_CERTS) { + return { ca: tls.rootCertificates.concat(NODE_EXTRA_CA_CERTS) } + } - return {} - }) + return {} } export { diff --git a/packages/network/lib/client-certificates.ts b/packages/network/lib/client-certificates.ts index b0c8c78a9fb..98ec5dc6812 100644 --- a/packages/network/lib/client-certificates.ts +++ b/packages/network/lib/client-certificates.ts @@ -2,8 +2,7 @@ import { URL, Url } from 'url' import debugModule from 'debug' import minimatch from 'minimatch' import fs from 'fs-extra' - -const { pki, asn1, pkcs12, util } = require('node-forge') +import { pki, asn1, pkcs12, util } from 'node-forge' const debug = debugModule('cypress:network:client-certificates') @@ -195,7 +194,22 @@ export const clientCertificateStoreSingleton = new ClientCertificateStore() * network ClientCertificateStore * @param config */ -export function loadClientCertificateConfig (config) { + +type Config = { + projectRoot: string + clientCertificates?: Array<{ + url: string + ca: string[] + certs: Array<{ + cert: string + key: string + passphrase: string + pfx: string + }> + }> +} + +export function loadClientCertificateConfig (config: Config) { const { clientCertificates } = config let index = 0 @@ -218,7 +232,7 @@ export function loadClientCertificateConfig (config) { const caRaw = loadBinaryFromFile(ca) try { - pki.certificateFromPem(caRaw) + pki.certificateFromPem(caRaw.toString()) } catch (error: any) { throw new Error(`Cannot parse CA cert: ${error.message}`) } @@ -248,10 +262,10 @@ export function loadClientCertificateConfig (config) { debug(`loading PEM cert from '${cert.cert}'`) const pemRaw = loadBinaryFromFile(cert.cert) - let pemParsed = undefined + let pemParsed: pki.Certificate | undefined = undefined try { - pemParsed = pki.certificateFromPem(pemRaw) + pemParsed = pki.certificateFromPem(pemRaw.toString()) } catch (error: any) { throw new Error(`Cannot parse PEM cert: ${error.message}`) } @@ -270,13 +284,13 @@ export function loadClientCertificateConfig (config) { try { if (passphrase) { - if (!pki.decryptRsaPrivateKey(pemKeyRaw, passphrase)) { + if (!pki.decryptRsaPrivateKey(pemKeyRaw.toString(), passphrase)) { throw new Error( `Cannot decrypt PEM key with supplied passphrase (check the passphrase file content and that it doesn't have unexpected whitespace at the end)`, ) } } else { - if (!pki.privateKeyFromPem(pemKeyRaw)) { + if (!pki.privateKeyFromPem(pemKeyRaw.toString())) { throw new Error('Cannot load PEM key') } } @@ -363,7 +377,7 @@ function loadTextFromFile (filepath: string): string { /** * Extract subject from supplied pem instance */ -function extractSubjectFromPem (pem): string { +function extractSubjectFromPem (pem: pki.Certificate): string { try { return pem.subject.attributes .map((attr) => [attr.shortName, attr.value].join('=')) @@ -391,9 +405,16 @@ function loadPfx (pfx: Buffer, passphrase: string | undefined) { /** * Extract subject from supplied pfx instance */ -function extractSubjectFromPfx (pfx) { +function extractSubjectFromPfx (pfx: pkcs12.Pkcs12Pfx) { try { - const certs = pfx.getBags({ bagType: pki.oids.certBag })[pki.oids.certBag].map((item) => item.cert) + const bags = pfx.getBags({ bagType: pki.oids.certBag }) + const certBag = bags[pki.oids.certBag] + + if (!certBag || certBag.length === 0) { + throw new Error('No certificate bag found in PFX file') + } + + const certs = certBag.map((item) => item.cert) as pki.Certificate[] return certs[0].subject.attributes.map((attr) => [attr.shortName, attr.value].join('=')).join(', ') } catch (e: any) { diff --git a/packages/network/lib/connect.ts b/packages/network/lib/connect.ts index 40763c2f635..09ef98beda7 100644 --- a/packages/network/lib/connect.ts +++ b/packages/network/lib/connect.ts @@ -1,6 +1,6 @@ -import Bluebird from 'bluebird' +import { promisify } from 'util' import debugModule from 'debug' -import dns, { LookupAddress, LookupAllOptions } from 'dns' +import dns from 'dns' import _ from 'lodash' import net from 'net' import tls from 'tls' @@ -10,7 +10,7 @@ const debug = debugModule('cypress:network:connect') export function byPortAndAddress (port: number, address: net.Address) { // https://nodejs.org/api/net.html#net_net_connect_port_host_connectlistener - return new Bluebird((resolve, reject) => { + return new Promise((resolve, reject) => { const onConnect = () => { client.destroy() resolve(address) @@ -22,43 +22,45 @@ export function byPortAndAddress (port: number, address: net.Address) { }) } -export function getAddress (port: number, hostname: string): Bluebird { +export async function getAddress (port: number, hostname: string): Promise { debug('beginning getAddress %o', { hostname, port }) - const fn = byPortAndAddress.bind({}, port) - // promisify at the very last second which enables us to // modify dns lookup function (via hosts overrides) - const lookupAsync = Bluebird.promisify(dns.lookup, { context: dns }) + const lookupAsync = promisify(dns.lookup) // this does not go out to the network to figure // out the addresses. in fact it respects the /etc/hosts file // https://github.com/nodejs/node/blob/dbdbdd4998e163deecefbb1d34cda84f749844a4/lib/dns.js#L108 // https://nodejs.org/api/dns.html#dns_dns_lookup_hostname_options_callback - // @ts-ignore - return lookupAsync(hostname, { all: true }) - .then((addresses) => { - debug('got addresses %o', { hostname, port, addresses }) - - // ipv6 addresses are causing problems with cypress in cypress internal e2e tests - // on windows, so we are filtering them out here - if (process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF_PARENT_PROJECT && os.platform() === 'win32') { - debug('filtering ipv6 addresses %o', { hostname, port, addresses }) - addresses = addresses.filter((address) => { - return address.family === 4 - }) - } - // convert to an array if string - return Array.prototype.concat.call(addresses).map(fn) - }) - .tapCatch((err) => { - debug('error getting address %o', { hostname, port, err }) - }) - .any() + let addresses = await lookupAsync(hostname, { all: true }) + + debug('got addresses %o', { hostname, port, addresses }) + + // ipv6 addresses are causing problems with cypress in cypress internal e2e tests + // on windows, so we are filtering them out here + if (process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF_PARENT_PROJECT && os.platform() === 'win32') { + debug('filtering ipv6 addresses %o', { hostname, port, addresses }) + addresses = addresses.filter((address) => { + return address.family === 4 + }) + } + + try { + const address = await Promise.any(addresses.map((address) => { + return byPortAndAddress(port, address as net.Address) + })) + + return address + } catch (error) { + debug('error getting address %o', { hostname, port, error }) + + throw error + } } -export function getDelayForRetry (iteration) { +export function getDelayForRetry (iteration: number) { return [0, 100, 200, 200][iteration] } @@ -67,10 +69,10 @@ export interface RetryingOptions { port: number host: string | undefined useTls: boolean - getDelayMsForRetry: (iteration: number, err: Error) => number | undefined + getDelayMsForRetry: (iteration: number, err: Error | undefined) => number | undefined } -function createSocket (opts: RetryingOptions, onConnect): net.Socket { +function createSocket (opts: RetryingOptions, onConnect: () => void): net.Socket { const netOpts = _.defaults(_.pick(opts, 'family', 'host', 'port'), { family: 4, }) @@ -91,7 +93,7 @@ export function createRetryingSocket ( } function tryConnect (iteration = 0) { - const retry = (err) => { + const retry = (err: Error | undefined) => { const delay = opts.getDelayMsForRetry(iteration, err) if (typeof delay === 'undefined') { @@ -107,7 +109,7 @@ export function createRetryingSocket ( }, delay) } - function onError (err) { + function onError (err: Error) { sock.on('error', (err) => { debug('second error received on retried socket %o', { opts, iteration, err }) }) diff --git a/packages/network/lib/index.ts b/packages/network/lib/index.ts index 747a71fb04e..d8754e4bc1d 100644 --- a/packages/network/lib/index.ts +++ b/packages/network/lib/index.ts @@ -1,18 +1,14 @@ import agent, { strictAgent } from './agent' import * as blocked from './blocked' import * as connect from './connect' -import * as cors from './cors' import * as httpUtils from './http-utils' -import * as uri from './uri' import * as clientCertificates from './client-certificates' export { agent, blocked, connect, - cors, httpUtils, - uri, clientCertificates, strictAgent, } @@ -21,4 +17,4 @@ export { allowDestroy } from './allow-destroy' export { concatStream } from './concat-stream' -export { DocumentDomainInjection } from './document-domain-injection' +export { CombinedAgent } from './agent' diff --git a/packages/network/package.json b/packages/network/package.json index 2a49042d073..90f788153b3 100644 --- a/packages/network/package.json +++ b/packages/network/package.json @@ -2,22 +2,24 @@ "name": "@packages/network", "version": "0.0.0-development", "private": true, - "main": "index.js", + "main": "cjs/index.js", "scripts": { - "build-prod": "tsc --project .", - "check-ts": "tsc --noEmit && yarn -s tslint", - "clean": "rimraf --glob 'lib/**/*.js'", + "build": "yarn build:esm && yarn build:cjs", + "build-prod": "yarn build", + "build:cjs": "rimraf cjs && tsc -p tsconfig.cjs.json", + "build:esm": "rimraf esm && tsc -p tsconfig.esm.json", + "check-ts": "tsc -p tsconfig.cjs.json --noEmit && yarn -s tslint -p tsconfig.cjs.json", + "clean": "rimraf cjs esm", "clean-deps": "rimraf node_modules", "lint": "eslint --ext .js,.jsx,.ts,.tsx,.json, .", "test": "yarn test-unit", "test-debug": "vitest --inspect-brk --no-file-parallelism --test-timeout=0 --hook-timeout=0", "test-unit": "vitest run", "test-watch": "yarn test-unit --watch", - "tslint": "tslint --config ../ts/tslint.json --project ." + "tslint": "tslint --config ../ts/tslint.json --project .", + "watch": "tsc -p tsconfig.cjs.json --watch" }, "dependencies": { - "@cypress/parse-domain": "2.4.0", - "bluebird": "3.5.3", "concat-stream": "1.6.2", "debug": "^4.3.4", "fs-extra": "9.1.0", @@ -31,15 +33,21 @@ "@cypress/request": "^3.0.9", "@cypress/request-promise": "^5.0.0", "@packages/https-proxy": "0.0.0-development", + "@packages/network-tools": "0.0.0-development", "@packages/socket": "0.0.0-development", "@types/concat-stream": "1.6.0", + "@types/node": "22.18.0", + "@types/proxy-from-env": "1.0.4", "express": "4.21.0", + "rimraf": "6.0.1", "typescript": "~5.4.5", "vitest": "^3.2.4" }, "files": [ - "lib" + "cjs", + "esm" ], - "types": "./lib/index.ts", + "types": "./cjs/index.d.ts", + "module": "esm/index.js", "nx": {} } diff --git a/packages/network/test/support/servers.ts b/packages/network/test/support/servers.ts index aaee766ed7b..4c71b3ff0ff 100644 --- a/packages/network/test/support/servers.ts +++ b/packages/network/test/support/servers.ts @@ -1,4 +1,4 @@ -import Bluebird from 'bluebird' +import { promisify } from 'util' import express from 'express' import http from 'http' import https from 'https' @@ -58,17 +58,25 @@ export class Servers { getCAInformation(), ]) - this.httpServer = Bluebird.promisifyAll( - allowDestroy(http.createServer(app)), - ) as http.Server & AsyncServer + const httpServer = allowDestroy(http.createServer(app)) + + this.httpServer = Object.assign(httpServer, { + closeAsync: promisify(httpServer.close.bind(httpServer)), + destroyAsync: promisify(httpServer.destroy.bind(httpServer)), + listenAsync: promisify(httpServer.listen.bind(httpServer)), + }) as http.Server & AsyncServer this.wsServer = new SocketIOServer(this.httpServer) this.caCertificatePath = caCertificatePath this.https = { cert: serverCertificateKeys[0], key: serverCertificateKeys[1] } - this.httpsServer = Bluebird.promisifyAll( - allowDestroy(https.createServer(this.https, app)), - ) as https.Server & AsyncServer + const httpsServer = allowDestroy(https.createServer(this.https, app)) + + this.httpsServer = Object.assign(httpsServer, { + closeAsync: promisify(httpsServer.close.bind(httpsServer)), + destroyAsync: promisify(httpsServer.destroy.bind(httpsServer)), + listenAsync: promisify(httpsServer.listen.bind(httpsServer)), + }) as https.Server & AsyncServer this.wssServer = new SocketIOServer(this.httpsServer) diff --git a/packages/network/test/unit/agent.spec.ts b/packages/network/test/unit/agent.spec.ts index 0d6dec72766..26318a6b212 100644 --- a/packages/network/test/unit/agent.spec.ts +++ b/packages/network/test/unit/agent.spec.ts @@ -1,5 +1,5 @@ import { describe, it, expect, beforeEach, afterEach, beforeAll, afterAll, vi } from 'vitest' -import Bluebird from 'bluebird' +import { promisify } from 'util' import http from 'http' import https from 'https' @@ -451,13 +451,15 @@ describe('lib/agent', function () { }) it('#createUpstreamProxyConnection throws when connection is accepted then closed', async () => { - const proxy = Bluebird.promisifyAll( - allowDestroy( - net.createServer((socket) => { - socket.end() - }), - ), - ) as net.Server & AsyncServer + const proxyServer = allowDestroy( + net.createServer((socket) => { + socket.end() + }), + ) + const proxy = Object.assign(proxyServer, { + destroyAsync: promisify(proxyServer.destroy.bind(proxyServer)), + listenAsync: promisify(proxyServer.listen.bind(proxyServer)), + }) as net.Server & AsyncServer const proxyPort = PROXY_PORT + 2 diff --git a/packages/network/tsconfig.base.json b/packages/network/tsconfig.base.json new file mode 100644 index 00000000000..45de1c97861 --- /dev/null +++ b/packages/network/tsconfig.base.json @@ -0,0 +1,21 @@ +{ + "include": [ + "lib/*.ts" + ], + "files": [ + "./../ts/index.d.ts" + ], + "compilerOptions": { + "types": [ + "node" + ], + "esModuleInterop": true, + "strict": true, + "noImplicitAny": true, + "useUnknownInCatchVariables": true, + "noUnusedLocals": true, + "noImplicitReturns": true, + "declaration": true, + "skipLibCheck": true + } +} \ No newline at end of file diff --git a/packages/network/tsconfig.cjs.json b/packages/network/tsconfig.cjs.json new file mode 100644 index 00000000000..709d6e27fca --- /dev/null +++ b/packages/network/tsconfig.cjs.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "rootDir": "./lib", + "outDir": "./cjs", + "target": "ES2022", + "module": "CommonJS", + "moduleResolution": "node" + } +} \ No newline at end of file diff --git a/packages/network/tsconfig.esm.json b/packages/network/tsconfig.esm.json new file mode 100644 index 00000000000..970560cc72e --- /dev/null +++ b/packages/network/tsconfig.esm.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "rootDir": "./lib", + "outDir": "./esm", + "target": "ES2022", + "module": "ES2022", + "moduleResolution": "node", + "noEmit": true + } +} \ No newline at end of file diff --git a/packages/network/tsconfig.json b/packages/network/tsconfig.json deleted file mode 100644 index 6e4f8367c92..00000000000 --- a/packages/network/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "./../ts/tsconfig.json", - "include": [ - "*.ts", - "lib/*.ts", - "lib/**/*.ts" - ], - "files": [ - "./../ts/index.d.ts" - ] -} diff --git a/packages/proxy/lib/http/request-middleware.ts b/packages/proxy/lib/http/request-middleware.ts index 2805bb64c28..8eeaebae11b 100644 --- a/packages/proxy/lib/http/request-middleware.ts +++ b/packages/proxy/lib/http/request-middleware.ts @@ -1,5 +1,5 @@ import _ from 'lodash' -import { blocked, cors } from '@packages/network' +import { blocked } from '@packages/network' import { InterceptRequest, SetMatchingRoutes } from '@packages/net-stubbing' import { telemetry } from '@packages/telemetry' import { isVerboseTelemetry as isVerbose } from '.' @@ -10,6 +10,7 @@ import { doesTopNeedToBeSimulated } from './util/top-simulation' import type { HttpMiddleware } from './' import type { CypressIncomingRequest } from '../types' +import { urlMatchesOriginProtectionSpace } from '@packages/network-tools' // do not use a debug namespace in this file - use the per-request `this.debug` instead // available as cypress-verbose:proxy:http @@ -375,7 +376,7 @@ const EndRequestsToBlockedHosts: RequestMiddleware = function () { }) if (blockHosts) { - const matches = blocked.matches(this.req.proxiedUrl, blockHosts) + const matches = blocked.matches(this.req.proxiedUrl, blockHosts as string[]) span?.setAttributes({ didUrlMatchBlockedHosts: !!matches, @@ -430,7 +431,7 @@ const StripUnsupportedAcceptEncoding: RequestMiddleware = function () { function reqNeedsBasicAuthHeaders (req, { auth, origin }: Cypress.RemoteState) { //if we have auth headers, this request matches our origin, protection space, and the user has not supplied auth headers - return auth && !req.headers['authorization'] && cors.urlMatchesOriginProtectionSpace(req.proxiedUrl, origin) + return auth && !req.headers['authorization'] && urlMatchesOriginProtectionSpace(req.proxiedUrl, origin) } const MaybeSetBasicAuthHeaders: RequestMiddleware = function () { diff --git a/packages/proxy/lib/http/response-middleware.ts b/packages/proxy/lib/http/response-middleware.ts index b1c8f11ef43..930495d1e42 100644 --- a/packages/proxy/lib/http/response-middleware.ts +++ b/packages/proxy/lib/http/response-middleware.ts @@ -6,7 +6,8 @@ import { PassThrough, Readable } from 'stream' import { URL } from 'url' import zlib from 'zlib' import { InterceptResponse } from '@packages/net-stubbing' -import { concatStream, cors, httpUtils, DocumentDomainInjection } from '@packages/network' +import { concatStream, httpUtils } from '@packages/network' +import { getDomainNameFromUrl, DocumentDomainInjection } from '@packages/network-tools' import { toughCookieToAutomationCookie } from '@packages/server/lib/util/cookies' import type { RemoteState } from '@packages/server/lib/remote_states' import { telemetry } from '@packages/telemetry' @@ -838,7 +839,7 @@ const MaybeInjectHtml: ResponseMiddleware = function () { const decodedBody = iconv.decode(body, nodeCharset) const injectedBody = await rewriter.html(decodedBody, { cspNonce: this.res.injectionNonce, - domainName: cors.getDomainNameFromUrl(this.req.proxiedUrl), + domainName: getDomainNameFromUrl(this.req.proxiedUrl), wantsInjection: this.res.wantsInjection, wantsSecurityRemoved: this.res.wantsSecurityRemoved, isNotJavascript: !resContentTypeIsJavaScript(this.incomingRes), diff --git a/packages/proxy/lib/http/util/buffers.ts b/packages/proxy/lib/http/util/buffers.ts index 36a38eba379..9b3b50e8bb6 100644 --- a/packages/proxy/lib/http/util/buffers.ts +++ b/packages/proxy/lib/http/util/buffers.ts @@ -1,6 +1,6 @@ import _ from 'lodash' import debugModule from 'debug' -import { uri } from '@packages/network' +import { removeDefaultPort } from '@packages/network-tools' import type { Readable } from 'stream' import type { IncomingMessage } from 'http' @@ -17,7 +17,7 @@ export type HttpBuffer = { const stripPort = (url) => { try { - return uri.removeDefaultPort(url).format() + return removeDefaultPort(url).format() } catch (e) { return url } diff --git a/packages/proxy/lib/http/util/cookies.ts b/packages/proxy/lib/http/util/cookies.ts index 29be10305f2..a5ae5330c53 100644 --- a/packages/proxy/lib/http/util/cookies.ts +++ b/packages/proxy/lib/http/util/cookies.ts @@ -1,12 +1,10 @@ import _ from 'lodash' import type Debug from 'debug' import { URL } from 'url' -import { cors } from '@packages/network' -import { urlOriginsMatch, urlSameSiteMatch } from '@packages/network/lib/cors' import { SerializableAutomationCookie, Cookie, CookieJar, toughCookieToAutomationCookie } from '@packages/server/lib/util/cookies' import type { RequestCredentialLevel } from '../../types' -// tslint:disable-next-line: no-implicit-dependencies - wants us to explicitly define cypress dep -import type { ResourceType } from 'cypress/types/net-stubbing' +import type { ResourceType } from '@packages/net-stubbing' +import { urlOriginsMatch, urlSameSiteMatch } from '@packages/network-tools' type SiteContext = 'same-origin' | 'same-site' | 'cross-site' @@ -123,7 +121,7 @@ export const getSameSiteContext = (autUrl: string | undefined, requestUrl: strin // if there's no AUT URL, it's a request for the first URL visited, or if // the request origin is considered the same site as the AUT origin; // both indicate that it's not a cross-site request - if (!autUrl || cors.urlSameSiteMatch(autUrl, requestUrl)) { + if (!autUrl || urlSameSiteMatch(autUrl, requestUrl)) { return 'strict' } diff --git a/packages/proxy/package.json b/packages/proxy/package.json index d0404d3910d..842976bda6b 100644 --- a/packages/proxy/package.json +++ b/packages/proxy/package.json @@ -37,6 +37,7 @@ "@packages/errors": "0.0.0-development", "@packages/net-stubbing": "0.0.0-development", "@packages/network": "0.0.0-development", + "@packages/network-tools": "0.0.0-development", "@packages/resolve-dist": "0.0.0-development", "@packages/rewriter": "0.0.0-development", "@packages/server": "0.0.0-development", diff --git a/packages/proxy/test/integration/net-stubbing.spec.ts b/packages/proxy/test/integration/net-stubbing.spec.ts index 8b790a6e7fb..6742a443fc6 100644 --- a/packages/proxy/test/integration/net-stubbing.spec.ts +++ b/packages/proxy/test/integration/net-stubbing.spec.ts @@ -9,7 +9,8 @@ import express from 'express' import sinon from 'sinon' import { expect } from 'chai' import supertest from 'supertest' -import { allowDestroy, DocumentDomainInjection } from '@packages/network' +import { allowDestroy } from '@packages/network' +import { DocumentDomainInjection } from '@packages/network-tools' import { EventEmitter } from 'events' import { RemoteStates } from '@packages/server/lib/remote_states' import { CookieJar } from '@packages/server/lib/util/cookies' diff --git a/packages/proxy/test/unit/http/request-middleware.spec.ts b/packages/proxy/test/unit/http/request-middleware.spec.ts index eb1c5023803..46916f0f879 100644 --- a/packages/proxy/test/unit/http/request-middleware.spec.ts +++ b/packages/proxy/test/unit/http/request-middleware.spec.ts @@ -8,7 +8,7 @@ import { HttpBuffer, HttpBuffers } from '../../../lib/http/util/buffers' import { RemoteStates } from '@packages/server/lib/remote_states' import { CookieJar } from '@packages/server/lib/util/cookies' import { HttpMiddlewareThis } from '../../../lib/http' -import { DocumentDomainInjection } from '@packages/network' +import { DocumentDomainInjection } from '@packages/network-tools' describe('http/request-middleware', () => { const serverPort = 3030 diff --git a/packages/proxy/test/unit/http/response-middleware.spec.ts b/packages/proxy/test/unit/http/response-middleware.spec.ts index 5f749f4eb23..a977f569217 100644 --- a/packages/proxy/test/unit/http/response-middleware.spec.ts +++ b/packages/proxy/test/unit/http/response-middleware.spec.ts @@ -9,7 +9,7 @@ import { Readable } from 'stream' import * as rewriter from '../../../lib/http/util/rewriter' import { nonceDirectives, problematicCspDirectives, unsupportedCSPDirectives } from '../../../lib/http/util/csp-header' import * as serviceWorkerInjector from '../../../lib/http/util/service-worker-injector' -import { DocumentDomainInjection } from '@packages/network' +import { DocumentDomainInjection } from '@packages/network-tools' describe('http/response-middleware', function () { const serverPort = 3030 diff --git a/packages/server/lib/automation/cookies.ts b/packages/server/lib/automation/cookies.ts index be469a84395..7e416f6e6ac 100644 --- a/packages/server/lib/automation/cookies.ts +++ b/packages/server/lib/automation/cookies.ts @@ -4,9 +4,9 @@ import extension from '@packages/extension' import { isHostOnlyCookie } from '../browsers/cdp_automation' import type { SerializableAutomationCookie } from '../util/cookies' -type AutomationFn = (data: V) => Bluebird.Promise +type AutomationFn = (data: V) => Promise -type AutomationMessageFn = (message: string, data: V) => Bluebird.Promise +type AutomationMessageFn = (message: string, data: V) => Promise export interface AutomationCookie { domain: string @@ -108,23 +108,22 @@ export class Cookies { } } - getCookies (data: { + async getCookies (data: { domain?: string }, automate: AutomationMessageFn) { debug('getting:cookies %o', data) - return automate('get:cookies', data) - .then((cookies) => { - cookies = normalizeGetCookies(cookies) - cookies = _.reject(cookies, (cookie) => this.isNamespaced(cookie)) as AutomationCookie[] + let cookies = await automate('get:cookies', data) - debug('received get:cookies %o', cookies) + cookies = normalizeGetCookies(cookies) + cookies = _.reject(cookies, (cookie) => this.isNamespaced(cookie)) as AutomationCookie[] - return cookies - }) + debug('received get:cookies %o', cookies) + + return cookies } - getCookie (data: { + async getCookie (data: { domain: string name: string }, automate: AutomationFn<{ @@ -133,8 +132,8 @@ export class Cookies { }, AutomationCookie | null>) { debug('getting:cookie %o', data) - return automate(data) - .then((cookie) => { + let cookie = await automate(data) + if (this.isNamespaced(cookie)) { throw new Error('Sorry, you cannot get a Cypress namespaced cookie.') } else { @@ -144,10 +143,9 @@ export class Cookies { return cookie } - }) } - setCookie (data: SerializableAutomationCookie, automate: AutomationFn) { + async setCookie (data: SerializableAutomationCookie, automate: AutomationFn) { this.throwIfNamespaced(data) const cookie = normalizeCookieProps(data) as AutomationCookie @@ -157,17 +155,16 @@ export class Cookies { debug('set:cookie %o', cookie) - return automate(cookie) - .then((cookie) => { - cookie = normalizeGetCookieProps(cookie) + let automationCookie = await automate(cookie) - debug('received set:cookie %o', cookie) + automationCookie = normalizeGetCookieProps(automationCookie) - return cookie - }) + debug('received set:cookie %o', automationCookie) + + return automationCookie } - setCookies ( + async setCookies ( cookies: SerializableAutomationCookie[] | AutomationCookie[], automate: AutomationMessageFn, eventName: 'set:cookies' | 'add:cookies' = 'set:cookies', @@ -185,8 +182,9 @@ export class Cookies { debug(`${eventName} %o`, cookies) - return automate(eventName, cookies as AutomationCookie[]) - .return(cookies) + let automationCookies = await automate(eventName, cookies as AutomationCookie[]) + + return automationCookies } // set:cookies will clear cookies first in browsers that use CDP. this is the @@ -199,7 +197,7 @@ export class Cookies { return this.setCookies(cookies, automate, 'add:cookies') } - clearCookie (data: { + async clearCookie (data: { domain: string name: string }, automate: AutomationFn<{ @@ -209,14 +207,13 @@ export class Cookies { this.throwIfNamespaced(data) debug('clear:cookie %o', data) - return automate(data) - .then((cookie) => { - cookie = normalizeCookieProps(cookie) + const cookie = await automate(data) - debug('received clear:cookie %o', cookie) + const normalizedCookie = normalizeCookieProps(cookie) - return cookie - }) + debug('received clear:cookie %o', normalizedCookie) + + return normalizedCookie } async clearCookies (data: AutomationCookie[], automate: AutomationMessageFn) { @@ -226,8 +223,10 @@ export class Cookies { debug('clear:cookies %o', cookies.length) - return automate('clear:cookies', cookies) - .mapSeries(normalizeCookieProps) + const automationCookies = await automate('clear:cookies', cookies) + const normalizedCookies = _.map(automationCookies, normalizeCookieProps) + + return normalizedCookies } changeCookie (data: { diff --git a/packages/server/lib/browsers/cdp_automation.ts b/packages/server/lib/browsers/cdp_automation.ts index b923c334101..988ce68f2da 100644 --- a/packages/server/lib/browsers/cdp_automation.ts +++ b/packages/server/lib/browsers/cdp_automation.ts @@ -4,7 +4,7 @@ import _ from 'lodash' import Bluebird from 'bluebird' import type { Protocol } from 'devtools-protocol' import type ProtocolMapping from 'devtools-protocol/types/protocol-mapping' -import { cors, uri } from '@packages/network' +import { parseDomain, isLocalhost as isLocalhostNetworkTools } from '@packages/network-tools' import debugModule from 'debug' import { URL } from 'url' import { performance } from 'perf_hooks' @@ -61,7 +61,7 @@ function convertSameSiteCdpToExtension (str: Protocol.Network.CookieSameSite): c export function isHostOnlyCookie (cookie) { if (cookie.domain[0] === '.') return false - const parsedDomain = cors.parseDomain(cookie.domain) + const parsedDomain = parseDomain(cookie.domain) // make every cookie non-hostOnly // unless it's a top-level domain (localhost, ...) or IP address @@ -380,7 +380,7 @@ export class CdpAutomation implements CDPClient, AutomationMiddleware { urls: [url], }) .then((result: Protocol.Network.GetCookiesResponse) => { - const isLocalhost = uri.isLocalhost(new URL(url)) + const isLocalhost = isLocalhostNetworkTools(new URL(url)) return normalizeGetCookies(result.cookies) .filter((cookie) => { diff --git a/packages/server/lib/cache.ts b/packages/server/lib/cache.ts index b3cd8206661..e36c8ef87e1 100644 --- a/packages/server/lib/cache.ts +++ b/packages/server/lib/cache.ts @@ -57,7 +57,7 @@ export const cache = { getProjectRoots (): Promise { return fileUtil.transaction((tx: Transaction) => { return this._getProjects(tx).then((projects) => { - const pathsToRemove = Promise.reduce(projects, (memo: string[], path) => { + const pathsToRemove = Promise.reduce(projects, (memo: string[], path: string) => { return fs.statAsync(path) .catch(() => { memo.push(path) diff --git a/packages/server/lib/cloud/api/cloud_request.ts b/packages/server/lib/cloud/api/cloud_request.ts index 48ea8e4a7c3..40d45b12918 100644 --- a/packages/server/lib/cloud/api/cloud_request.ts +++ b/packages/server/lib/cloud/api/cloud_request.ts @@ -5,7 +5,7 @@ import os from 'os' import followRedirects from 'follow-redirects' import axios, { AxiosInstance } from 'axios' import pkg from '@packages/root' -import { strictAgent } from '@packages/network/lib/agent' +import { strictAgent } from '@packages/network' import app_config from '../../../config/app.json' import { installErrorTransform } from './axios_middleware/transform_error' diff --git a/packages/server/lib/cloud/api/index.ts b/packages/server/lib/cloud/api/index.ts index 16c775cd324..46efada09b2 100644 --- a/packages/server/lib/cloud/api/index.ts +++ b/packages/server/lib/cloud/api/index.ts @@ -1,22 +1,21 @@ -const _ = require('lodash') -const os = require('os') -const debug = require('debug')('cypress:server:cloud:api') -const debugProtocol = require('debug')('cypress:server:protocol') -const request = require('@cypress/request-promise') -const humanInterval = require('human-interval') +import _ from 'lodash' +import os from 'os' +import debugModule from 'debug' +import request from '@cypress/request-promise' +import humanInterval from 'human-interval' -const RequestErrors = require('@cypress/request-promise/errors') +import * as RequestErrors from '@cypress/request-promise/errors' -const pkg = require('@packages/root') +import pkg from '@packages/root' -const machineId = require('../machine_id') -const errors = require('../../errors') +import * as machineId from '../machine_id' +import * as errors from '../../errors' import Bluebird from 'bluebird' import type { AfterSpecDurations } from '@packages/types' -import agent from '@packages/network/lib/agent' -import type { CombinedAgent } from '@packages/network/lib/agent' +import { agent } from '@packages/network' +import type { CombinedAgent } from '@packages/network' import { apiUrl, apiRoutes, makeRoutes } from '../routes' import { getText } from '../../util/status_code' @@ -39,6 +38,9 @@ import { transformError } from './axios_middleware/transform_error' import { DecryptionError } from './cloud_request_errors' import { isNonRetriableCertErrorCode } from '../network/non_retriable_cert_error_codes' +const debug = debugModule('cypress:server:cloud:api') +const debugProtocol = debugModule('cypress:server:protocol') + const THIRTY_SECONDS = humanInterval('30 seconds') const SIXTY_SECONDS = humanInterval('60 seconds') const TWO_MINUTES = humanInterval('2 minutes') diff --git a/packages/server/lib/cloud/machine_id.ts b/packages/server/lib/cloud/machine_id.ts index 2ed9802b38e..4f3340f9252 100644 --- a/packages/server/lib/cloud/machine_id.ts +++ b/packages/server/lib/cloud/machine_id.ts @@ -1,12 +1,8 @@ -const nmi = require('node-machine-id') +import nmi from 'node-machine-id' -function machineId () { +export function machineId () { return nmi.machineId() .catch(() => { return null }) } - -module.exports = { - machineId, -} diff --git a/packages/server/lib/cloud/protocol.ts b/packages/server/lib/cloud/protocol.ts index 60adabb72c0..ce7873894b6 100644 --- a/packages/server/lib/cloud/protocol.ts +++ b/packages/server/lib/cloud/protocol.ts @@ -11,12 +11,11 @@ import pkg from '@packages/root' import env from '../util/env' import { putProtocolArtifact } from './api/put_protocol_artifact' import { requireScript } from './require_script' +import * as routes from './routes' import type { Readable } from 'stream' import type { ProtocolManagerShape, AppCaptureProtocolInterface, CDPClient, ProtocolError, CaptureArtifact, ProtocolErrorReport, ProtocolCaptureMethod, ProtocolManagerOptions, ResponseStreamOptions, ResponseEndedWithEmptyBodyOptions, ResponseStreamTimedOutOptions, AfterSpecDurations, FoundSpec } from '@packages/types' -const routes = require('./routes') - const debug = Debug('cypress:server:protocol') const debugVerbose = Debug('cypress-verbose:server:protocol') diff --git a/packages/server/lib/cloud/user.ts b/packages/server/lib/cloud/user.ts index 51f1008b1b2..11fa3cc7484 100644 --- a/packages/server/lib/cloud/user.ts +++ b/packages/server/lib/cloud/user.ts @@ -1,5 +1,5 @@ -const api = require('./api').default -const { cache } = require('../cache') +import api from './api' +import { cache } from '../cache' import type { CachedUser } from '@packages/types' import type Bluebird from 'bluebird' @@ -9,7 +9,7 @@ export = { return cache.getUser() }, - set (user: CachedUser): Bluebird { + set (user: CachedUser): Bluebird { return cache.setUser(user) }, diff --git a/packages/server/lib/controllers/files.ts b/packages/server/lib/controllers/files.ts index dd285009253..4892c550cdd 100644 --- a/packages/server/lib/controllers/files.ts +++ b/packages/server/lib/controllers/files.ts @@ -4,7 +4,7 @@ import cwd from '../cwd' import Debug from 'debug' import { escapeFilenameInUrl } from '../util/escape_filename' import { getCtx } from '@packages/data-context' -import { DocumentDomainInjection } from '@packages/network/lib/document-domain-injection' +import { DocumentDomainInjection } from '@packages/network-tools' import { privilegedCommandsManager } from '../privileged-commands/privileged-commands-manager' import type { Cfg } from '../project-base' import type { RemoteStates } from '../remote_states' diff --git a/packages/server/lib/remote_states.ts b/packages/server/lib/remote_states.ts index 79304916ee8..aabe32612a9 100644 --- a/packages/server/lib/remote_states.ts +++ b/packages/server/lib/remote_states.ts @@ -1,8 +1,7 @@ -import { cors, uri } from '@packages/network' +import { origin, getDomainNameFromParsedHost, parseUrlIntoHostProtocolDomainTldPort } from '@packages/network-tools' import Debug from 'debug' import _ from 'lodash' -import type { ParsedHostWithProtocolAndHost } from '@packages/network/lib/types' -import type { DocumentDomainInjection } from '@packages/network' +import type { DocumentDomainInjection, ParsedHostWithProtocolAndHost } from '@packages/network-tools' export const DEFAULT_DOMAIN_NAME = 'localhost' @@ -115,8 +114,8 @@ export class RemoteStates { } private _stateFromUrl (url: string): RemoteState { - const remoteOrigin = uri.origin(url) - const remoteProps = cors.parseUrlIntoHostProtocolDomainTldPort(remoteOrigin) + const remoteOrigin = origin(url) + const remoteProps = parseUrlIntoHostProtocolDomainTldPort(remoteOrigin) if ((url === '') || !fullyQualifiedRe.test(url)) { return { @@ -132,7 +131,7 @@ export class RemoteStates { origin: remoteOrigin, strategy: 'http', fileServer: null, - domainName: cors.getDomainNameFromParsedHost(remoteProps), + domainName: getDomainNameFromParsedHost(remoteProps), props: remoteProps, } } diff --git a/packages/server/lib/routes.ts b/packages/server/lib/routes.ts index 478c6997167..dcb8ab30301 100644 --- a/packages/server/lib/routes.ts +++ b/packages/server/lib/routes.ts @@ -3,7 +3,7 @@ import Debug from 'debug' import { ErrorRequestHandler, Request, Router } from 'express' import send from 'send' import { getPathToDist } from '@packages/resolve-dist' -import { cors } from '@packages/network' +import { domainPropsToHostname } from '@packages/network-tools' import type { NetworkProxy } from '@packages/proxy' import type { Cfg } from './project-base' import xhrs from './controllers/xhrs' @@ -86,7 +86,7 @@ export const createCommonRoutes = ({ return next() } - const primaryHostname = cors.domainPropsToHostname(primary.props) + const primaryHostname = domainPropsToHostname(primary.props) // domain matches (example.com === example.com), but incoming request is // https:// (established above), while the domain the user is trying to diff --git a/packages/server/lib/saved_state.ts b/packages/server/lib/saved_state.ts index 01a6201c525..273acd16926 100644 --- a/packages/server/lib/saved_state.ts +++ b/packages/server/lib/saved_state.ts @@ -100,6 +100,7 @@ export const create = (projectRoot?: string, isTextTerminal: boolean = false): B return Bluebird.resolve(FileUtil.noopFile) } + // @ts-ignore return formStatePath(projectRoot) .then((statePath: string) => { const fullStatePath = appData.projectsPath(statePath) @@ -120,6 +121,7 @@ export const create = (projectRoot?: string, isTextTerminal: boolean = false): B stateFile.set = _.wrap(stateFile.set.bind(stateFile), normalizeAndAllowSet) + // @ts-ignore stateFiles[fullStatePath] = stateFile return stateFile as SavedStateAPI diff --git a/packages/server/lib/server-base.ts b/packages/server/lib/server-base.ts index 4addafeb176..7dfac2d7048 100644 --- a/packages/server/lib/server-base.ts +++ b/packages/server/lib/server-base.ts @@ -14,7 +14,8 @@ import url from 'url' import la from 'lazy-ass' import httpsProxy from '@packages/https-proxy' import { getRoutesForRequest, netStubbingState, NetStubbingState } from '@packages/net-stubbing' -import { agent, clientCertificates, cors, httpUtils, uri, concatStream, DocumentDomainInjection } from '@packages/network' +import { agent, clientCertificates, httpUtils, concatStream } from '@packages/network' +import { DocumentDomainInjection, getPath, parseUrlIntoHostProtocolDomainTldPort, removeDefaultPort } from '@packages/network-tools' import { NetworkProxy, BrowserPreRequest } from '@packages/proxy' import type { SocketCt } from './socket-ct' import * as errors from './errors' @@ -123,9 +124,9 @@ const setProxiedUrl = function (req) { // and only leave the path which is // how browsers would normally send // use their url - req.proxiedUrl = uri.removeDefaultPort(req.url).format() + req.proxiedUrl = removeDefaultPort(req.url).format() - req.url = uri.getPath(req.url) + req.url = getPath(req.url) } const notSSE = (req, res) => { @@ -163,6 +164,7 @@ export class ServerBase { protected _nodeProxy?: httpProxy protected _networkProxy?: NetworkProxy protected _netStubbingState?: NetStubbingState + // @ts-ignore protected _httpsProxy?: httpsProxy protected _graphqlWS?: WebSocketServer protected _eventBus: EventEmitter @@ -620,7 +622,7 @@ export class ServerBase { // get the port & hostname from host header const fullUrl = `${req.connection.encrypted ? 'https' : 'http'}://${host}` const { hostname, protocol } = url.parse(fullUrl) - const { port } = cors.parseUrlIntoHostProtocolDomainTldPort(fullUrl) + const { port } = parseUrlIntoHostProtocolDomainTldPort(fullUrl) const onProxyErr = (err, req, res) => { return debug('Got ERROR proxying websocket connection', { err, port, protocol, hostname, req }) @@ -1016,6 +1018,7 @@ export class ServerBase { debug('sending request with options %o', options) return runPhase(() => { + // @ts-ignore return request.sendStream(userAgent, automationRequest, options) .then((createReqStream) => { const stream = createReqStream() diff --git a/packages/server/lib/util/ensure-url.ts b/packages/server/lib/util/ensure-url.ts index 3e23a9c594e..eb0505a6329 100644 --- a/packages/server/lib/util/ensure-url.ts +++ b/packages/server/lib/util/ensure-url.ts @@ -70,5 +70,12 @@ export const isListening = (urlStr: string) => { .catch({ name: 'StatusCodeError' }, () => {}) // we just care if it can connect, not if it's a valid resource } - return connect.getAddress(Number(port), String(hostname)) + // With https://github.com/cypress-io/cypress/pull/32633, the @packages/network package has refactored some methods to use + // native promises. This method wraps the native promise in a Bluebird promise to ensure that the method returns a Bluebird promise + // until we are able to refactor it. + return new Bluebird((resolve, reject) => { + connect.getAddress(Number(port), String(hostname)) + .then(resolve) + .catch(reject) + }) } diff --git a/packages/server/lib/util/server_destroy.ts b/packages/server/lib/util/server_destroy.ts index b8086466c6e..9d48d45cff9 100644 --- a/packages/server/lib/util/server_destroy.ts +++ b/packages/server/lib/util/server_destroy.ts @@ -1,6 +1,6 @@ import Bluebird from 'bluebird' import type http from 'http' -import * as network from '@packages/network' +import { allowDestroy as allowDestroyNetwork } from '@packages/network' export interface DestroyableHttpServer extends http.Server { /** asynchronously destroys the http server, waiting @@ -10,7 +10,7 @@ export interface DestroyableHttpServer extends http.Server { } export const allowDestroy = (server) => { - network.allowDestroy(server) + allowDestroyNetwork(server) server.destroyAsync = () => { return Bluebird.promisify(server.destroy)() diff --git a/packages/server/package.json b/packages/server/package.json index 7dfadcb0f2a..fc0715eddbf 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -158,6 +158,7 @@ "@packages/launcher": "0.0.0-development", "@packages/net-stubbing": "0.0.0-development", "@packages/network": "0.0.0-development", + "@packages/network-tools": "0.0.0-development", "@packages/proxy": "0.0.0-development", "@packages/resolve-dist": "0.0.0-development", "@packages/rewriter": "0.0.0-development", diff --git a/packages/server/test/unit/cloud/api/api_spec.js b/packages/server/test/unit/cloud/api/api_spec.js index 83691b3df1a..e1388b9ba4b 100644 --- a/packages/server/test/unit/cloud/api/api_spec.js +++ b/packages/server/test/unit/cloud/api/api_spec.js @@ -11,6 +11,7 @@ const encryption = require('../../../../lib/cloud/encryption') const { agent, + CombinedAgent, } = require('@packages/network') const pkg = require('@packages/root') const api = require('../../../../lib/cloud/api').default @@ -140,7 +141,10 @@ describe('lib/cloud/api', () => { context('.rp', () => { beforeEach(() => { - sinon.spy(agent, 'addRequest') + // Because @packages/network is a bundled ES6 class with https://github.com/cypress-io/cypress/pull/32633 + // we can no longer mock the addRequest method on the agent singleton instance directly + // Until we are able to convert this test to Vitest/TypeScript, we need to spy on the prototype + sinon.spy(CombinedAgent.prototype, 'addRequest') return nock.enableNetConnect() }) // nock will prevent requests from reaching the agent @@ -151,9 +155,9 @@ describe('lib/cloud/api', () => { return api.ping() .thenThrow() .catch(() => { - expect(agent.addRequest).to.be.calledOnce + expect(CombinedAgent.prototype.addRequest).to.be.calledOnce - expect(agent.addRequest).to.be.calledWithMatch(sinon.match.any, { + expect(CombinedAgent.prototype.addRequest).to.be.calledWithMatch(sinon.match.any, { href: 'http://localhost:1234/ping', }) }) @@ -165,9 +169,9 @@ describe('lib/cloud/api', () => { return api.ping() .thenThrow() .catch(() => { - expect(agent.addRequest).to.be.calledOnce + expect(CombinedAgent.prototype.addRequest).to.be.calledOnce - expect(agent.addRequest).to.be.calledWithMatch(sinon.match.any, { + expect(CombinedAgent.prototype.addRequest).to.be.calledWithMatch(sinon.match.any, { rejectUnauthorized: true, }) }) @@ -185,9 +189,9 @@ describe('lib/cloud/api', () => { return api.ping() .thenThrow() .catch(() => { - expect(agent.addRequest).to.be.calledOnce + expect(CombinedAgent.prototype.addRequest).to.be.calledOnce - expect(agent.addRequest).to.be.calledWithMatch(sinon.match.any, { + expect(CombinedAgent.prototype.addRequest).to.be.calledWithMatch(sinon.match.any, { href: 'http://localhost:1234/ping', }) }) diff --git a/packages/server/test/unit/cloud/api/cloud_request_spec.ts b/packages/server/test/unit/cloud/api/cloud_request_spec.ts index 1c9ef2b711d..ca424f4905f 100644 --- a/packages/server/test/unit/cloud/api/cloud_request_spec.ts +++ b/packages/server/test/unit/cloud/api/cloud_request_spec.ts @@ -1,7 +1,7 @@ import sinon from 'sinon' import sinonChai from 'sinon-chai' import chai, { expect } from 'chai' -import agent, { strictAgent } from '@packages/network/lib/agent' +import agent, { strictAgent } from '@packages/network' import axios, { CreateAxiosDefaults, AxiosInstance } from 'axios' import debugLib from 'debug' import stripAnsi from 'strip-ansi' diff --git a/packages/server/test/unit/remote_states.spec.ts b/packages/server/test/unit/remote_states.spec.ts index 085a58b621d..86a23f13416 100644 --- a/packages/server/test/unit/remote_states.spec.ts +++ b/packages/server/test/unit/remote_states.spec.ts @@ -4,7 +4,7 @@ import chaiAsPromised from 'chai-as-promised' import chaiSubset from 'chai-subset' import sinonChai from '@cypress/sinon-chai' import Sinon from 'sinon' -import { OriginBehavior } from '@packages/network/lib/document-domain-injection' +import { OriginBehavior } from '@packages/network-tools' import { RemoteStates, DEFAULT_DOMAIN_NAME } from '../../lib/remote_states' diff --git a/tooling/v8-snapshot/package.json b/tooling/v8-snapshot/package.json index d235a83b2c1..45cd8c7ffac 100644 --- a/tooling/v8-snapshot/package.json +++ b/tooling/v8-snapshot/package.json @@ -33,7 +33,8 @@ "cpr": "^3.0.1", "mocha": "7.0.1", "snap-shot-it": "7.9.10", - "stealthy-require": "^1.1.1" + "stealthy-require": "^1.1.1", + "typescript": "~5.4.5" }, "files": [ "dist", diff --git a/yarn.lock b/yarn.lock index 6ac4e67f682..8f83eee1f74 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8706,6 +8706,13 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== +"@types/proxy-from-env@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@types/proxy-from-env/-/proxy-from-env-1.0.4.tgz#0a0545768f2d6c16b81a84ffefb53b423807907c" + integrity sha512-TPR9/bCZAr3V1eHN4G3LD3OLicdJjqX1QRXWuNcCYgE66f/K8jO2ZRtHxI2D9MbnuUP6+qiKSS8eUHp6TFHGCw== + dependencies: + "@types/node" "*" + "@types/qs@*": version "6.9.6" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.6.tgz#df9c3c8b31a247ec315e6996566be3171df4b3b1" From e9f5df0530dd2bea2a31b5ac982ea4efc1f902a1 Mon Sep 17 00:00:00 2001 From: Bill Glesias Date: Fri, 3 Oct 2025 14:09:00 -0400 Subject: [PATCH 2/3] chore: sync cloud validations after building packages as the network libs are needed --- .circleci/src/pipeline/@pipeline.yml | 10 ++++------ .../server/test/unit/cloud/api/cloud_request_spec.ts | 4 +++- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.circleci/src/pipeline/@pipeline.yml b/.circleci/src/pipeline/@pipeline.yml index ac4500fd1f4..8c11c23ce54 100644 --- a/.circleci/src/pipeline/@pipeline.yml +++ b/.circleci/src/pipeline/@pipeline.yml @@ -195,17 +195,15 @@ commands: description: Save entire folder as artifact for other jobs to run without reinstalling steps: - run: - name: Sync Cloud Validations + name: Build packages command: | source ./scripts/ensure-node.sh - yarn workspace @packages/network-tools build - yarn workspace @packages/network build - yarn gulp syncCloudValidations + yarn build - run: - name: Build packages + name: Sync Cloud Validations command: | source ./scripts/ensure-node.sh - yarn build + yarn gulp syncCloudValidations - run: name: Generate v8 snapshot command: | diff --git a/packages/server/test/unit/cloud/api/cloud_request_spec.ts b/packages/server/test/unit/cloud/api/cloud_request_spec.ts index ca424f4905f..b5b498b4405 100644 --- a/packages/server/test/unit/cloud/api/cloud_request_spec.ts +++ b/packages/server/test/unit/cloud/api/cloud_request_spec.ts @@ -1,7 +1,9 @@ import sinon from 'sinon' import sinonChai from 'sinon-chai' import chai, { expect } from 'chai' -import agent, { strictAgent } from '@packages/network' +// NOTE: having to import directly from the lib folder as we cannot test ES6 classes effectively with sinon. +// Since this is a test, this is OK, but testing directly from lib in other modules is not a best practice. +import agent, { strictAgent } from '@packages/network/lib/agent' import axios, { CreateAxiosDefaults, AxiosInstance } from 'axios' import debugLib from 'debug' import stripAnsi from 'strip-ansi' From 91201d81e827f9308ec1a4dbab0fb2d1fcfc95b5 Mon Sep 17 00:00:00 2001 From: Bill Glesias Date: Wed, 8 Oct 2025 12:29:00 -0400 Subject: [PATCH 3/3] chore: fix cloud specs --- .../server/test/unit/cloud/api/api_spec.js | 18 +++++++----------- .../test/unit/cloud/api/cloud_request_spec.ts | 4 +--- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/packages/server/test/unit/cloud/api/api_spec.js b/packages/server/test/unit/cloud/api/api_spec.js index e1388b9ba4b..83691b3df1a 100644 --- a/packages/server/test/unit/cloud/api/api_spec.js +++ b/packages/server/test/unit/cloud/api/api_spec.js @@ -11,7 +11,6 @@ const encryption = require('../../../../lib/cloud/encryption') const { agent, - CombinedAgent, } = require('@packages/network') const pkg = require('@packages/root') const api = require('../../../../lib/cloud/api').default @@ -141,10 +140,7 @@ describe('lib/cloud/api', () => { context('.rp', () => { beforeEach(() => { - // Because @packages/network is a bundled ES6 class with https://github.com/cypress-io/cypress/pull/32633 - // we can no longer mock the addRequest method on the agent singleton instance directly - // Until we are able to convert this test to Vitest/TypeScript, we need to spy on the prototype - sinon.spy(CombinedAgent.prototype, 'addRequest') + sinon.spy(agent, 'addRequest') return nock.enableNetConnect() }) // nock will prevent requests from reaching the agent @@ -155,9 +151,9 @@ describe('lib/cloud/api', () => { return api.ping() .thenThrow() .catch(() => { - expect(CombinedAgent.prototype.addRequest).to.be.calledOnce + expect(agent.addRequest).to.be.calledOnce - expect(CombinedAgent.prototype.addRequest).to.be.calledWithMatch(sinon.match.any, { + expect(agent.addRequest).to.be.calledWithMatch(sinon.match.any, { href: 'http://localhost:1234/ping', }) }) @@ -169,9 +165,9 @@ describe('lib/cloud/api', () => { return api.ping() .thenThrow() .catch(() => { - expect(CombinedAgent.prototype.addRequest).to.be.calledOnce + expect(agent.addRequest).to.be.calledOnce - expect(CombinedAgent.prototype.addRequest).to.be.calledWithMatch(sinon.match.any, { + expect(agent.addRequest).to.be.calledWithMatch(sinon.match.any, { rejectUnauthorized: true, }) }) @@ -189,9 +185,9 @@ describe('lib/cloud/api', () => { return api.ping() .thenThrow() .catch(() => { - expect(CombinedAgent.prototype.addRequest).to.be.calledOnce + expect(agent.addRequest).to.be.calledOnce - expect(CombinedAgent.prototype.addRequest).to.be.calledWithMatch(sinon.match.any, { + expect(agent.addRequest).to.be.calledWithMatch(sinon.match.any, { href: 'http://localhost:1234/ping', }) }) diff --git a/packages/server/test/unit/cloud/api/cloud_request_spec.ts b/packages/server/test/unit/cloud/api/cloud_request_spec.ts index b5b498b4405..5e7a220d1af 100644 --- a/packages/server/test/unit/cloud/api/cloud_request_spec.ts +++ b/packages/server/test/unit/cloud/api/cloud_request_spec.ts @@ -1,9 +1,7 @@ import sinon from 'sinon' import sinonChai from 'sinon-chai' import chai, { expect } from 'chai' -// NOTE: having to import directly from the lib folder as we cannot test ES6 classes effectively with sinon. -// Since this is a test, this is OK, but testing directly from lib in other modules is not a best practice. -import agent, { strictAgent } from '@packages/network/lib/agent' +import { agent, strictAgent } from '@packages/network' import axios, { CreateAxiosDefaults, AxiosInstance } from 'axios' import debugLib from 'debug' import stripAnsi from 'strip-ansi'