feat(lint): enable lint (#11)

pull/29/head
Minghe 2022-07-30 21:36:01 +08:00 committed by GitHub
parent 7166554bf0
commit ce086cba19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1255 additions and 118 deletions

1
.eslintignore 100644
View File

@ -0,0 +1 @@
dist

60
.eslintrc.js 100644
View File

@ -0,0 +1,60 @@
const { defineConfig } = require('eslint-define-config')
module.exports = defineConfig({
root: true,
extends: [
'eslint:recommended',
'plugin:node/recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module',
ecmaVersion: 2021,
},
plugins: ['@typescript-eslint', 'import'],
globals: {
fetch: false,
Response: false,
Request: false,
addEventListener: false,
},
rules: {
quotes: ['error', 'single'],
semi: ['error', 'never'],
'no-debugger': ['error'],
'no-empty': ['warn', { allowEmptyCatch: true }],
'no-process-exit': 'off',
'no-useless-escape': 'off',
'prefer-const': [
'warn',
{
destructuring: 'all',
},
],
'@typescript-eslint/ban-types': [
'error',
{
types: {
Function: false,
},
},
],
'sort-imports': 0,
'import/order': [2, { alphabetize: { order: 'asc' } }],
'node/no-missing-import': 'off',
'node/no-missing-require': 'off',
'node/no-deprecated-api': 'off',
'node/no-unpublished-import': 'off',
'node/no-unpublished-require': 'off',
'node/no-unsupported-features/es-syntax': 'off',
'@typescript-eslint/no-empty-function': ['error', { allow: ['arrowFunctions'] }],
'@typescript-eslint/no-empty-interface': 'off',
'@typescript-eslint/no-inferrable-types': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
},
})

View File

@ -14,6 +14,7 @@ jobs:
with: with:
node-version: 16.x node-version: 16.x
- run: yarn - run: yarn
- run: yarn lint
- run: yarn build - run: yarn build
- run: yarn test - run: yarn test

9
.prettierrc 100644
View File

@ -0,0 +1,9 @@
{
"printWidth": 100,
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"jsxSingleQuote": true,
"endOfLine": "lf"
}

View File

@ -21,6 +21,8 @@
"test:all": "yarn test && yarn test:deno && yarn test:bun", "test:all": "yarn test && yarn test:deno && yarn test:bun",
"denoify": "rimraf deno_dist && denoify && rimraf 'deno_dist/**/*.test.ts'", "denoify": "rimraf deno_dist && denoify && rimraf 'deno_dist/**/*.test.ts'",
"build": "rimraf dist && tsc --project tsconfig.build.json", "build": "rimraf dist && tsc --project tsconfig.build.json",
"lint": "eslint --ext js,ts src .eslintrc.js",
"lint:fix": "eslint --ext js,ts src .eslintrc.js --fix",
"release": "npm publish" "release": "npm publish"
}, },
"dependencies": { "dependencies": {
@ -33,9 +35,22 @@
"denoify": "^0.11.1", "denoify": "^0.11.1",
"jest": "^28.1.2", "jest": "^28.1.2",
"jest-environment-miniflare": "^2.6.0", "jest-environment-miniflare": "^2.6.0",
"@typescript-eslint/eslint-plugin": "^5.21.0",
"@typescript-eslint/parser": "^5.21.0",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"eslint": "^8.14.0",
"eslint-config-prettier": "^8.5.0",
"eslint-define-config": "^1.4.0",
"eslint-import-resolver-typescript": "^2.7.1",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-flowtype": "^8.0.3",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-node": "^11.1.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"ts-jest": "^28.0.5", "ts-jest": "^28.0.5",
"typescript": "^4.7.4" "typescript": "^4.7.4"
},
"engines": {
"node": ">=11.0.0"
} }
} }

View File

@ -10,7 +10,7 @@ import {
specifiedRules, specifiedRules,
getOperationAST, getOperationAST,
GraphQLError, GraphQLError,
} from "graphql"; } from 'graphql'
import type { import type {
GraphQLSchema, GraphQLSchema,
@ -18,101 +18,88 @@ import type {
ValidationRule, ValidationRule,
FormattedExecutionResult, FormattedExecutionResult,
GraphQLFormattedError, GraphQLFormattedError,
} from "graphql"; } from 'graphql'
import type { Context } from "hono"; import type { Context } from 'hono'
import type { Next } from "hono"; import type { Next } from 'hono'
import { parseBody } from "./parse-body"; import { parseBody } from './parse-body'
type Options = { type Options = {
schema: GraphQLSchema; schema: GraphQLSchema
rootValue?: unknown; rootValue?: unknown
pretty?: boolean; pretty?: boolean
validationRules?: ReadonlyArray<ValidationRule>; validationRules?: ReadonlyArray<ValidationRule>
// graphiql?: boolean // graphiql?: boolean
}; }
export const graphqlServer = (options: Options) => { export const graphqlServer = (options: Options) => {
const schema = options.schema; const schema = options.schema
const rootValue = options.rootValue; const rootValue = options.rootValue
const pretty = options.pretty ?? false; const pretty = options.pretty ?? false
const validationRules = options.validationRules ?? []; const validationRules = options.validationRules ?? []
// const showGraphiQL = options.graphiql ?? false // const showGraphiQL = options.graphiql ?? false
return async (c: Context, next: Next) => { return async (c: Context, next: Next) => {
// GraphQL HTTP only supports GET and POST methods. // GraphQL HTTP only supports GET and POST methods.
if (c.req.method !== "GET" && c.req.method !== "POST") { if (c.req.method !== 'GET' && c.req.method !== 'POST') {
return c.json( return c.json(errorMessages(['GraphQL only supports GET and POST requests.']), 405, {
errorMessages(["GraphQL only supports GET and POST requests."]), Allow: 'GET, POST',
405, })
{
Allow: "GET, POST",
}
);
} }
let params: GraphQLParams; let params: GraphQLParams
try { try {
params = await getGraphQLParams(c.req); params = await getGraphQLParams(c.req)
} catch (e) { } catch (e) {
if (e instanceof Error) { if (e instanceof Error) {
console.error(`${e.stack || e.message}`); console.error(`${e.stack || e.message}`)
return c.json(errorMessages([e.message], [e]), 400); return c.json(errorMessages([e.message], [e]), 400)
} }
throw e; throw e
} }
const { query, variables, operationName } = params; const { query, variables, operationName } = params
if (query == null) { if (query == null) {
return c.json(errorMessages(["Must provide query string."]), 400); return c.json(errorMessages(['Must provide query string.']), 400)
} }
const schemaValidationErrors = validateSchema(schema); const schemaValidationErrors = validateSchema(schema)
if (schemaValidationErrors.length > 0) { if (schemaValidationErrors.length > 0) {
// Return 500: Internal Server Error if invalid schema. // Return 500: Internal Server Error if invalid schema.
return c.json( return c.json(
errorMessages( errorMessages(['GraphQL schema validation error.'], schemaValidationErrors),
["GraphQL schema validation error."],
schemaValidationErrors
),
500 500
); )
} }
let documentAST: DocumentNode; let documentAST: DocumentNode
try { try {
documentAST = parse(new Source(query, "GraphQL request")); documentAST = parse(new Source(query, 'GraphQL request'))
} catch (syntaxError: unknown) { } catch (syntaxError: unknown) {
// Return 400: Bad Request if any syntax errors errors exist. // Return 400: Bad Request if any syntax errors errors exist.
if (syntaxError instanceof Error) { if (syntaxError instanceof Error) {
console.error(`${syntaxError.stack || syntaxError.message}`); console.error(`${syntaxError.stack || syntaxError.message}`)
const e = new GraphQLError(syntaxError.message, { const e = new GraphQLError(syntaxError.message, {
originalError: syntaxError, originalError: syntaxError,
}); })
return c.json(errorMessages(["GraphQL syntax error."], [e]), 400); return c.json(errorMessages(['GraphQL syntax error.'], [e]), 400)
} }
throw syntaxError; throw syntaxError
} }
// Validate AST, reporting any errors. // Validate AST, reporting any errors.
const validationErrors = validate(schema, documentAST, [ const validationErrors = validate(schema, documentAST, [...specifiedRules, ...validationRules])
...specifiedRules,
...validationRules,
]);
if (validationErrors.length > 0) { if (validationErrors.length > 0) {
// Return 400: Bad Request if any validation errors exist. // Return 400: Bad Request if any validation errors exist.
return c.json( return c.json(errorMessages(['GraphQL validation error.'], validationErrors), 400)
errorMessages(["GraphQL validation error."], validationErrors),
400
);
} }
if (c.req.method === "GET") { if (c.req.method === 'GET') {
// Determine if this GET request will perform a non-query. // Determine if this GET request will perform a non-query.
const operationAST = getOperationAST(documentAST, operationName); const operationAST = getOperationAST(documentAST, operationName)
if (operationAST && operationAST.operation !== "query") { if (operationAST && operationAST.operation !== 'query') {
/* /*
Now , does not support GraphiQL Now , does not support GraphiQL
if (showGraphiQL) { if (showGraphiQL) {
@ -126,12 +113,12 @@ export const graphqlServer = (options: Options) => {
`Can only perform a ${operationAST.operation} operation from a POST request.`, `Can only perform a ${operationAST.operation} operation from a POST request.`,
]), ]),
405, 405,
{ Allow: "POST" } { Allow: 'POST' }
); )
} }
} }
let result: FormattedExecutionResult; let result: FormattedExecutionResult
try { try {
result = await execute({ result = await execute({
@ -140,29 +127,23 @@ export const graphqlServer = (options: Options) => {
rootValue, rootValue,
variableValues: variables, variableValues: variables,
operationName: operationName, operationName: operationName,
}); })
} catch (contextError: unknown) { } catch (contextError: unknown) {
if (contextError instanceof Error) { if (contextError instanceof Error) {
console.error(`${contextError.stack || contextError.message}`); console.error(`${contextError.stack || contextError.message}`)
const e = new GraphQLError(contextError.message, { const e = new GraphQLError(contextError.message, {
originalError: contextError, originalError: contextError,
nodes: documentAST, nodes: documentAST,
}); })
// Return 400: Bad Request if any execution context errors exist. // Return 400: Bad Request if any execution context errors exist.
return c.json( return c.json(errorMessages(['GraphQL execution context error.'], [e]), 400)
errorMessages(["GraphQL execution context error."], [e]),
400
);
} }
throw contextError; throw contextError
} }
if (!result.data) { if (!result.data) {
if (result.errors) { if (result.errors) {
return c.json( return c.json(errorMessages([result.errors.toString()], result.errors), 500)
errorMessages([result.errors.toString()], result.errors),
500
);
} }
} }
@ -173,70 +154,67 @@ export const graphqlServer = (options: Options) => {
*/ */
if (pretty) { if (pretty) {
const payload = JSON.stringify(result, null, pretty ? 2 : 0); const payload = JSON.stringify(result, null, pretty ? 2 : 0)
return c.text(payload, 200, { return c.text(payload, 200, {
"Content-Type": "application/json", 'Content-Type': 'application/json',
}); })
} else { } else {
return c.json(result); return c.json(result)
} }
await next(); // XXX await next() // XXX
}; }
};
export interface GraphQLParams {
query: string | null;
variables: { readonly [name: string]: unknown } | null;
operationName: string | null;
raw: boolean;
} }
export const getGraphQLParams = async ( export interface GraphQLParams {
request: Request query: string | null
): Promise<GraphQLParams> => { variables: { readonly [name: string]: unknown } | null
const urlData = new URLSearchParams(request.url.split("?")[1]); operationName: string | null
const bodyData = await parseBody(request); raw: boolean
}
export const getGraphQLParams = async (request: Request): Promise<GraphQLParams> => {
const urlData = new URLSearchParams(request.url.split('?')[1])
const bodyData = await parseBody(request)
// GraphQL Query string. // GraphQL Query string.
let query = urlData.get("query") ?? (bodyData.query as string | null); let query = urlData.get('query') ?? (bodyData.query as string | null)
if (typeof query !== "string") { if (typeof query !== 'string') {
query = null; query = null
} }
// Parse the variables if needed. // Parse the variables if needed.
let variables = (urlData.get("variables") ?? bodyData.variables) as { let variables = (urlData.get('variables') ?? bodyData.variables) as {
readonly [name: string]: unknown; readonly [name: string]: unknown
} | null; } | null
if (typeof variables === "string") { if (typeof variables === 'string') {
try { try {
variables = JSON.parse(variables); variables = JSON.parse(variables)
} catch { } catch {
throw Error("Variables are invalid JSON."); throw Error('Variables are invalid JSON.')
} }
} else if (typeof variables !== "object") { } else if (typeof variables !== 'object') {
variables = null; variables = null
} }
// Name of GraphQL operation to execute. // Name of GraphQL operation to execute.
let operationName = let operationName = urlData.get('operationName') ?? (bodyData.operationName as string | null)
urlData.get("operationName") ?? (bodyData.operationName as string | null); if (typeof operationName !== 'string') {
if (typeof operationName !== "string") { operationName = null
operationName = null;
} }
const raw = urlData.get("raw") != null || bodyData.raw !== undefined; const raw = urlData.get('raw') != null || bodyData.raw !== undefined
const params: GraphQLParams = { const params: GraphQLParams = {
query: query, query: query,
variables: variables, variables: variables,
operationName: operationName, operationName: operationName,
raw: raw, raw: raw,
}; }
return params; return params
}; }
export const errorMessages = ( export const errorMessages = (
messages: string[], messages: string[],
@ -245,16 +223,16 @@ export const errorMessages = (
if (graphqlErrors) { if (graphqlErrors) {
return { return {
errors: graphqlErrors, errors: graphqlErrors,
}; }
} }
return { return {
errors: messages.map((message) => { errors: messages.map((message) => {
return { return {
message: message, message: message,
}; }
}), }),
}; }
}; }
// export const graphiQLResponse = () => {} // export const graphiQLResponse = () => {}

1093
yarn.lock

File diff suppressed because it is too large Load Diff