Skip to content

Commit

Permalink
feat: migrate to @nexus/schema from nexus framework (#68)
Browse files Browse the repository at this point in the history
* migrate to nexus schema + fixes

* add tsconfig.cjs.json

* fix package.json

* add copyDirWiithTemplate

* tests and utils

* Update template/pages/api/graphql.ts.ejs

* fix up
  • Loading branch information
cball authored Oct 6, 2020
1 parent 6261fe7 commit 4c7d557
Show file tree
Hide file tree
Showing 24 changed files with 371 additions and 119 deletions.
72 changes: 19 additions & 53 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,20 @@
"use strict";
const { promisify } = require("util");
const path = require("path");
const fs = require("fs");
const slugify = require("slugify");
const execa = require("execa");
const Listr = require("listr");
const cpy = require("cpy");
const nodegit = require("nodegit");
const ejs = require("ejs");
const color = require("chalk");
const { copyWithTemplate } = require("./utils/copyWithTemplate");
const {
copyDirectoryWithTemplate,
} = require("./utils/copyDirectoryWithTemplate");

const writeFile = promisify(fs.writeFile);
const mkdir = promisify(fs.mkdir);
const templateFolder = path.join(__dirname, "template");
const fromPath = (file) => path.join(templateFolder, file);

/**
* Copies a file to a different location, running it through an optional ejs template
* @param {*} from The source file
* @param {*} to The path to write
* @param {*} variables Variables to pass to the template
*/
async function copyWithTemplate(from, to, variables) {
const generatedSource = await ejs.renderFile(
from,
{ ...variables, color },
{
async: true,
}
);

try {
await mkdir(path.dirname(to), { recursive: true });
} catch (e) {}

await writeFile(to, generatedSource);
}

module.exports = async ({ name, ...answers }) => {
const pkgName = slugify(name);
const targetFolder = path.join(process.cwd(), pkgName);
Expand Down Expand Up @@ -92,33 +70,33 @@ module.exports = async ({ name, ...answers }) => {
variables
),

copyWithTemplate(
fromPath(".github/workflows/main.js.yml.ejs"),
toPath(".github/workflows/main.js.yml"),
copyDirectoryWithTemplate(
fromPath(".github"),
toPath(".github"),
variables
),

copyWithTemplate(
fromPath(".github/PULL_REQUEST_TEMPLATE.md"),
toPath(".github/PULL_REQUEST_TEMPLATE.md"),
copyDirectoryWithTemplate(
fromPath("pages"),
toPath("pages"),
variables
),

copyWithTemplate(
fromPath("pages/api/graphql.ts.ejs"),
toPath("pages/api/graphql.ts"),
fromPath("prisma/_.env.ejs"),
toPath("prisma/.env"),
variables
),

copyWithTemplate(
fromPath("prisma/_.env.ejs"),
toPath("prisma/.env"),
copyDirectoryWithTemplate(
fromPath("graphql"),
toPath("graphql"),
variables
),

copyWithTemplate(
fromPath("tests/jest.setup.js.ejs"),
toPath("tests/jest.setup.js"),
copyDirectoryWithTemplate(
fromPath("tests"),
toPath("tests"),
variables
),

Expand All @@ -134,18 +112,13 @@ module.exports = async ({ name, ...answers }) => {
"components",
"context",
"cypress",
"graphql",
"layouts",
"lib",
"pages",
"!pages/api/graphql*",
"prisma",
"!prisma/_.env*",
"public",
"scripts",
"services",
"tests",
"!tests/jest.setup*",
"utils",
".eslintrc.js",
".gitignore",
Expand All @@ -159,6 +132,7 @@ module.exports = async ({ name, ...answers }) => {
"next-env.d.ts",
"prettier.config.js",
"tsconfig.json",
"tsconfig.cjs.json",
"types.ts",
],
targetFolder,
Expand All @@ -185,14 +159,6 @@ module.exports = async ({ name, ...answers }) => {
});
},
},
{
title: "Generate Nexus types",
task: async () => {
return execa("yarn", ["nexus", "build", "--no-bundle"], {
cwd: pkgName,
});
},
},
{
title: "Git init",
task: async () => {
Expand Down
6 changes: 2 additions & 4 deletions scripts/createApp.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
const { promisify } = require("util");
const fs = require("fs");
const os = require("os");
const path = require("path");
const mkdtemp = promisify(fs.mkdtemp);
const execa = require("execa");
const { makeTempDir } = require("../utils/makeTempDir");
module.exports = {};

async function init() {
Expand All @@ -12,7 +10,7 @@ async function init() {
}

async function createTmpDir() {
const tmpDir = await mkdtemp(`${os.tmpdir()}${path.sep}`);
const tmpDir = await makeTempDir();

if (!fs.existsSync("./tmp")) {
await fs.promises.mkdir("./tmp");
Expand Down
4 changes: 2 additions & 2 deletions template/.github/workflows/main.js.yml.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ jobs:
with:
heroku_api_key: ${{secrets.HEROKU_API_KEY}}
heroku_email: ${{secrets.HEROKU_EMAIL}}
heroku_app_name: <%= host.staging.name %>
heroku_app_name: <%= host.staging?.name %>
<% } -%>
deployProd:
Expand Down Expand Up @@ -166,5 +166,5 @@ jobs:
with:
heroku_api_key: ${{secrets.HEROKU_API_KEY}}
heroku_email: ${{secrets.HEROKU_EMAIL}}
heroku_app_name: <%= host.production.name %>
heroku_app_name: <%= host.production?.name %>
<% } -%>
13 changes: 7 additions & 6 deletions template/_templates/graphql/new/graphql.ejs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
---
to: graphql/<%= name %>.ts
to: graphql/modules/<%= name %>.ts
---
<% camelized = h.inflection.camelize(name) -%>
<% plural = h.inflection.pluralize(camelized) -%>
import { schema } from 'nexus';
import { AuthenticationError, UserInputError, /*ForbiddenError*/ } from 'apollo-server-errors';
import { objectType, extendType } from '@nexus/schema';
import { UserInputError, /*ForbiddenError*/ } from 'apollo-server-micro';

// import { isAdmin } from '../services/permissions';

// <%= camelized %> Type
schema.objectType({
export const <%= camelized %> = objectType({
name: '<%= camelized %>',
description: 'A <%= camelized %>',
definition(t) {
Expand All @@ -27,7 +27,7 @@ export const CallPreference = enumType({
});

// Queries
schema.extendType({
export const <%= camelized %>Queries = extendType({
type: 'Query',
definition: (t) => {
// List <%= plural %> Query (admin only)
Expand Down Expand Up @@ -58,7 +58,8 @@ schema.extendType({
});

// Mutations
schema.mutationType({
export const <%= camelized %>Mutations = extendType({
type: 'Mutation',
definition: (t) => {
},
});
Expand Down
8 changes: 4 additions & 4 deletions template/_templates/graphql/new/injectImport.ejs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
inject: true
to: graphql/schema.ts
after: import './context'
skip_if: import './<%= name %>'
to: graphql/modules/index.ts
after: export * from './scalars'
skip_if: export * from './<%= name %>'
---
import './<%= name %>';
export * from './<%= name %>';
30 changes: 24 additions & 6 deletions template/graphql/context.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,33 @@
import { schema } from 'nexus';
import { IncomingMessage } from 'http';

import { Context as ApolloContext } from 'apollo-server-core';
import { PrismaClient, User } from '@prisma/client';

import { prisma } from '../lib/prisma';
import { verifyAuthHeader } from '../services/auth';

schema.addToContext(async (data) => {
const authHeader = verifyAuthHeader((data as any).req.headers.authorization);
let user = null;
/**
* Populates a context object for use in resolvers.
* If there is a valid auth token in the authorization header, it will add the user to the context
* @param context context from apollo server
*/
export async function createContext(context: ApolloApiContext): Promise<Context> {
const authHeader = verifyAuthHeader(context.req.headers.authorization);
let user: User | null = null;

if (authHeader) {
user = await prisma.user.findOne({ where: { id: authHeader.userId } });
}

return { user };
});
return {
db: prisma,
user,
};
}

type ApolloApiContext = ApolloContext<{ req: IncomingMessage }>;

export type Context = {
db: PrismaClient;
user: User;
};
3 changes: 3 additions & 0 deletions template/graphql/modules/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './scalars';
export * from './user';
export * from './profile';
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { schema } from 'nexus';
import { objectType } from '@nexus/schema';

// Profile Type
schema.objectType({
export const Profile = objectType({
name: 'Profile',
description: 'A User Profile',
definition(t) {
Expand Down
5 changes: 5 additions & 0 deletions template/graphql/modules/scalars.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { DateTimeResolver, JSONObjectResolver } from 'graphql-scalars';
import { asNexusMethod } from '@nexus/schema';

export const jsonScalar = asNexusMethod(JSONObjectResolver, 'json');
export const dateTimeScalar = asNexusMethod(DateTimeResolver, 'date');
29 changes: 18 additions & 11 deletions template/graphql/user.ts → template/graphql/modules/user.ts.ejs
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { schema } from 'nexus';
import { objectType, extendType, inputObjectType, stringArg, arg } from '@nexus/schema';
import { Role } from '@prisma/client';
<% if (host.name === 'vercel') { -%>
import { UserInputError, ForbiddenError } from 'apollo-server-micro';
<% } -%>
<% if (host.name === 'heroku') { -%>
import { UserInputError, ForbiddenError } from 'apollo-server-express';
<% } -%>

import { hashPassword, appJwtForUser, comparePasswords } from '../services/auth';
import { isAdmin, canAccess } from '../services/permissions';
import { hashPassword, appJwtForUser, comparePasswords } from '../../services/auth';
import { isAdmin, canAccess } from '../../services/permissions';

// User Type
schema.objectType({
export const User = objectType({
name: 'User',
description: 'A User',
definition(t) {
Expand All @@ -26,7 +31,7 @@ schema.objectType({
});

// Auth Payload Type
schema.objectType({
export const AuthPayload = objectType({
name: 'AuthPayload',
description: 'Payload returned if login or signup is successful',
definition(t) {
Expand All @@ -38,7 +43,8 @@ schema.objectType({
});

// Queries
schema.queryType({
export const UserQueries = extendType({
type: 'Query',
definition: (t) => {
// Me Query
t.field('me', {
Expand All @@ -65,15 +71,16 @@ schema.queryType({
});

// Mutations
schema.mutationType({
export const Mutations = extendType({
type: 'Mutation',
definition: (t) => {
// Login Mutation
t.field('login', {
type: 'AuthPayload',
description: 'Login to an existing account',
args: {
email: schema.stringArg({ required: true }),
password: schema.stringArg({ required: true }),
email: stringArg({ required: true }),
password: stringArg({ required: true }),
},
resolve: async (_root, args, ctx) => {
const { email, password } = args;
Expand Down Expand Up @@ -134,7 +141,7 @@ schema.mutationType({
type: 'AuthPayload',
description: 'Signup for an account',
args: {
data: schema.arg({ type: 'SignupInput', required: true }),
data: arg({ type: 'SignupInput', required: true }),
},
resolve: async (_root, args, ctx) => {
const existingUser = await ctx.db.user.findOne({ where: { email: args.data.email } });
Expand Down Expand Up @@ -166,7 +173,7 @@ schema.mutationType({
},
});

schema.inputObjectType({
export const SignupInput = inputObjectType({
name: 'SignupInput',
definition: (t) => {
t.string('email', { required: true });
Expand Down
Loading

0 comments on commit 4c7d557

Please sign in to comment.