feat: merging / delegating remote queries to cloud schema (#17875)

Changes:
- Pulls down & stitches in the "Cypress Cloud" schema from remote. Currently defaulted to "staging" so we can make quick fixes / iteration there
- Add dedicated type checking CircleCI job `check-ts`
- Adds [graphcache](https://formidable.com/open-source/urql/docs/graphcache/) to normalize the cache & provide better auto-reactivity when data changes
- Removes `LocalProject` / `DashboardProject` in favor of `Project` / `CloudProject`
- General cleanup of Vue components' GraphQL fragments
- Parallelizes launchpad tests & recording to new Cypress project: https://dashboard.cypress.io/projects/sehy69/runs
  - Did this b/c tests were frequently timing out, need to figure out the source of this
- Basic mocks for remote schema
This commit is contained in:
Tim Griesser
2021-09-20 17:12:59 -04:00
committed by GitHub
parent 5f94486bc2
commit 94541d4f18
137 changed files with 2797 additions and 2051 deletions
+2 -1
View File
@@ -1,6 +1,7 @@
{
"extends": "../.eslintrc.json",
"rules": {
"no-console": "off"
"no-console": "off",
"no-empty": "off"
}
}
+26 -2
View File
@@ -1,10 +1,34 @@
import getenv from 'getenv'
// Where to fetch the remote "federated" schema TODO: add w/ stitching PR
// export const CYPRESS_INTERNAL_CLOUD_ENV = 'production'
type Maybe<T> = T | null | undefined
// Where to fetch the remote "federated" schema. If you have a long-running branch
// against a development schema, it's probably easiest to set this manually to "develop"
export const CYPRESS_INTERNAL_CLOUD_ENV = getenv('CYPRESS_INTERNAL_CLOUD_ENV', 'staging') as 'production' | 'staging' | 'development'
process.env.CYPRESS_INTERNAL_CLOUD_ENV = CYPRESS_INTERNAL_CLOUD_ENV
process.env.CYPRESS_KONFIG_ENV = getenv('CYPRESS_KONFIG_ENV', CYPRESS_INTERNAL_CLOUD_ENV)
export const CYPRESS_INTERNAL_DEBUG_PORT_STARTUP = getenv.int('CYPRESS_INTERNAL_DEBUG_PORT_STARTUP', 7200)
export const CYPRESS_INTERNAL_DEBUG_PORT_ELECTRON = getenv.int('CYPRESS_INTERNAL_DEBUG_PORT_ELECTRON', 7201)
export const CYPRESS_INTERNAL_DEBUG_PORT_CODEGEN = getenv.int('CYPRESS_INTERNAL_DEBUG_PORT_CODEGEN', 7202)
interface GulpGlobalVals {
debug?: Maybe<'--inspect' | '--inspect-brk'>
shouldWatch?: boolean
}
const globalVals: GulpGlobalVals = {
shouldWatch: true,
}
export function setGulpGlobal<K extends keyof GulpGlobalVals> (k: K, v: GulpGlobalVals[K]) {
globalVals[k] = v
}
export function getGulpGlobal<K extends keyof GulpGlobalVals> (k: K): GulpGlobalVals[K] {
return globalVals[k]
}
+38 -3
View File
@@ -1,9 +1,11 @@
import gulp from 'gulp'
import { autobarrelWatcher } from './tasks/gulpAutobarrel'
import { startCypressWatch } from './tasks/gulpCypress'
import { graphqlCodegen, graphqlCodegenWatch, nexusCodegen, nexusCodegenWatch } from './tasks/gulpGraphql'
import { graphqlCodegen, graphqlCodegenWatch, nexusCodegen, nexusCodegenWatch, printUrqlSchema, syncRemoteGraphQL } from './tasks/gulpGraphql'
import { checkTs } from './tasks/gulpTsc'
import { viteApp, viteCleanApp, viteCleanLaunchpad, viteLaunchpad } from './tasks/gulpVite'
import { makePathMap } from './utils/makePathMap'
import { setGulpGlobal } from './gulpConstants'
gulp.task(
'dev',
@@ -12,8 +14,7 @@ gulp.task(
autobarrelWatcher,
// Fetch the latest "remote" schema from the Cypress cloud
// TODO: with stitching bracnh
// fetchCloudSchema,
syncRemoteGraphQL,
gulp.parallel(
// Clean the vite apps
@@ -37,7 +38,38 @@ gulp.task(
),
)
gulp.task(
'devNoWatch',
gulp.series(
async function setupDevNoWatch () {
setGulpGlobal('shouldWatch', false)
},
'dev',
),
)
gulp.task(
'debug',
gulp.series(
async function setupDebug () {
setGulpGlobal('debug', '--inspect')
},
'dev',
),
)
gulp.task(
'debugBrk',
gulp.series(
async function setupDebugBrk () {
setGulpGlobal('debug', '--inspect-brk')
},
'dev',
),
)
gulp.task('buildProd', gulp.series(
syncRemoteGraphQL,
nexusCodegen,
graphqlCodegen,
))
@@ -62,6 +94,9 @@ gulp.task(
// 'debug', // Tim: TODO
// )
gulp.task(checkTs)
gulp.task(syncRemoteGraphQL)
gulp.task(printUrqlSchema)
gulp.task(makePathMap)
gulp.task(nexusCodegen)
gulp.task(nexusCodegenWatch)
+21 -6
View File
@@ -4,17 +4,24 @@ import childProcess, { ChildProcess } from 'child_process'
import pDefer from 'p-defer'
import { monorepoPaths } from '../monorepoPaths'
import { getGulpGlobal } from '../gulpConstants'
/**
* Starts cypress, but watches the GraphQL files & restarts the server
* when any of those change
*/
export function startCypressWatch () {
const watcher = chokidar.watch('src/**/*.{js,ts}', {
cwd: monorepoPaths.pkgGraphql,
const shouldWatch = getGulpGlobal('shouldWatch')
const watcher = shouldWatch ? chokidar.watch([
'packages/graphql/src/**/*.{js,ts}',
'packages/server/lib/graphql/**/*.{js,ts}',
], {
cwd: monorepoPaths.root,
ignored: /\.gen\.ts/,
ignoreInitial: true,
})
}) : null
let child: ChildProcess | null = null
let isClosing = false
@@ -36,13 +43,19 @@ export function startCypressWatch () {
argv.push('--dev')
}
const debugFlag = getGulpGlobal('debug')
if (debugFlag) {
process.env.CYPRESS_INTERNAL_DEV_DEBUG = debugFlag
}
child = childProcess.fork(pathToCli, ['open', ...argv], {
stdio: 'inherit',
execArgv: [],
env: {
...process.env,
LAUNCHPAD: '1',
CYPRESS_INTERNAL_DEV_WATCH: 'true',
CYPRESS_INTERNAL_DEV_WATCH: shouldWatch ? 'true' : undefined,
},
})
@@ -77,8 +90,10 @@ export function startCypressWatch () {
openServer()
}
watcher.on('add', restartServer)
watcher.on('change', restartServer)
if (shouldWatch) {
watcher?.on('add', restartServer)
watcher?.on('change', restartServer)
}
openServer()
+37
View File
@@ -1,11 +1,17 @@
// @ts-expect-error - no types
import rp from '@cypress/request-promise'
import path from 'path'
import pDefer from 'p-defer'
import chalk from 'chalk'
import fs from 'fs-extra'
import { buildSchema, introspectionFromSchema } from 'graphql'
import { minifyIntrospectionQuery } from '@urql/introspection'
import { nexusTypegen, watchNexusTypegen } from '../utils/nexusTypegenUtil'
import { monorepoPaths } from '../monorepoPaths'
import { spawned } from '../utils/childProcessUtils'
import { spawn } from 'child_process'
import { CYPRESS_INTERNAL_CLOUD_ENV } from '../gulpConstants'
export async function nexusCodegen () {
return nexusTypegen({
@@ -75,3 +81,34 @@ export async function graphqlCodegenWatch () {
return dfd.promise
}
const ENV_MAP = {
development: 'http://localhost:3000',
staging: 'https://dashboard-staging.cypress.io',
production: 'https://dashboard.cypress.io',
}
export async function syncRemoteGraphQL () {
if (!ENV_MAP[CYPRESS_INTERNAL_CLOUD_ENV]) {
throw new Error(`Expected --env to be one of ${Object.keys(ENV_MAP).join(', ')}`)
}
const body = await rp.get(`${ENV_MAP[CYPRESS_INTERNAL_CLOUD_ENV]}/test-runner-graphql-schema`)
// TODO(tim): fix
await fs.promises.writeFile(path.join(monorepoPaths.pkgGraphql, 'schemas/cloud.graphql'), body)
await fs.promises.writeFile(path.join(monorepoPaths.pkgGraphql, 'src/gen/cloud-introspection.gen.json'), JSON.stringify(introspectionFromSchema(buildSchema(body)), null, 2))
}
export async function printUrqlSchema () {
const schemaContents = await fs.promises.readFile(path.join(monorepoPaths.pkgGraphql, 'schemas/schema.graphql'), 'utf8')
const URQL_INTROSPECTION_PATH = path.join(monorepoPaths.pkgFrontendShared, 'src/generated/urql-introspection.gen.ts')
await fs.ensureDir(path.dirname(URQL_INTROSPECTION_PATH))
await fs.promises.writeFile(
URQL_INTROSPECTION_PATH,
`/* eslint-disable */\nexport const urqlSchema = ${JSON.stringify(minifyIntrospectionQuery(introspectionFromSchema(buildSchema(schemaContents))), null, 2)} as const`,
)
}
+25 -17
View File
@@ -12,7 +12,18 @@ process.on('unhandledRejection', (reason) => {
process.exit(1)
})
export function getTsPackages (packagesPath: string = ''): Promise<Set<string>> {
export async function checkTs () {
const [a, b] = await Promise.all([
getTsPackages(),
getTsPackages('npm'),
])
await buildPackageTsc({
tsPackages: new Set([...a, ...b]),
})
}
export function getTsPackages (packagesPath: string = 'packages'): Promise<Set<string>> {
const dfd = {} as Record<string, any>
dfd.promise = new Promise((resolve, reject) => {
@@ -21,7 +32,7 @@ export function getTsPackages (packagesPath: string = ''): Promise<Set<string>>
})
glob(
path.join(monorepoPaths.root, packagesPath, '/packages/*/package.json'),
path.join(monorepoPaths.root, packagesPath, '/*/package.json'),
(err, packageJsons) => {
if (err) {
return dfd.reject(err)
@@ -40,9 +51,9 @@ export function getTsPackages (packagesPath: string = ''): Promise<Set<string>>
const packages = new Set<string>()
required.forEach((r) => {
// Select only packages that have build-ts
if (r.scripts && r.scripts['build-ts']) {
packages.add(r.name)
// Select only packages that have check-ts
if (r.scripts && r.scripts['check-ts']) {
packages.add(`${packagesPath}/${r.name}`)
}
})
@@ -54,7 +65,6 @@ export function getTsPackages (packagesPath: string = ''): Promise<Set<string>>
}
export async function buildPackageTsc ({
packagesPath = '',
tsPackages = new Set(),
onlyPackages,
}: {
@@ -73,30 +83,28 @@ export async function buildPackageTsc ({
const packages = onlyPackages || [...Array.from(tsPackages)]
for (const pkg of packages) {
const cwd = path.join(
monorepoPaths.root,
`/${pkg.replace(/\@(packages|cypress)\//, '')}`,
)
try {
await execAsync('tsc', {
cwd: path.join(
__dirname,
'../../',
packagesPath,
`/packages/${pkg.replace(/\@(packages|cypress|frontend)\//, '')}`,
),
})
await execAsync('yarn check-ts', { cwd })
built++
console.log(
`${chalk.green(`Built`)} ${pkg} ${chalk.magenta(
`${chalk.green(`Built`)} ${cwd} ${chalk.magenta(
`${built} / ${packages.length}`,
)}`,
)
} catch (e) {
console.log(
`${chalk.red(`Failed built`)} ${pkg} ${chalk.magenta(
`${chalk.red(`Failed building`)} ${cwd} ${chalk.magenta(
`${built} / ${packages.length}`,
)}`,
)
errors.push({ package: pkg, stdout: e.stdout })
errors.push({ package: cwd, stdout: e.stdout })
}
}
+2
View File
@@ -1,5 +1,7 @@
{
"compilerOptions": {
"target": "ES2018",
"moduleResolution": "node",
"noEmit": true,
"strict": true,
"esModuleInterop": true,
+10 -1
View File
@@ -3,6 +3,11 @@ import chalk from 'chalk'
import pDefer from 'p-defer'
import chokidar from 'chokidar'
import _ from 'lodash'
import path from 'path'
import fs from 'fs-extra'
import { printUrqlSchema } from '../tasks/gulpGraphql'
import { monorepoPaths } from '../monorepoPaths'
interface NexusTypegenCfg {
cwd: string
@@ -21,6 +26,8 @@ export async function nexusTypegen (cfg: NexusTypegenCfg) {
const dfd = pDefer()
if (cfg.outputPath) {
await fs.ensureDir(path.join(monorepoPaths.pkgGraphql, 'src/gen'))
execSync(`touch ${path.join(monorepoPaths.pkgGraphql, 'src/gen/cloud-source-types.gen.ts')}`)
execSync(`touch ${cfg.outputPath}`)
}
@@ -51,7 +58,9 @@ export async function nexusTypegen (cfg: NexusTypegenCfg) {
out.on('error', dfd.reject)
return dfd.promise
return dfd.promise.then(() => {
return printUrqlSchema()
})
}
let debounced: Record<string, Function> = {}