chore: refactor server files to TypeScript (#32838)

* convert file_server to TypeScript

* chore: convert random to TypeScript

* convert a handful of files to TypeScript
This commit is contained in:
Bill Glesias
2025-10-31 12:39:02 -04:00
committed by GitHub
parent 04a5e9f1b9
commit f7e684174c
22 changed files with 192 additions and 183 deletions

View File

@@ -2,3 +2,5 @@ import { create as createProxy, reset as resetProxy } from './proxy'
import { CA } from './ca'
export { createProxy, resetProxy, CA }
export type { Server } from './server'

View File

@@ -3,7 +3,7 @@ import Promise from 'bluebird'
import { globalPubSub } from '@packages/data-context'
import { fs } from './util/fs'
import appData from './util/app_data'
import FileUtil from './util/file'
import { File as FileUtil } from './util/file'
import type { Cache, CachedUser, Preferences, Cohort } from '@packages/types'
interface Transaction {
@@ -55,6 +55,7 @@ export const cache = {
},
getProjectRoots (): Promise<string[]> {
// @ts-expect-error - transaction is untyped currently
return fileUtil.transaction((tx: Transaction) => {
return this._getProjects(tx).then((projects) => {
const pathsToRemove = Promise.reduce(projects, (memo: string[], path: string) => {
@@ -76,6 +77,7 @@ export const cache = {
},
removeProject (path: string): Promise<void> {
// @ts-expect-error - transaction is untyped currently
return fileUtil.transaction((tx: Transaction) => {
return this._getProjects(tx).then((projects) => {
return this._removeProjects(tx, projects, path)
@@ -84,6 +86,7 @@ export const cache = {
},
insertProject (path: string): Promise<void> {
// @ts-expect-error - transaction is untyped currently
return fileUtil.transaction((tx: Transaction) => {
return this._getProjects(tx).then((projects) => {
// projects are sorted by most recently used, so add a project to
@@ -124,6 +127,7 @@ export const cache = {
},
insertProjectPreferences (projectTitle: string, projectPreferences: Preferences): Promise<void> {
// @ts-expect-error - transaction is untyped currently
return fileUtil.transaction((tx: Transaction) => {
return tx.get('PROJECT_PREFERENCES', {}).then((preferences) => {
return tx.set('PROJECT_PREFERENCES', {
@@ -163,6 +167,7 @@ export const cache = {
},
insertCohort (cohort: Cohort): Promise<void> {
// @ts-expect-error - transaction is untyped currently
return fileUtil.transaction((tx: Transaction) => {
return tx.get('COHORTS', {}).then((cohorts) => {
return tx.set('COHORTS', {

View File

@@ -9,7 +9,7 @@ const url = require('url')
const { shell } = require('electron')
const machineId = require('./machine_id')
const random = require('../util/random')
import { id as randomId } from '../util/random'
const user = require('./user')
let app
@@ -29,7 +29,7 @@ const buildFullLoginUrl = (baseLoginUrl, server, utmSource, utmMedium, utmConten
const { port } = server.address()
if (!authState) {
authState = random.id(32)
authState = randomId(32)
}
const authUrl = url.parse(baseLoginUrl)

View File

@@ -8,7 +8,7 @@ import os from 'os'
import path from 'path'
import { strictAgent } from '@packages/network'
import pkg from '@packages/root'
import env from '../util/env'
import * as env from '../util/env'
import { putProtocolArtifact } from './api/put_protocol_artifact'
import { requireScript } from './require_script'
import * as routes from './routes'

View File

@@ -1,81 +0,0 @@
// TODO: move this to packages/core-file-server
const _ = require('lodash')
const debug = require('debug')('cypress:server:file_server')
const url = require('url')
const http = require('http')
const path = require('path')
const send = require('send')
const { httpUtils } = require('@packages/network')
const { allowDestroy } = require('./util/server_destroy')
const random = require('./util/random')
const networkFailures = require('./util/network_failures')
const onRequest = function (req, res, expectedToken, fileServerFolder) {
const token = req.headers['x-cypress-authorization']
if (token !== expectedToken) {
debug('authorization failed on file_server request %o', { reqUrl: req.url, expectedToken, token })
res.statusCode = 401
res.end()
return
}
const args = _.compact([
fileServerFolder,
req.url,
])
// strip off any query params from our req's url
// since we're pulling this from the file system
// it does not understand query params
// and make sure we decode the uri which swaps out
// %20 with white space
const file = decodeURI(url.parse(path.join(...args)).pathname)
res.setHeader('x-cypress-file-path', encodeURI(file))
return send(req, url.parse(req.url).pathname, {
root: path.resolve(fileServerFolder),
})
.on('error', (err) => {
res.setHeader('x-cypress-file-server-error', true)
res.setHeader('content-type', 'text/html')
res.statusCode = err.status
return res.end(networkFailures.get(file, err.status))
}).pipe(res)
}
module.exports = {
create (fileServerFolder) {
return new Promise(((resolve) => {
const token = random.id(64)
const srv = http.createServer(httpUtils.lenientOptions, (req, res) => {
return onRequest(req, res, token, fileServerFolder)
})
allowDestroy(srv)
return srv.listen(0, '127.0.0.1', () => {
return resolve({
token,
port () {
return srv.address().port
},
address () {
return `http://localhost:${this.port()}`
},
close () {
return srv.destroyAsync()
},
})
})
}))
},
}

View File

@@ -0,0 +1,88 @@
// TODO: move this to packages/core-file-server
import _ from 'lodash'
import debugModule from 'debug'
import url from 'url'
import http from 'http'
import path from 'path'
import send from 'send'
import { httpUtils } from '@packages/network'
import { allowDestroy } from './util/server_destroy'
import { id as randomId } from './util/random'
import networkFailures from './util/network_failures'
import type { AddressInfo } from 'net'
const debug = debugModule('cypress:server:file_server')
export type FileServer = {
token: string
port: () => number
address: () => string
close: () => void
}
const onRequest = function (req: http.IncomingMessage, res: http.ServerResponse, expectedToken: string, fileServerFolder: string) {
const token = req.headers['x-cypress-authorization']
if (token !== expectedToken) {
debug('authorization failed on file_server request %o', { reqUrl: req.url, expectedToken, token })
res.statusCode = 401
res.end()
return
}
const args = _.compact([
fileServerFolder,
req.url,
])
// strip off any query params from our req's url
// since we're pulling this from the file system
// it does not understand query params
// and make sure we decode the uri which swaps out
// %20 with white space
const file = decodeURI(url.parse(path.join(...args)).pathname as string)
res.setHeader('x-cypress-file-path', encodeURI(file))
return send(req, url.parse(req.url as string).pathname as string, {
root: path.resolve(fileServerFolder),
})
.on('error', (err) => {
res.setHeader('x-cypress-file-server-error', 'true')
res.setHeader('content-type', 'text/html')
res.statusCode = err.status
return res.end(networkFailures.get(file, err.status))
}).pipe(res)
}
export const create = (fileServerFolder: string): Promise<FileServer> => {
return new Promise(((resolve) => {
const token = randomId(64)
const srv = http.createServer(httpUtils.lenientOptions, (req: http.IncomingMessage, res: http.ServerResponse) => {
return onRequest(req, res, token, fileServerFolder)
})
allowDestroy(srv)
return srv.listen(0, '127.0.0.1', () => {
const server: FileServer = {
token,
port () {
return (srv.address() as AddressInfo).port
},
address () {
return `http://localhost:${this.port()}`
},
close () {
// @ts-expect-error - destroyAsync is defined when allowDestroy is called
return srv.destroyAsync()
},
}
return resolve(server)
})
}))
}

View File

@@ -2,7 +2,7 @@ import { setCtx } from '@packages/data-context'
import _ from 'lodash'
import { makeDataContext } from '../makeDataContext'
import random from '../util/random'
import { id as randomId } from '../util/random'
import { telemetry } from '@packages/telemetry'
export = (mode, options) => {
@@ -14,7 +14,7 @@ export = (mode, options) => {
if (mode === 'run') {
_.defaults(options, {
socketId: random.id(10),
socketId: randomId(10),
isTextTerminal: true,
browser: 'electron',
quiet: false,

View File

@@ -16,7 +16,7 @@ import type { AllCypressErrorNames } from '@packages/errors'
import { get as getErrors, warning as errorsWarning, throwErr } from '../errors'
import * as capture from '../capture'
import { getResolvedRuntimeConfig } from '../config'
import env from '../util/env'
import * as env from '../util/env'
import ciProvider from '../util/ci_provider'
import { flattenSuiteIntoRunnables } from '../util/tests_utils'
import { countStudioUsage } from '../util/spec_writer'

View File

@@ -15,9 +15,9 @@ import { openProject } from '../open_project'
import * as videoCapture from '../video_capture'
import { fs, getPath } from '../util/fs'
import runEvents from '../plugins/run_events'
import env from '../util/env'
import * as env from '../util/env'
import trash from '../util/trash'
import random from '../util/random'
import { id as randomId } from '../util/random'
import system from '../util/system'
import chromePolicyCheck from '../util/chrome_policy_check'
import type { SpecWithRelativeRoot, SpecFile, TestingType, OpenProjectLaunchOpts, FoundBrowser, BrowserVideoController, VideoRecording, ProcessOptions, ProtocolManagerShape, AutomationCommands } from '@packages/types'
@@ -764,7 +764,7 @@ async function waitForTestsToFinishRunning (options: { project: Project, screens
function screenshotMetadata (data: any, resp: any) {
return {
screenshotId: random.id(),
screenshotId: randomId(),
name: data.name || null,
testId: data.testId,
testAttemptIndex: data.testAttemptIndex,

View File

@@ -4,7 +4,7 @@ import Debug from 'debug'
import Bluebird from 'bluebird'
import appData from './util/app_data'
import { getCwd } from './cwd'
import FileUtil from './util/file'
import { File as FileUtil } from './util/file'
import { fs } from './util/fs'
import { AllowedState, allowedKeys } from '@packages/types'
import { globalPubSub } from '@packages/data-context'

View File

@@ -12,6 +12,7 @@ import type { AddressInfo } from 'net'
import url from 'url'
import la from 'lazy-ass'
import { createProxy as createHttpsProxy } from '@packages/https-proxy'
import type { Server as HttpsProxyServer } from '@packages/https-proxy'
import { getRoutesForRequest, netStubbingState, NetStubbingState } from '@packages/net-stubbing'
import { agent, clientCertificates, httpUtils, concatStream } from '@packages/network'
import { DocumentDomainInjection, getPath, parseUrlIntoHostProtocolDomainTldPort, removeDefaultPort } from '@packages/network-tools'
@@ -33,7 +34,7 @@ import type { Server as WebSocketServer } from 'ws'
import { RemoteStates, RemoteState } from './remote_states'
import { cookieJar, SerializableAutomationCookie } from './util/cookies'
import { resourceTypeAndCredentialManager, ResourceTypeAndCredentialManager } from './util/resourceTypeAndCredentialManager'
import fileServer from './file_server'
import * as fileServer from './file_server'
import appData from './util/app_data'
import { graphqlWS } from '@packages/data-context/graphql/makeGraphQLServer'
import statusCode from './util/status_code'
@@ -291,11 +292,11 @@ export class ServerBase<TSocket extends SocketE2E | SocketCt> {
onUpgrade: this.onSniUpgrade.bind(this),
}),
fileServer.create(fileServerFolder),
fileServer.create(fileServerFolder as string),
])
.spread((httpsProxy, fileServer) => {
this._httpsProxy = httpsProxy
this._fileServer = fileServer
this._httpsProxy = httpsProxy as HttpsProxyServer
this._fileServer = fileServer as FileServer
// if we have a baseUrl let's go ahead
// and make sure the server is connectable!

View File

@@ -1,13 +0,0 @@
const set = (key, val) => {
return process.env[key] = val
}
const get = (key) => {
return process.env[key]
}
module.exports = {
set,
get,
}

View File

@@ -0,0 +1,7 @@
export const set = (key: string, val: string): string | undefined => {
return process.env[key] = val
}
export const get = (key: string): string | undefined => {
return process.env[key]
}

View File

@@ -1,21 +1,24 @@
const _ = require('lodash')
const os = require('os')
const md5 = require('md5')
const path = require('path')
const debugVerbose = require('debug')('cypress-verbose:server:util:file')
const Promise = require('bluebird')
const lockFile = Promise.promisifyAll(require('lockfile'))
const { fs } = require('./fs')
const env = require('./env')
const exit = require('./exit')
const { default: pQueue } = require('p-queue')
import _ from 'lodash'
import os from 'os'
import md5 from 'md5'
import path from 'path'
import debugModule from 'debug'
import Promise from 'bluebird'
import lockFileModule from 'lockfile'
import { fs } from './fs'
import * as env from './env'
import exit from './exit'
import pQueue from 'p-queue'
const lockFile = Promise.promisifyAll(lockFileModule)
const debugVerbose = debugModule('cypress-verbose:server:util:file')
const DEBOUNCE_LIMIT = 1000
const LOCK_TIMEOUT = 2000
function getUid () {
try {
// eslint-disable-next-line no-restricted-properties
// @ts-expect-error - process.geteuid is defined
return process.geteuid()
} catch (err) {
// process.geteuid() can fail, return a constant
@@ -24,8 +27,28 @@ function getUid () {
}
}
class File {
constructor (options = {}) {
export class File {
_lockFileDir!: string
_lockFilePath!: string
_queue!: pQueue
_cache!: Record<string, any>
_lastRead!: number
path: string
static noopFile = {
get () {
return Promise.resolve({})
},
set () {
return Promise.resolve()
},
transaction () {},
remove () {
return Promise.resolve()
},
}
constructor (options: { path?: string } = {}) {
if (!options.path) {
throw new Error('Must specify path to file when creating new FileUtil()')
}
@@ -70,13 +93,13 @@ class File {
get (...args) {
debugVerbose('get values from %s', this.path)
return this._get(false, ...args)
return this._get(false, ...(args as [string, any]))
}
set (...args) {
debugVerbose('set values in %s', this.path)
return this._set(false, ...args)
return this._set(false, ...(args as [string, any]))
}
remove () {
@@ -136,6 +159,7 @@ class File {
.then(() => {
debugVerbose('read %s', this.path)
// @ts-expect-error
return fs.readJsonAsync(this.path, 'utf8')
})
.catch((err) => {
@@ -208,6 +232,7 @@ class File {
.then(() => {
debugVerbose('write %s', this.path)
// @ts-expect-error
return fs.outputJsonAsync(this.path, this._cache, { spaces: 2 })
})
.finally(() => {
@@ -221,6 +246,7 @@ class File {
debugVerbose('attempt to get lock on %s', this.path)
return fs
// @ts-expect-error
.ensureDirAsync(this._lockFileDir)
.then(() => {
// polls every 100ms up to 2000ms to obtain lock, otherwise rejects
@@ -245,18 +271,3 @@ class File {
})
}
}
File.noopFile = {
get () {
return Promise.resolve({})
},
set () {
return Promise.resolve()
},
transaction () {},
remove () {
return Promise.resolve()
},
}
module.exports = File

View File

@@ -8,7 +8,7 @@ import pkg from '@packages/root'
import humanTime from './human_time'
import duration from './duration'
import newlines from './newlines'
import env from './env'
import * as env from './env'
import terminal from './terminal'
import { getIsCi } from './ci_provider'
import * as experiments from '../experiments'
@@ -704,7 +704,7 @@ export const beginUploadActivityOutput = () => {
process.stdout.write(chalk.bold.blue('. '))
const uploadActivityInterval = setInterval(() => {
process.stdout.write(chalk.bold.blue('. '))
}, UPLOAD_ACTIVITY_INTERVAL)
}, Number(UPLOAD_ACTIVITY_INTERVAL))
return () => {
if (uploadActivityInterval) {

View File

@@ -1,13 +1,9 @@
const random = require('randomstring')
import random from 'randomstring'
// return a random id
const id = (length = 5) => {
export const id = (length = 5): string => {
return random.generate({
length,
capitalization: 'lowercase',
})
}
module.exports = {
id,
}

View File

@@ -1,4 +1,3 @@
const env = require('./env')
const debug = require('debug')('cypress:server:plugins')
module.exports = {
@@ -9,7 +8,7 @@ module.exports = {
* @returns {string|null} path if typescript exists, otherwise null
*/
typescript: (projectRoot) => {
if (env.get('CYPRESS_INTERNAL_NO_TYPESCRIPT') === '1' || !projectRoot) {
if (process.env['CYPRESS_INTERNAL_NO_TYPESCRIPT'] === '1' || !projectRoot) {
return null
}

View File

@@ -1,17 +0,0 @@
const termSize = require('term-size')
const env = require('./env')
const get = () => {
const obj = termSize()
if (env.get('CI')) {
// reset to 100
obj.columns = 100
}
return obj
}
module.exports = {
get,
}

View File

@@ -0,0 +1,13 @@
import termSize from 'term-size'
import * as env from './env'
export const get = (): { columns: number, rows: number } => {
const obj = termSize()
if (env.get('CI')) {
// reset to 100
obj.columns = 100
}
return obj
}

View File

@@ -3,7 +3,7 @@ require('../spec_helper')
const path = require('path')
const Promise = require('bluebird')
const { fs } = require(`../../lib/util/fs`)
const FileUtil = require(`../../lib/util/file`)
const FileUtil = require(`../../lib/util/file`).File
const appData = require(`../../lib/util/app_data`)
const { START_TAG, END_TAG } = require(`@packages/stderr-filtering`)
const savedState = require(`../../lib/saved_state`)

View File

@@ -1,13 +1,13 @@
require('../../spec_helper')
import os from 'os'
import path from 'path'
import Promise from 'bluebird'
import lockFileModule from 'lockfile'
import { fs } from '../../../lib/util/fs'
import * as env from '../../../lib/util/env'
import exit from '../../../lib/util/exit'
import { File as FileUtil } from '../../../lib/util/file'
const os = require('os')
const path = require('path')
const Promise = require('bluebird')
const lockFile = Promise.promisifyAll(require('lockfile'))
const { fs } = require(`../../../lib/util/fs`)
const env = require(`../../../lib/util/env`)
const exit = require(`../../../lib/util/exit`)
const FileUtil = require(`../../../lib/util/file`)
const lockFile = Promise.promisifyAll(lockFileModule)
describe('lib/util/file', () => {
beforeEach(function () {

View File

@@ -1,13 +1,11 @@
require('../../spec_helper')
const randomstring = require('randomstring')
const random = require(`../../../lib/util/random`)
import randomstring from 'randomstring'
import { id as randomId } from '../../../lib/util/random'
context('.id', () => {
it('returns random.generate string with length 5 by default', () => {
sinon.spy(randomstring, 'generate')
const id = random.id()
const id = randomId()
expect(id.length).to.eq(5)
@@ -20,7 +18,7 @@ context('.id', () => {
it('passes the length parameter if supplied', () => {
sinon.spy(randomstring, 'generate')
const id = random.id(32)
const id = randomId(32)
expect(id.length).to.eq(32)