From ac2f624ea9a44dbe0043af3f5db5576ee78993cd Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 16 Nov 2021 09:30:17 -0500 Subject: [PATCH 01/11] feat: improve vite DX --- .../data-context/src/actions/DevActions.ts | 12 ++++- .../data-context/src/sources/EnvDataSource.ts | 5 +- .../src/sources/HtmlDataSource.ts | 21 ++++---- .../cypress/e2e/e2ePluginSetup.ts | 4 ++ .../cypress/fixtures/config.json | 2 - .../frontend-shared/src/graphql/urqlClient.ts | 48 +++++++++++-------- packages/launchpad/src/error/BaseError.vue | 3 +- packages/launchpad/src/main.ts | 28 ++++++++--- packages/resolve-dist/lib/index.ts | 6 +-- packages/server/lib/gui/makeGraphQLServer.ts | 9 ++++ packages/server/lib/routes.ts | 21 ++------ scripts/gulp/gulpConstants.ts | 2 - scripts/gulp/gulpfile.ts | 14 +++--- scripts/gulp/tasks/gulpCypress.ts | 10 +++- scripts/gulp/tasks/gulpVite.ts | 26 +++++++--- 15 files changed, 129 insertions(+), 82 deletions(-) diff --git a/packages/data-context/src/actions/DevActions.ts b/packages/data-context/src/actions/DevActions.ts index 1eda8b8d5264..b936ea174249 100644 --- a/packages/data-context/src/actions/DevActions.ts +++ b/packages/data-context/src/actions/DevActions.ts @@ -32,8 +32,18 @@ export class DevActions { } } + // In a setTimeout so that we flush the triggering response to the client before sending triggerRelaunch () { - return this.ctx.fs.writeFile(DevActions.CY_TRIGGER_UPDATE, JSON.stringify(new Date())) + setTimeout(async () => { + try { + await this.ctx.destroy() + } catch (e) { + this.ctx.logError(e) + } finally { + process.exitCode = 0 + await this.ctx.fs.writeFile(DevActions.CY_TRIGGER_UPDATE, JSON.stringify(new Date())) + } + }, 10) } dismissRelaunch () { diff --git a/packages/data-context/src/sources/EnvDataSource.ts b/packages/data-context/src/sources/EnvDataSource.ts index dd7af0e138ca..55f7c310b49c 100644 --- a/packages/data-context/src/sources/EnvDataSource.ts +++ b/packages/data-context/src/sources/EnvDataSource.ts @@ -2,11 +2,8 @@ import type { DataContext } from '../DataContext' /** * Centralizes all of the "env" + * TODO: see if/how we might want to use this? */ export class EnvDataSource { constructor (private ctx: DataContext) {} - - get CYPRESS_INTERNAL_VITE_APP_PORT () { - return process.env.CYPRESS_INTERNAL_VITE_APP_PORT - } } diff --git a/packages/data-context/src/sources/HtmlDataSource.ts b/packages/data-context/src/sources/HtmlDataSource.ts index d129ae6537cc..bfa406bc8f38 100644 --- a/packages/data-context/src/sources/HtmlDataSource.ts +++ b/packages/data-context/src/sources/HtmlDataSource.ts @@ -9,27 +9,32 @@ import { getPathToDist } from '@packages/resolve-dist' export class HtmlDataSource { constructor (private ctx: DataContext) {} - async fetchAppInitialData () { + async fetchLaunchpadInitialData () { const graphql = this.ctx.graphqlClient() await Promise.all([ + graphql.executeQuery('HeaderBar_HeaderBarQueryDocument', {}), graphql.executeQuery('AppQueryDocument', {}), + graphql.executeQuery('MainLaunchpadQueryDocument', {}), + ]) + + return graphql.getSSRData() + } + + async fetchAppInitialData () { + const graphql = this.ctx.graphqlClient() + + await Promise.all([ graphql.executeQuery('SettingsDocument', {}), graphql.executeQuery('SpecsPageContainerDocument', {}), graphql.executeQuery('HeaderBar_HeaderBarQueryDocument', {}), + graphql.executeQuery('SideBarNavigationDocument', {}), ]) return graphql.getSSRData() } async fetchAppHtml () { - if (this.ctx.env.CYPRESS_INTERNAL_VITE_APP_PORT) { - const response = await this.ctx.util.fetch(`http://localhost:${process.env.CYPRESS_INTERNAL_VITE_APP_PORT}/`, { method: 'GET' }) - const html = await response.text() - - return html - } - return this.ctx.fs.readFile(getPathToDist('app', 'index.html'), 'utf8') } diff --git a/packages/frontend-shared/cypress/e2e/e2ePluginSetup.ts b/packages/frontend-shared/cypress/e2e/e2ePluginSetup.ts index 31f0c7dd2df8..65c26974e2fb 100644 --- a/packages/frontend-shared/cypress/e2e/e2ePluginSetup.ts +++ b/packages/frontend-shared/cypress/e2e/e2ePluginSetup.ts @@ -30,6 +30,10 @@ chai.use(sinonChai) export async function e2ePluginSetup (projectRoot: string, on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions) { process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF = 'true' + delete process.env.CYPRESS_INTERNAL_GRAPHQL_PORT + delete process.env.CYPRESS_INTERNAL_VITE_DEV + delete process.env.CYPRESS_INTERNAL_VITE_APP_PORT + delete process.env.CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT // require'd so we don't import the types from @packages/server which would // pollute strict type checking const { runInternalServer } = require('@packages/server/lib/modes/internal-server') diff --git a/packages/frontend-shared/cypress/fixtures/config.json b/packages/frontend-shared/cypress/fixtures/config.json index b49120c39226..ffdf340af261 100644 --- a/packages/frontend-shared/cypress/fixtures/config.json +++ b/packages/frontend-shared/cypress/fixtures/config.json @@ -47,8 +47,6 @@ { "value": { "INTERNAL_CLOUD_ENV": "staging", - "INTERNAL_VITE_APP_PORT": 3333, - "INTERNAL_VITE_LAUNCHPAD_PORT": 3001, "KONFIG_ENV": "staging" }, "field": "env", diff --git a/packages/frontend-shared/src/graphql/urqlClient.ts b/packages/frontend-shared/src/graphql/urqlClient.ts index b3fe08a8efe4..f083933f6e2a 100644 --- a/packages/frontend-shared/src/graphql/urqlClient.ts +++ b/packages/frontend-shared/src/graphql/urqlClient.ts @@ -35,23 +35,37 @@ declare global { } } -export function makeUrqlClient (target: 'launchpad' | 'app'): Client { - let gqlPort: string - +function gqlPort () { if (GQL_PORT_MATCH) { - gqlPort = GQL_PORT_MATCH[1] - } else if (window.__CYPRESS_GRAPHQL_PORT__) { - gqlPort = window.__CYPRESS_GRAPHQL_PORT__ - } else { - throw new Error(`${window.location.href} cannot be visited without a gqlPort`) + return GQL_PORT_MATCH[1] + } + + if (window.__CYPRESS_GRAPHQL_PORT__) { + return window.__CYPRESS_GRAPHQL_PORT__ + } + + throw new Error(`${window.location.href} cannot be visited without a gqlPort`) +} + +export async function preloadLaunchpadData () { + try { + const resp = await fetch(`http://localhost:${gqlPort()}/__cypress/launchpad-preload`) + + window.__CYPRESS_INITIAL_DATA__ = await resp.json() + } catch { + // } +} - const GRAPHQL_URL = `http://localhost:${gqlPort}/graphql` +export function makeUrqlClient (target: 'launchpad' | 'app'): Client { + const port = gqlPort() + + const GRAPHQL_URL = `http://localhost:${port}/graphql` // If we're in the launchpad, we connect to the known GraphQL Socket port, // otherwise we connect to the /__socket.io of the current domain, unless we've explicitly // - const io = getPubSubSource({ target, gqlPort, serverPort: SERVER_PORT_MATCH?.[1] }) + const io = getPubSubSource({ target, gqlPort: port, serverPort: SERVER_PORT_MATCH?.[1] }) const exchanges: Exchange[] = [ dedupExchange, @@ -76,6 +90,10 @@ export function makeUrqlClient (target: 'launchpad' | 'app'): Client { }), // https://formidable.com/open-source/urql/docs/graphcache/errors/ makeCacheExchange(), + ssrExchange({ + isClient: true, + initialState: window.__CYPRESS_INITIAL_DATA__ ?? {}, + }), namedRouteExchange, // TODO(tim): add this when we want to use the socket as the GraphQL // transport layer for all operations @@ -83,16 +101,6 @@ export function makeUrqlClient (target: 'launchpad' | 'app'): Client { fetchExchange, ] - // If we're in the launched app, we want to use the SSR exchange - if (target === 'app') { - exchanges.push(ssrExchange({ - isClient: true, - initialState: window.__CYPRESS_INITIAL_DATA__ ?? {}, - })) - } - - exchanges.push(fetchExchange) - if (import.meta.env.DEV) { exchanges.unshift(devtoolsExchange) } diff --git a/packages/launchpad/src/error/BaseError.vue b/packages/launchpad/src/error/BaseError.vue index bdb4b3de5872..fb37a7bb9401 100644 --- a/packages/launchpad/src/error/BaseError.vue +++ b/packages/launchpad/src/error/BaseError.vue @@ -97,7 +97,8 @@ const props = defineProps<{ const latestOperation = window.localStorage.getItem('latestGQLOperation') const retry = async () => { - const { launchpadClient } = await import('../main') + const { getLaunchpadClient } = await import('../main') + const launchpadClient = getLaunchpadClient() const op = latestOperation ? JSON.parse(latestOperation) : null diff --git a/packages/launchpad/src/main.ts b/packages/launchpad/src/main.ts index ec49343998df..f8db0e03059d 100644 --- a/packages/launchpad/src/main.ts +++ b/packages/launchpad/src/main.ts @@ -1,28 +1,42 @@ import { createApp } from 'vue' import './main.scss' import 'virtual:windi.css' -import urql from '@urql/vue' +import urql, { Client } from '@urql/vue' import App from './App.vue' import Toast, { POSITION } from 'vue-toastification' import 'vue-toastification/dist/index.css' -import { makeUrqlClient } from '@packages/frontend-shared/src/graphql/urqlClient' +import { makeUrqlClient, preloadLaunchpadData } from '@packages/frontend-shared/src/graphql/urqlClient' import { createI18n } from '@cy/i18n' import { initHighlighter } from '@cy/components/ShikiHighlight.vue' const app = createApp(App) -export const launchpadClient = makeUrqlClient('launchpad') - app.use(Toast, { position: POSITION.BOTTOM_RIGHT, }) -app.use(urql, launchpadClient) app.use(createI18n()) +let launchpadClient: Client + +// TODO: (tim) remove this when we refactor to remove the retry plugin logic +export function getLaunchpadClient () { + if (!launchpadClient) { + throw new Error(`Cannot access launchpadClient before app has been init`) + } + + return launchpadClient +} + // Make sure highlighter is initialized before // we show any code to avoid jank at rendering -// @ts-ignore -initHighlighter().then(() => { +Promise.all([ + // @ts-ignore + initHighlighter(), + preloadLaunchpadData(), +]).then(() => { + launchpadClient = makeUrqlClient('launchpad') + app.use(urql, launchpadClient) + app.mount('#app') }) diff --git a/packages/resolve-dist/lib/index.ts b/packages/resolve-dist/lib/index.ts index 371db5f6661d..5e9d8a2ed713 100644 --- a/packages/resolve-dist/lib/index.ts +++ b/packages/resolve-dist/lib/index.ts @@ -21,10 +21,10 @@ export const getPathToIndex = (pkg: RunnerPkg) => { } export const getPathToDesktopIndex = (pkg: 'desktop-gui' | 'launchpad', graphqlPort?: number) => { - // For now, if we see that there's a CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT + // For now, if we see that there's a CYPRESS_INTERNAL_VITE_DEV // we assume we're running Cypress targeting that (dev server) - if (pkg === 'launchpad' && process.env.CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT) { - return `http://localhost:${process.env.CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT}?gqlPort=${graphqlPort}` + if (pkg === 'launchpad' && process.env.CYPRESS_INTERNAL_VITE_DEV) { + return `http://localhost:${process.env.CYPRESS_INTERNAL_VITE_DEV}?gqlPort=${graphqlPort}` } return `file://${path.join(__dirname, '..', '..', pkg, 'dist', 'index.html')}?gqlPort=${graphqlPort}` diff --git a/packages/server/lib/gui/makeGraphQLServer.ts b/packages/server/lib/gui/makeGraphQLServer.ts index 18f158bf69cf..c938f5adbcb4 100644 --- a/packages/server/lib/gui/makeGraphQLServer.ts +++ b/packages/server/lib/gui/makeGraphQLServer.ts @@ -12,6 +12,15 @@ export async function makeGraphQLServer (ctx: DataContext) { app.use(cors()) + app.get('/__cypress/launchpad-preload', (req, res) => { + ctx.html.fetchLaunchpadInitialData().then((data) => { + res.json(data) + }).catch((e) => { + ctx.logError(e) + res.json({}) + }) + }) + // TODO: Figure out how we want to cleanup & juggle the config, so // it's not jammed into the projects addGraphQLHTTP(app, ctx) diff --git a/packages/server/lib/routes.ts b/packages/server/lib/routes.ts index 448b1a117340..c72178d9632b 100644 --- a/packages/server/lib/routes.ts +++ b/packages/server/lib/routes.ts @@ -5,7 +5,6 @@ import { ErrorRequestHandler, Router } from 'express' import send from 'send' import { getPathToDist } from '@packages/resolve-dist' -import { fs } from './util/fs' import type { SpecsStore } from './specs-store' import type { Browser } from './browsers/types' import type { NetworkProxy } from '@packages/proxy' @@ -31,10 +30,6 @@ export interface InitializeRoutes { exit?: boolean } -function replaceBody (ctx: DataContext) { - return `\n` -} - export const createCommonRoutes = ({ config, networkProxy, @@ -89,28 +84,18 @@ export const createCommonRoutes = ({ res.json(options) }) - if (process.env.CYPRESS_INTERNAL_VITE_APP_PORT) { + if (process.env.CYPRESS_INTERNAL_VITE_DEV) { const proxy = httpProxy.createProxyServer({ target: `http://localhost:${process.env.CYPRESS_INTERNAL_VITE_APP_PORT}/`, }) - // TODO: can namespace this onto a "unified" route like __app-unified__ - // make sure to update the generated routes inside of vite.config.ts - router.get('/__vite__/*', (req, res) => { - debug('Proxy to __vite__') + router.get('/__cypress/assets/*', (req, res) => { proxy.web(req, res, {}, (e) => {}) }) } else { - router.get('/__vite__/*', (req, res) => { + router.get('/__cypress/assets/*', (req, res) => { const pathToFile = getPathToDist('app', req.params[0]) - if (req.params[0] === '') { - return fs.readFile(pathToFile, 'utf8') - .then((file) => { - res.send(file.replace('', replaceBody(ctx))) - }) - } - return send(req, pathToFile).pipe(res) }) } diff --git a/scripts/gulp/gulpConstants.ts b/scripts/gulp/gulpConstants.ts index bb4ec161ef8d..48a7f2280ee9 100644 --- a/scripts/gulp/gulpConstants.ts +++ b/scripts/gulp/gulpConstants.ts @@ -23,8 +23,6 @@ export const ENV_VARS = { DEV: { CYPRESS_KONFIG_ENV: DEFAULT_INTERNAL_CLOUD_ENV, // TODO: Change this / remove konfig CYPRESS_INTERNAL_CLOUD_ENV: DEFAULT_INTERNAL_CLOUD_ENV, // staging for now, until we get an e2e workflow w/ cloud project - CYPRESS_INTERNAL_VITE_APP_PORT: `3333`, - CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT: `3001`, }, } diff --git a/scripts/gulp/gulpfile.ts b/scripts/gulp/gulpfile.ts index 3ec98bb4219c..182131d37bce 100644 --- a/scripts/gulp/gulpfile.ts +++ b/scripts/gulp/gulpfile.ts @@ -65,8 +65,10 @@ gulp.task( 'dev', gulp.series( makePathMap, - - 'codegen', + gulp.parallel( + 'viteClean', + 'codegen', + ), killExistingCypress, @@ -78,18 +80,14 @@ gulp.task( e2eTestScaffoldWatch, ), + symlinkViteProjects, + // And we're finally ready for electron, watching for changes in // /graphql to auto-restart the server startCypressWatch, ), ) -gulp.task('dev:clean', gulp.series( - // Clean any vite assets - 'viteClean', - 'dev', -)) - gulp.task( 'debug', gulp.series( diff --git a/scripts/gulp/tasks/gulpCypress.ts b/scripts/gulp/tasks/gulpCypress.ts index f3c0429bcdb3..12d13cfabd31 100644 --- a/scripts/gulp/tasks/gulpCypress.ts +++ b/scripts/gulp/tasks/gulpCypress.ts @@ -138,7 +138,11 @@ export async function startCypressWatch () { fs.ensureDirSync(path.dirname(DevActions.CY_STATE_PATH)) function signalRestart () { - fs.writeFile(DevActions.CY_STATE_PATH, JSON.stringify(new Date().toString())) + if (!child) { + restartServer() + } else { + fs.writeFile(DevActions.CY_STATE_PATH, JSON.stringify(new Date().toString())) + } } /** @@ -186,7 +190,9 @@ export async function startCypressWatch () { watcher.close() }) - const restartWatcher = chokidar.watch(DevActions.CY_TRIGGER_UPDATE) + const restartWatcher = chokidar.watch(DevActions.CY_TRIGGER_UPDATE, { + ignoreInitial: true, + }) restartWatcher.on('add', restartServer) restartWatcher.on('change', restartServer) diff --git a/scripts/gulp/tasks/gulpVite.ts b/scripts/gulp/tasks/gulpVite.ts index c6ef3e93d655..4bd47cce5d8a 100644 --- a/scripts/gulp/tasks/gulpVite.ts +++ b/scripts/gulp/tasks/gulpVite.ts @@ -11,8 +11,6 @@ import type { SpawnOptions } from 'child_process' import fs from 'fs-extra' import path from 'path' -import { ENV_VARS } from '../gulpConstants' - import { monorepoPaths } from '../monorepoPaths' import { AllSpawnableApps, spawned, spawnUntilMatch } from '../utils/childProcessUtils' @@ -25,17 +23,28 @@ import { AllSpawnableApps, spawned, spawnUntilMatch } from '../utils/childProces *------------------------------------------------------------------------**/ export function viteApp () { - const APP_PORT = ENV_VARS.DEV.CYPRESS_INTERNAL_VITE_APP_PORT + const baseSuffix = `--base /__cypress/assets/` + + // TODO: remove once we have sourcemap + if (process.env.CYPRESS_INTERNAL_VITE_DEV) { + const port = process.env.CYPRESS_INTERNAL_VITE_APP_PORT ??= '3333' + + return spawnViteDevServer('vite-app', `yarn vite --port ${port} ${baseSuffix}`) + } - return spawnViteDevServer('vite-app', `yarn vite --port ${APP_PORT} --base /__vite__/`, { + return watchViteBuild('vite-app', `yarn vite build --mode development --minify false --watch ${baseSuffix}`, { cwd: monorepoPaths.pkgApp, }) } export function viteLaunchpad () { - const LAUNCHPAD_PORT = ENV_VARS.DEV.CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT + if (process.env.CYPRESS_INTERNAL_VITE_DEV) { + const port = process.env.CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT ??= '3001' - return spawnViteDevServer('vite-launchpad', `yarn vite --port ${LAUNCHPAD_PORT}`, { + return spawnViteDevServer('vite-launchpad', `yarn vite --port ${port}`) + } + + return watchViteBuild('vite-launchpad', `yarn vite build --mode development --minify false --watch`, { cwd: monorepoPaths.pkgLaunchpad, }) } @@ -81,6 +90,11 @@ const DIST_SOURCES = { } as const export async function symlinkViteProjects () { + // If we're running the vite server script, we don't have a dist + if (process.env.CYPRESS_INTERNAL_VITE_DEV) { + return + } + for (const basePath of [monorepoPaths.pkgLaunchpad, monorepoPaths.pkgApp]) { for (const target of ['dist-launchpad', 'dist-app'] as const) { if (!fs.existsSync(path.join(basePath, target))) { From 108178d276766573a2afb71394c52276f7adcc36 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 16 Nov 2021 09:46:59 -0500 Subject: [PATCH 02/11] Dedicated app data folder for e2e testing self, remove existsSync warning in fixtures --- cli/lib/util.js | 6 +++++- packages/server/lib/util/app_data.js | 4 ++++ system-tests/lib/fixtures.js | 3 ++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/cli/lib/util.js b/cli/lib/util.js index fa8e43a80c1c..6db85b2a3ad6 100644 --- a/cli/lib/util.js +++ b/cli/lib/util.js @@ -252,13 +252,17 @@ const getApplicationDataFolder = (...paths) => { const { env } = process // allow overriding the app_data folder - const folder = env.CYPRESS_KONFIG_ENV || env.CYPRESS_INTERNAL_ENV || 'development' + let folder = env.CYPRESS_KONFIG_ENV || env.CYPRESS_INTERNAL_ENV || 'development' const PRODUCT_NAME = pkg.productName || pkg.name const OS_DATA_PATH = ospath.data() const ELECTRON_APP_DATA_PATH = path.join(OS_DATA_PATH, PRODUCT_NAME) + if (process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF) { + folder = `${folder}-e2e-test` + } + const p = path.join(ELECTRON_APP_DATA_PATH, 'cy', folder, ...paths) return p diff --git a/packages/server/lib/util/app_data.js b/packages/server/lib/util/app_data.js index b0d84e381326..340887a6f16e 100644 --- a/packages/server/lib/util/app_data.js +++ b/packages/server/lib/util/app_data.js @@ -94,6 +94,10 @@ module.exports = { // allow overriding the app_data folder let folder = env.CYPRESS_KONFIG_ENV || env.CYPRESS_INTERNAL_ENV + if (process.env.CYPRESS_INTERNAL_E2E_TESTING_SELF) { + folder = `${folder}-e2e-test` + } + const p = path.join(ELECTRON_APP_DATA_PATH, 'cy', folder, ...paths) log('path: %s', p) diff --git a/system-tests/lib/fixtures.js b/system-tests/lib/fixtures.js index d3e1e0f19b23..991f5b7e7df3 100644 --- a/system-tests/lib/fixtures.js +++ b/system-tests/lib/fixtures.js @@ -1,6 +1,7 @@ const fs = require('fs-extra') const path = require('path') const chokidar = require('chokidar') +const _fs = require('fs') const root = path.join(__dirname, '..') @@ -32,7 +33,7 @@ module.exports = { const from = path.join(projects, project) const to = path.join(tmpDir, project) - if (fs.existsSync(to)) { + if (_fs.existsSync(to)) { fs.removeSync(to) } From a52c988853118b70941fe1433984506ab7b8ce50 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 16 Nov 2021 15:49:59 -0500 Subject: [PATCH 03/11] don't clean internal vite during dev --- scripts/gulp/gulpfile.ts | 7 ++----- scripts/gulp/tasks/gulpVite.ts | 12 ++++++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/scripts/gulp/gulpfile.ts b/scripts/gulp/gulpfile.ts index 182131d37bce..393b170b7a0b 100644 --- a/scripts/gulp/gulpfile.ts +++ b/scripts/gulp/gulpfile.ts @@ -11,7 +11,7 @@ import gulp from 'gulp' import { autobarrelWatcher } from './tasks/gulpAutobarrel' import { startCypressWatch, openCypressLaunchpad, openCypressApp, runCypressLaunchpad, wrapRunWithExit, runCypressApp, killExistingCypress } from './tasks/gulpCypress' import { graphqlCodegen, graphqlCodegenWatch, nexusCodegen, nexusCodegenWatch, generateFrontendSchema, syncRemoteGraphQL } from './tasks/gulpGraphql' -import { viteApp, viteCleanApp, viteCleanLaunchpad, viteLaunchpad, viteBuildApp, viteBuildAndWatchApp, viteBuildLaunchpad, viteBuildAndWatchLaunchpad, symlinkViteProjects, generateShikiTheme } from './tasks/gulpVite' +import { viteApp, viteCleanApp, viteCleanLaunchpad, viteLaunchpad, viteBuildApp, viteBuildAndWatchApp, viteBuildLaunchpad, viteBuildAndWatchLaunchpad, symlinkViteProjects, generateShikiTheme, viteClean } from './tasks/gulpVite' import { checkTs } from './tasks/gulpTsc' import { makePathMap } from './utils/makePathMap' import { makePackage } from './tasks/gulpMakePackage' @@ -26,10 +26,7 @@ import { e2eTestScaffold, e2eTestScaffoldWatch } from './tasks/gulpE2ETestScaffo * * `yarn dev` is your primary command for getting work done *------------------------------------------------------------------------**/ -gulp.task('viteClean', gulp.parallel( - viteCleanApp, - viteCleanLaunchpad, -)) +gulp.task(viteClean) gulp.task( 'codegen', diff --git a/scripts/gulp/tasks/gulpVite.ts b/scripts/gulp/tasks/gulpVite.ts index 4bd47cce5d8a..7b5d280e4ff5 100644 --- a/scripts/gulp/tasks/gulpVite.ts +++ b/scripts/gulp/tasks/gulpVite.ts @@ -144,6 +144,18 @@ export function generateShikiTheme () { }) } +export async function viteClean () { + // Don't clear the .vite cache if we're doing the internal dev flag + if (process.env.CYPRESS_INTERNAL_VITE_DEV) { + return + } + + return Promise.all([ + viteCleanApp(), + viteCleanLaunchpad(), + ]) +} + export function viteCleanApp () { return spawned('vite-clean', `yarn clean`, { cwd: monorepoPaths.pkgApp, From 76b9af22092f0dd6d1cf7db7627a556bd8cb2bb0 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 16 Nov 2021 16:00:23 -0500 Subject: [PATCH 04/11] Fix resolved port --- packages/resolve-dist/lib/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/resolve-dist/lib/index.ts b/packages/resolve-dist/lib/index.ts index 5e9d8a2ed713..d8f9f7ab6637 100644 --- a/packages/resolve-dist/lib/index.ts +++ b/packages/resolve-dist/lib/index.ts @@ -24,7 +24,7 @@ export const getPathToDesktopIndex = (pkg: 'desktop-gui' | 'launchpad', graphqlP // For now, if we see that there's a CYPRESS_INTERNAL_VITE_DEV // we assume we're running Cypress targeting that (dev server) if (pkg === 'launchpad' && process.env.CYPRESS_INTERNAL_VITE_DEV) { - return `http://localhost:${process.env.CYPRESS_INTERNAL_VITE_DEV}?gqlPort=${graphqlPort}` + return `http://localhost:${process.env.CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT}?gqlPort=${graphqlPort}` } return `file://${path.join(__dirname, '..', '..', pkg, 'dist', 'index.html')}?gqlPort=${graphqlPort}` From c958186b2e9cbb118a9cddc5ad294d0336aafece Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 16 Nov 2021 17:32:17 -0500 Subject: [PATCH 05/11] fix --- scripts/gulp/gulpfile.ts | 8 ++++---- scripts/gulp/tasks/gulpVite.ts | 13 ++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/scripts/gulp/gulpfile.ts b/scripts/gulp/gulpfile.ts index 393b170b7a0b..d88a7d62648e 100644 --- a/scripts/gulp/gulpfile.ts +++ b/scripts/gulp/gulpfile.ts @@ -63,7 +63,7 @@ gulp.task( gulp.series( makePathMap, gulp.parallel( - 'viteClean', + viteClean, 'codegen', ), @@ -112,7 +112,7 @@ gulp.task( gulp.task('buildProd', gulp.series( - 'viteClean', + viteClean, syncRemoteGraphQL, nexusCodegen, @@ -222,7 +222,7 @@ const cyOpenApp = gulp.series( // Open Cypress in production mode. // Rebuild the Launchpad app between changes. gulp.task('cyOpenLaunchpadE2E', gulp.series( - 'viteClean', + viteClean, // 1. Build the Cypress App itself 'commonSetup', @@ -234,7 +234,7 @@ gulp.task('cyOpenLaunchpadE2E', gulp.series( // Open Cypress in production mode. // Rebuild the Launchpad app between changes. gulp.task('cyOpenAppE2E', gulp.series( - 'viteClean', + viteClean, // 1. Build the Cypress App itself 'commonSetup', diff --git a/scripts/gulp/tasks/gulpVite.ts b/scripts/gulp/tasks/gulpVite.ts index 7b5d280e4ff5..97942b60ca7a 100644 --- a/scripts/gulp/tasks/gulpVite.ts +++ b/scripts/gulp/tasks/gulpVite.ts @@ -29,7 +29,9 @@ export function viteApp () { if (process.env.CYPRESS_INTERNAL_VITE_DEV) { const port = process.env.CYPRESS_INTERNAL_VITE_APP_PORT ??= '3333' - return spawnViteDevServer('vite-app', `yarn vite --port ${port} ${baseSuffix}`) + return spawnViteDevServer('vite-app', `yarn vite --port ${port} ${baseSuffix}`, { + cwd: monorepoPaths.pkgApp, + }) } return watchViteBuild('vite-app', `yarn vite build --mode development --minify false --watch ${baseSuffix}`, { @@ -41,7 +43,9 @@ export function viteLaunchpad () { if (process.env.CYPRESS_INTERNAL_VITE_DEV) { const port = process.env.CYPRESS_INTERNAL_VITE_LAUNCHPAD_PORT ??= '3001' - return spawnViteDevServer('vite-launchpad', `yarn vite --port ${port}`) + return spawnViteDevServer('vite-launchpad', `yarn vite --port ${port}`, { + cwd: monorepoPaths.pkgLaunchpad, + }) } return watchViteBuild('vite-launchpad', `yarn vite build --mode development --minify false --watch`, { @@ -145,11 +149,6 @@ export function generateShikiTheme () { } export async function viteClean () { - // Don't clear the .vite cache if we're doing the internal dev flag - if (process.env.CYPRESS_INTERNAL_VITE_DEV) { - return - } - return Promise.all([ viteCleanApp(), viteCleanLaunchpad(), From b3f52421abbf227ecc9b0171d2add0cb5126929e Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 16 Nov 2021 23:09:29 -0500 Subject: [PATCH 06/11] Fix import --- system-tests/lib/fixtures.js | 1 - 1 file changed, 1 deletion(-) diff --git a/system-tests/lib/fixtures.js b/system-tests/lib/fixtures.js index 4197276a0f45..66c69c97ad58 100644 --- a/system-tests/lib/fixtures.js +++ b/system-tests/lib/fixtures.js @@ -2,7 +2,6 @@ const _fs = require('fs') const fs = require('fs-extra') const path = require('path') const chokidar = require('chokidar') -const _fs = require('fs') const root = path.join(__dirname, '..') From dfb044679f7ae49141c88192501429278d65dcae Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Tue, 16 Nov 2021 23:59:34 -0500 Subject: [PATCH 07/11] Fix failing CI --- packages/server/lib/modes/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/server/lib/modes/index.ts b/packages/server/lib/modes/index.ts index 2bd54d29bac5..99b0c0383580 100644 --- a/packages/server/lib/modes/index.ts +++ b/packages/server/lib/modes/index.ts @@ -29,7 +29,8 @@ export = (mode, options) => { // Change default for `cypress open` to be LAUNCHPAD=1 if (process.env.LAUNCHPAD === '0') { delete process.env.LAUNCHPAD - } else { + // Temporary (tim), we want existing integration tests running w/ previous structure + } else if (process.env.CI !== '1') { process.env.LAUNCHPAD = '1' } From 830acd41f3882adc4475eb9136274c029e00888f Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Wed, 17 Nov 2021 09:20:03 -0500 Subject: [PATCH 08/11] Fix failing tests --- packages/server/lib/modes/index.ts | 3 +-- packages/server/test/integration/cypress_spec.js | 8 ++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/server/lib/modes/index.ts b/packages/server/lib/modes/index.ts index 99b0c0383580..2bd54d29bac5 100644 --- a/packages/server/lib/modes/index.ts +++ b/packages/server/lib/modes/index.ts @@ -29,8 +29,7 @@ export = (mode, options) => { // Change default for `cypress open` to be LAUNCHPAD=1 if (process.env.LAUNCHPAD === '0') { delete process.env.LAUNCHPAD - // Temporary (tim), we want existing integration tests running w/ previous structure - } else if (process.env.CI !== '1') { + } else { process.env.LAUNCHPAD = '1' } diff --git a/packages/server/test/integration/cypress_spec.js b/packages/server/test/integration/cypress_spec.js index caf2f6144551..fbce1b25a11a 100644 --- a/packages/server/test/integration/cypress_spec.js +++ b/packages/server/test/integration/cypress_spec.js @@ -1714,6 +1714,9 @@ describe('lib/cypress', () => { return settings.writeOnly(this.todosPath, json) }).then(() => { + // TODO(tim): this shouldn't be needed when we refactor the ctx setup + process.env.LAUNCHPAD = '0' + return cypress.start([ '--port=2121', '--config', @@ -1722,6 +1725,7 @@ describe('lib/cypress', () => { '--env=baz=baz', ]) }).then(() => { + delete process.env.LAUNCHPAD const options = Events.start.firstCall.args[0] return Events.handleEvent(options, {}, {}, 123, 'open:project', this.todosPath) @@ -1794,8 +1798,12 @@ describe('lib/cypress', () => { sinon.stub(ServerE2E.prototype, 'open').resolves([2121, warning]) + // TODO(tim): this shouldn't be needed when we refactor the ctx setup + process.env.LAUNCHPAD = '0' + return cypress.start(['--port=2121', '--config', 'pageLoadTimeout=1000', '--foo=bar', '--env=baz=baz']) .then(() => { + delete process.env.LAUNCHPAD const options = Events.start.firstCall.args[0] Events.handleEvent(options, bus, event, 123, 'on:project:warning') From 068e0856213766a1ce6e7d48cb9d937be6e04590 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Wed, 17 Nov 2021 10:38:21 -0500 Subject: [PATCH 09/11] fix: handle error in versions, dont show toast in prod --- .../src/sources/VersionsDataSource.ts | 11 +++++--- .../src/gql-components/topnav/TopNav.vue | 4 +++ .../frontend-shared/src/graphql/urqlClient.ts | 9 ++++--- packages/graphql/schemas/schema.graphql | 2 +- .../src/schemaTypes/objectTypes/gql-Query.ts | 2 +- packages/launchpad/src/App.vue | 26 ++++++++++--------- packages/server/lib/modes/interactive-e2e.ts | 3 ++- scripts/gulp/tasks/gulpCypress.ts | 2 +- 8 files changed, 37 insertions(+), 22 deletions(-) diff --git a/packages/data-context/src/sources/VersionsDataSource.ts b/packages/data-context/src/sources/VersionsDataSource.ts index 96f058ef7071..6cab250ac610 100644 --- a/packages/data-context/src/sources/VersionsDataSource.ts +++ b/packages/data-context/src/sources/VersionsDataSource.ts @@ -12,6 +12,8 @@ interface VersionData { } export class VersionsDataSource { + static result: undefined | Promise + /** * Returns most recent and current version of Cypress * { @@ -27,9 +29,12 @@ export class VersionsDataSource { */ async versions (): Promise { const currentCypressVersion = require('@packages/root') - const result = await execa(`npm`, [`view`, `cypress`, `time`, `--json`]) - const json = JSON.parse(result.stdout) + VersionsDataSource.result ??= execa(`npm`, [`view`, `cypress`, `time`, `--json`]).then((result) => result.stdout) + + const result = await VersionsDataSource.result + + const json = JSON.parse(result) delete json['modified'] delete json['created'] @@ -50,7 +55,7 @@ export class VersionsDataSource { latest: latestVersion, current: { version: currentCypressVersion.version, - released: currentCypressVersion.version === '0.0.0-development' ? new Date().toISOString() : json[currentCypressVersion.version], + released: json[currentCypressVersion.version] ?? new Date().toISOString(), id: currentCypressVersion.version, }, } diff --git a/packages/frontend-shared/src/gql-components/topnav/TopNav.vue b/packages/frontend-shared/src/gql-components/topnav/TopNav.vue index f96c58a15fcc..216d952455cc 100644 --- a/packages/frontend-shared/src/gql-components/topnav/TopNav.vue +++ b/packages/frontend-shared/src/gql-components/topnav/TopNav.vue @@ -292,6 +292,10 @@ const promptsEl: Ref = ref(null) // so it doesn't reopen on the one of the prompts const versions = computed(() => { + if (!props.gql.versions) { + return null + } + return { current: { released: useTimeAgo(new Date(props.gql.versions.current.released)).value, diff --git a/packages/frontend-shared/src/graphql/urqlClient.ts b/packages/frontend-shared/src/graphql/urqlClient.ts index f083933f6e2a..8a41dd2a742d 100644 --- a/packages/frontend-shared/src/graphql/urqlClient.ts +++ b/packages/frontend-shared/src/graphql/urqlClient.ts @@ -81,9 +81,12 @@ export function makeUrqlClient (target: 'launchpad' | 'app'): Client { ${error.stack ?? ''} ` - toast.error(message, { - timeout: false, - }) + if (process.env.NODE_ENV !== 'production') { + toast.error(message, { + timeout: false, + }) + } + // eslint-disable-next-line console.error(error) }, diff --git a/packages/graphql/schemas/schema.graphql b/packages/graphql/schemas/schema.graphql index 8681c406bef3..c2d86e27a6bf 100644 --- a/packages/graphql/schemas/schema.graphql +++ b/packages/graphql/schemas/schema.graphql @@ -608,7 +608,7 @@ type Query { projects: [ProjectLike!]! """Previous versions of cypress and their release date""" - versions: VersionData! + versions: VersionData """Metadata about the wizard, null if we arent showing the wizard""" wizard: Wizard! diff --git a/packages/graphql/src/schemaTypes/objectTypes/gql-Query.ts b/packages/graphql/src/schemaTypes/objectTypes/gql-Query.ts index 7652c14c935a..8b7f43ca8598 100644 --- a/packages/graphql/src/schemaTypes/objectTypes/gql-Query.ts +++ b/packages/graphql/src/schemaTypes/objectTypes/gql-Query.ts @@ -27,7 +27,7 @@ export const Query = objectType({ resolve: (root, args, ctx) => ctx.coreData.dev, }) - t.nonNull.field('versions', { + t.field('versions', { type: VersionData, description: 'Previous versions of cypress and their release date', resolve: (root, args, ctx) => { diff --git a/packages/launchpad/src/App.vue b/packages/launchpad/src/App.vue index 7556f4b286d3..02630d8ca6d9 100644 --- a/packages/launchpad/src/App.vue +++ b/packages/launchpad/src/App.vue @@ -62,18 +62,20 @@ let isShowingRelaunch = ref(false) const toast = useToast() watch(query.data, () => { - if (query.data.value?.dev.needsRelaunch && !isShowingRelaunch.value) { - isShowingRelaunch.value = true - toast.info('Server updated, click to relaunch', { - timeout: false, - onClick: () => { - relaunchMutation.executeMutation({ action: 'trigger' }) - }, - onClose: () => { - isShowingRelaunch.value = false - relaunchMutation.executeMutation({ action: 'dismiss' }) - }, - }) + if (process.env.NODE_ENV !== 'production') { + if (query.data.value?.dev.needsRelaunch && !isShowingRelaunch.value) { + isShowingRelaunch.value = true + toast.info('Server updated, click to relaunch', { + timeout: false, + onClick: () => { + relaunchMutation.executeMutation({ action: 'trigger' }) + }, + onClose: () => { + isShowingRelaunch.value = false + relaunchMutation.executeMutation({ action: 'dismiss' }) + }, + }) + } } }) diff --git a/packages/server/lib/modes/interactive-e2e.ts b/packages/server/lib/modes/interactive-e2e.ts index 06dbba32a62f..c9a70dc02700 100644 --- a/packages/server/lib/modes/interactive-e2e.ts +++ b/packages/server/lib/modes/interactive-e2e.ts @@ -13,7 +13,8 @@ import type { LaunchArgs, PlatformName } from '@packages/types' import EventEmitter from 'events' const isDev = () => { - return process.env['CYPRESS_INTERNAL_ENV'] === 'development' + // TODO: (tim) ensure the process.env.LAUNCHPAD gets removed before release + return Boolean(process.env['CYPRESS_INTERNAL_ENV'] === 'development' || process.env.LAUNCHPAD) } export = { diff --git a/scripts/gulp/tasks/gulpCypress.ts b/scripts/gulp/tasks/gulpCypress.ts index 12d13cfabd31..def89089c010 100644 --- a/scripts/gulp/tasks/gulpCypress.ts +++ b/scripts/gulp/tasks/gulpCypress.ts @@ -139,7 +139,7 @@ export async function startCypressWatch () { function signalRestart () { if (!child) { - restartServer() + startCypressWithListeners() } else { fs.writeFile(DevActions.CY_STATE_PATH, JSON.stringify(new Date().toString())) } From 1595e15f220e6d8f6b58e9dcc8bc3ecd63941951 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Wed, 17 Nov 2021 10:48:33 -0500 Subject: [PATCH 10/11] fix types --- packages/frontend-shared/src/gql-components/topnav/TopNav.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend-shared/src/gql-components/topnav/TopNav.vue b/packages/frontend-shared/src/gql-components/topnav/TopNav.vue index 216d952455cc..a229222e1039 100644 --- a/packages/frontend-shared/src/gql-components/topnav/TopNav.vue +++ b/packages/frontend-shared/src/gql-components/topnav/TopNav.vue @@ -309,7 +309,7 @@ const versions = computed(() => { }) const runningOldVersion = computed(() => { - return props.gql.versions.current.released < props.gql.versions.latest.released + return props.gql.versions ? props.gql.versions.current.released < props.gql.versions.latest.released : false }) onClickOutside(promptsEl, () => { From c3bc186f71fdd3210d42b2ba5686c24a04be1349 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Wed, 17 Nov 2021 11:08:14 -0500 Subject: [PATCH 11/11] Don't modify the context url on cache first requests --- .../frontend-shared/src/graphql/urqlExchangeNamedRoute.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/frontend-shared/src/graphql/urqlExchangeNamedRoute.ts b/packages/frontend-shared/src/graphql/urqlExchangeNamedRoute.ts index 985c600acccc..d3715bc6f9d9 100644 --- a/packages/frontend-shared/src/graphql/urqlExchangeNamedRoute.ts +++ b/packages/frontend-shared/src/graphql/urqlExchangeNamedRoute.ts @@ -6,6 +6,10 @@ export const namedRouteExchange: Exchange = ({ client, forward }) => { return forward(pipe( ops$, map((o) => { + if (o.context.requestPolicy === 'cache-first' || o.context.requestPolicy === 'cache-only') { + return o + } + if (!o.context.url.endsWith('/graphql')) { throw new Error(`Infinite loop detected? Ping @tgriesser to help debug`) }