Browse Source

feat: introduce gql schema sdl generation to the backend (#35)

* feat: introduce gql schema sdl generation to the backend

* chore: update gql-codegen consumers to get schema from generated sdl

* chore: hoppscotch-backend generates gql sdl on postinstall

* fix: add back missed part of generate-gql-sdl script

* chore: updated generate sdl script to hardcode whitelisted domains

* chore: add prisma generate on postinstall script

---------

Co-authored-by: ankitsridhar16 <ankit.sridhar16@gmail.com>
Andrew Bastin 2 years ago
parent
commit
65719b560b

+ 3 - 0
.gitignore

@@ -171,3 +171,6 @@ tests/*/videos
 
 # PNPM
 .pnpm-store
+
+# GQL SDL generated for the frontends
+gql-gen/

+ 2 - 0
package.json

@@ -9,6 +9,7 @@
     "preinstall": "npx only-allow pnpm",
     "prepare": "husky install",
     "dev": "pnpm -r do-dev",
+    "gen-gql": "cross-env GQL_SCHEMA_EMIT_LOCATION='../../../gql-gen/backend-schema.gql' pnpm -r generate-gql-sdl",
     "generate": "pnpm -r do-build-prod",
     "start": "http-server packages/hoppscotch-web/dist -p 3000",
     "lint": "pnpm -r do-lint",
@@ -29,6 +30,7 @@
     "@commitlint/cli": "^16.2.3",
     "@commitlint/config-conventional": "^16.2.1",
     "@types/node": "^17.0.24",
+    "cross-env": "^7.0.3",
     "http-server": "^14.1.1"
   }
 }

+ 3 - 0
packages/hoppscotch-backend/.gitignore

@@ -38,3 +38,6 @@ lerna-debug.log*
 !.vscode/tasks.json
 !.vscode/launch.json
 !.vscode/extensions.json
+
+# Generated artifacts (GQL Schema SDL generation etc.)
+gen/

+ 3 - 0
packages/hoppscotch-backend/package.json

@@ -8,6 +8,7 @@
   "scripts": {
     "prebuild": "rimraf dist",
     "build": "nest build",
+    "generate-gql-sdl": "cross-env GQL_SCHEMA_EMIT_LOCATION='../../../gql-gen/backend-schema.gql' GENERATE_GQL_SCHEMA=true WHITELISTED_ORIGINS='' nest start",
     "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
     "start": "nest start",
     "start:dev": "nest start --watch",
@@ -15,6 +16,7 @@
     "start:prod": "node dist/main",
     "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
     "test": "jest",
+    "postinstall": "prisma generate && pnpm run generate-gql-sdl",
     "test:watch": "jest --watch",
     "test:cov": "jest --coverage",
     "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
@@ -76,6 +78,7 @@
     "@types/supertest": "^2.0.12",
     "@typescript-eslint/eslint-plugin": "^5.45.0",
     "@typescript-eslint/parser": "^5.45.0",
+    "cross-env": "^7.0.3",
     "eslint": "^8.29.0",
     "eslint-config-prettier": "^8.5.0",
     "eslint-plugin-prettier": "^4.2.1",

+ 95 - 0
packages/hoppscotch-backend/src/gql-schema.ts

@@ -0,0 +1,95 @@
+import { NestFactory } from '@nestjs/core';
+import {
+  GraphQLSchemaBuilderModule,
+  GraphQLSchemaFactory,
+} from '@nestjs/graphql';
+import { printSchema } from 'graphql/utilities';
+import * as path from 'path';
+import * as fs from 'fs';
+import { ShortcodeResolver } from './shortcode/shortcode.resolver';
+import { TeamCollectionResolver } from './team-collection/team-collection.resolver';
+import { TeamEnvironmentsResolver } from './team-environments/team-environments.resolver';
+import { TeamInvitationResolver } from './team-invitation/team-invitation.resolver';
+import { TeamRequestResolver } from './team-request/team-request.resolver';
+import { TeamMemberResolver } from './team/team-member.resolver';
+import { TeamResolver } from './team/team.resolver';
+import { UserCollectionResolver } from './user-collection/user-collection.resolver';
+import { UserEnvironmentsResolver } from './user-environment/user-environments.resolver';
+import { UserHistoryResolver } from './user-history/user-history.resolver';
+import { UserRequestResolver } from './user-request/resolvers/user-request.resolver';
+import { UserSettingsResolver } from './user-settings/user-settings.resolver';
+import { UserResolver } from './user/user.resolver';
+import { Logger } from '@nestjs/common';
+
+/**
+ * All the resolvers present in the application.
+ *
+ * NOTE: This needs to be KEPT UP-TO-DATE to keep the schema accurate
+ */
+const RESOLVERS = [
+  ShortcodeResolver,
+  TeamResolver,
+  TeamMemberResolver,
+  TeamCollectionResolver,
+  TeamEnvironmentsResolver,
+  TeamInvitationResolver,
+  TeamRequestResolver,
+  UserResolver,
+  UserCollectionResolver,
+  UserEnvironmentsResolver,
+  UserHistoryResolver,
+  UserCollectionResolver,
+  UserRequestResolver,
+  UserSettingsResolver,
+];
+
+/**
+ * All the custom scalars present in the application.
+ *
+ * NOTE: This needs to be KEPT UP-TO-DATE to keep the schema accurate
+ */
+const SCALARS = [];
+
+/**
+ * Generates the GraphQL Schema SDL definition and writes it into the location
+ * specified by the `GQL_SCHEMA_EMIT_LOCATION` environment variable.
+ */
+export async function emitGQLSchemaFile() {
+  const logger = new Logger('emitGQLSchemaFile');
+
+  try {
+    const destination = path.resolve(
+      __dirname,
+      process.env.GQL_SCHEMA_EMIT_LOCATION ?? '../gen/schema.gql',
+    );
+
+    logger.log(`GQL_SCHEMA_EMIT_LOCATION: ${destination}`);
+
+    const app = await NestFactory.create(GraphQLSchemaBuilderModule);
+    await app.init();
+
+    const gqlSchemaFactory = app.get(GraphQLSchemaFactory);
+
+    logger.log(
+      `Generating Schema against ${RESOLVERS.length} resolvers and ${SCALARS.length} custom scalars`,
+    );
+
+    const schema = await gqlSchemaFactory.create(RESOLVERS, SCALARS);
+
+    const schemaString = printSchema(schema, {
+      commentDescriptions: true,
+    });
+
+    logger.log(`Writing schema to GQL_SCHEMA_EMIT_LOCATION (${destination})`);
+
+    // Generating folders if required to emit to the given output
+    fs.mkdirSync(path.dirname(destination), { recursive: true });
+    fs.writeFileSync(destination, schemaString);
+
+    logger.log(`Wrote schema to GQL_SCHEMA_EMIT_LOCATION (${destination})`);
+  } catch (e) {
+    logger.error(
+      `Failed writing schema to GQL_SCHEMA_EMIT_LOCATION. Reason: ${e}`,
+    );
+  }
+}

+ 7 - 1
packages/hoppscotch-backend/src/main.ts

@@ -3,6 +3,7 @@ import { json } from 'express';
 import { AppModule } from './app.module';
 import * as cookieParser from 'cookie-parser';
 import { VersioningType } from '@nestjs/common';
+import { emitGQLSchemaFile } from './gql-schema';
 
 async function bootstrap() {
   console.log(`Running in production: ${process.env.PRODUCTION}`);
@@ -38,4 +39,9 @@ async function bootstrap() {
   app.use(cookieParser());
   await app.listen(process.env.PORT || 3170);
 }
-bootstrap();
+
+if (!process.env.GENERATE_GQL_SCHEMA) {
+  bootstrap();
+} else {
+  emitGQLSchemaFile();
+}

+ 1 - 2
packages/hoppscotch-common/gql-codegen.yml

@@ -1,6 +1,5 @@
 overwrite: true
-schema:
-  - ${VITE_BACKEND_GQL_URL}
+schema: "../../gql-gen/*.gql"
 generates:
   src/helpers/backend/graphql.ts:
     documents: "src/**/*.graphql"

+ 1 - 1
packages/hoppscotch-sh-admin/gql-codegen.yml

@@ -1,5 +1,5 @@
 overwrite: true
-schema: ${VITE_BACKEND_GQL_URL}
+schema: "../../gql-gen/*.gql"
 generates:
   src/helpers/backend/graphql.ts:
     documents: 'src/**/*.graphql'

+ 5 - 1
pnpm-lock.yaml

@@ -7,6 +7,7 @@ importers:
       '@commitlint/cli': ^16.2.3
       '@commitlint/config-conventional': ^16.2.1
       '@types/node': ^17.0.24
+      cross-env: ^7.0.3
       http-server: ^14.1.1
       husky: ^7.0.4
       lint-staged: ^12.3.8
@@ -17,6 +18,7 @@ importers:
       '@commitlint/cli': 16.3.0
       '@commitlint/config-conventional': 16.2.4
       '@types/node': 17.0.45
+      cross-env: 7.0.3
       http-server: 14.1.1
 
   packages/codemirror-lang-graphql:
@@ -77,6 +79,7 @@ importers:
       bcrypt: ^5.1.0
       cookie: ^0.5.0
       cookie-parser: ^1.4.6
+      cross-env: ^7.0.3
       eslint: ^8.29.0
       eslint-config-prettier: ^8.5.0
       eslint-plugin-prettier: ^4.2.1
@@ -166,6 +169,7 @@ importers:
       '@types/supertest': 2.0.12
       '@typescript-eslint/eslint-plugin': 5.45.0_yjegg5cyoezm3fzsmuszzhetym
       '@typescript-eslint/parser': 5.45.0_s5ps7njkmjlaqajutnox5ntcla
+      cross-env: 7.0.3
       eslint: 8.29.0
       eslint-config-prettier: 8.5.0_eslint@8.29.0
       eslint-plugin-prettier: 4.2.1_eabs6augosioka4cd2cz5tdama
@@ -9957,7 +9961,7 @@ packages:
     dev: false
 
   /concat-map/0.0.1:
-    resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+    resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
 
   /concat-stream/1.6.2:
     resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==}