From 323445a66a42841b8d3c0af56927007af0b41dcd Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Mon, 26 Jul 2021 20:20:01 -0400 Subject: [PATCH 01/29] wip: Structuring context & schema so it can be used on the client --- .../launchpad/cypress/support/commands.ts | 14 +- .../cypress/support/testApolloClient.ts | 27 +++ packages/launchpad/graphql-codegen.yml | 2 +- packages/launchpad/package.json | 7 +- packages/launchpad/script/build-schema.js | 1 + packages/launchpad/src/App.vue | 6 +- packages/launchpad/src/components/Wizard.vue | 3 + .../launchpad/src/graphql/apolloClient.ts | 36 ++-- packages/launchpad/src/graphql/graphqlIpc.ts | 2 +- packages/launchpad/vite.config.ts | 3 + packages/server/.eslintrc.json | 12 ++ packages/server/lib/graphql/ExecContext.ts | 3 - packages/server/lib/graphql/GraphQL-FAQ.md | 10 ++ .../server/lib/graphql/actions/BaseActions.ts | 11 ++ .../lib/graphql/actions/ClientTestActions.ts | 12 ++ .../lib/graphql/actions/ServerActions.ts | 15 ++ .../lib/graphql/constants/WizardConstants.ts | 60 +++++++ .../server/lib/graphql/constants/index.ts | 1 + .../server/lib/graphql/context/BaseContext.ts | 17 ++ .../lib/graphql/context/ClientTestContext.ts | 9 + .../lib/graphql/context/ServerContext.ts | 6 + packages/server/lib/graphql/entities/App.ts | 27 ++- .../server/lib/graphql/entities/Mutation.ts | 52 ++++++ .../server/lib/graphql/entities/Projects.ts | 2 +- packages/server/lib/graphql/entities/Query.ts | 11 ++ .../lib/graphql/entities/TestingType.ts | 20 +++ .../server/lib/graphql/entities/Wizard.ts | 167 +++++++++++++++++- packages/server/lib/graphql/entities/index.ts | 6 + packages/server/lib/graphql/gen/nxs.gen.ts | 90 +++++++++- packages/server/lib/graphql/schema.ts | 21 ++- packages/server/lib/graphql/server.ts | 8 +- packages/server/lib/graphql/utils/index.ts | 4 +- packages/server/lib/gui/events.js | 4 +- packages/server/schema.graphql | 104 ++++++++++- scripts/watch.js | 2 +- yarn.lock | 49 +++-- 36 files changed, 736 insertions(+), 88 deletions(-) create mode 100644 packages/launchpad/cypress/support/testApolloClient.ts delete mode 100644 packages/server/lib/graphql/ExecContext.ts create mode 100644 packages/server/lib/graphql/GraphQL-FAQ.md create mode 100644 packages/server/lib/graphql/actions/BaseActions.ts create mode 100644 packages/server/lib/graphql/actions/ClientTestActions.ts create mode 100644 packages/server/lib/graphql/actions/ServerActions.ts create mode 100644 packages/server/lib/graphql/constants/WizardConstants.ts create mode 100644 packages/server/lib/graphql/constants/index.ts create mode 100644 packages/server/lib/graphql/context/BaseContext.ts create mode 100644 packages/server/lib/graphql/context/ClientTestContext.ts create mode 100644 packages/server/lib/graphql/context/ServerContext.ts create mode 100644 packages/server/lib/graphql/entities/Mutation.ts create mode 100644 packages/server/lib/graphql/entities/Query.ts create mode 100644 packages/server/lib/graphql/entities/TestingType.ts diff --git a/packages/launchpad/cypress/support/commands.ts b/packages/launchpad/cypress/support/commands.ts index d104ae934dd5..beac01f46457 100644 --- a/packages/launchpad/cypress/support/commands.ts +++ b/packages/launchpad/cypress/support/commands.ts @@ -1,9 +1,9 @@ import { mount } from '@cypress/vue' import { provideApolloClient } from '@vue/apollo-composable' import { createStoreApp, StoreApp } from '../../src/store/app' -import { initGraphQLipc } from '../../src/graphql/graphqlIpc' -import { apolloClient } from '../../src/graphql/apolloClient' import { createStoreConfig, StoreConfig } from '../../src/store/config' +import { testApolloClient } from './testApolloClient' +import { ClientTestContext } from '@packages/server/lib/graphql/context/ClientTestContext' /** * This variable is mimicing ipc provided by electron. @@ -15,8 +15,6 @@ import { createStoreConfig, StoreConfig } from '../../src/store/config' send: () => {}, } -initGraphQLipc() - Cypress.Commands.add( 'mount', (comp: Parameters[0], options: Parameters[1]) => { @@ -32,9 +30,11 @@ Cypress.Commands.add( options.global.plugins = options.global.plugins || [] options.global.plugins.push(storeApp) options.global.plugins.push(storeConfig) - options.global.plugins.push({ install (app) { - provideApolloClient(apolloClient) - } }) + options.global.plugins.push({ + install (app) { + provideApolloClient(testApolloClient(new ClientTestContext())) + }, + }) return mount(comp, options) }, diff --git a/packages/launchpad/cypress/support/testApolloClient.ts b/packages/launchpad/cypress/support/testApolloClient.ts new file mode 100644 index 000000000000..429b19dc4fc5 --- /dev/null +++ b/packages/launchpad/cypress/support/testApolloClient.ts @@ -0,0 +1,27 @@ +import { ApolloLink, FetchResult, ApolloClient, InMemoryCache, Observable } from '@apollo/client/core' +import { graphqlSchema } from '@packages/server/lib/graphql/schema' +import { ClientTestContext } from '@packages/server/lib/graphql/context/ClientTestContext' +import { graphql, print } from 'graphql' + +export function testApolloClient (ctx: ClientTestContext) { + const ipcLink = new ApolloLink((op) => { + return new Observable((obs) => { + graphql({ + source: print(op.query), + schema: graphqlSchema, + contextValue: new ClientTestContext(), + }).then((result) => { + obs.next(result as FetchResult) + obs.complete() + }).catch((err) => { + obs.error(err) + obs.complete() + }) + }) + }) + + return new ApolloClient({ + link: ipcLink, + cache: new InMemoryCache(), + }) +} diff --git a/packages/launchpad/graphql-codegen.yml b/packages/launchpad/graphql-codegen.yml index 13fe6337ce6e..e1cf9bbe2773 100644 --- a/packages/launchpad/graphql-codegen.yml +++ b/packages/launchpad/graphql-codegen.yml @@ -2,7 +2,7 @@ overwrite: true schema: '../server/schema.graphql' documents: 'src/**/*.vue' generates: - src/generated/graphql.tsx: + src/generated/graphql.ts: config: immutableTypes: true useTypeImports: true diff --git a/packages/launchpad/package.json b/packages/launchpad/package.json index 2140262e066b..2eb6a6b1531a 100644 --- a/packages/launchpad/package.json +++ b/packages/launchpad/package.json @@ -3,7 +3,7 @@ "version": "0.0.0-development", "private": true, "scripts": { - "prebuild": "yarn codegen", + "prebuild": "node script/build-schema.js && yarn codegen", "build": "vite build", "build-prod": "cross-env NODE_ENV=production yarn build", "clean-deps": "rm -rf node_modules", @@ -13,7 +13,7 @@ "dev": "NODE_ENV=development LAUNCHPAD=1 yarn start-test 5050 cypress:open", "postinstall": "yarn codegen && echo '@packages/launchpad needs: yarn build'", "start": "http-server -p 5050 dist", - "watch": "yarn build --watch", + "watch": "concurrently \"vite build --watch\" \"yarn codegen --watch\"", "codegen": "graphql-codegen --config ${PWD}/graphql-codegen.yml" }, "dependencies": { @@ -33,9 +33,10 @@ "@iconify/vue": "3.0.0-beta.1", "@vitejs/plugin-vue": "1.2.4", "@vitejs/plugin-vue-jsx": "1.1.6", - "@vue/apollo-composable": "4.0.0-alpha.12", + "@vue/apollo-composable": "^4.0.0-alpha.14", "bluebird": "3.5.3", "classnames": "2.3.1", + "concurrently": "^6.2.0", "cross-env": "6.0.3", "graphql": "^15.5.1", "graphql-tag": "^2.12.5", diff --git a/packages/launchpad/script/build-schema.js b/packages/launchpad/script/build-schema.js index 172573ae8590..a781f185da5b 100644 --- a/packages/launchpad/script/build-schema.js +++ b/packages/launchpad/script/build-schema.js @@ -1,3 +1,4 @@ +process.env.CYPRESS_INTERNAL_ENV = 'development' process.env.GRAPHQL_CODEGEN = 'true' require('@packages/ts/register') require('../../server/lib/graphql/schema') diff --git a/packages/launchpad/src/App.vue b/packages/launchpad/src/App.vue index 00ca48281d8a..dac491045645 100644 --- a/packages/launchpad/src/App.vue +++ b/packages/launchpad/src/App.vue @@ -6,8 +6,7 @@ diff --git a/packages/launchpad/src/components/Wizard.vue b/packages/launchpad/src/components/Wizard.vue index 6cb15efe40a6..30b391ea73a4 100644 --- a/packages/launchpad/src/components/Wizard.vue +++ b/packages/launchpad/src/components/Wizard.vue @@ -33,6 +33,9 @@ gql` query Wizard { app { isFirstOpen + wizard { + step + } } } ` diff --git a/packages/launchpad/src/graphql/apolloClient.ts b/packages/launchpad/src/graphql/apolloClient.ts index 06af1589187a..d5692b82f88e 100644 --- a/packages/launchpad/src/graphql/apolloClient.ts +++ b/packages/launchpad/src/graphql/apolloClient.ts @@ -1,25 +1,23 @@ import { ApolloLink, FetchResult, ApolloClient, InMemoryCache, Observable } from '@apollo/client/core' -import { fetchGraphql } from './graphqlIpc' +import { fetchGraphql, initGraphQLIPC } from './graphqlIpc' -const ipcLink = new ApolloLink((op) => { - return new Observable((obs) => { - fetchGraphql(op).then((result) => { - obs.next(result as FetchResult) - obs.complete() +export function makeApolloClient () { + initGraphQLIPC() - return result - }).catch((err) => { - obs.error(err) - obs.complete() + const ipcLink = new ApolloLink((op) => { + return new Observable((obs) => { + fetchGraphql(op).then((result) => { + obs.next(result as FetchResult) + obs.complete() + }).catch((err) => { + obs.error(err) + obs.complete() + }) }) }) -}) -// Cache implementation -const cache = new InMemoryCache() - -// Create the apollo client -export const apolloClient = new ApolloClient({ - link: ipcLink, - cache, -}) + return new ApolloClient({ + link: ipcLink, + cache: new InMemoryCache(), + }) +} diff --git a/packages/launchpad/src/graphql/graphqlIpc.ts b/packages/launchpad/src/graphql/graphqlIpc.ts index baff9cc7d48f..bbe410bf6929 100644 --- a/packages/launchpad/src/graphql/graphqlIpc.ts +++ b/packages/launchpad/src/graphql/graphqlIpc.ts @@ -9,7 +9,7 @@ interface GraphQLResponseShape { result: any } -export function initGraphQLipc () { +export function initGraphQLIPC () { window.ipc.on('graphql:response', (event, obj: GraphQLResponseShape) => { const dfd = ipcInFlight.get(obj.id) diff --git a/packages/launchpad/vite.config.ts b/packages/launchpad/vite.config.ts index 128750b737d2..88df043bf6a4 100644 --- a/packages/launchpad/vite.config.ts +++ b/packages/launchpad/vite.config.ts @@ -23,4 +23,7 @@ export default defineConfig({ }), WindiCSS(), ], + define: { + 'process.env': {}, + }, }) diff --git a/packages/server/.eslintrc.json b/packages/server/.eslintrc.json index a888e77e78dd..df5daebc38ba 100644 --- a/packages/server/.eslintrc.json +++ b/packages/server/.eslintrc.json @@ -8,5 +8,17 @@ }, "plugins": [ "cypress" + ], + "overrides": [ + { + "files": [ + "./lib/graphql/entities/**/*.ts" + ], + "rules": { + "@typescript-eslint/explicit-function-return-type": [ + "error" + ] + } + } ] } diff --git a/packages/server/lib/graphql/ExecContext.ts b/packages/server/lib/graphql/ExecContext.ts deleted file mode 100644 index 2f3f3776cb63..000000000000 --- a/packages/server/lib/graphql/ExecContext.ts +++ /dev/null @@ -1,3 +0,0 @@ -export class ExecContext { - constructor (readonly options: any) {} -} diff --git a/packages/server/lib/graphql/GraphQL-FAQ.md b/packages/server/lib/graphql/GraphQL-FAQ.md new file mode 100644 index 000000000000..edb1f3b51438 --- /dev/null +++ b/packages/server/lib/graphql/GraphQL-FAQ.md @@ -0,0 +1,10 @@ +## Why are my types not showing up in the schema + +Ensure that the types are exported so that they are imported into the root `makeSchema`. + +There are often "barrel" files that re-export the types, such as in [`./entities/index.ts`](./entities/index.ts) or [`./constants/index.ts`](./constants/index.ts) + +## Why is my query / mutation not being added + +Queries & mutations must be `static` properties if using nexus-decorators + diff --git a/packages/server/lib/graphql/actions/BaseActions.ts b/packages/server/lib/graphql/actions/BaseActions.ts new file mode 100644 index 000000000000..6b5e281d3f84 --- /dev/null +++ b/packages/server/lib/graphql/actions/BaseActions.ts @@ -0,0 +1,11 @@ +import { BaseContext } from '../context/BaseContext' + +/** + * Acts as the contract for all actions, implemented by ServerActions + * and ClientTestActions + */ +export abstract class BaseActions { + constructor (protected ctx: BaseContext) {} + + abstract installDependencies (): void +} diff --git a/packages/server/lib/graphql/actions/ClientTestActions.ts b/packages/server/lib/graphql/actions/ClientTestActions.ts new file mode 100644 index 000000000000..dbae303e8b02 --- /dev/null +++ b/packages/server/lib/graphql/actions/ClientTestActions.ts @@ -0,0 +1,12 @@ +import { ClientTestContext } from '../context/ClientTestContext' +import { BaseActions } from './BaseActions' + +export class ClientTestActions extends BaseActions { + constructor (protected ctx: ClientTestContext) { + super(ctx) + } + + installDependencies (): void { + return + } +} diff --git a/packages/server/lib/graphql/actions/ServerActions.ts b/packages/server/lib/graphql/actions/ServerActions.ts new file mode 100644 index 000000000000..194d5f997a76 --- /dev/null +++ b/packages/server/lib/graphql/actions/ServerActions.ts @@ -0,0 +1,15 @@ +import { ServerContext } from '../context/ServerContext' +import { BaseActions } from './BaseActions' + +/** + * + */ +export class ServerActions extends BaseActions { + constructor (protected ctx: ServerContext) { + super(ctx) + } + + installDependencies () { + // + } +} diff --git a/packages/server/lib/graphql/constants/WizardConstants.ts b/packages/server/lib/graphql/constants/WizardConstants.ts new file mode 100644 index 000000000000..e5aadcf27cd9 --- /dev/null +++ b/packages/server/lib/graphql/constants/WizardConstants.ts @@ -0,0 +1,60 @@ +import { enumType } from 'nexus' + +import type { NexusGenEnums } from '../gen/nxs.gen' + +export const BUNDLER = ['webpack', 'vite'] as const + +export type Bundler = typeof BUNDLER[number] + +export const BundlerEnum = enumType({ + name: 'SupportedBundlers', + description: 'The bundlers that we can use with Cypress', + members: BUNDLER, +}) + +export const BundlerDisplayNames: Record = { + vite: 'Vite', + webpack: 'Webpack', +} + +export const FRONTEND_FRAMEWORK = ['nuxtjs', 'nextjs', 'cra', 'vuecli', 'reactjs', 'vuejs'] as const + +export type FrontendFramework = typeof FRONTEND_FRAMEWORK[number] + +export const FrontendFrameworkEnum = enumType({ + name: 'FrontendFramework', + members: FRONTEND_FRAMEWORK, +}) + +export const FrameworkDisplayNames: Record = { + cra: 'Create React App', + vuecli: 'Vue CLI', + reactjs: 'React.js', + vuejs: 'Vue.js', + nextjs: 'Next.js', + nuxtjs: 'Nuxt.js', +} + +export const TESTING_TYPES = ['component', 'e2e'] as const + +export type TestingType = typeof TESTING_TYPES[number] + +export const TestingTypeEnum = enumType({ + name: 'TestingTypeEnum', + members: TESTING_TYPES, +}) + +export const WIZARD_STEP = [ + 'welcome', + 'selectFramework', + 'installDependencies', + 'createConfig', + 'setupComplete', +] as const + +export type WizardStep = typeof WIZARD_STEP[number] + +export const WizardStepEnum = enumType({ + name: 'WizardStep', + members: WIZARD_STEP, +}) diff --git a/packages/server/lib/graphql/constants/index.ts b/packages/server/lib/graphql/constants/index.ts new file mode 100644 index 000000000000..efbf6b0d5310 --- /dev/null +++ b/packages/server/lib/graphql/constants/index.ts @@ -0,0 +1 @@ +export * from './WizardConstants' diff --git a/packages/server/lib/graphql/context/BaseContext.ts b/packages/server/lib/graphql/context/BaseContext.ts new file mode 100644 index 000000000000..aabc2429c1b7 --- /dev/null +++ b/packages/server/lib/graphql/context/BaseContext.ts @@ -0,0 +1,17 @@ +import { BaseActions } from '../actions/BaseActions' +import { Wizard } from '../entities' + +/** + * The "Base Context" is the class type that we will use to encapsulate the server state. + * It will be implemented by ServerContext (real state) and TestContext (client state). + * + * This allows us to re-use the entire GraphQL server definition client side for testing, + * without the need to endlessly mock things. + */ +export abstract class BaseContext { + abstract readonly actions: BaseActions + + wizard = new Wizard() + + isFirstOpen = false +} diff --git a/packages/server/lib/graphql/context/ClientTestContext.ts b/packages/server/lib/graphql/context/ClientTestContext.ts new file mode 100644 index 000000000000..ab679196bb8d --- /dev/null +++ b/packages/server/lib/graphql/context/ClientTestContext.ts @@ -0,0 +1,9 @@ +import { ClientTestActions } from '../actions/ClientTestActions' +import { BaseContext } from './BaseContext' + +/** + * A client test context allows us to isolate the behavior of the + */ +export class ClientTestContext extends BaseContext { + readonly actions = new ClientTestActions(this) +} diff --git a/packages/server/lib/graphql/context/ServerContext.ts b/packages/server/lib/graphql/context/ServerContext.ts new file mode 100644 index 000000000000..5288ee7296af --- /dev/null +++ b/packages/server/lib/graphql/context/ServerContext.ts @@ -0,0 +1,6 @@ +import { ServerActions } from '../actions/ServerActions' +import { BaseContext } from './BaseContext' + +export class ServerContext extends BaseContext { + readonly actions = new ServerActions(this) +} diff --git a/packages/server/lib/graphql/entities/App.ts b/packages/server/lib/graphql/entities/App.ts index a6f2edd2e5f7..38b41d1b6a85 100644 --- a/packages/server/lib/graphql/entities/App.ts +++ b/packages/server/lib/graphql/entities/App.ts @@ -1,27 +1,22 @@ -import { nxs } from 'nexus-decorators' -import { ExecContext } from '../ExecContext' +import { nxs, NxsResult } from 'nexus-decorators' +import { NexusGenTypes } from '../gen/nxs.gen' +import { Wizard } from './Wizard' @nxs.objectType({ description: 'Namespace for information related to the app', }) export class App { - ctx: ExecContext - - constructor (ctx: ExecContext) { - this.ctx = ctx - } - - @nxs.queryField(() => { - return { type: App } - }) - static app (_, ctx) { - return new App(ctx) - } - @nxs.field.nonNull.boolean({ description: 'Whether this is the first open of the application or not', }) - get isFirstOpen () { + static get isFirstOpen (): NxsResult<'App', 'isFirstOpen'> { return true } + + @nxs.field.type(() => Wizard, { + description: 'Metadata about the wizard', + }) + wizard (args, ctx: NexusGenTypes['context']): NxsResult<'App', 'wizard'> { + return ctx.wizard + } } diff --git a/packages/server/lib/graphql/entities/Mutation.ts b/packages/server/lib/graphql/entities/Mutation.ts new file mode 100644 index 000000000000..17e65e9680a9 --- /dev/null +++ b/packages/server/lib/graphql/entities/Mutation.ts @@ -0,0 +1,52 @@ +import { mutationType, nonNull } from 'nexus' +import { BundlerEnum, FrontendFrameworkEnum, TestingTypeEnum } from '../constants' + +export const mutation = mutationType({ + definition (t) { + // TODO(tim): in nexus, allow for t.wizard(...) + + t.field('wizardSetTestingType', { + type: 'Wizard', + description: 'Sets the current testing type we want to use', + args: { type: nonNull(TestingTypeEnum) }, + resolve: (root, args, ctx) => ctx.wizard.setTestingType(args.type), + }) + + t.field('wizardSetFramework', { + type: 'Wizard', + description: 'Sets the frontend framework we want to use for the project', + args: { framework: nonNull(FrontendFrameworkEnum) }, + resolve: (_, args, ctx) => ctx.wizard.setFramework(args.framework), + }) + + t.field('wizardNavigateBack', { + type: 'Wizard', + description: 'Navigates backward in the wizard', + args: { type: nonNull(TestingTypeEnum) }, + resolve: (_, __, ctx) => ctx.wizard.navigateBack(), + }) + + t.field('wizardSetBundler', { + type: 'Wizard', + description: 'Sets the frontend bundler we want to use for the project', + args: { + name: BundlerEnum, + }, + resolve: (root, args, ctx) => ctx.wizard.setBundler(args.name), + }) + + t.field('wizardInstallDependencies', { + type: 'Wizard', + description: 'Installs the dependencies for the component testing step', + resolve: (root, args, ctx) => ctx.wizard, + }) + + t.field('wizardValidateManualInstall', { + type: 'Wizard', + description: 'Validates that the manual install has occurred properly', + resolve: (root, args, ctx) => { + return ctx.wizard + }, + }) + }, +}) diff --git a/packages/server/lib/graphql/entities/Projects.ts b/packages/server/lib/graphql/entities/Projects.ts index bfad4b0093e5..05e830451ff3 100644 --- a/packages/server/lib/graphql/entities/Projects.ts +++ b/packages/server/lib/graphql/entities/Projects.ts @@ -1,5 +1,5 @@ import { inputObjectType, nonNull, mutationField, queryField } from 'nexus' -import { projects } from '../../projects' +// import { projects } from '../../projects' import { Project } from './types' import { formatProject } from '../utils' diff --git a/packages/server/lib/graphql/entities/Query.ts b/packages/server/lib/graphql/entities/Query.ts new file mode 100644 index 000000000000..bac3469b2211 --- /dev/null +++ b/packages/server/lib/graphql/entities/Query.ts @@ -0,0 +1,11 @@ +import { nxs, NxsQueryResult } from 'nexus-decorators' +import { App } from './App' + +export class Query { + @nxs.queryField(() => { + return { type: App } + }) + static app (_, ctx: NexusGen['context']): NxsQueryResult<'app'> { + return new App() + } +} diff --git a/packages/server/lib/graphql/entities/TestingType.ts b/packages/server/lib/graphql/entities/TestingType.ts new file mode 100644 index 000000000000..c76081aefa08 --- /dev/null +++ b/packages/server/lib/graphql/entities/TestingType.ts @@ -0,0 +1,20 @@ +import { nxs, NxsResult } from 'nexus-decorators' +import { TestingTypeEnum } from '../constants' + +@nxs.objectType() +export class TestingType { + @nxs.field.type(() => TestingTypeEnum) + get id (): NxsResult<'TestingType', 'id'> { + return 'component' + } + + @nxs.field.string() + get title (): NxsResult<'TestingType', 'title'> { + return 'Some Title' + } + + @nxs.field.string() + get description (): NxsResult<'TestingType', 'description'> { + return 'Some Description' + } +} diff --git a/packages/server/lib/graphql/entities/Wizard.ts b/packages/server/lib/graphql/entities/Wizard.ts index 8238531546eb..9a3c6fdea393 100644 --- a/packages/server/lib/graphql/entities/Wizard.ts +++ b/packages/server/lib/graphql/entities/Wizard.ts @@ -1,6 +1,169 @@ -import { nxs } from 'nexus-decorators' +import { nxs, NxsResult } from 'nexus-decorators' +import { FrontendFrameworkEnum, BUNDLER, FrontendFramework, Bundler, FRONTEND_FRAMEWORK, TestingType, TestingTypeEnum, BundlerDisplayNames, BundlerEnum, WizardStepEnum, WIZARD_STEP, WizardStep } from '../constants/WizardConstants' -@nxs.objectType() +@nxs.objectType({ + description: 'The Wizard is a container for any state associated with initial onboarding to Cypress', +}) export class Wizard { + private currentStep: WizardStep = 'welcome' + private chosenTestingType: TestingType | null + private chosenBundler: Bundler | null + private chosenFramework: FrontendFramework | null + + constructor () { + this.chosenTestingType = null + this.chosenBundler = null + this.chosenFramework = null + } + + get framework (): FrontendFramework | null { + return this.chosenFramework + } + + get bundler (): Bundler | null { + return this.chosenBundler + } + + // GraphQL Fields: + + @nxs.field.type(() => WizardStepEnum) + step (): NxsResult<'Wizard', 'step'> { + return this.currentStep + } + + @nxs.field.type(() => TestingTypeEnum, { + description: 'The testing type we are setting in the wizard, null if this has not been chosen', + }) + testingType (): NxsResult<'Wizard', 'testingType'> { + return this.chosenTestingType + } + + @nxs.field.list.type(() => WizardFrontendFramework, { + description: 'All of the component testing frameworks to choose from', + }) + frameworks (): NxsResult<'Wizard', 'frameworks'> { + return FRONTEND_FRAMEWORK.map((f) => new WizardFrontendFramework(f)) + } + + @nxs.field.list.type(() => WizardBundler, { + description: 'All of the bundlers to choose from', + }) + allBundlers (): NxsResult<'Wizard', 'allBundlers'> { + return BUNDLER.map((bundler) => new WizardBundler(this, bundler)) + } + + // Internal Setters: + + setTestingType (testingType: TestingType | null): Wizard { + this.chosenTestingType = testingType + this.currentStep = 'selectFramework' + + return this + } + + setFramework (framework: FrontendFramework): Wizard { + this.chosenFramework = framework + + return this + } + + setBundler (bundler?: Bundler | null): Wizard { + this.chosenBundler = bundler ?? null + + return this + } + + navigateBack (): Wizard { + const idx = WIZARD_STEP.indexOf(this.currentStep) + + if (idx !== 0) { + this.currentStep = WIZARD_STEP[idx - 1] + } + + return this + } + + navigateForward (): Wizard { + const idx = WIZARD_STEP.indexOf(this.currentStep) + + if (idx !== WIZARD_STEP.length + 1) { + this.currentStep = WIZARD_STEP[idx + 1] + } + + return this + } +} + +@nxs.objectType({ + description: 'A frontend framework that we can setup within the app', +}) +export class WizardFrontendFramework { + constructor (private framework: FrontendFramework) {} + + @nxs.field.type(() => FrontendFrameworkEnum, { + description: 'The name of the framework', + }) + get name (): NxsResult<'WizardFrontendFramework', 'name'> { + return this.framework + } + + @nxs.field.list.type(() => WizardBundler, { + description: 'All of the supported bundlers for this framework', + }) + get supportedBundlers (): NxsResult<'WizardFrontendFramework', 'supportedBundlers'> { + return [] + } + + @nxs.field.list.type(() => WizardNpmPackage, { + description: 'A list of packages to install, null if we have not chosen both a framework and bundler', + }) + get packagesToInstall (): NxsResult<'WizardFrontendFramework', 'packagesToInstall'> { + return [] + } +} + +@nxs.objectType() +export class WizardBundler { + constructor (private wizard: Wizard, private bundler: Bundler) {} + + @nxs.field.type(() => BundlerEnum) + get id (): NxsResult<'WizardBundler', 'id'> { + return this.bundler + } + + @nxs.field.string() + get name (): NxsResult<'WizardBundler', 'name'> { + return BundlerDisplayNames[this.bundler] + } + + @nxs.field.boolean({ + description: 'Whether this is the selected framework bundler', + }) + isSelected (): NxsResult<'WizardBundler', 'isSelected'> { + return this.wizard.bundler === this.bundler + } + + @nxs.field.boolean({ + description: 'Whether there are multiple options to choose from given the framework', + }) + isOnlyOption (): NxsResult<'WizardBundler', 'isOnlyOption'> { + return true + } +} + +@nxs.objectType({ + description: 'Details about an NPM Package listed during the wizard install', +}) +export class WizardNpmPackage { + @nxs.field.string({ + description: 'The package name that you would npm install', + }) + name (): NxsResult<'WizardNpmPackage', 'name'> { + return 'name' + } + @nxs.field.string() + description (): NxsResult<'WizardNpmPackage', 'description'> { + return 'description' + } } diff --git a/packages/server/lib/graphql/entities/index.ts b/packages/server/lib/graphql/entities/index.ts index 59bed57ff763..1eadb0051831 100644 --- a/packages/server/lib/graphql/entities/index.ts +++ b/packages/server/lib/graphql/entities/index.ts @@ -3,3 +3,9 @@ export * from './App' export * from './Projects' export * from './Wizard' + +export * from './Mutation' + +export * from './Query' + +export * from './TestingType' diff --git a/packages/server/lib/graphql/gen/nxs.gen.ts b/packages/server/lib/graphql/gen/nxs.gen.ts index 8395febfe876..73c0a5f91aec 100644 --- a/packages/server/lib/graphql/gen/nxs.gen.ts +++ b/packages/server/lib/graphql/gen/nxs.gen.ts @@ -5,8 +5,10 @@ */ +import type { BaseContext } from "./../context/BaseContext" import type { App } from "./../entities/App" -import type { Wizard } from "./../entities/Wizard" +import type { Wizard, WizardFrontendFramework, WizardBundler, WizardNpmPackage } from "./../entities/Wizard" +import type { TestingType } from "./../entities/TestingType" import type { core } from "nexus" declare global { interface NexusGenCustomInputMethods { @@ -47,7 +49,11 @@ export interface NexusGenInputs { } export interface NexusGenEnums { + FrontendFramework: "cra" | "nextjs" | "nuxtjs" | "reactjs" | "vuecli" | "vuejs" PluginsState: "error" | "initialized" | "initializing" | "uninitialized" + SupportedBundlers: "vite" | "webpack" + TestingTypeEnum: "component" | "e2e" + WizardStep: "createConfig" | "installDependencies" | "selectFramework" | "setupComplete" | "welcome" } export interface NexusGenScalars { @@ -74,7 +80,11 @@ export interface NexusGenObjects { projectRoot: string; // String! } Query: {}; + TestingType: TestingType; Wizard: Wizard; + WizardBundler: WizardBundler; + WizardFrontendFramework: WizardFrontendFramework; + WizardNpmPackage: WizardNpmPackage; } export interface NexusGenInterfaces { @@ -90,6 +100,7 @@ export type NexusGenAllTypes = NexusGenRootTypes & NexusGenScalars & NexusGenEnu export interface NexusGenFieldTypes { App: { // field return type isFirstOpen: boolean; // Boolean! + wizard: NexusGenRootTypes['Wizard'] | null; // Wizard } InitPluginsStatus: { // field return type message: string | null; // String @@ -98,6 +109,12 @@ export interface NexusGenFieldTypes { Mutation: { // field return type addProject: NexusGenRootTypes['Project']; // Project! initializePlugins: NexusGenRootTypes['Project']; // Project! + wizardInstallDependencies: NexusGenRootTypes['Wizard'] | null; // Wizard + wizardNavigateBack: NexusGenRootTypes['Wizard'] | null; // Wizard + wizardSetBundler: NexusGenRootTypes['Wizard'] | null; // Wizard + wizardSetFramework: NexusGenRootTypes['Wizard'] | null; // Wizard + wizardSetTestingType: NexusGenRootTypes['Wizard'] | null; // Wizard + wizardValidateManualInstall: NexusGenRootTypes['Wizard'] | null; // Wizard } Project: { // field return type isCurrent: boolean; // Boolean! @@ -110,14 +127,38 @@ export interface NexusGenFieldTypes { openProject: NexusGenRootTypes['Project'] | null; // Project projects: Array; // [Project]! } + TestingType: { // field return type + description: string | null; // String + id: NexusGenEnums['TestingTypeEnum'] | null; // TestingTypeEnum + title: string | null; // String + } Wizard: { // field return type - todo: boolean | null; // Boolean + allBundlers: Array | null; // [WizardBundler] + frameworks: Array | null; // [WizardFrontendFramework] + step: NexusGenEnums['WizardStep'] | null; // WizardStep + testingType: NexusGenEnums['TestingTypeEnum'] | null; // TestingTypeEnum + } + WizardBundler: { // field return type + id: NexusGenEnums['SupportedBundlers'] | null; // SupportedBundlers + isOnlyOption: boolean | null; // Boolean + isSelected: boolean | null; // Boolean + name: string | null; // String + } + WizardFrontendFramework: { // field return type + name: NexusGenEnums['FrontendFramework'] | null; // FrontendFramework + packagesToInstall: Array | null; // [WizardNpmPackage] + supportedBundlers: Array | null; // [WizardBundler] + } + WizardNpmPackage: { // field return type + description: string | null; // String + name: string | null; // String } } export interface NexusGenFieldTypeNames { App: { // field return type name isFirstOpen: 'Boolean' + wizard: 'Wizard' } InitPluginsStatus: { // field return type name message: 'String' @@ -126,6 +167,12 @@ export interface NexusGenFieldTypeNames { Mutation: { // field return type name addProject: 'Project' initializePlugins: 'Project' + wizardInstallDependencies: 'Wizard' + wizardNavigateBack: 'Wizard' + wizardSetBundler: 'Wizard' + wizardSetFramework: 'Wizard' + wizardSetTestingType: 'Wizard' + wizardValidateManualInstall: 'Wizard' } Project: { // field return type name isCurrent: 'Boolean' @@ -138,8 +185,31 @@ export interface NexusGenFieldTypeNames { openProject: 'Project' projects: 'Project' } + TestingType: { // field return type name + description: 'String' + id: 'TestingTypeEnum' + title: 'String' + } Wizard: { // field return type name - todo: 'Boolean' + allBundlers: 'WizardBundler' + frameworks: 'WizardFrontendFramework' + step: 'WizardStep' + testingType: 'TestingTypeEnum' + } + WizardBundler: { // field return type name + id: 'SupportedBundlers' + isOnlyOption: 'Boolean' + isSelected: 'Boolean' + name: 'String' + } + WizardFrontendFramework: { // field return type name + name: 'FrontendFramework' + packagesToInstall: 'WizardNpmPackage' + supportedBundlers: 'WizardBundler' + } + WizardNpmPackage: { // field return type name + description: 'String' + name: 'String' } } @@ -148,6 +218,18 @@ export interface NexusGenArgTypes { addProject: { // args input: NexusGenInputs['AddProjectInput']; // AddProjectInput! } + wizardNavigateBack: { // args + type: NexusGenEnums['TestingTypeEnum']; // TestingTypeEnum! + } + wizardSetBundler: { // args + name?: NexusGenEnums['SupportedBundlers'] | null; // SupportedBundlers + } + wizardSetFramework: { // args + framework: NexusGenEnums['FrontendFramework']; // FrontendFramework! + } + wizardSetTestingType: { // args + type: NexusGenEnums['TestingTypeEnum']; // TestingTypeEnum! + } } } @@ -182,7 +264,7 @@ export type NexusGenFeaturesConfig = { } export interface NexusGenTypes { - context: any; + context: BaseContext; inputTypes: NexusGenInputs; rootTypes: NexusGenRootTypes; inputTypeShapes: NexusGenInputs & NexusGenEnums & NexusGenScalars; diff --git a/packages/server/lib/graphql/schema.ts b/packages/server/lib/graphql/schema.ts index 6ade54681492..d3a35ee4dccd 100644 --- a/packages/server/lib/graphql/schema.ts +++ b/packages/server/lib/graphql/schema.ts @@ -2,18 +2,31 @@ import { makeSchema, asNexusMethod } from 'nexus' import path from 'path' import { JSONResolver, DateTimeResolver } from 'graphql-scalars' import * as entities from './entities' +import * as constants from './constants' const customScalars = [ asNexusMethod(JSONResolver, 'json'), asNexusMethod(DateTimeResolver, 'dateTime'), ] +// for vite +const dirname = typeof __dirname !== 'undefined' ? __dirname : '' + +// for vite +process.cwd ??= () => '' + export const graphqlSchema = makeSchema({ - types: [entities, customScalars], + types: [entities, constants, customScalars], shouldGenerateArtifacts: true, - outputs: { - typegen: path.join(__dirname, 'gen/nxs.gen.ts'), - schema: path.join(__dirname, '..', '..', 'schema.graphql'), + shouldExitAfterGenerateArtifacts: Boolean(process.env.GRAPHQL_CODEGEN), + // for vite + outputs: typeof __dirname !== 'undefined' ? { + typegen: path.join(dirname, 'gen/nxs.gen.ts'), + schema: path.join(dirname, '..', '..', 'schema.graphql'), + } : false, + contextType: { + module: path.join(dirname, './context/BaseContext.ts'), + export: 'BaseContext', }, formatTypegen (content, type) { if (type === 'schema') { diff --git a/packages/server/lib/graphql/server.ts b/packages/server/lib/graphql/server.ts index 80447c73106f..9228571f0007 100644 --- a/packages/server/lib/graphql/server.ts +++ b/packages/server/lib/graphql/server.ts @@ -5,14 +5,14 @@ import { Server } from 'http' import type { AddressInfo } from 'net' import { graphqlSchema } from './schema' -import { ExecContext } from './ExecContext' +import { ServerContext } from './context/ServerContext' const debug = Debug('cypress:server:graphql') let app: ReturnType let server: Server -export function closeGraphQLServer () { +export function closeGraphQLServer (): Promise { if (!server || !server.listening) { return Promise.resolve(null) } @@ -28,14 +28,14 @@ export function closeGraphQLServer () { }) } -export function startGraphQLServer () { +export function startGraphQLServer (): {server: Server, app: Express.Application} { app = express() app.use('/graphql', graphqlHTTP(() => { return { schema: graphqlSchema, graphiql: true, - context: new ExecContext({}), + context: new ServerContext(), } })) diff --git a/packages/server/lib/graphql/utils/index.ts b/packages/server/lib/graphql/utils/index.ts index b990a21b856e..356d8eebb795 100644 --- a/packages/server/lib/graphql/utils/index.ts +++ b/packages/server/lib/graphql/utils/index.ts @@ -1,5 +1,5 @@ -import { projects } from '../../projects' -import { ProjectBase } from '../../project-base' +// import { projects } from '../../projects' +import type { ProjectBase } from '../../project-base' import { NexusGenFieldTypes } from '../gen/nxs.gen' export function formatProject (project: ProjectBase): NexusGenFieldTypes['Project'] { diff --git a/packages/server/lib/gui/events.js b/packages/server/lib/gui/events.js index 93e89fa483be..3af6d0528c79 100644 --- a/packages/server/lib/gui/events.js +++ b/packages/server/lib/gui/events.js @@ -31,7 +31,7 @@ const api = require('../api') const savedState = require('../saved_state') const { graphqlSchema } = require('../graphql/schema') const { startGraphQLServer } = require('../graphql/server') -const { ExecContext } = require('../graphql/ExecContext') +const { ServerContext } = require('../graphql/context/ServerContext') const nullifyUnserializableValues = (obj) => { // nullify values that cannot be cloned @@ -511,7 +511,7 @@ module.exports = { document: parse(params.text), operationName: params.name, variableValues: variables, - contextValue: new ExecContext(options), + contextValue: new ServerContext(options), }) evt.sender.send('graphql:response', { diff --git a/packages/server/schema.graphql b/packages/server/schema.graphql index ebd6fb219ee6..c317c0396af8 100644 --- a/packages/server/schema.graphql +++ b/packages/server/schema.graphql @@ -12,6 +12,9 @@ input AddProjectInput { type App { """Whether this is the first open of the application or not""" isFirstOpen: Boolean! + + """Metadata about the wizard""" + wizard: Wizard } """ @@ -19,6 +22,15 @@ A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `dat """ scalar DateTime +enum FrontendFramework { + cra + nextjs + nuxtjs + reactjs + vuecli + vuejs +} + type InitPluginsStatus { message: String state: PluginsState! @@ -32,6 +44,24 @@ scalar JSON @specifiedBy(url: "http://www.ecma-international.org/publications/fi type Mutation { addProject(input: AddProjectInput!): Project! initializePlugins: Project! + + """Installs the dependencies for the component testing step""" + wizardInstallDependencies: Wizard + + """Navigates backward in the wizard""" + wizardNavigateBack(type: TestingTypeEnum!): Wizard + + """Sets the frontend bundler we want to use for the project""" + wizardSetBundler(name: SupportedBundlers): Wizard + + """Sets the frontend framework we want to use for the project""" + wizardSetFramework(framework: FrontendFramework!): Wizard + + """Sets the current testing type we want to use""" + wizardSetTestingType(type: TestingTypeEnum!): Wizard + + """Validates that the manual install has occurred properly""" + wizardValidateManualInstall: Wizard } enum PluginsState { @@ -54,7 +84,77 @@ type Query { projects: [Project]! } +"""The bundlers that we can use with Cypress""" +enum SupportedBundlers { + vite + webpack +} + +type TestingType { + description: String + id: TestingTypeEnum + title: String +} + +enum TestingTypeEnum { + component + e2e +} + +""" +The Wizard is a container for any state associated with initial onboarding to Cypress +""" type Wizard { - """Auto-generated by nexus-decorators as the objectType is missing fields""" - todo: Boolean + """All of the bundlers to choose from""" + allBundlers: [WizardBundler] + + """All of the component testing frameworks to choose from""" + frameworks: [WizardFrontendFramework] + step: WizardStep + + """ + The testing type we are setting in the wizard, null if this has not been chosen + """ + testingType: TestingTypeEnum +} + +type WizardBundler { + id: SupportedBundlers + + """Whether there are multiple options to choose from given the framework""" + isOnlyOption: Boolean + + """Whether this is the selected framework bundler""" + isSelected: Boolean + name: String +} + +"""A frontend framework that we can setup within the app""" +type WizardFrontendFramework { + """The name of the framework""" + name: FrontendFramework + + """ + A list of packages to install, null if we have not chosen both a framework and bundler + """ + packagesToInstall: [WizardNpmPackage] + + """All of the supported bundlers for this framework""" + supportedBundlers: [WizardBundler] +} + +"""Details about an NPM Package listed during the wizard install""" +type WizardNpmPackage { + description: String + + """The package name that you would npm install""" + name: String +} + +enum WizardStep { + createConfig + installDependencies + selectFramework + setupComplete + welcome } diff --git a/scripts/watch.js b/scripts/watch.js index afd7fba59ae3..e2cae16bb994 100644 --- a/scripts/watch.js +++ b/scripts/watch.js @@ -5,7 +5,7 @@ const pDefer = require('p-defer') const watcher = chokidar.watch('packages/server/lib/graphql/**/*.{js,ts}', { cwd: path.join(__dirname, '..'), - ignored: '*.gen.ts', + ignored: '**/nxs.gen.ts', ignoreInitial: true, }) diff --git a/yarn.lock b/yarn.lock index 749b1aac86a7..ff0417af6378 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9164,13 +9164,13 @@ resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-1.2.4.tgz#a7aa6e6a31c556a8b781de730316deeecf7f56f2" integrity sha512-D/3H9plevPQGgQGwmV6eecvOnooLTecPR63HPffVVWPEhbfvmtYLWgznzs456NBb2DItiRTCIa1yWxvGqC+I8A== -"@vue/apollo-composable@4.0.0-alpha.12": - version "4.0.0-alpha.12" - resolved "https://registry.yarnpkg.com/@vue/apollo-composable/-/apollo-composable-4.0.0-alpha.12.tgz#5aab61bf9703a039e95ef60ecb2293e1da80c87f" - integrity sha512-BhCHhDgcDPkKOE3ILb7xIGI98dAsX2Ao3TvOjcMlr89IOK3TuD5B9xgPTsFdctTbupWIVdR/n0FDVXjha7g5iA== +"@vue/apollo-composable@^4.0.0-alpha.14": + version "4.0.0-alpha.14" + resolved "https://registry.yarnpkg.com/@vue/apollo-composable/-/apollo-composable-4.0.0-alpha.14.tgz#6e3358fa9b815b38384d3da59b5c2b77b2b229d3" + integrity sha512-bIOWRGk1j+b2ebsLfwQkzqvJd1tCpHZiBF2Qe88RTLUxtypdkdSW6YpLxwJgNoRv5W55sM9WI+nP2ziv2nqBZg== dependencies: throttle-debounce "^2.3.0" - vue-demi "^0.4.0" + vue-demi "^0.9.1" "@vue/babel-helper-vue-jsx-merge-props@^1.2.1": version "1.2.1" @@ -15033,6 +15033,21 @@ concat-with-sourcemaps@^1.1.0: dependencies: source-map "^0.6.1" +concurrently@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-6.2.0.tgz#587e2cb8afca7234172d8ea55176088632c4c56d" + integrity sha512-v9I4Y3wFoXCSY2L73yYgwA9ESrQMpRn80jMcqMgHx720Hecz2GZAvTI6bREVST6lkddNypDKRN22qhK0X8Y00g== + dependencies: + chalk "^4.1.0" + date-fns "^2.16.1" + lodash "^4.17.21" + read-pkg "^5.2.0" + rxjs "^6.6.3" + spawn-command "^0.0.2-1" + supports-color "^8.1.0" + tree-kill "^1.2.2" + yargs "^16.2.0" + condense-newlines@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/condense-newlines/-/condense-newlines-0.2.1.tgz#3de985553139475d32502c83b02f60684d24c55f" @@ -16511,6 +16526,11 @@ date-fns@^1.27.2: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== +date-fns@^2.16.1: + version "2.23.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.23.0.tgz#4e886c941659af0cf7b30fafdd1eaa37e88788a9" + integrity sha512-5ycpauovVyAk0kXNZz6ZoB9AYMZB4DObse7P3BPWmyEjXNORTI8EJ6X0uaSAq4sCHzM1uajzrkr6HnsLQpxGXA== + dateformat@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" @@ -35020,7 +35040,7 @@ rxjs@6.6.3: dependencies: tslib "^1.9.0" -rxjs@6.6.7, rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.3, rxjs@^6.5.4, rxjs@^6.6.0, rxjs@^6.6.6, rxjs@~6.6.0: +rxjs@6.6.7, rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.5.3, rxjs@^6.5.4, rxjs@^6.6.0, rxjs@^6.6.3, rxjs@^6.6.6, rxjs@~6.6.0: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== @@ -36616,6 +36636,11 @@ sparkles@^1.0.0: resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.1.tgz#008db65edce6c50eec0c5e228e1945061dd0437c" integrity sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw== +spawn-command@^0.0.2-1: + version "0.0.2-1" + resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" + integrity sha1-YvXpRmmBwbeW3Fkpk34RycaSG9A= + spawn-error-forwarder@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz#1afd94738e999b0346d7b9fc373be55e07577029" @@ -37588,7 +37613,7 @@ supports-color@6.1.0, supports-color@^6.1.0: dependencies: has-flag "^3.0.0" -supports-color@8.1.1, supports-color@^8.0.0, supports-color@^8.1.1: +supports-color@8.1.1, supports-color@^8.0.0, supports-color@^8.1.0, supports-color@^8.1.1: version "8.1.1" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== @@ -38526,7 +38551,7 @@ traverse@~0.6.6: resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" integrity sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc= -tree-kill@1.2.2: +tree-kill@1.2.2, tree-kill@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== @@ -40317,10 +40342,10 @@ void-elements@^2.0.1: resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w= -vue-demi@^0.4.0: - version "0.4.5" - resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.4.5.tgz#ea422a4468cb6321a746826a368a770607f87791" - integrity sha512-51xf1B6hV2PfjnzYHO/yUForFCRQ49KS8ngQb5T6l1HDEmfghTFtsxtRa5tbx4eqQsH76ll/0gIxuf1gei0ubw== +vue-demi@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.9.1.tgz#25d6e1ebd4d4010757ff3571e2bf6a1d7bf3de82" + integrity sha512-7s1lufRD2l369eFWPjgLvhqCRk0XzGWJsQc7K4q+0mZtixyGIvsK1Cg88P4NcaRIEiBuuN4q1NN4SZKFKwQswA== vue-eslint-parser@^7.0.0: version "7.6.0" From b5e1cfaa18a85cf9cbe2d0af025530fccf1c75c7 Mon Sep 17 00:00:00 2001 From: Lachlan Miller Date: Wed, 28 Jul 2021 14:31:32 +1000 Subject: [PATCH 02/29] chore: fix dev mode --- packages/launchpad/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/launchpad/package.json b/packages/launchpad/package.json index 2eb6a6b1531a..4db3af2dde06 100644 --- a/packages/launchpad/package.json +++ b/packages/launchpad/package.json @@ -8,9 +8,10 @@ "build-prod": "cross-env NODE_ENV=production yarn build", "clean-deps": "rm -rf node_modules", "test": "yarn cypress:run:ct", + "cypress:launch": "cross-env TZ=America/New_York node ../../scripts/cypress open --project ${PWD}", "cypress:open": "cross-env TZ=America/New_York node ../../scripts/cypress open-ct --project ${PWD}", "cypress:run": "cross-env TZ=America/New_York node ../../scripts/cypress run-ct --project ${PWD}", - "dev": "NODE_ENV=development LAUNCHPAD=1 yarn start-test 5050 cypress:open", + "dev": "NODE_ENV=development LAUNCHPAD=1 yarn start-test 5050 cypress:launch", "postinstall": "yarn codegen && echo '@packages/launchpad needs: yarn build'", "start": "http-server -p 5050 dist", "watch": "concurrently \"vite build --watch\" \"yarn codegen --watch\"", From 539d0ab5d7dd14386afb3011613c5d661a0497e2 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Wed, 28 Jul 2021 10:54:00 -0400 Subject: [PATCH 03/29] Continuing to restructure graphql patterns --- packages/graphql/tsconfig.json | 10 ++++ .../cypress/support/testApolloClient.ts | 2 +- packages/launchpad/graphql-codegen.yml | 7 +-- packages/launchpad/package.json | 1 + packages/launchpad/src/utils/frameworks.ts | 10 ++++ packages/launchpad/tsconfig.json | 2 +- packages/launchpad/vite.config.ts | 4 -- .../server/lib/graphql/actions/BaseActions.ts | 22 +++++++ .../lib/graphql/actions/ClientTestActions.ts | 6 +- .../lib/graphql/constants/ProjectConstants.ts | 10 ++++ .../server/lib/graphql/context/BaseContext.ts | 7 ++- .../graphql/contracts/ProjectBaseContract.ts | 3 + packages/server/lib/graphql/entities/App.ts | 18 ++++++ .../server/lib/graphql/entities/Mutation.ts | 44 +++++++++++++- .../server/lib/graphql/entities/Project.ts | 58 +++++++++++++++++++ .../server/lib/graphql/entities/Projects.ts | 57 ------------------ packages/server/lib/graphql/entities/Query.ts | 2 +- .../server/lib/graphql/entities/Wizard.ts | 18 +++++- packages/server/lib/graphql/entities/index.ts | 8 +-- packages/server/lib/graphql/entities/types.ts | 28 --------- packages/server/lib/graphql/gen/nxs.gen.ts | 35 +++++------ packages/server/lib/graphql/server.ts | 4 +- .../lib/graphql/testing/ProjectBaseTest.ts | 1 + packages/server/lib/graphql/utils/index.ts | 12 ---- packages/server/schema.graphql | 27 +++++++-- 25 files changed, 253 insertions(+), 143 deletions(-) create mode 100644 packages/graphql/tsconfig.json create mode 100644 packages/server/lib/graphql/constants/ProjectConstants.ts create mode 100644 packages/server/lib/graphql/contracts/ProjectBaseContract.ts create mode 100644 packages/server/lib/graphql/entities/Project.ts delete mode 100644 packages/server/lib/graphql/entities/Projects.ts delete mode 100644 packages/server/lib/graphql/entities/types.ts create mode 100644 packages/server/lib/graphql/testing/ProjectBaseTest.ts delete mode 100644 packages/server/lib/graphql/utils/index.ts diff --git a/packages/graphql/tsconfig.json b/packages/graphql/tsconfig.json new file mode 100644 index 000000000000..77c2ba5464b8 --- /dev/null +++ b/packages/graphql/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../ts/tsconfig.json", + "compilerOptions": { + "strict": true, + "noImplicitAny": true, + "allowJs": false, + "noUncheckedIndexedAccess": true, + "importsNotUsedAsValues": "error" + } +} \ No newline at end of file diff --git a/packages/launchpad/cypress/support/testApolloClient.ts b/packages/launchpad/cypress/support/testApolloClient.ts index 429b19dc4fc5..4508ad9a59e3 100644 --- a/packages/launchpad/cypress/support/testApolloClient.ts +++ b/packages/launchpad/cypress/support/testApolloClient.ts @@ -9,7 +9,7 @@ export function testApolloClient (ctx: ClientTestContext) { graphql({ source: print(op.query), schema: graphqlSchema, - contextValue: new ClientTestContext(), + contextValue: ctx, }).then((result) => { obs.next(result as FetchResult) obs.complete() diff --git a/packages/launchpad/graphql-codegen.yml b/packages/launchpad/graphql-codegen.yml index e1cf9bbe2773..2c10caf0faf6 100644 --- a/packages/launchpad/graphql-codegen.yml +++ b/packages/launchpad/graphql-codegen.yml @@ -11,9 +11,8 @@ generates: avoidOptionals: true enumsAsTypes: true plugins: + - add: + content: '/* eslint-disable */' - 'typescript' - 'typescript-operations' - - 'typed-document-node' -hooks: - afterOneFileWrite: - - eslint --fix \ No newline at end of file + - 'typed-document-node' \ No newline at end of file diff --git a/packages/launchpad/package.json b/packages/launchpad/package.json index 4db3af2dde06..fa3b1e3b0579 100644 --- a/packages/launchpad/package.json +++ b/packages/launchpad/package.json @@ -25,6 +25,7 @@ "devDependencies": { "@apollo/client": "^3.3.21", "@cypress/vue": "0.0.0-development", + "@graphql-codegen/add": "^2.0.2", "@graphql-codegen/cli": "^1.21.6", "@graphql-codegen/typed-document-node": "^1.18.9", "@graphql-codegen/typescript": "^1.22.4", diff --git a/packages/launchpad/src/utils/frameworks.ts b/packages/launchpad/src/utils/frameworks.ts index 7808545c3ac5..9db14b66b2e2 100644 --- a/packages/launchpad/src/utils/frameworks.ts +++ b/packages/launchpad/src/utils/frameworks.ts @@ -9,6 +9,16 @@ import ConfigNextTs from '../samples/next/ts.ts?raw' import ConfigNextJs from '../samples/next/js.js?raw' import ConfigNuxtTs from '../samples/nuxt/ts.ts?raw' import ConfigNuxtJs from '../samples/nuxt/js.js?raw' +import { FrontendFramework } from '@packages/server/lib/graphql/constants' + +export const FrameworkLogo: Record = { + nextjs: LogoNext, + nuxtjs: LogoNuxt, + vuejs: LogoVue, + vuecli: LogoVue, + reactjs: LogoReact, + cra: LogoReact, +} export type Framework = { id: string diff --git a/packages/launchpad/tsconfig.json b/packages/launchpad/tsconfig.json index a4d431dc8360..9dbd538387be 100644 --- a/packages/launchpad/tsconfig.json +++ b/packages/launchpad/tsconfig.json @@ -51,6 +51,6 @@ "allowSyntheticDefaultImports": true, "esModuleInterop": true, "noErrorTruncation": true, - "experimentalDecorators": true + "experimentalDecorators": true, }, } diff --git a/packages/launchpad/vite.config.ts b/packages/launchpad/vite.config.ts index 88df043bf6a4..dfa8a27c655d 100644 --- a/packages/launchpad/vite.config.ts +++ b/packages/launchpad/vite.config.ts @@ -10,10 +10,6 @@ export default defineConfig({ build: { minify: false, }, - optimizeDeps: { - include: ['@apollo/client/core'], - exclude: ['@apollo/client'], - }, plugins: [ vue(), vueJsx(), diff --git a/packages/server/lib/graphql/actions/BaseActions.ts b/packages/server/lib/graphql/actions/BaseActions.ts index 6b5e281d3f84..f636201e3bae 100644 --- a/packages/server/lib/graphql/actions/BaseActions.ts +++ b/packages/server/lib/graphql/actions/BaseActions.ts @@ -1,4 +1,6 @@ +import { NxsMutationArgs } from 'nexus-decorators' import { BaseContext } from '../context/BaseContext' +import { Project } from '../entities/Project' /** * Acts as the contract for all actions, implemented by ServerActions @@ -8,4 +10,24 @@ export abstract class BaseActions { constructor (protected ctx: BaseContext) {} abstract installDependencies (): void + + abstract initializePlugins(): Promise + + /** + * Adds a new project + */ + addProject (input: NxsMutationArgs<'addProject'>['input']): Project { + const existing = this.ctx.projects.find((p) => p.projectRoot === input.projectRoot) + + // If we don't already have a projec + if (existing) { + return existing + } + + const newProject = new Project({ projectRoot: input.projectRoot }) + + this.ctx.projects.push(newProject) + + return newProject + } } diff --git a/packages/server/lib/graphql/actions/ClientTestActions.ts b/packages/server/lib/graphql/actions/ClientTestActions.ts index dbae303e8b02..dfc33031476d 100644 --- a/packages/server/lib/graphql/actions/ClientTestActions.ts +++ b/packages/server/lib/graphql/actions/ClientTestActions.ts @@ -6,7 +6,11 @@ export class ClientTestActions extends BaseActions { super(ctx) } - installDependencies (): void { + installDependencies () { + return + } + + initializePlugins () { return } } diff --git a/packages/server/lib/graphql/constants/ProjectConstants.ts b/packages/server/lib/graphql/constants/ProjectConstants.ts new file mode 100644 index 000000000000..3ef2d33c3ba6 --- /dev/null +++ b/packages/server/lib/graphql/constants/ProjectConstants.ts @@ -0,0 +1,10 @@ +import { enumType } from 'nexus' + +export const PLUGINS_STATE = ['uninitialized', 'initializing', 'initialized', 'error'] as const + +export type PluginsState = typeof PLUGINS_STATE[number] + +export const PluginsStateEnum = enumType({ + name: 'PluginsState', + members: PLUGINS_STATE, +}) diff --git a/packages/server/lib/graphql/context/BaseContext.ts b/packages/server/lib/graphql/context/BaseContext.ts index aabc2429c1b7..b99805194fa5 100644 --- a/packages/server/lib/graphql/context/BaseContext.ts +++ b/packages/server/lib/graphql/context/BaseContext.ts @@ -1,5 +1,6 @@ -import { BaseActions } from '../actions/BaseActions' -import { Wizard } from '../entities' +import type { BaseActions } from '../actions/BaseActions' +import { App, Wizard } from '../entities' +import { Project } from '../entities/Project' /** * The "Base Context" is the class type that we will use to encapsulate the server state. @@ -10,8 +11,10 @@ import { Wizard } from '../entities' */ export abstract class BaseContext { abstract readonly actions: BaseActions + abstract projects: Project[] wizard = new Wizard() + app = new App(this) isFirstOpen = false } diff --git a/packages/server/lib/graphql/contracts/ProjectBaseContract.ts b/packages/server/lib/graphql/contracts/ProjectBaseContract.ts new file mode 100644 index 000000000000..5ee5bf1eec7b --- /dev/null +++ b/packages/server/lib/graphql/contracts/ProjectBaseContract.ts @@ -0,0 +1,3 @@ +export interface ProjectBaseContract { + isOpen: boolean +} diff --git a/packages/server/lib/graphql/entities/App.ts b/packages/server/lib/graphql/entities/App.ts index 38b41d1b6a85..5d83e9b6f8f7 100644 --- a/packages/server/lib/graphql/entities/App.ts +++ b/packages/server/lib/graphql/entities/App.ts @@ -1,11 +1,15 @@ import { nxs, NxsResult } from 'nexus-decorators' +import { BaseContext } from '../context/BaseContext' import { NexusGenTypes } from '../gen/nxs.gen' +import { Project } from './Project' import { Wizard } from './Wizard' @nxs.objectType({ description: 'Namespace for information related to the app', }) export class App { + constructor (private ctx: BaseContext) {} + @nxs.field.nonNull.boolean({ description: 'Whether this is the first open of the application or not', }) @@ -19,4 +23,18 @@ export class App { wizard (args, ctx: NexusGenTypes['context']): NxsResult<'App', 'wizard'> { return ctx.wizard } + + @nxs.field.nonNull.list.nonNull.type(() => Project, { + description: 'All known projects for the app', + }) + get projects (): NxsResult<'App', 'projects'> { + return this.ctx.projects + } + + @nxs.field.type(() => Project, { + description: 'The active project in the app', + }) + get activeProject (): NxsResult<'App', 'activeProject'> { + return this.projects.find((p) => p.isOpen) ?? null + } } diff --git a/packages/server/lib/graphql/entities/Mutation.ts b/packages/server/lib/graphql/entities/Mutation.ts index 17e65e9680a9..43eca9acac55 100644 --- a/packages/server/lib/graphql/entities/Mutation.ts +++ b/packages/server/lib/graphql/entities/Mutation.ts @@ -1,4 +1,4 @@ -import { mutationType, nonNull } from 'nexus' +import { inputObjectType, mutationType, nonNull } from 'nexus' import { BundlerEnum, FrontendFrameworkEnum, TestingTypeEnum } from '../constants' export const mutation = mutationType({ @@ -19,10 +19,15 @@ export const mutation = mutationType({ resolve: (_, args, ctx) => ctx.wizard.setFramework(args.framework), }) + t.field('wizardNavigateForward', { + type: 'Wizard', + description: 'Navigates forward in the wizard', + resolve: (_, __, ctx) => ctx.wizard.navigateForward(), + }) + t.field('wizardNavigateBack', { type: 'Wizard', description: 'Navigates backward in the wizard', - args: { type: nonNull(TestingTypeEnum) }, resolve: (_, __, ctx) => ctx.wizard.navigateBack(), }) @@ -45,7 +50,40 @@ export const mutation = mutationType({ type: 'Wizard', description: 'Validates that the manual install has occurred properly', resolve: (root, args, ctx) => { - return ctx.wizard + return ctx.wizard.validateManualInstall() + }, + }) + + t.nonNull.field('addProject', { + type: 'Project', + description: 'Adds a new project to the app', + args: { + input: nonNull( + inputObjectType({ + name: 'AddProjectInput', + definition (t) { + t.nonNull.string('projectRoot') + t.nonNull.string('testingType') + t.nonNull.boolean('isCurrent') + }, + }), + ), + }, + async resolve (_root, args, ctx) { + const addedProject = await ctx.actions.addProject(args.input) + + return addedProject + }, + }) + + t.field('initializePlugins', { + type: 'Project', + description: 'Initializes the plugins for the current active project', + async resolve (_root, args, ctx) { + // TODO: should we await here, or return a pending state to the client? + await ctx.actions.initializePlugins() + + return ctx.app.activeProject }, }) }, diff --git a/packages/server/lib/graphql/entities/Project.ts b/packages/server/lib/graphql/entities/Project.ts new file mode 100644 index 000000000000..c8af98485796 --- /dev/null +++ b/packages/server/lib/graphql/entities/Project.ts @@ -0,0 +1,58 @@ +// import { createHash } from 'crypto' +import { objectType } from 'nexus' +import { nxs, NxsResult } from 'nexus-decorators' +import { PluginsStateEnum } from '../constants/ProjectConstants' +import { ProjectBaseContract } from '../contracts/ProjectBaseContract' + +export interface ProjectConfig { + projectRoot: string + projectBase: ProjectBaseContract +} + +@nxs.objectType({ + description: 'A Cypress project is a container', +}) +export class Project { + constructor (private config: ProjectConfig) {} + + @nxs.field.nonNull.id() + id (): NxsResult<'Project', 'id'> { + return '1' + // return createHash('sha1').update(this.projectRoot).digest('hex') + } + + @nxs.field.nonNull.string() + get projectRoot (): NxsResult<'Project', 'projectRoot'> { + return this.config.projectRoot + } + + @nxs.field.nonNull.boolean() + get isOpen (): NxsResult<'Project', 'isOpen'> { + return this.config.projectBase.isOpen + } + + @nxs.field.nonNull.boolean() + isCurrent (): NxsResult<'Project', 'isCurrent'> { + return false + } + + @nxs.field.nonNull.list.nonNull.boolean() + plugins (): NxsResult<'Project', 'plugins'> { + return [] + } + + @nxs.field.nonNull.type(() => InitPluginsStatus) + pluginStatus (): NxsResult<'Project', 'pluginStatus'> { + return this + } +} + +export const InitPluginsStatus = objectType({ + name: 'InitPluginsStatus', + definition (t) { + t.nonNull.field('state', { + type: PluginsStateEnum, + }), + t.string('message') + }, +}) diff --git a/packages/server/lib/graphql/entities/Projects.ts b/packages/server/lib/graphql/entities/Projects.ts deleted file mode 100644 index 05e830451ff3..000000000000 --- a/packages/server/lib/graphql/entities/Projects.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { inputObjectType, nonNull, mutationField, queryField } from 'nexus' -// import { projects } from '../../projects' -import { Project } from './types' -import { formatProject } from '../utils' - -const AddProjectInput = inputObjectType({ - name: 'AddProjectInput', - definition (t) { - t.nonNull.string('projectRoot') - t.nonNull.string('testingType') - t.nonNull.boolean('isCurrent') - }, -}) - -export const allProjects = queryField((t) => { - t.nonNull.list.field('projects', { - type: Project, - resolve (_root, args, ctx) { - return Object.values(projects.projects).map(formatProject) - }, - }) -}) - -export const openProject = queryField((t) => { - t.field('openProject', { - type: Project, - resolve (_root, args, ctx) { - return projects.openProject ? formatProject(projects.openProject) : null - }, - }) -}) - -export const InitializePlugins = mutationField((t) => { - t.nonNull.field('initializePlugins', { - type: Project, - async resolve (_root, args, ctx) { - // TODO: should we await here, or return a pending state to the client? - await projects.initializePlugins() - - return formatProject(projects.openProject!) - }, - }) -}) - -export const addProject = mutationField((t) => { - t.nonNull.field('addProject', { - type: Project, - args: { - input: nonNull(AddProjectInput), - }, - async resolve (_root, args, ctx) { - const addedProject = await projects.addProject(args.input) - - return formatProject(addedProject) - }, - }) -}) diff --git a/packages/server/lib/graphql/entities/Query.ts b/packages/server/lib/graphql/entities/Query.ts index bac3469b2211..5c5abc7ae1a1 100644 --- a/packages/server/lib/graphql/entities/Query.ts +++ b/packages/server/lib/graphql/entities/Query.ts @@ -6,6 +6,6 @@ export class Query { return { type: App } }) static app (_, ctx: NexusGen['context']): NxsQueryResult<'app'> { - return new App() + return ctx.app } } diff --git a/packages/server/lib/graphql/entities/Wizard.ts b/packages/server/lib/graphql/entities/Wizard.ts index 9a3c6fdea393..7e3253db159a 100644 --- a/packages/server/lib/graphql/entities/Wizard.ts +++ b/packages/server/lib/graphql/entities/Wizard.ts @@ -86,12 +86,17 @@ export class Wizard { navigateForward (): Wizard { const idx = WIZARD_STEP.indexOf(this.currentStep) - if (idx !== WIZARD_STEP.length + 1) { + if (idx !== WIZARD_STEP.length - 1) { this.currentStep = WIZARD_STEP[idx + 1] } return this } + + validateManualInstall (): Wizard { + // + return this + } } @nxs.objectType({ @@ -120,9 +125,18 @@ export class WizardFrontendFramework { get packagesToInstall (): NxsResult<'WizardFrontendFramework', 'packagesToInstall'> { return [] } + + @nxs.field.boolean({ + description: 'Whether this is the selected framework in the wizard', + }) + get isSelected (): NxsResult<'WizardFrontendFramework', 'isSelected'> { + return true + } } -@nxs.objectType() +@nxs.objectType({ + description: 'Wizard bundler', +}) export class WizardBundler { constructor (private wizard: Wizard, private bundler: Bundler) {} diff --git a/packages/server/lib/graphql/entities/index.ts b/packages/server/lib/graphql/entities/index.ts index 1eadb0051831..5eea3156787f 100644 --- a/packages/server/lib/graphql/entities/index.ts +++ b/packages/server/lib/graphql/entities/index.ts @@ -1,11 +1,11 @@ export * from './App' -export * from './Projects' - -export * from './Wizard' - export * from './Mutation' +export * from './Project' + export * from './Query' export * from './TestingType' + +export * from './Wizard' diff --git a/packages/server/lib/graphql/entities/types.ts b/packages/server/lib/graphql/entities/types.ts deleted file mode 100644 index 2b4dea1d83d7..000000000000 --- a/packages/server/lib/graphql/entities/types.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { objectType, enumType } from 'nexus' - -const PluginsState = enumType({ - name: 'PluginsState', - members: ['uninitialized', 'initializing', 'initialized', 'error'], -}) - -const InitPluginsStatus = objectType({ - name: 'InitPluginsStatus', - definition (t) { - t.nonNull.field('state', { - type: PluginsState, - }), - t.string('message') - }, -}) - -export const Project = objectType({ - name: 'Project', - definition (t) { - t.nonNull.string('projectRoot') - t.nonNull.boolean('isOpen') - t.nonNull.boolean('isCurrent') - t.field('plugins', { - type: InitPluginsStatus, - }) - }, -}) diff --git a/packages/server/lib/graphql/gen/nxs.gen.ts b/packages/server/lib/graphql/gen/nxs.gen.ts index 73c0a5f91aec..8d58f39cf888 100644 --- a/packages/server/lib/graphql/gen/nxs.gen.ts +++ b/packages/server/lib/graphql/gen/nxs.gen.ts @@ -7,8 +7,9 @@ import type { BaseContext } from "./../context/BaseContext" import type { App } from "./../entities/App" -import type { Wizard, WizardFrontendFramework, WizardBundler, WizardNpmPackage } from "./../entities/Wizard" +import type { Project } from "./../entities/Project" import type { TestingType } from "./../entities/TestingType" +import type { Wizard, WizardFrontendFramework, WizardBundler, WizardNpmPackage } from "./../entities/Wizard" import type { core } from "nexus" declare global { interface NexusGenCustomInputMethods { @@ -73,12 +74,7 @@ export interface NexusGenObjects { state: NexusGenEnums['PluginsState']; // PluginsState! } Mutation: {}; - Project: { // root type - isCurrent: boolean; // Boolean! - isOpen: boolean; // Boolean! - plugins?: NexusGenRootTypes['InitPluginsStatus'] | null; // InitPluginsStatus - projectRoot: string; // String! - } + Project: Project; Query: {}; TestingType: TestingType; Wizard: Wizard; @@ -99,7 +95,9 @@ export type NexusGenAllTypes = NexusGenRootTypes & NexusGenScalars & NexusGenEnu export interface NexusGenFieldTypes { App: { // field return type + activeProject: NexusGenRootTypes['Project'] | null; // Project isFirstOpen: boolean; // Boolean! + projects: NexusGenRootTypes['Project'][]; // [Project!]! wizard: NexusGenRootTypes['Wizard'] | null; // Wizard } InitPluginsStatus: { // field return type @@ -108,24 +106,25 @@ export interface NexusGenFieldTypes { } Mutation: { // field return type addProject: NexusGenRootTypes['Project']; // Project! - initializePlugins: NexusGenRootTypes['Project']; // Project! + initializePlugins: NexusGenRootTypes['Project'] | null; // Project wizardInstallDependencies: NexusGenRootTypes['Wizard'] | null; // Wizard wizardNavigateBack: NexusGenRootTypes['Wizard'] | null; // Wizard + wizardNavigateForward: NexusGenRootTypes['Wizard'] | null; // Wizard wizardSetBundler: NexusGenRootTypes['Wizard'] | null; // Wizard wizardSetFramework: NexusGenRootTypes['Wizard'] | null; // Wizard wizardSetTestingType: NexusGenRootTypes['Wizard'] | null; // Wizard wizardValidateManualInstall: NexusGenRootTypes['Wizard'] | null; // Wizard } Project: { // field return type + id: string; // ID! isCurrent: boolean; // Boolean! isOpen: boolean; // Boolean! - plugins: NexusGenRootTypes['InitPluginsStatus'] | null; // InitPluginsStatus + pluginStatus: NexusGenRootTypes['InitPluginsStatus']; // InitPluginsStatus! + plugins: boolean[]; // [Boolean!]! projectRoot: string; // String! } Query: { // field return type app: NexusGenRootTypes['App'] | null; // App - openProject: NexusGenRootTypes['Project'] | null; // Project - projects: Array; // [Project]! } TestingType: { // field return type description: string | null; // String @@ -145,6 +144,7 @@ export interface NexusGenFieldTypes { name: string | null; // String } WizardFrontendFramework: { // field return type + isSelected: boolean | null; // Boolean name: NexusGenEnums['FrontendFramework'] | null; // FrontendFramework packagesToInstall: Array | null; // [WizardNpmPackage] supportedBundlers: Array | null; // [WizardBundler] @@ -157,7 +157,9 @@ export interface NexusGenFieldTypes { export interface NexusGenFieldTypeNames { App: { // field return type name + activeProject: 'Project' isFirstOpen: 'Boolean' + projects: 'Project' wizard: 'Wizard' } InitPluginsStatus: { // field return type name @@ -169,21 +171,22 @@ export interface NexusGenFieldTypeNames { initializePlugins: 'Project' wizardInstallDependencies: 'Wizard' wizardNavigateBack: 'Wizard' + wizardNavigateForward: 'Wizard' wizardSetBundler: 'Wizard' wizardSetFramework: 'Wizard' wizardSetTestingType: 'Wizard' wizardValidateManualInstall: 'Wizard' } Project: { // field return type name + id: 'ID' isCurrent: 'Boolean' isOpen: 'Boolean' - plugins: 'InitPluginsStatus' + pluginStatus: 'InitPluginsStatus' + plugins: 'Boolean' projectRoot: 'String' } Query: { // field return type name app: 'App' - openProject: 'Project' - projects: 'Project' } TestingType: { // field return type name description: 'String' @@ -203,6 +206,7 @@ export interface NexusGenFieldTypeNames { name: 'String' } WizardFrontendFramework: { // field return type name + isSelected: 'Boolean' name: 'FrontendFramework' packagesToInstall: 'WizardNpmPackage' supportedBundlers: 'WizardBundler' @@ -218,9 +222,6 @@ export interface NexusGenArgTypes { addProject: { // args input: NexusGenInputs['AddProjectInput']; // AddProjectInput! } - wizardNavigateBack: { // args - type: NexusGenEnums['TestingTypeEnum']; // TestingTypeEnum! - } wizardSetBundler: { // args name?: NexusGenEnums['SupportedBundlers'] | null; // SupportedBundlers } diff --git a/packages/server/lib/graphql/server.ts b/packages/server/lib/graphql/server.ts index 9228571f0007..dc280d5b0a7d 100644 --- a/packages/server/lib/graphql/server.ts +++ b/packages/server/lib/graphql/server.ts @@ -31,11 +31,13 @@ export function closeGraphQLServer (): Promise { export function startGraphQLServer (): {server: Server, app: Express.Application} { app = express() + const context = new ServerContext() + app.use('/graphql', graphqlHTTP(() => { return { schema: graphqlSchema, graphiql: true, - context: new ServerContext(), + context, } })) diff --git a/packages/server/lib/graphql/testing/ProjectBaseTest.ts b/packages/server/lib/graphql/testing/ProjectBaseTest.ts new file mode 100644 index 000000000000..999504d1745f --- /dev/null +++ b/packages/server/lib/graphql/testing/ProjectBaseTest.ts @@ -0,0 +1 @@ +export class ProjectBaseTest {} diff --git a/packages/server/lib/graphql/utils/index.ts b/packages/server/lib/graphql/utils/index.ts deleted file mode 100644 index 356d8eebb795..000000000000 --- a/packages/server/lib/graphql/utils/index.ts +++ /dev/null @@ -1,12 +0,0 @@ -// import { projects } from '../../projects' -import type { ProjectBase } from '../../project-base' -import { NexusGenFieldTypes } from '../gen/nxs.gen' - -export function formatProject (project: ProjectBase): NexusGenFieldTypes['Project'] { - return { - projectRoot: project.projectRoot, - isOpen: project.isOpen, - plugins: project.pluginsStatus, - isCurrent: project.id === projects.currentProjectId, - } -} diff --git a/packages/server/schema.graphql b/packages/server/schema.graphql index c317c0396af8..57fa2f68d891 100644 --- a/packages/server/schema.graphql +++ b/packages/server/schema.graphql @@ -10,9 +10,15 @@ input AddProjectInput { """Namespace for information related to the app""" type App { + """The active project in the app""" + activeProject: Project + """Whether this is the first open of the application or not""" isFirstOpen: Boolean! + """All known projects for the app""" + projects: [Project!]! + """Metadata about the wizard""" wizard: Wizard } @@ -42,14 +48,20 @@ The `JSON` scalar type represents JSON values as specified by [ECMA-404](http:// scalar JSON @specifiedBy(url: "http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf") type Mutation { + """Adds a new project to the app""" addProject(input: AddProjectInput!): Project! - initializePlugins: Project! + + """Initializes the plugins for the current active project""" + initializePlugins: Project """Installs the dependencies for the component testing step""" wizardInstallDependencies: Wizard """Navigates backward in the wizard""" - wizardNavigateBack(type: TestingTypeEnum!): Wizard + wizardNavigateBack: Wizard + + """Navigates forward in the wizard""" + wizardNavigateForward: Wizard """Sets the frontend bundler we want to use for the project""" wizardSetBundler(name: SupportedBundlers): Wizard @@ -71,17 +83,18 @@ enum PluginsState { uninitialized } +"""A Cypress project is a container""" type Project { + id: ID! isCurrent: Boolean! isOpen: Boolean! - plugins: InitPluginsStatus + pluginStatus: InitPluginsStatus! + plugins: [Boolean!]! projectRoot: String! } type Query { app: App - openProject: Project - projects: [Project]! } """The bundlers that we can use with Cypress""" @@ -118,6 +131,7 @@ type Wizard { testingType: TestingTypeEnum } +"""Wizard bundler""" type WizardBundler { id: SupportedBundlers @@ -131,6 +145,9 @@ type WizardBundler { """A frontend framework that we can setup within the app""" type WizardFrontendFramework { + """Whether this is the selected framework in the wizard""" + isSelected: Boolean + """The name of the framework""" name: FrontendFramework From 3c305ac3bf881dc9afec01b4d7553b3c3ab97f10 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Wed, 28 Jul 2021 22:05:06 -0400 Subject: [PATCH 04/29] Cleaning up the server --- packages/launchpad/package.json | 1 + packages/server/lib/graphql/GraphQL-FAQ.md | 7 ++ .../server/lib/graphql/actions/BaseActions.ts | 24 ++-- .../lib/graphql/actions/ClientTestActions.ts | 14 ++- .../lib/graphql/actions/ServerActions.ts | 10 ++ .../lib/graphql/constants/WizardConstants.ts | 26 +++++ .../server/lib/graphql/context/BaseContext.ts | 4 + .../lib/graphql/context/ClientTestContext.ts | 1 + .../lib/graphql/context/ContextUtils.ts | 3 + .../lib/graphql/context/ServerContext.ts | 3 + .../graphql/contracts/ProjectBaseContract.ts | 1 + packages/server/lib/graphql/entities/App.ts | 9 -- .../server/lib/graphql/entities/Mutation.ts | 6 +- .../server/lib/graphql/entities/Project.ts | 61 ++++++---- packages/server/lib/graphql/entities/Query.ts | 20 +++- .../lib/graphql/entities/TestingType.ts | 20 ---- .../lib/graphql/entities/TestingTypeInfo.ts | 22 ++++ .../server/lib/graphql/entities/Wizard.ts | 107 ++++-------------- .../lib/graphql/entities/WizardBundler.ts | 34 ++++++ .../entities/WizardFrontendFramework.ts | 39 +++++++ .../lib/graphql/entities/WizardNpmPackage.ts | 18 +++ packages/server/lib/graphql/entities/index.ts | 2 +- packages/server/lib/graphql/gen/nxs.gen.ts | 50 ++++---- packages/server/lib/graphql/schema.ts | 2 +- packages/server/lib/graphql/server.ts | 20 +++- .../server/lib/gui/{events.js => events.ts} | 22 ++-- packages/server/lib/project-base.ts | 17 +-- packages/server/lib/projects.ts | 91 --------------- packages/server/schema.graphql | 41 ++++--- 29 files changed, 367 insertions(+), 308 deletions(-) create mode 100644 packages/server/lib/graphql/context/ContextUtils.ts delete mode 100644 packages/server/lib/graphql/entities/TestingType.ts create mode 100644 packages/server/lib/graphql/entities/TestingTypeInfo.ts create mode 100644 packages/server/lib/graphql/entities/WizardBundler.ts create mode 100644 packages/server/lib/graphql/entities/WizardFrontendFramework.ts create mode 100644 packages/server/lib/graphql/entities/WizardNpmPackage.ts rename packages/server/lib/gui/{events.js => events.ts} (96%) delete mode 100644 packages/server/lib/projects.ts diff --git a/packages/launchpad/package.json b/packages/launchpad/package.json index fa3b1e3b0579..d4426cc086f6 100644 --- a/packages/launchpad/package.json +++ b/packages/launchpad/package.json @@ -43,6 +43,7 @@ "graphql": "^15.5.1", "graphql-tag": "^2.12.5", "prismjs": "1.24.0", + "rollup-plugin-polyfill-node": "^0.7.0", "vite": "2.4.1", "vite-plugin-components": "0.11.3", "vite-plugin-icons": "0.6.3", diff --git a/packages/server/lib/graphql/GraphQL-FAQ.md b/packages/server/lib/graphql/GraphQL-FAQ.md index edb1f3b51438..b871f277f10a 100644 --- a/packages/server/lib/graphql/GraphQL-FAQ.md +++ b/packages/server/lib/graphql/GraphQL-FAQ.md @@ -1,3 +1,10 @@ +## Development Process + +1. From the monorepo root, run `LAUNCHPAD=1 yarn dev:watch` +2. In launchpad directory, run `yarn watch` + + + ## Why are my types not showing up in the schema Ensure that the types are exported so that they are imported into the root `makeSchema`. diff --git a/packages/server/lib/graphql/actions/BaseActions.ts b/packages/server/lib/graphql/actions/BaseActions.ts index f636201e3bae..22e0601bf58b 100644 --- a/packages/server/lib/graphql/actions/BaseActions.ts +++ b/packages/server/lib/graphql/actions/BaseActions.ts @@ -1,33 +1,41 @@ import { NxsMutationArgs } from 'nexus-decorators' import { BaseContext } from '../context/BaseContext' +import { ProjectBaseContract } from '../contracts/ProjectBaseContract' import { Project } from '../entities/Project' /** - * Acts as the contract for all actions, implemented by ServerActions - * and ClientTestActions + * Acts as the contract for all actions, inherited by: + * - ServerActions + * - ClientTestActions + * + * By having a "base actions" class, we can reuse this code on the client + * and make the client-only test doubles work as realistically as possible */ export abstract class BaseActions { constructor (protected ctx: BaseContext) {} abstract installDependencies (): void - abstract initializePlugins(): Promise - /** - * Adds a new project + * Adds a new project if it doesn't already exist */ - addProject (input: NxsMutationArgs<'addProject'>['input']): Project { + async addProject (input: NxsMutationArgs<'addProject'>['input']): Promise { + // Prevent adding the existing project again const existing = this.ctx.projects.find((p) => p.projectRoot === input.projectRoot) - // If we don't already have a projec if (existing) { return existing } - const newProject = new Project({ projectRoot: input.projectRoot }) + const newProject = new Project({ + projectRoot: input.projectRoot, + projectBase: await this.createProjectBase(input), + }) this.ctx.projects.push(newProject) return newProject } + + abstract createProjectBase(input: NxsMutationArgs<'addProject'>['input']): ProjectBaseContract | Promise } diff --git a/packages/server/lib/graphql/actions/ClientTestActions.ts b/packages/server/lib/graphql/actions/ClientTestActions.ts index dfc33031476d..996c35b37fb6 100644 --- a/packages/server/lib/graphql/actions/ClientTestActions.ts +++ b/packages/server/lib/graphql/actions/ClientTestActions.ts @@ -1,4 +1,5 @@ import { ClientTestContext } from '../context/ClientTestContext' +import { ProjectBaseContract } from '../contracts/ProjectBaseContract' import { BaseActions } from './BaseActions' export class ClientTestActions extends BaseActions { @@ -6,11 +7,20 @@ export class ClientTestActions extends BaseActions { super(ctx) } - installDependencies () { + async installDependencies () { return } - initializePlugins () { + async initializePlugins () { return } + + createProjectBase (): ProjectBaseContract { + return { + isOpen: false, + async initializePlugins () { + return + }, + } + } } diff --git a/packages/server/lib/graphql/actions/ServerActions.ts b/packages/server/lib/graphql/actions/ServerActions.ts index 194d5f997a76..003c26262e9a 100644 --- a/packages/server/lib/graphql/actions/ServerActions.ts +++ b/packages/server/lib/graphql/actions/ServerActions.ts @@ -1,3 +1,5 @@ +import type { NxsMutationArgs } from 'nexus-decorators' +import { ProjectBase } from '../../project-base' import { ServerContext } from '../context/ServerContext' import { BaseActions } from './BaseActions' @@ -12,4 +14,12 @@ export class ServerActions extends BaseActions { installDependencies () { // } + + createProjectBase (input: NxsMutationArgs<'addProject'>['input']) { + return new ProjectBase({ + projectRoot: input.projectRoot, + testingType: 'component', + options: {}, + }) + } } diff --git a/packages/server/lib/graphql/constants/WizardConstants.ts b/packages/server/lib/graphql/constants/WizardConstants.ts index e5aadcf27cd9..b5489e11cd75 100644 --- a/packages/server/lib/graphql/constants/WizardConstants.ts +++ b/packages/server/lib/graphql/constants/WizardConstants.ts @@ -44,6 +44,16 @@ export const TestingTypeEnum = enumType({ members: TESTING_TYPES, }) +export const TestingTypeNames: Record = { + component: 'Component Testing', + e2e: 'E2E Testing', +} + +export const TestingTypeDescriptions: Record = { + component: 'Aenean lacinia bibendum nulla sed consectetur. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Aenean lacinia bibendum nulla sed consectetur.', + e2e: 'Aenean lacinia bibendum nulla sed consectetur. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Aenean lacinia bibendum nulla sed consectetur.', +} + export const WIZARD_STEP = [ 'welcome', 'selectFramework', @@ -58,3 +68,19 @@ export const WizardStepEnum = enumType({ name: 'WizardStep', members: WIZARD_STEP, }) + +export const WIZARD_TITLES: Record = { + welcome: 'Welcome to Cypress', + selectFramework: 'Project Setup', + installDependencies: 'Install Dev Dependencies', + createConfig: 'Cypress.config', + setupComplete: 'Setup Finished', +} + +export const WIZARD_DESCRIPTIONS: Record = { + welcome: 'Choose which method of testing you would like to set up first.', + selectFramework: 'Confirm the front-end framework and bundler fused in your project.', + installDependencies: 'We need to install the following packages in order for component testing to work.', + createConfig: 'Cypress will now create the following config file in the local directory for this project.', + setupComplete: 'cypress.config.js file was successfully added to your project.Let’s open your browser and start testing some components!', +} diff --git a/packages/server/lib/graphql/context/BaseContext.ts b/packages/server/lib/graphql/context/BaseContext.ts index b99805194fa5..1c3f70c65900 100644 --- a/packages/server/lib/graphql/context/BaseContext.ts +++ b/packages/server/lib/graphql/context/BaseContext.ts @@ -17,4 +17,8 @@ export abstract class BaseContext { app = new App(this) isFirstOpen = false + + get activeProject () { + return this.app.activeProject + } } diff --git a/packages/server/lib/graphql/context/ClientTestContext.ts b/packages/server/lib/graphql/context/ClientTestContext.ts index ab679196bb8d..1369fbb3a15c 100644 --- a/packages/server/lib/graphql/context/ClientTestContext.ts +++ b/packages/server/lib/graphql/context/ClientTestContext.ts @@ -6,4 +6,5 @@ import { BaseContext } from './BaseContext' */ export class ClientTestContext extends BaseContext { readonly actions = new ClientTestActions(this) + readonly projects = [] } diff --git a/packages/server/lib/graphql/context/ContextUtils.ts b/packages/server/lib/graphql/context/ContextUtils.ts new file mode 100644 index 000000000000..6e2ba31f5e51 --- /dev/null +++ b/packages/server/lib/graphql/context/ContextUtils.ts @@ -0,0 +1,3 @@ +export class ContextUtils { + +} diff --git a/packages/server/lib/graphql/context/ServerContext.ts b/packages/server/lib/graphql/context/ServerContext.ts index 5288ee7296af..4cd0e7232391 100644 --- a/packages/server/lib/graphql/context/ServerContext.ts +++ b/packages/server/lib/graphql/context/ServerContext.ts @@ -1,6 +1,9 @@ import { ServerActions } from '../actions/ServerActions' +import { Project } from '../entities/Project' import { BaseContext } from './BaseContext' export class ServerContext extends BaseContext { readonly actions = new ServerActions(this) + + projects: Project[] = [] } diff --git a/packages/server/lib/graphql/contracts/ProjectBaseContract.ts b/packages/server/lib/graphql/contracts/ProjectBaseContract.ts index 5ee5bf1eec7b..5c71649fe53e 100644 --- a/packages/server/lib/graphql/contracts/ProjectBaseContract.ts +++ b/packages/server/lib/graphql/contracts/ProjectBaseContract.ts @@ -1,3 +1,4 @@ export interface ProjectBaseContract { isOpen: boolean + initializePlugins(): Promise } diff --git a/packages/server/lib/graphql/entities/App.ts b/packages/server/lib/graphql/entities/App.ts index 5d83e9b6f8f7..460fa75b39d3 100644 --- a/packages/server/lib/graphql/entities/App.ts +++ b/packages/server/lib/graphql/entities/App.ts @@ -1,8 +1,6 @@ import { nxs, NxsResult } from 'nexus-decorators' import { BaseContext } from '../context/BaseContext' -import { NexusGenTypes } from '../gen/nxs.gen' import { Project } from './Project' -import { Wizard } from './Wizard' @nxs.objectType({ description: 'Namespace for information related to the app', @@ -17,13 +15,6 @@ export class App { return true } - @nxs.field.type(() => Wizard, { - description: 'Metadata about the wizard', - }) - wizard (args, ctx: NexusGenTypes['context']): NxsResult<'App', 'wizard'> { - return ctx.wizard - } - @nxs.field.nonNull.list.nonNull.type(() => Project, { description: 'All known projects for the app', }) diff --git a/packages/server/lib/graphql/entities/Mutation.ts b/packages/server/lib/graphql/entities/Mutation.ts index 43eca9acac55..e84dd38be34b 100644 --- a/packages/server/lib/graphql/entities/Mutation.ts +++ b/packages/server/lib/graphql/entities/Mutation.ts @@ -3,7 +3,7 @@ import { BundlerEnum, FrontendFrameworkEnum, TestingTypeEnum } from '../constant export const mutation = mutationType({ definition (t) { - // TODO(tim): in nexus, allow for t.wizard(...) + // TODO(tim): in nexus, support for t.wizard(...) t.field('wizardSetTestingType', { type: 'Wizard', @@ -81,9 +81,7 @@ export const mutation = mutationType({ description: 'Initializes the plugins for the current active project', async resolve (_root, args, ctx) { // TODO: should we await here, or return a pending state to the client? - await ctx.actions.initializePlugins() - - return ctx.app.activeProject + return await ctx.activeProject?.initializePlugins() ?? null }, }) }, diff --git a/packages/server/lib/graphql/entities/Project.ts b/packages/server/lib/graphql/entities/Project.ts index c8af98485796..fd81817aec93 100644 --- a/packages/server/lib/graphql/entities/Project.ts +++ b/packages/server/lib/graphql/entities/Project.ts @@ -1,7 +1,6 @@ -// import { createHash } from 'crypto' -import { objectType } from 'nexus' +import { createHash } from 'crypto' import { nxs, NxsResult } from 'nexus-decorators' -import { PluginsStateEnum } from '../constants/ProjectConstants' +import { PluginsState, PluginsStateEnum } from '../constants/ProjectConstants' import { ProjectBaseContract } from '../contracts/ProjectBaseContract' export interface ProjectConfig { @@ -13,12 +12,17 @@ export interface ProjectConfig { description: 'A Cypress project is a container', }) export class Project { - constructor (private config: ProjectConfig) {} + readonly projectBase: ProjectBaseContract + private _pluginsState: PluginsState = 'uninitialized' + private _pluginsErrorMessage?: string + + constructor (private config: ProjectConfig) { + this.projectBase = config.projectBase + } @nxs.field.nonNull.id() id (): NxsResult<'Project', 'id'> { - return '1' - // return createHash('sha1').update(this.projectRoot).digest('hex') + return createHash('sha1').update(this.projectRoot).digest('hex') } @nxs.field.nonNull.string() @@ -36,23 +40,38 @@ export class Project { return false } - @nxs.field.nonNull.list.nonNull.boolean() - plugins (): NxsResult<'Project', 'plugins'> { - return [] + @nxs.field.type(() => PluginsStateEnum, { + description: 'Plugin state for a project', + }) + get pluginsState (): NxsResult<'Project', 'pluginsState'> { + return this._pluginsState + } + + @nxs.field.string({ + description: 'If the plugin has errored, contains the associated error message', + }) + get pluginsErrorMessage (): NxsResult<'Project', 'pluginsErrorMessage'> { + if (this.pluginsState === 'error') { + return this._pluginsErrorMessage ?? null + } + + return null } - @nxs.field.nonNull.type(() => InitPluginsStatus) - pluginStatus (): NxsResult<'Project', 'pluginStatus'> { + async initializePlugins (): Promise { + if (this.pluginsState !== 'uninitialized' && this.pluginsState !== 'error') { + return this + } + + try { + this._pluginsState = 'initializing' + await this.projectBase.initializePlugins() + this._pluginsState = 'initialized' + } catch (e) { + this._pluginsState = 'error' + this._pluginsErrorMessage = e.message + } + return this } } - -export const InitPluginsStatus = objectType({ - name: 'InitPluginsStatus', - definition (t) { - t.nonNull.field('state', { - type: PluginsStateEnum, - }), - t.string('message') - }, -}) diff --git a/packages/server/lib/graphql/entities/Query.ts b/packages/server/lib/graphql/entities/Query.ts index 5c5abc7ae1a1..609deb756740 100644 --- a/packages/server/lib/graphql/entities/Query.ts +++ b/packages/server/lib/graphql/entities/Query.ts @@ -1,11 +1,21 @@ -import { nxs, NxsQueryResult } from 'nexus-decorators' +import { nxs, NxsQueryResult, NxsResult } from 'nexus-decorators' +import { NexusGenTypes } from '../gen/nxs.gen' import { App } from './App' +import { Wizard } from './Wizard' +@nxs.objectType({ + description: 'The root "Query" type containing all entry fields for our querying', +}) export class Query { - @nxs.queryField(() => { - return { type: App } - }) - static app (_, ctx: NexusGen['context']): NxsQueryResult<'app'> { + @nxs.field.nonNull.type(() => App) + app (_, ctx: NexusGen['context']): NxsQueryResult<'app'> { return ctx.app } + + @nxs.field.type(() => Wizard, { + description: 'Metadata about the wizard, null if we arent showing the wizard', + }) + wizard (args, ctx: NexusGenTypes['context']): NxsResult<'App', 'wizard'> { + return ctx.wizard + } } diff --git a/packages/server/lib/graphql/entities/TestingType.ts b/packages/server/lib/graphql/entities/TestingType.ts deleted file mode 100644 index c76081aefa08..000000000000 --- a/packages/server/lib/graphql/entities/TestingType.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { nxs, NxsResult } from 'nexus-decorators' -import { TestingTypeEnum } from '../constants' - -@nxs.objectType() -export class TestingType { - @nxs.field.type(() => TestingTypeEnum) - get id (): NxsResult<'TestingType', 'id'> { - return 'component' - } - - @nxs.field.string() - get title (): NxsResult<'TestingType', 'title'> { - return 'Some Title' - } - - @nxs.field.string() - get description (): NxsResult<'TestingType', 'description'> { - return 'Some Description' - } -} diff --git a/packages/server/lib/graphql/entities/TestingTypeInfo.ts b/packages/server/lib/graphql/entities/TestingTypeInfo.ts new file mode 100644 index 000000000000..b5b54d4c4dc0 --- /dev/null +++ b/packages/server/lib/graphql/entities/TestingTypeInfo.ts @@ -0,0 +1,22 @@ +import { nxs, NxsResult } from 'nexus-decorators' +import { TestingTypeEnum, TestingType as _TestingType, TestingTypeNames, TestingTypeDescriptions } from '../constants' + +@nxs.objectType() +export class TestingTypeInfo { + constructor (private _id: _TestingType) {} + + @nxs.field.nonNull.type(() => TestingTypeEnum) + get id (): NxsResult<'TestingType', 'id'> { + return this._id + } + + @nxs.field.string() + get title (): NxsResult<'TestingType', 'title'> { + return TestingTypeNames[this.id] + } + + @nxs.field.string() + get description (): NxsResult<'TestingType', 'description'> { + return TestingTypeDescriptions[this.id] + } +} diff --git a/packages/server/lib/graphql/entities/Wizard.ts b/packages/server/lib/graphql/entities/Wizard.ts index 7e3253db159a..81b80c3cf200 100644 --- a/packages/server/lib/graphql/entities/Wizard.ts +++ b/packages/server/lib/graphql/entities/Wizard.ts @@ -1,5 +1,8 @@ import { nxs, NxsResult } from 'nexus-decorators' -import { FrontendFrameworkEnum, BUNDLER, FrontendFramework, Bundler, FRONTEND_FRAMEWORK, TestingType, TestingTypeEnum, BundlerDisplayNames, BundlerEnum, WizardStepEnum, WIZARD_STEP, WizardStep } from '../constants/WizardConstants' +import { BUNDLER, FrontendFramework, Bundler, FRONTEND_FRAMEWORK, TestingTypeEnum, WizardStepEnum, WIZARD_STEP, WizardStep, WIZARD_TITLES, WIZARD_DESCRIPTIONS, TESTING_TYPES, TestingType } from '../constants/WizardConstants' +import { TestingTypeInfo } from './TestingTypeInfo' +import { WizardBundler } from './WizardBundler' +import { WizardFrontendFramework } from './WizardFrontendFramework' @nxs.objectType({ description: 'The Wizard is a container for any state associated with initial onboarding to Cypress', @@ -24,6 +27,20 @@ export class Wizard { return this.chosenBundler } + @nxs.field.string({ + description: 'The title of the page, given the current step of the wizard', + }) + get title (): NxsResult<'Wizard', 'title'> { + return WIZARD_TITLES[this.currentStep] + } + + @nxs.field.string({ + description: 'The title of the page, given the current step of the wizard', + }) + get description (): NxsResult<'Wizard', 'title'> { + return WIZARD_DESCRIPTIONS[this.currentStep] + } + // GraphQL Fields: @nxs.field.type(() => WizardStepEnum) @@ -38,6 +55,11 @@ export class Wizard { return this.chosenTestingType } + @nxs.field.list.nonNull.type(() => TestingTypeInfo) + testingTypes (): NxsResult<'Wizard', 'testingTypes'> { + return TESTING_TYPES.map((t) => new TestingTypeInfo(t)) + } + @nxs.field.list.type(() => WizardFrontendFramework, { description: 'All of the component testing frameworks to choose from', }) @@ -98,86 +120,3 @@ export class Wizard { return this } } - -@nxs.objectType({ - description: 'A frontend framework that we can setup within the app', -}) -export class WizardFrontendFramework { - constructor (private framework: FrontendFramework) {} - - @nxs.field.type(() => FrontendFrameworkEnum, { - description: 'The name of the framework', - }) - get name (): NxsResult<'WizardFrontendFramework', 'name'> { - return this.framework - } - - @nxs.field.list.type(() => WizardBundler, { - description: 'All of the supported bundlers for this framework', - }) - get supportedBundlers (): NxsResult<'WizardFrontendFramework', 'supportedBundlers'> { - return [] - } - - @nxs.field.list.type(() => WizardNpmPackage, { - description: 'A list of packages to install, null if we have not chosen both a framework and bundler', - }) - get packagesToInstall (): NxsResult<'WizardFrontendFramework', 'packagesToInstall'> { - return [] - } - - @nxs.field.boolean({ - description: 'Whether this is the selected framework in the wizard', - }) - get isSelected (): NxsResult<'WizardFrontendFramework', 'isSelected'> { - return true - } -} - -@nxs.objectType({ - description: 'Wizard bundler', -}) -export class WizardBundler { - constructor (private wizard: Wizard, private bundler: Bundler) {} - - @nxs.field.type(() => BundlerEnum) - get id (): NxsResult<'WizardBundler', 'id'> { - return this.bundler - } - - @nxs.field.string() - get name (): NxsResult<'WizardBundler', 'name'> { - return BundlerDisplayNames[this.bundler] - } - - @nxs.field.boolean({ - description: 'Whether this is the selected framework bundler', - }) - isSelected (): NxsResult<'WizardBundler', 'isSelected'> { - return this.wizard.bundler === this.bundler - } - - @nxs.field.boolean({ - description: 'Whether there are multiple options to choose from given the framework', - }) - isOnlyOption (): NxsResult<'WizardBundler', 'isOnlyOption'> { - return true - } -} - -@nxs.objectType({ - description: 'Details about an NPM Package listed during the wizard install', -}) -export class WizardNpmPackage { - @nxs.field.string({ - description: 'The package name that you would npm install', - }) - name (): NxsResult<'WizardNpmPackage', 'name'> { - return 'name' - } - - @nxs.field.string() - description (): NxsResult<'WizardNpmPackage', 'description'> { - return 'description' - } -} diff --git a/packages/server/lib/graphql/entities/WizardBundler.ts b/packages/server/lib/graphql/entities/WizardBundler.ts new file mode 100644 index 000000000000..8eb2edd680bb --- /dev/null +++ b/packages/server/lib/graphql/entities/WizardBundler.ts @@ -0,0 +1,34 @@ +import { nxs, NxsResult } from 'nexus-decorators' +import { Bundler, BundlerDisplayNames, BundlerEnum } from '../constants' +import { Wizard } from './Wizard' + +@nxs.objectType({ + description: 'Wizard bundler', +}) +export class WizardBundler { + constructor (private wizard: Wizard, private bundler: Bundler) {} + + @nxs.field.type(() => BundlerEnum) + get id (): NxsResult<'WizardBundler', 'id'> { + return this.bundler + } + + @nxs.field.string() + get name (): NxsResult<'WizardBundler', 'name'> { + return BundlerDisplayNames[this.bundler] + } + + @nxs.field.boolean({ + description: 'Whether this is the selected framework bundler', + }) + isSelected (): NxsResult<'WizardBundler', 'isSelected'> { + return this.wizard.bundler === this.bundler + } + + @nxs.field.boolean({ + description: 'Whether there are multiple options to choose from given the framework', + }) + isOnlyOption (): NxsResult<'WizardBundler', 'isOnlyOption'> { + return true + } +} diff --git a/packages/server/lib/graphql/entities/WizardFrontendFramework.ts b/packages/server/lib/graphql/entities/WizardFrontendFramework.ts new file mode 100644 index 000000000000..27bbe87ef169 --- /dev/null +++ b/packages/server/lib/graphql/entities/WizardFrontendFramework.ts @@ -0,0 +1,39 @@ +import { nxs, NxsResult } from 'nexus-decorators' +import { FrontendFramework, FrontendFrameworkEnum } from '../constants' +import { WizardBundler } from './WizardBundler' +import { WizardNpmPackage } from './WizardNpmPackage' + +@nxs.objectType({ + description: 'A frontend framework that we can setup within the app', +}) +export class WizardFrontendFramework { + constructor (private framework: FrontendFramework) {} + + @nxs.field.type(() => FrontendFrameworkEnum, { + description: 'The name of the framework', + }) + get name (): NxsResult<'WizardFrontendFramework', 'name'> { + return this.framework + } + + @nxs.field.list.type(() => WizardBundler, { + description: 'All of the supported bundlers for this framework', + }) + get supportedBundlers (): NxsResult<'WizardFrontendFramework', 'supportedBundlers'> { + return [] + } + + @nxs.field.list.type(() => WizardNpmPackage, { + description: 'A list of packages to install, null if we have not chosen both a framework and bundler', + }) + get packagesToInstall (): NxsResult<'WizardFrontendFramework', 'packagesToInstall'> { + return [] + } + + @nxs.field.boolean({ + description: 'Whether this is the selected framework in the wizard', + }) + get isSelected (): NxsResult<'WizardFrontendFramework', 'isSelected'> { + return true + } +} diff --git a/packages/server/lib/graphql/entities/WizardNpmPackage.ts b/packages/server/lib/graphql/entities/WizardNpmPackage.ts new file mode 100644 index 000000000000..c16c335a6588 --- /dev/null +++ b/packages/server/lib/graphql/entities/WizardNpmPackage.ts @@ -0,0 +1,18 @@ +import { nxs, NxsResult } from 'nexus-decorators' + +@nxs.objectType({ + description: 'Details about an NPM Package listed during the wizard install', +}) +export class WizardNpmPackage { + @nxs.field.string({ + description: 'The package name that you would npm install', + }) + name (): NxsResult<'WizardNpmPackage', 'name'> { + return 'name' + } + + @nxs.field.string() + description (): NxsResult<'WizardNpmPackage', 'description'> { + return 'description' + } +} diff --git a/packages/server/lib/graphql/entities/index.ts b/packages/server/lib/graphql/entities/index.ts index 5eea3156787f..d0a9781869b7 100644 --- a/packages/server/lib/graphql/entities/index.ts +++ b/packages/server/lib/graphql/entities/index.ts @@ -6,6 +6,6 @@ export * from './Project' export * from './Query' -export * from './TestingType' +export * from './TestingTypeInfo' export * from './Wizard' diff --git a/packages/server/lib/graphql/gen/nxs.gen.ts b/packages/server/lib/graphql/gen/nxs.gen.ts index 8d58f39cf888..73e72f1177b3 100644 --- a/packages/server/lib/graphql/gen/nxs.gen.ts +++ b/packages/server/lib/graphql/gen/nxs.gen.ts @@ -8,8 +8,12 @@ import type { BaseContext } from "./../context/BaseContext" import type { App } from "./../entities/App" import type { Project } from "./../entities/Project" -import type { TestingType } from "./../entities/TestingType" -import type { Wizard, WizardFrontendFramework, WizardBundler, WizardNpmPackage } from "./../entities/Wizard" +import type { Query } from "./../entities/Query" +import type { TestingTypeInfo } from "./../entities/TestingTypeInfo" +import type { Wizard } from "./../entities/Wizard" +import type { WizardFrontendFramework } from "./../entities/WizardFrontendFramework" +import type { WizardBundler } from "./../entities/WizardBundler" +import type { WizardNpmPackage } from "./../entities/WizardNpmPackage" import type { core } from "nexus" declare global { interface NexusGenCustomInputMethods { @@ -69,14 +73,10 @@ export interface NexusGenScalars { export interface NexusGenObjects { App: App; - InitPluginsStatus: { // root type - message?: string | null; // String - state: NexusGenEnums['PluginsState']; // PluginsState! - } Mutation: {}; Project: Project; - Query: {}; - TestingType: TestingType; + Query: Query; + TestingTypeInfo: TestingTypeInfo; Wizard: Wizard; WizardBundler: WizardBundler; WizardFrontendFramework: WizardFrontendFramework; @@ -98,11 +98,6 @@ export interface NexusGenFieldTypes { activeProject: NexusGenRootTypes['Project'] | null; // Project isFirstOpen: boolean; // Boolean! projects: NexusGenRootTypes['Project'][]; // [Project!]! - wizard: NexusGenRootTypes['Wizard'] | null; // Wizard - } - InitPluginsStatus: { // field return type - message: string | null; // String - state: NexusGenEnums['PluginsState']; // PluginsState! } Mutation: { // field return type addProject: NexusGenRootTypes['Project']; // Project! @@ -119,23 +114,27 @@ export interface NexusGenFieldTypes { id: string; // ID! isCurrent: boolean; // Boolean! isOpen: boolean; // Boolean! - pluginStatus: NexusGenRootTypes['InitPluginsStatus']; // InitPluginsStatus! - plugins: boolean[]; // [Boolean!]! + pluginsErrorMessage: string | null; // String + pluginsState: NexusGenEnums['PluginsState'] | null; // PluginsState projectRoot: string; // String! } Query: { // field return type - app: NexusGenRootTypes['App'] | null; // App + app: NexusGenRootTypes['App']; // App! + wizard: NexusGenRootTypes['Wizard'] | null; // Wizard } - TestingType: { // field return type + TestingTypeInfo: { // field return type description: string | null; // String - id: NexusGenEnums['TestingTypeEnum'] | null; // TestingTypeEnum + id: NexusGenEnums['TestingTypeEnum']; // TestingTypeEnum! title: string | null; // String } Wizard: { // field return type allBundlers: Array | null; // [WizardBundler] + description: string | null; // String frameworks: Array | null; // [WizardFrontendFramework] step: NexusGenEnums['WizardStep'] | null; // WizardStep testingType: NexusGenEnums['TestingTypeEnum'] | null; // TestingTypeEnum + testingTypes: NexusGenRootTypes['TestingTypeInfo'][] | null; // [TestingTypeInfo!] + title: string | null; // String } WizardBundler: { // field return type id: NexusGenEnums['SupportedBundlers'] | null; // SupportedBundlers @@ -160,11 +159,6 @@ export interface NexusGenFieldTypeNames { activeProject: 'Project' isFirstOpen: 'Boolean' projects: 'Project' - wizard: 'Wizard' - } - InitPluginsStatus: { // field return type name - message: 'String' - state: 'PluginsState' } Mutation: { // field return type name addProject: 'Project' @@ -181,23 +175,27 @@ export interface NexusGenFieldTypeNames { id: 'ID' isCurrent: 'Boolean' isOpen: 'Boolean' - pluginStatus: 'InitPluginsStatus' - plugins: 'Boolean' + pluginsErrorMessage: 'String' + pluginsState: 'PluginsState' projectRoot: 'String' } Query: { // field return type name app: 'App' + wizard: 'Wizard' } - TestingType: { // field return type name + TestingTypeInfo: { // field return type name description: 'String' id: 'TestingTypeEnum' title: 'String' } Wizard: { // field return type name allBundlers: 'WizardBundler' + description: 'String' frameworks: 'WizardFrontendFramework' step: 'WizardStep' testingType: 'TestingTypeEnum' + testingTypes: 'TestingTypeInfo' + title: 'String' } WizardBundler: { // field return type name id: 'SupportedBundlers' diff --git a/packages/server/lib/graphql/schema.ts b/packages/server/lib/graphql/schema.ts index d3a35ee4dccd..f4f1a3292b22 100644 --- a/packages/server/lib/graphql/schema.ts +++ b/packages/server/lib/graphql/schema.ts @@ -20,7 +20,7 @@ export const graphqlSchema = makeSchema({ shouldGenerateArtifacts: true, shouldExitAfterGenerateArtifacts: Boolean(process.env.GRAPHQL_CODEGEN), // for vite - outputs: typeof __dirname !== 'undefined' ? { + outputs: dirname && __filename.endsWith('.ts') ? { typegen: path.join(dirname, 'gen/nxs.gen.ts'), schema: path.join(dirname, '..', '..', 'schema.graphql'), } : false, diff --git a/packages/server/lib/graphql/server.ts b/packages/server/lib/graphql/server.ts index dc280d5b0a7d..00777dc791dd 100644 --- a/packages/server/lib/graphql/server.ts +++ b/packages/server/lib/graphql/server.ts @@ -28,16 +28,30 @@ export function closeGraphQLServer (): Promise { }) } +// singleton during the lifetime of the application +let serverContext: ServerContext | undefined + +// Injected this way, since we want to set this up where the IPC layer +// is established in the server package, which should be an independent +// layer from GraphQL +export function setServerContext (ctx: ServerContext) { + serverContext = ctx + + return ctx +} + export function startGraphQLServer (): {server: Server, app: Express.Application} { app = express() - const context = new ServerContext() - app.use('/graphql', graphqlHTTP(() => { + if (!serverContext) { + throw new Error(`setServerContext has not been called`) + } + return { schema: graphqlSchema, graphiql: true, - context, + context: serverContext, } })) diff --git a/packages/server/lib/gui/events.js b/packages/server/lib/gui/events.ts similarity index 96% rename from packages/server/lib/gui/events.js rename to packages/server/lib/gui/events.ts index 3af6d0528c79..4f5cdb68567f 100644 --- a/packages/server/lib/gui/events.js +++ b/packages/server/lib/gui/events.ts @@ -20,7 +20,6 @@ const Updater = require('../updater') const ProjectStatic = require('../project_static') const openProject = require('../open_project') -const { projects } = require('../projects') const ensureUrl = require('../util/ensure-url') const chromePolicyCheck = require('../util/chrome_policy_check') const browsers = require('../browsers') @@ -29,9 +28,10 @@ const editors = require('../util/editors') const fileOpener = require('../util/file-opener') const api = require('../api') const savedState = require('../saved_state') -const { graphqlSchema } = require('../graphql/schema') -const { startGraphQLServer } = require('../graphql/server') -const { ServerContext } = require('../graphql/context/ServerContext') + +import { ServerContext } from '../graphql/context/ServerContext' +import { graphqlSchema } from '../graphql/schema' +import { startGraphQLServer, setServerContext } from '../graphql/server' const nullifyUnserializableValues = (obj) => { // nullify values that cannot be cloned @@ -40,6 +40,8 @@ const nullifyUnserializableValues = (obj) => { if (_.isFunction(val)) { return null } + + return val }) } @@ -389,9 +391,7 @@ const handleEvent = function (options, bus, event, id, type, arg) { .catch((err) => { err.type = _.get(err, 'statusCode') === 403 ? 'ALREADY_MEMBER' - : (_.get(err, 'statusCode') === 422) && /existing/.test(err.errors && err.errors.userId, (x) => { - return x.join('') - }) ? + : (_.get(err, 'statusCode') === 422) && /existing/.test(err.errors?.join('')) ? 'ALREADY_REQUESTED' : err.type || 'UNKNOWN' @@ -494,10 +494,14 @@ module.exports = { return } + // TODO: Figure out how we want to cleanup & juggle the config, so it's not jammed + // into the projects + const serverContext = setServerContext(new ServerContext()) + startGraphQLServer() if (options.projectRoot) { - projects.addProject({ + serverContext.actions.addProject({ projectRoot: options.projectRoot, testingType: options.testingType, isCurrent: true, @@ -511,7 +515,7 @@ module.exports = { document: parse(params.text), operationName: params.name, variableValues: variables, - contextValue: new ServerContext(options), + contextValue: serverContext, }) evt.sender.send('graphql:response', { diff --git a/packages/server/lib/project-base.ts b/packages/server/lib/project-base.ts index e8303555d509..0f73eb3de411 100644 --- a/packages/server/lib/project-base.ts +++ b/packages/server/lib/project-base.ts @@ -33,7 +33,7 @@ import { SpecsStore } from './specs-store' import { createRoutes as createE2ERoutes } from './routes' import { createRoutes as createCTRoutes } from '@packages/server-ct/src/routes-ct' import { checkSupportFile } from './project_utils' -import { NexusGenObjects } from './graphql/gen/nxs.gen' +import { ProjectBaseContract } from './graphql/contracts/ProjectBaseContract' // Cannot just use RuntimeConfigOptions as is because some types are not complete. // Instead, this is an interface of values that have been manually validated to exist @@ -78,7 +78,7 @@ type StartWebsocketOptions = Pick extends EE { +export class ProjectBase extends EE implements ProjectBaseContract { // id is sha256 of projectRoot public id: string @@ -93,9 +93,6 @@ export class ProjectBase extends EE { public testingType: Cypress.TestingType public spec: Cypress.Cypress['spec'] | null public isOpen: boolean = false - public pluginsStatus: NexusGenObjects['InitPluginsStatus'] = { - state: 'uninitialized', - } private generatedProjectIdTimestamp: any projectRoot: string @@ -399,7 +396,8 @@ export class ProjectBase extends EE { return this.initSpecStore({ specs, config: updatedConfig }) } - async initializePlugins (cfg, options) { + // TODO(tim): Improve this when we completely overhaul the rest of the code here, + async initializePlugins (cfg = this._cfg, options = this.options) { // only init plugins with the // allowed config values to // prevent tampering with the @@ -482,6 +480,11 @@ export class ProjectBase extends EE { return Promise.resolve() } + // TODO(tim): remove this when we properly clean all of this up + if (options) { + this.options = options + } + const found = await fs.pathExists(cfg.pluginsFile) debug(`plugins file found? ${found}`) @@ -498,7 +501,7 @@ export class ProjectBase extends EE { debug('plugins file changed') // re-init plugins after a change - this.initializePlugins(cfg, options) + this.initializePlugins(cfg) .catch((err) => { options.onError(err) }) diff --git a/packages/server/lib/projects.ts b/packages/server/lib/projects.ts deleted file mode 100644 index d74167de3504..000000000000 --- a/packages/server/lib/projects.ts +++ /dev/null @@ -1,91 +0,0 @@ -import browsers from './browsers' -import { ProjectBase, Server } from './project-base' -import { NexusGenInputs } from './graphql/gen/nxs.gen' - -export type TestingType = 'e2e' | 'component' - -class Projects { - currentProjectId?: string - projects: ProjectBase[] = [] - - async addProject ({ - projectRoot, - testingType, - isCurrent, - }: NexusGenInputs['AddProjectInput']): Promise> { - const exists = this.projects.find((x) => x.projectRoot === projectRoot) - - if (exists) { - return exists - } - - const type = testingType === 'component' ? 'ct' : 'e2e' - - const projectBase = new ProjectBase({ - projectType: type, - projectRoot, - options: { - projectRoot, - testingType: type, - }, - }) - - const allBrowsers = await browsers.getAllBrowsersWith() - - this.currentProjectId = projectBase.id - - await projectBase.initializeConfig({ browsers: allBrowsers }) - - if (isCurrent) { - this.currentProjectId = projectBase.id - } - - this.projects.push(projectBase) - - return projectBase - } - - setTestingType (testingType: TestingType) { - if (!this.openProject) { - return - } - - this.openProject.projectType = testingType === 'component' ? 'ct' : 'e2e' - } - - async initializePlugins () { - if (!this.openProject) { - return - } - - if (this.openProject.pluginsStatus.state === 'initialized') { - // Do we need to initialize *again*? - // Consider a `reinitialize` argument to facilitate this. - return - } - - try { - this.openProject.pluginsStatus = { state: 'initializing' } - const updatedConfig = await this.openProject.initializePlugins( - await this.openProject.getConfig(), - this.openProject.options, - ) - - this.openProject.__setConfig(updatedConfig) - this.openProject.pluginsStatus = { state: 'initialized' } - } catch (e) { - this.openProject.pluginsStatus = { - state: 'error', - message: e.details, - } - } - } - - get openProject () { - return this.currentProjectId - ? this.projects.find((p) => p.id === this.currentProjectId)! - : undefined - } -} - -export const projects = new Projects() diff --git a/packages/server/schema.graphql b/packages/server/schema.graphql index 57fa2f68d891..8807e12ce16a 100644 --- a/packages/server/schema.graphql +++ b/packages/server/schema.graphql @@ -18,9 +18,6 @@ type App { """All known projects for the app""" projects: [Project!]! - - """Metadata about the wizard""" - wizard: Wizard } """ @@ -37,11 +34,6 @@ enum FrontendFramework { vuejs } -type InitPluginsStatus { - message: String - state: PluginsState! -} - """ The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). """ @@ -88,13 +80,21 @@ type Project { id: ID! isCurrent: Boolean! isOpen: Boolean! - pluginStatus: InitPluginsStatus! - plugins: [Boolean!]! + + """If the plugin has errored, contains the associated error message""" + pluginsErrorMessage: String + + """Plugin state for a project""" + pluginsState: PluginsState projectRoot: String! } +"""The root "Query" type containing all entry fields for our querying""" type Query { - app: App + app: App! + + """Metadata about the wizard, null if we arent showing the wizard""" + wizard: Wizard } """The bundlers that we can use with Cypress""" @@ -103,17 +103,17 @@ enum SupportedBundlers { webpack } -type TestingType { - description: String - id: TestingTypeEnum - title: String -} - enum TestingTypeEnum { component e2e } +type TestingTypeInfo { + description: String + id: TestingTypeEnum! + title: String +} + """ The Wizard is a container for any state associated with initial onboarding to Cypress """ @@ -121,6 +121,9 @@ type Wizard { """All of the bundlers to choose from""" allBundlers: [WizardBundler] + """The title of the page, given the current step of the wizard""" + description: String + """All of the component testing frameworks to choose from""" frameworks: [WizardFrontendFramework] step: WizardStep @@ -129,6 +132,10 @@ type Wizard { The testing type we are setting in the wizard, null if this has not been chosen """ testingType: TestingTypeEnum + testingTypes: [TestingTypeInfo!] + + """The title of the page, given the current step of the wizard""" + title: String } """Wizard bundler""" From 846d5b574f09f0f821cd8f6e098582490f2e1da3 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Wed, 28 Jul 2021 22:07:10 -0400 Subject: [PATCH 05/29] Don't hash for now, for vite --- packages/launchpad/src/graphql/apolloClient.ts | 11 ++++++++++- packages/server/lib/graphql/entities/Project.ts | 5 +++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/launchpad/src/graphql/apolloClient.ts b/packages/launchpad/src/graphql/apolloClient.ts index d5692b82f88e..91b09c529023 100644 --- a/packages/launchpad/src/graphql/apolloClient.ts +++ b/packages/launchpad/src/graphql/apolloClient.ts @@ -18,6 +18,15 @@ export function makeApolloClient () { return new ApolloClient({ link: ipcLink, - cache: new InMemoryCache(), + cache: new InMemoryCache({ + typePolicies: { + Wizard: { + keyFields: [], + }, + App: { + keyFields: [], + }, + }, + }), }) } diff --git a/packages/server/lib/graphql/entities/Project.ts b/packages/server/lib/graphql/entities/Project.ts index fd81817aec93..27f48efb7816 100644 --- a/packages/server/lib/graphql/entities/Project.ts +++ b/packages/server/lib/graphql/entities/Project.ts @@ -1,4 +1,4 @@ -import { createHash } from 'crypto' +// import { createHash } from 'crypto' import { nxs, NxsResult } from 'nexus-decorators' import { PluginsState, PluginsStateEnum } from '../constants/ProjectConstants' import { ProjectBaseContract } from '../contracts/ProjectBaseContract' @@ -22,7 +22,8 @@ export class Project { @nxs.field.nonNull.id() id (): NxsResult<'Project', 'id'> { - return createHash('sha1').update(this.projectRoot).digest('hex') + return this.projectRoot + // return createHash('sha1').update(this.projectRoot).digest('hex') } @nxs.field.nonNull.string() From 8182dae37264731a2c53a115a79f38a00c84935f Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Wed, 28 Jul 2021 22:39:18 -0400 Subject: [PATCH 06/29] fix for CI --- packages/launchpad/src/components/Wizard.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/launchpad/src/components/Wizard.vue b/packages/launchpad/src/components/Wizard.vue index 30b391ea73a4..0ad6b648f3db 100644 --- a/packages/launchpad/src/components/Wizard.vue +++ b/packages/launchpad/src/components/Wizard.vue @@ -33,9 +33,9 @@ gql` query Wizard { app { isFirstOpen - wizard { - step - } + } + wizard { + step } } ` From 09342c2ffe1e237d749e97bff7b8962403127627 Mon Sep 17 00:00:00 2001 From: Tim Griesser Date: Thu, 29 Jul 2021 00:38:35 -0400 Subject: [PATCH 07/29] WIP on integrating with vue --- .../cypress/support/testApolloClient.ts | 5 +- .../launchpad/src/components/ButtonBar.vue | 27 +-- .../src/components/EnvironmentSetup.vue | 158 +++++++----------- .../src/components/InstallDependencies.vue | 23 +-- .../src/components/ManualInstall.vue | 29 +++- .../launchpad/src/components/OpenBrowser.vue | 7 - .../launchpad/src/components/PackagesList.vue | 39 +++-- packages/launchpad/src/components/Select.vue | 2 +- .../launchpad/src/components/TestingType.vue | 57 ++++--- packages/launchpad/src/components/Wizard.vue | 51 +++--- .../launchpad/src/components/WizardLayout.vue | 50 +++++- .../launchpad/src/graphql/apolloClient.ts | 27 +-- packages/launchpad/src/store/config.ts | 6 +- packages/launchpad/src/utils/testingTypes.ts | 28 +--- .../server/lib/graphql/entities/Mutation.ts | 4 +- .../server/lib/graphql/entities/Wizard.ts | 37 +++- .../lib/graphql/entities/WizardBundler.ts | 9 +- .../entities/WizardFrontendFramework.ts | 29 ++-- .../lib/graphql/entities/WizardNpmPackage.ts | 35 +++- packages/server/lib/graphql/gen/nxs.gen.ts | 34 ++-- packages/server/schema.graphql | 36 ++-- 21 files changed, 404 insertions(+), 289 deletions(-) diff --git a/packages/launchpad/cypress/support/testApolloClient.ts b/packages/launchpad/cypress/support/testApolloClient.ts index 4508ad9a59e3..cc6a30f331cb 100644 --- a/packages/launchpad/cypress/support/testApolloClient.ts +++ b/packages/launchpad/cypress/support/testApolloClient.ts @@ -1,7 +1,8 @@ -import { ApolloLink, FetchResult, ApolloClient, InMemoryCache, Observable } from '@apollo/client/core' +import { ApolloLink, FetchResult, ApolloClient, Observable } from '@apollo/client/core' import { graphqlSchema } from '@packages/server/lib/graphql/schema' import { ClientTestContext } from '@packages/server/lib/graphql/context/ClientTestContext' import { graphql, print } from 'graphql' +import { makeApolloCache } from '../../src/graphql/apolloClient' export function testApolloClient (ctx: ClientTestContext) { const ipcLink = new ApolloLink((op) => { @@ -22,6 +23,6 @@ export function testApolloClient (ctx: ClientTestContext) { return new ApolloClient({ link: ipcLink, - cache: new InMemoryCache(), + cache: makeApolloCache(), }) } diff --git a/packages/launchpad/src/components/ButtonBar.vue b/packages/launchpad/src/components/ButtonBar.vue index ed06b21f2067..8d0632cd1b80 100644 --- a/packages/launchpad/src/components/ButtonBar.vue +++ b/packages/launchpad/src/components/ButtonBar.vue @@ -21,8 +21,7 @@ diff --git a/packages/launchpad/src/components/EnvironmentSetup.vue b/packages/launchpad/src/components/EnvironmentSetup.vue index 78d7119b1f80..6ac5057af851 100644 --- a/packages/launchpad/src/components/EnvironmentSetup.vue +++ b/packages/launchpad/src/components/EnvironmentSetup.vue @@ -4,16 +4,16 @@ @@ -21,114 +21,86 @@ diff --git a/packages/launchpad/src/components/ManualInstall.vue b/packages/launchpad/src/components/ManualInstall.vue index e5972122d537..3286d502aa93 100644 --- a/packages/launchpad/src/components/ManualInstall.vue +++ b/packages/launchpad/src/components/ManualInstall.vue @@ -25,25 +25,38 @@ diff --git a/packages/launchpad/src/components/Select.vue b/packages/launchpad/src/components/Select.vue index 89ee8629b18e..7ef88c9ff99b 100644 --- a/packages/launchpad/src/components/Select.vue +++ b/packages/launchpad/src/components/Select.vue @@ -129,7 +129,7 @@ export default defineComponent({ }); const selectOption = (opt: Option) => { - emit("select", opt); + emit("select", opt.id); }; const disabledClass = computed(() => diff --git a/packages/launchpad/src/components/TestingType.vue b/packages/launchpad/src/components/TestingType.vue index 177eec0a10a4..c5dce1583ae8 100644 --- a/packages/launchpad/src/components/TestingType.vue +++ b/packages/launchpad/src/components/TestingType.vue @@ -7,38 +7,57 @@ @click="selectTestingType(type.id)" > -

{{ type.name }}

+

{{ type.title }}

diff --git a/packages/launchpad/src/components/Wizard.vue b/packages/launchpad/src/components/Wizard.vue index 0ad6b648f3db..b109900e02e9 100644 --- a/packages/launchpad/src/components/Wizard.vue +++ b/packages/launchpad/src/components/Wizard.vue @@ -1,25 +1,26 @@ diff --git a/packages/launchpad/src/components/WizardLayout.vue b/packages/launchpad/src/components/WizardLayout.vue index d7db3cc7ca27..d5c7214cc91f 100644 --- a/packages/launchpad/src/components/WizardLayout.vue +++ b/packages/launchpad/src/components/WizardLayout.vue @@ -13,13 +13,39 @@

- + diff --git a/packages/launchpad/src/graphql/apolloClient.ts b/packages/launchpad/src/graphql/apolloClient.ts index 91b09c529023..412c4ecb9f97 100644 --- a/packages/launchpad/src/graphql/apolloClient.ts +++ b/packages/launchpad/src/graphql/apolloClient.ts @@ -1,6 +1,22 @@ import { ApolloLink, FetchResult, ApolloClient, InMemoryCache, Observable } from '@apollo/client/core' import { fetchGraphql, initGraphQLIPC } from './graphqlIpc' +export function makeApolloCache () { + return new InMemoryCache({ + typePolicies: { + Wizard: { + keyFields: [], + }, + App: { + keyFields: [], + }, + WizardNpmPackage: { + keyFields: ['name'], + }, + }, + }) +} + export function makeApolloClient () { initGraphQLIPC() @@ -18,15 +34,6 @@ export function makeApolloClient () { return new ApolloClient({ link: ipcLink, - cache: new InMemoryCache({ - typePolicies: { - Wizard: { - keyFields: [], - }, - App: { - keyFields: [], - }, - }, - }), + cache: makeApolloCache(), }) } diff --git a/packages/launchpad/src/store/config.ts b/packages/launchpad/src/store/config.ts index 64e521dddc32..83754f3a9934 100644 --- a/packages/launchpad/src/store/config.ts +++ b/packages/launchpad/src/store/config.ts @@ -1,7 +1,7 @@ import { reactive, readonly, inject, App } from 'vue' +import { TestingTypeEnum } from '../generated/graphql' import { Bundler } from '../utils/bundler' import { Framework } from '../utils/frameworks' -import { TestingType } from '../utils/testingTypes' import { StoreApp } from './app' interface ComponentSetup { @@ -11,7 +11,7 @@ interface ComponentSetup { interface StateConfig { firstOpen: boolean - testingType?: TestingType + testingType?: TestingTypeEnum component?: ComponentSetup } @@ -40,7 +40,7 @@ export class StoreConfig { return readonly(this.state) } - setTestingType (testingType?: TestingType) { + setTestingType (testingType?: TestingTypeEnum) { this.state.testingType = testingType if (testingType === 'component') { this.storeApp.flagComponent() diff --git a/packages/launchpad/src/utils/testingTypes.ts b/packages/launchpad/src/utils/testingTypes.ts index cc7927addd38..4bc3d812a6c7 100644 --- a/packages/launchpad/src/utils/testingTypes.ts +++ b/packages/launchpad/src/utils/testingTypes.ts @@ -1,26 +1,8 @@ +import { TestingTypeEnum } from '../generated/graphql' import componentLogo from '../images/testingTypes/component.svg' import e2eLogo from '../images/testingTypes/e2e.svg' -export type TestingType = 'e2e' | 'component'; - -export const testingTypes: Array<{ - name: string - icon: string - description: string - id: TestingType - }> = [ - { - name: 'Component Testing', - icon: componentLogo, - description: - 'Aenean lacinia bibendum nulla sed consectetur. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Aenean lacinia bibendum nulla sed consectetur.', - id: 'component', - }, - { - name: 'E2E Testing', - icon: e2eLogo, - description: - 'Aenean lacinia bibendum nulla sed consectetur. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Integer posuere erat a ante venenatis dapibus posuere velit aliquet. Aenean lacinia bibendum nulla sed consectetur.', - id: 'e2e', - }, - ] +export const TestingTypeIcons: Record = { + e2e: e2eLogo, + component: componentLogo, +} diff --git a/packages/server/lib/graphql/entities/Mutation.ts b/packages/server/lib/graphql/entities/Mutation.ts index e84dd38be34b..2e94af2191d3 100644 --- a/packages/server/lib/graphql/entities/Mutation.ts +++ b/packages/server/lib/graphql/entities/Mutation.ts @@ -35,9 +35,9 @@ export const mutation = mutationType({ type: 'Wizard', description: 'Sets the frontend bundler we want to use for the project', args: { - name: BundlerEnum, + bundler: nonNull(BundlerEnum), }, - resolve: (root, args, ctx) => ctx.wizard.setBundler(args.name), + resolve: (root, args, ctx) => ctx.wizard.setBundler(args.bundler), }) t.field('wizardInstallDependencies', { diff --git a/packages/server/lib/graphql/entities/Wizard.ts b/packages/server/lib/graphql/entities/Wizard.ts index 81b80c3cf200..b0c39c5a39d3 100644 --- a/packages/server/lib/graphql/entities/Wizard.ts +++ b/packages/server/lib/graphql/entities/Wizard.ts @@ -3,6 +3,7 @@ import { BUNDLER, FrontendFramework, Bundler, FRONTEND_FRAMEWORK, TestingTypeEnu import { TestingTypeInfo } from './TestingTypeInfo' import { WizardBundler } from './WizardBundler' import { WizardFrontendFramework } from './WizardFrontendFramework' +import { BundleMapping, PackageMapping, WizardNpmPackage } from './WizardNpmPackage' @nxs.objectType({ description: 'The Wizard is a container for any state associated with initial onboarding to Cypress', @@ -19,12 +20,28 @@ export class Wizard { this.chosenFramework = null } - get framework (): FrontendFramework | null { - return this.chosenFramework + @nxs.field.type(() => WizardFrontendFramework) + get framework (): NxsResult<'Wizard', 'framework'> | null { + return this.chosenFramework ? new WizardFrontendFramework(this, this.chosenFramework) : null } - get bundler (): Bundler | null { - return this.chosenBundler + @nxs.field.type(() => WizardBundler) + get bundler (): NxsResult<'Wizard', 'bundler'> | null { + return this.chosenBundler ? new WizardBundler(this, this.chosenBundler) : null + } + + @nxs.field.list.nonNull.type(() => WizardNpmPackage, { + description: 'A list of packages to install, null if we have not chosen both a framework and bundler', + }) + get packagesToInstall (): NxsResult<'WizardFrontendFramework', 'packagesToInstall'> { + if (!this.chosenFramework || !this.chosenBundler) { + return null + } + + return [ + new WizardNpmPackage(PackageMapping[this.chosenFramework]), + new WizardNpmPackage(BundleMapping[this.chosenBundler]), + ] } @nxs.field.string({ @@ -43,7 +60,7 @@ export class Wizard { // GraphQL Fields: - @nxs.field.type(() => WizardStepEnum) + @nxs.field.nonNull.type(() => WizardStepEnum) step (): NxsResult<'Wizard', 'step'> { return this.currentStep } @@ -64,10 +81,10 @@ export class Wizard { description: 'All of the component testing frameworks to choose from', }) frameworks (): NxsResult<'Wizard', 'frameworks'> { - return FRONTEND_FRAMEWORK.map((f) => new WizardFrontendFramework(f)) + return FRONTEND_FRAMEWORK.map((f) => new WizardFrontendFramework(this, f)) } - @nxs.field.list.type(() => WizardBundler, { + @nxs.field.nonNull.list.nonNull.type(() => WizardBundler, { description: 'All of the bundlers to choose from', }) allBundlers (): NxsResult<'Wizard', 'allBundlers'> { @@ -95,6 +112,12 @@ export class Wizard { return this } + @nxs.field.nonNull.boolean() + canNavigateForward (): NxsResult<'Wizard', 'canNavigateForward'> { + // TODO: add constraints here to determine if we can move forward + return true + } + navigateBack (): Wizard { const idx = WIZARD_STEP.indexOf(this.currentStep) diff --git a/packages/server/lib/graphql/entities/WizardBundler.ts b/packages/server/lib/graphql/entities/WizardBundler.ts index 8eb2edd680bb..04d5573e6071 100644 --- a/packages/server/lib/graphql/entities/WizardBundler.ts +++ b/packages/server/lib/graphql/entities/WizardBundler.ts @@ -8,12 +8,12 @@ import { Wizard } from './Wizard' export class WizardBundler { constructor (private wizard: Wizard, private bundler: Bundler) {} - @nxs.field.type(() => BundlerEnum) + @nxs.field.nonNull.type(() => BundlerEnum) get id (): NxsResult<'WizardBundler', 'id'> { return this.bundler } - @nxs.field.string() + @nxs.field.nonNull.string() get name (): NxsResult<'WizardBundler', 'name'> { return BundlerDisplayNames[this.bundler] } @@ -22,13 +22,14 @@ export class WizardBundler { description: 'Whether this is the selected framework bundler', }) isSelected (): NxsResult<'WizardBundler', 'isSelected'> { - return this.wizard.bundler === this.bundler + return this.wizard.bundler?.id === this.bundler } @nxs.field.boolean({ description: 'Whether there are multiple options to choose from given the framework', }) isOnlyOption (): NxsResult<'WizardBundler', 'isOnlyOption'> { - return true + // TODO: base this on the options available + return false } } diff --git a/packages/server/lib/graphql/entities/WizardFrontendFramework.ts b/packages/server/lib/graphql/entities/WizardFrontendFramework.ts index 27bbe87ef169..ebd4b66e9793 100644 --- a/packages/server/lib/graphql/entities/WizardFrontendFramework.ts +++ b/packages/server/lib/graphql/entities/WizardFrontendFramework.ts @@ -1,39 +1,40 @@ import { nxs, NxsResult } from 'nexus-decorators' -import { FrontendFramework, FrontendFrameworkEnum } from '../constants' +import { BUNDLER, FrameworkDisplayNames, FrontendFramework, FrontendFrameworkEnum } from '../constants' +import { Wizard } from './Wizard' import { WizardBundler } from './WizardBundler' -import { WizardNpmPackage } from './WizardNpmPackage' @nxs.objectType({ description: 'A frontend framework that we can setup within the app', }) export class WizardFrontendFramework { - constructor (private framework: FrontendFramework) {} + constructor (private wizard: Wizard, private framework: FrontendFramework) {} @nxs.field.type(() => FrontendFrameworkEnum, { description: 'The name of the framework', }) - get name (): NxsResult<'WizardFrontendFramework', 'name'> { + get id (): NxsResult<'WizardFrontendFramework', 'id'> { return this.framework } - @nxs.field.list.type(() => WizardBundler, { - description: 'All of the supported bundlers for this framework', + @nxs.field.nonNull.string({ + description: 'The name of the framework', }) - get supportedBundlers (): NxsResult<'WizardFrontendFramework', 'supportedBundlers'> { - return [] + get name (): NxsResult<'WizardFrontendFramework', 'name'> { + return FrameworkDisplayNames[this.framework] } - @nxs.field.list.type(() => WizardNpmPackage, { - description: 'A list of packages to install, null if we have not chosen both a framework and bundler', + @nxs.field.nonNull.list.nonNull.type(() => WizardBundler, { + description: 'All of the supported bundlers for this framework', }) - get packagesToInstall (): NxsResult<'WizardFrontendFramework', 'packagesToInstall'> { - return [] + get supportedBundlers (): NxsResult<'WizardFrontendFramework', 'supportedBundlers'> { + // TODO: make this filtered to the framework + return BUNDLER.map((bundler) => new WizardBundler(this.wizard, bundler)) } - @nxs.field.boolean({ + @nxs.field.nonNull.boolean({ description: 'Whether this is the selected framework in the wizard', }) get isSelected (): NxsResult<'WizardFrontendFramework', 'isSelected'> { - return true + return this.wizard.framework?.id === this.framework } } diff --git a/packages/server/lib/graphql/entities/WizardNpmPackage.ts b/packages/server/lib/graphql/entities/WizardNpmPackage.ts index c16c335a6588..02215a33b9b5 100644 --- a/packages/server/lib/graphql/entities/WizardNpmPackage.ts +++ b/packages/server/lib/graphql/entities/WizardNpmPackage.ts @@ -1,18 +1,45 @@ import { nxs, NxsResult } from 'nexus-decorators' +import { Bundler, FrontendFramework } from '../constants' @nxs.objectType({ description: 'Details about an NPM Package listed during the wizard install', }) export class WizardNpmPackage { - @nxs.field.string({ + constructor (private pkg: NpmPackages) {} + + @nxs.field.nonNull.string({ description: 'The package name that you would npm install', }) name (): NxsResult<'WizardNpmPackage', 'name'> { - return 'name' + return this.pkg } - @nxs.field.string() + @nxs.field.nonNull.string() description (): NxsResult<'WizardNpmPackage', 'description'> { - return 'description' + return PACKAGES_DESCRIPTIONS[this.pkg] } } + +export const PACKAGES_DESCRIPTIONS = { + '@cypress/vue': 'Allows Cypress to mount each Vue component using cy.mount()', + '@cypress/react': 'Allows Cypress to mount each React component using cy.mount()', + '@cypress/webpack-dev-server': 'Allows Cypress to use your existing build configuration in order to bundle and run your tests', + '@cypress/vite-dev-server': 'Allows Cypress to use your existing build configuration in order to bundle and run your tests', + '@cypress/storybook': 'Allows Cypress to automatically read and test each of your stories', +} as const + +export type NpmPackages = keyof typeof PACKAGES_DESCRIPTIONS + +export const PackageMapping: Record = { + nextjs: '@cypress/react', + cra: '@cypress/react', + reactjs: '@cypress/react', + nuxtjs: '@cypress/vue', + vuecli: '@cypress/vue', + vuejs: '@cypress/vue', +} + +export const BundleMapping: Record = { + vite: '@cypress/vite-dev-server', + webpack: '@cypress/webpack-dev-server', +} diff --git a/packages/server/lib/graphql/gen/nxs.gen.ts b/packages/server/lib/graphql/gen/nxs.gen.ts index 73e72f1177b3..ed4def39dedb 100644 --- a/packages/server/lib/graphql/gen/nxs.gen.ts +++ b/packages/server/lib/graphql/gen/nxs.gen.ts @@ -128,29 +128,33 @@ export interface NexusGenFieldTypes { title: string | null; // String } Wizard: { // field return type - allBundlers: Array | null; // [WizardBundler] + allBundlers: NexusGenRootTypes['WizardBundler'][]; // [WizardBundler!]! + bundler: NexusGenRootTypes['WizardBundler'] | null; // WizardBundler + canNavigateForward: boolean; // Boolean! description: string | null; // String + framework: NexusGenRootTypes['WizardFrontendFramework'] | null; // WizardFrontendFramework frameworks: Array | null; // [WizardFrontendFramework] - step: NexusGenEnums['WizardStep'] | null; // WizardStep + packagesToInstall: NexusGenRootTypes['WizardNpmPackage'][] | null; // [WizardNpmPackage!] + step: NexusGenEnums['WizardStep']; // WizardStep! testingType: NexusGenEnums['TestingTypeEnum'] | null; // TestingTypeEnum testingTypes: NexusGenRootTypes['TestingTypeInfo'][] | null; // [TestingTypeInfo!] title: string | null; // String } WizardBundler: { // field return type - id: NexusGenEnums['SupportedBundlers'] | null; // SupportedBundlers + id: NexusGenEnums['SupportedBundlers']; // SupportedBundlers! isOnlyOption: boolean | null; // Boolean isSelected: boolean | null; // Boolean - name: string | null; // String + name: string; // String! } WizardFrontendFramework: { // field return type - isSelected: boolean | null; // Boolean - name: NexusGenEnums['FrontendFramework'] | null; // FrontendFramework - packagesToInstall: Array | null; // [WizardNpmPackage] - supportedBundlers: Array | null; // [WizardBundler] + id: NexusGenEnums['FrontendFramework'] | null; // FrontendFramework + isSelected: boolean; // Boolean! + name: string; // String! + supportedBundlers: NexusGenRootTypes['WizardBundler'][]; // [WizardBundler!]! } WizardNpmPackage: { // field return type - description: string | null; // String - name: string | null; // String + description: string; // String! + name: string; // String! } } @@ -190,8 +194,12 @@ export interface NexusGenFieldTypeNames { } Wizard: { // field return type name allBundlers: 'WizardBundler' + bundler: 'WizardBundler' + canNavigateForward: 'Boolean' description: 'String' + framework: 'WizardFrontendFramework' frameworks: 'WizardFrontendFramework' + packagesToInstall: 'WizardNpmPackage' step: 'WizardStep' testingType: 'TestingTypeEnum' testingTypes: 'TestingTypeInfo' @@ -204,9 +212,9 @@ export interface NexusGenFieldTypeNames { name: 'String' } WizardFrontendFramework: { // field return type name + id: 'FrontendFramework' isSelected: 'Boolean' - name: 'FrontendFramework' - packagesToInstall: 'WizardNpmPackage' + name: 'String' supportedBundlers: 'WizardBundler' } WizardNpmPackage: { // field return type name @@ -221,7 +229,7 @@ export interface NexusGenArgTypes { input: NexusGenInputs['AddProjectInput']; // AddProjectInput! } wizardSetBundler: { // args - name?: NexusGenEnums['SupportedBundlers'] | null; // SupportedBundlers + bundler: NexusGenEnums['SupportedBundlers']; // SupportedBundlers! } wizardSetFramework: { // args framework: NexusGenEnums['FrontendFramework']; // FrontendFramework! diff --git a/packages/server/schema.graphql b/packages/server/schema.graphql index 8807e12ce16a..2f8ba780a8e2 100644 --- a/packages/server/schema.graphql +++ b/packages/server/schema.graphql @@ -56,7 +56,7 @@ type Mutation { wizardNavigateForward: Wizard """Sets the frontend bundler we want to use for the project""" - wizardSetBundler(name: SupportedBundlers): Wizard + wizardSetBundler(bundler: SupportedBundlers!): Wizard """Sets the frontend framework we want to use for the project""" wizardSetFramework(framework: FrontendFramework!): Wizard @@ -119,14 +119,22 @@ The Wizard is a container for any state associated with initial onboarding to Cy """ type Wizard { """All of the bundlers to choose from""" - allBundlers: [WizardBundler] + allBundlers: [WizardBundler!]! + bundler: WizardBundler + canNavigateForward: Boolean! """The title of the page, given the current step of the wizard""" description: String + framework: WizardFrontendFramework """All of the component testing frameworks to choose from""" frameworks: [WizardFrontendFramework] - step: WizardStep + + """ + A list of packages to install, null if we have not chosen both a framework and bundler + """ + packagesToInstall: [WizardNpmPackage!] + step: WizardStep! """ The testing type we are setting in the wizard, null if this has not been chosen @@ -140,39 +148,37 @@ type Wizard { """Wizard bundler""" type WizardBundler { - id: SupportedBundlers + id: SupportedBundlers! """Whether there are multiple options to choose from given the framework""" isOnlyOption: Boolean """Whether this is the selected framework bundler""" isSelected: Boolean - name: String + name: String! } """A frontend framework that we can setup within the app""" type WizardFrontendFramework { + """The name of the framework""" + id: FrontendFramework + """Whether this is the selected framework in the wizard""" - isSelected: Boolean + isSelected: Boolean! """The name of the framework""" - name: FrontendFramework - - """ - A list of packages to install, null if we have not chosen both a framework and bundler - """ - packagesToInstall: [WizardNpmPackage] + name: String! """All of the supported bundlers for this framework""" - supportedBundlers: [WizardBundler] + supportedBundlers: [WizardBundler!]! } """Details about an NPM Package listed during the wizard install""" type WizardNpmPackage { - description: String + description: String! """The package name that you would npm install""" - name: String + name: String! } enum WizardStep { From afbbc88ceb4f9f5bffee43a9547770fc42376f91 Mon Sep 17 00:00:00 2001 From: Lachlan Miller Date: Thu, 29 Jul 2021 15:07:50 +1000 Subject: [PATCH 08/29] yarn lock --- yarn.lock | 45 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index ff0417af6378..cc45af2a036b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3095,6 +3095,14 @@ dependencies: prop-types "^15.7.2" +"@graphql-codegen/add@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@graphql-codegen/add/-/add-2.0.2.tgz#4acbb95be9ebb859a3cebfe7132fdf49ffe06dd8" + integrity sha512-0X1ofeSvAjCNcLar2ZR1EOmm5dvyKJMFbgM+ySf1PaHyoi3yf/xRI2Du81ONzQ733Lhmn3KTX1VKybm/OB1Qtg== + dependencies: + "@graphql-codegen/plugin-helpers" "^1.18.2" + tslib "~2.0.1" + "@graphql-codegen/cli@^1.21.6": version "1.21.6" resolved "https://registry.yarnpkg.com/@graphql-codegen/cli/-/cli-1.21.6.tgz#d06b5f6cb625541f3981d69f99966e520b958072" @@ -3150,16 +3158,16 @@ "@graphql-tools/utils" "^7.9.1" tslib "~2.2.0" -"@graphql-codegen/plugin-helpers@^1.18.7": - version "1.18.7" - resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.18.7.tgz#465af3e5b02de89e49ddc76ad2546b880fe240f2" - integrity sha512-8ICOrXlsvyL1dpVz8C9b7H31d4DJpDd75WfjMn6Xjqz81Ah8xDn1Bi+7YXRCCILCBmvI94k6fi8qpsIVhFBBjQ== +"@graphql-codegen/plugin-helpers@^1.18.2", "@graphql-codegen/plugin-helpers@^1.18.7": + version "1.18.8" + resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.18.8.tgz#39aac745b9e22e28c781cc07cf74836896a3a905" + integrity sha512-mb4I9j9lMGqvGggYuZ0CV+Hme08nar68xkpPbAVotg/ZBmlhZIok/HqW2BcMQi7Rj+Il5HQMeQ1wQ1M7sv/TlQ== dependencies: "@graphql-tools/utils" "^7.9.1" common-tags "1.8.0" - import-from "3.0.0" + import-from "4.0.0" lodash "~4.17.0" - tslib "~2.2.0" + tslib "~2.3.0" "@graphql-codegen/typed-document-node@^1.18.9": version "1.18.9" @@ -6633,6 +6641,15 @@ "@rollup/pluginutils" "^3.1.0" mini-svg-data-uri "^1.2.3" +"@rollup/plugin-inject@^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@rollup/plugin-inject/-/plugin-inject-4.0.2.tgz#55b21bb244a07675f7fdde577db929c82fc17395" + integrity sha512-TSLMA8waJ7Dmgmoc8JfPnwUwVZgLjjIAM6MqeIFqPO2ODK36JqE0Cf2F54UTgCUuW8da93Mvoj75a6KAVWgylw== + dependencies: + "@rollup/pluginutils" "^3.0.4" + estree-walker "^1.0.1" + magic-string "^0.25.5" + "@rollup/plugin-json@^4.1.0": version "4.1.0" resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3" @@ -6660,7 +6677,7 @@ "@rollup/pluginutils" "^3.1.0" resolve "^1.17.0" -"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": +"@rollup/pluginutils@^3.0.4", "@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b" integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg== @@ -22778,6 +22795,11 @@ import-from@3.0.0, import-from@^3.0.0: dependencies: resolve-from "^5.0.0" +import-from@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/import-from/-/import-from-4.0.0.tgz#2710b8d66817d232e16f4166e319248d3d5492e2" + integrity sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ== + import-from@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/import-from/-/import-from-2.1.0.tgz#335db7f2a7affd53aaa471d4b8021dee36b7f3b1" @@ -26867,7 +26889,7 @@ magic-string@0.25.2: dependencies: sourcemap-codec "^1.4.4" -magic-string@0.25.7, magic-string@^0.25.0, magic-string@^0.25.7: +magic-string@0.25.7, magic-string@^0.25.0, magic-string@^0.25.5, magic-string@^0.25.7: version "0.25.7" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== @@ -34919,6 +34941,13 @@ rollup-plugin-peer-deps-external@2.2.4: resolved "https://registry.yarnpkg.com/rollup-plugin-peer-deps-external/-/rollup-plugin-peer-deps-external-2.2.4.tgz#8a420bbfd6dccc30aeb68c9bf57011f2f109570d" integrity sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g== +rollup-plugin-polyfill-node@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-polyfill-node/-/rollup-plugin-polyfill-node-0.7.0.tgz#938e13278c98a582a4f8814975ddd26f90afddcc" + integrity sha512-iJLZDfvxcQh3SpC0OiYlZG9ik26aRM29hiC2sARbAPXYunB8rzW8GtVaWuJgiCtX1hNAo/OaYvVXfPp15fMs7g== + dependencies: + "@rollup/plugin-inject" "^4.0.0" + rollup-plugin-postcss@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/rollup-plugin-postcss/-/rollup-plugin-postcss-4.0.0.tgz#2131fb6db0d5dce01a37235e4f6ad4523c681cea" From 01396ce760ca7dd807e850a0b030471a2b0b308e Mon Sep 17 00:00:00 2001 From: Lachlan Miller Date: Thu, 29 Jul 2021 15:39:00 +1000 Subject: [PATCH 09/29] no need to return props from setup --- packages/launchpad/src/components/EnvironmentSetup.vue | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/launchpad/src/components/EnvironmentSetup.vue b/packages/launchpad/src/components/EnvironmentSetup.vue index 6ac5057af851..8c4d5ab24bd3 100644 --- a/packages/launchpad/src/components/EnvironmentSetup.vue +++ b/packages/launchpad/src/components/EnvironmentSetup.vue @@ -86,8 +86,6 @@ export default defineComponent({ const disabledBundlerSelect = ref(false); - debugger - const setFEBundler = (bundler: SupportedBundlers) => { setBundler.mutate({ bundler }) }; @@ -97,10 +95,8 @@ export default defineComponent({ }; return { - gql: props.gql, setFEFramework, setFEBundler, - bundlers: props.gql?.allBundlers, disabledBundlerSelect, }; }, From 511db1aeb1ed3ff1d9da81f6272523f6439306a8 Mon Sep 17 00:00:00 2001 From: Lachlan Miller Date: Thu, 29 Jul 2021 16:31:52 +1000 Subject: [PATCH 10/29] use useResult for more concise and better type safety --- .../launchpad/src/components/ButtonBar.vue | 8 +++--- .../src/components/EnvironmentSetup.vue | 20 +++++++++++--- packages/launchpad/src/components/Select.vue | 4 +-- packages/launchpad/src/components/Wizard.vue | 27 +++++++++++-------- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/packages/launchpad/src/components/ButtonBar.vue b/packages/launchpad/src/components/ButtonBar.vue index 8d0632cd1b80..d4eb188501ba 100644 --- a/packages/launchpad/src/components/ButtonBar.vue +++ b/packages/launchpad/src/components/ButtonBar.vue @@ -10,10 +10,10 @@ rounded-b " > - - + +
-
+
@@ -60,7 +60,7 @@ export default defineComponent({ props.altFn?.(!altValue.value); }; - return { nextFunction: props.nextFn, backFunction: props.backFn, altFunction: props.altFn, altValue, handleAlt }; + return { altValue, handleAlt }; }, }); diff --git a/packages/launchpad/src/components/EnvironmentSetup.vue b/packages/launchpad/src/components/EnvironmentSetup.vue index 8c4d5ab24bd3..115afc68908d 100644 --- a/packages/launchpad/src/components/EnvironmentSetup.vue +++ b/packages/launchpad/src/components/EnvironmentSetup.vue @@ -4,7 +4,7 @@