chore: use the latest eslint and `@hono/eslint-config` (#904)
* chore: use the latest eslint and `@hono/eslint-config` * update codespull/905/head
parent
755d5cb84d
commit
7a401b0850
|
@ -1 +0,0 @@
|
||||||
dist
|
|
|
@ -1,3 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
extends: ['./packages/eslint-config/index.js'],
|
|
||||||
}
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import baseConfig from './packages/eslint-config/index.js'
|
||||||
|
|
||||||
|
export default [
|
||||||
|
...baseConfig,
|
||||||
|
{
|
||||||
|
ignores: ['**/dist/*'],
|
||||||
|
},
|
||||||
|
]
|
|
@ -61,9 +61,7 @@
|
||||||
"@types/node": "^20.10.4",
|
"@types/node": "^20.10.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.7.0",
|
"@typescript-eslint/eslint-plugin": "^8.7.0",
|
||||||
"@typescript-eslint/parser": "^8.7.0",
|
"@typescript-eslint/parser": "^8.7.0",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^9.17.0",
|
||||||
"eslint-plugin-import-x": "^4.1.1",
|
|
||||||
"eslint-plugin-n": "^17.10.2",
|
|
||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"jest-environment-miniflare": "^2.14.1",
|
"jest-environment-miniflare": "^2.14.1",
|
||||||
"npm-run-all2": "^6.2.2",
|
"npm-run-all2": "^6.2.2",
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import type { Context, Env, MiddlewareHandler, ValidationTargets } from 'hono';
|
import { Ajv } from 'ajv'
|
||||||
import { validator } from 'hono/validator';
|
import type { JSONSchemaType, ErrorObject } from 'ajv'
|
||||||
import { Ajv, type JSONSchemaType, type ErrorObject } from 'ajv';
|
import type { Context, Env, MiddlewareHandler, ValidationTargets } from 'hono'
|
||||||
|
import { validator } from 'hono/validator'
|
||||||
|
|
||||||
type Hook<T, E extends Env, P extends string> = (
|
type Hook<T, E extends Env, P extends string> = (
|
||||||
result: { success: true; data: T } | { success: false; errors: ErrorObject[] },
|
result: { success: true; data: T } | { success: false; errors: ErrorObject[] },
|
||||||
c: Context<E, P>
|
c: Context<E, P>
|
||||||
) => Response | Promise<Response> | void;
|
) => Response | Promise<Response> | void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hono middleware that validates incoming data via an Ajv JSON schema.
|
* Hono middleware that validates incoming data via an Ajv JSON schema.
|
||||||
|
@ -75,32 +76,32 @@ export function ajvValidator<
|
||||||
E,
|
E,
|
||||||
P,
|
P,
|
||||||
{
|
{
|
||||||
in: { [K in Target]: T };
|
in: { [K in Target]: T }
|
||||||
out: { [K in Target]: T };
|
out: { [K in Target]: T }
|
||||||
}
|
}
|
||||||
> {
|
> {
|
||||||
const ajv = new Ajv();
|
const ajv = new Ajv()
|
||||||
const validate = ajv.compile(schema);
|
const validate = ajv.compile(schema)
|
||||||
|
|
||||||
return validator(target, (data, c) => {
|
return validator(target, (data, c) => {
|
||||||
const valid = validate(data);
|
const valid = validate(data)
|
||||||
if (valid) {
|
if (valid) {
|
||||||
if (hook) {
|
if (hook) {
|
||||||
const hookResult = hook({ success: true, data: data as T }, c);
|
const hookResult = hook({ success: true, data: data as T }, c)
|
||||||
if (hookResult instanceof Response || hookResult instanceof Promise) {
|
if (hookResult instanceof Response || hookResult instanceof Promise) {
|
||||||
return hookResult;
|
return hookResult
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return data as T;
|
return data as T
|
||||||
}
|
}
|
||||||
|
|
||||||
const errors = validate.errors || [];
|
const errors = validate.errors || []
|
||||||
if (hook) {
|
if (hook) {
|
||||||
const hookResult = hook({ success: false, errors }, c);
|
const hookResult = hook({ success: false, errors }, c)
|
||||||
if (hookResult instanceof Response || hookResult instanceof Promise) {
|
if (hookResult instanceof Response || hookResult instanceof Promise) {
|
||||||
return hookResult;
|
return hookResult
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return c.json({ success: false, errors }, 400);
|
return c.json({ success: false, errors }, 400)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { Hono } from 'hono';
|
import type { JSONSchemaType, type ErrorObject } from 'ajv'
|
||||||
import type { Equal, Expect } from 'hono/utils/types';
|
import { Hono } from 'hono'
|
||||||
import { ajvValidator } from '../src';
|
import type { Equal, Expect } from 'hono/utils/types'
|
||||||
import { JSONSchemaType, type ErrorObject } from 'ajv';
|
import { ajvValidator } from '../src'
|
||||||
|
|
||||||
type ExtractSchema<T> = T extends Hono<infer _, infer S> ? S : never;
|
type ExtractSchema<T> = T extends Hono<infer _, infer S> ? S : never
|
||||||
|
|
||||||
describe('Basic', () => {
|
describe('Basic', () => {
|
||||||
const app = new Hono();
|
const app = new Hono()
|
||||||
|
|
||||||
const schema: JSONSchemaType<{ name: string; age: number }> = {
|
const schema: JSONSchemaType<{ name: string; age: number }> = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
@ -16,35 +16,35 @@ describe('Basic', () => {
|
||||||
},
|
},
|
||||||
required: ['name', 'age'],
|
required: ['name', 'age'],
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
};
|
}
|
||||||
|
|
||||||
const route = app.post('/author', ajvValidator('json', schema), (c) => {
|
const route = app.post('/author', ajvValidator('json', schema), (c) => {
|
||||||
const data = c.req.valid('json');
|
const data = c.req.valid('json')
|
||||||
return c.json({
|
return c.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: `${data.name} is ${data.age}`,
|
message: `${data.name} is ${data.age}`,
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
type Actual = ExtractSchema<typeof route>;
|
type Actual = ExtractSchema<typeof route>
|
||||||
type Expected = {
|
type Expected = {
|
||||||
'/author': {
|
'/author': {
|
||||||
$post: {
|
$post: {
|
||||||
input: {
|
input: {
|
||||||
json: {
|
json: {
|
||||||
name: string;
|
name: string
|
||||||
age: number;
|
age: number
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
output: {
|
output: {
|
||||||
success: boolean;
|
success: boolean
|
||||||
message: string;
|
message: string
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
type verify = Expect<Equal<Expected, Actual>>;
|
type verify = Expect<Equal<Expected, Actual>>
|
||||||
|
|
||||||
it('Should return 200 response', async () => {
|
it('Should return 200 response', async () => {
|
||||||
const req = new Request('http://localhost/author', {
|
const req = new Request('http://localhost/author', {
|
||||||
|
@ -56,15 +56,15 @@ describe('Basic', () => {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
const res = await app.request(req);
|
const res = await app.request(req)
|
||||||
expect(res).not.toBeNull();
|
expect(res).not.toBeNull()
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200)
|
||||||
expect(await res.json()).toEqual({
|
expect(await res.json()).toEqual({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Superman is 20',
|
message: 'Superman is 20',
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
it('Should return 400 response', async () => {
|
it('Should return 400 response', async () => {
|
||||||
const req = new Request('http://localhost/author', {
|
const req = new Request('http://localhost/author', {
|
||||||
|
@ -76,17 +76,17 @@ describe('Basic', () => {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
const res = await app.request(req);
|
const res = await app.request(req)
|
||||||
expect(res).not.toBeNull();
|
expect(res).not.toBeNull()
|
||||||
expect(res.status).toBe(400);
|
expect(res.status).toBe(400)
|
||||||
const data = (await res.json()) as { success: boolean };
|
const data = (await res.json()) as { success: boolean }
|
||||||
expect(data.success).toBe(false);
|
expect(data.success).toBe(false)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
describe('With Hook', () => {
|
describe('With Hook', () => {
|
||||||
const app = new Hono();
|
const app = new Hono()
|
||||||
|
|
||||||
const schema: JSONSchemaType<{ id: number; title: string }> = {
|
const schema: JSONSchemaType<{ id: number; title: string }> = {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
|
@ -96,39 +96,39 @@ describe('With Hook', () => {
|
||||||
},
|
},
|
||||||
required: ['id', 'title'],
|
required: ['id', 'title'],
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
};
|
}
|
||||||
|
|
||||||
app
|
app
|
||||||
.post(
|
.post(
|
||||||
'/post',
|
'/post',
|
||||||
ajvValidator('json', schema, (result, c) => {
|
ajvValidator('json', schema, (result, c) => {
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
return c.text('Invalid!', 400);
|
return c.text('Invalid!', 400)
|
||||||
}
|
}
|
||||||
const data = result.data;
|
const data = result.data
|
||||||
return c.text(`${data.id} is valid!`);
|
return c.text(`${data.id} is valid!`)
|
||||||
}),
|
}),
|
||||||
(c) => {
|
(c) => {
|
||||||
const data = c.req.valid('json');
|
const data = c.req.valid('json')
|
||||||
return c.json({
|
return c.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: `${data.id} is ${data.title}`,
|
message: `${data.id} is ${data.title}`,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.post(
|
.post(
|
||||||
'/errorTest',
|
'/errorTest',
|
||||||
ajvValidator('json', schema, (result, c) => {
|
ajvValidator('json', schema, (result, c) => {
|
||||||
return c.json(result, 400);
|
return c.json(result, 400)
|
||||||
}),
|
}),
|
||||||
(c) => {
|
(c) => {
|
||||||
const data = c.req.valid('json');
|
const data = c.req.valid('json')
|
||||||
return c.json({
|
return c.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: `${data.id} is ${data.title}`,
|
message: `${data.id} is ${data.title}`,
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
it('Should return 200 response', async () => {
|
it('Should return 200 response', async () => {
|
||||||
const req = new Request('http://localhost/post', {
|
const req = new Request('http://localhost/post', {
|
||||||
|
@ -140,12 +140,12 @@ describe('With Hook', () => {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
const res = await app.request(req);
|
const res = await app.request(req)
|
||||||
expect(res).not.toBeNull();
|
expect(res).not.toBeNull()
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200)
|
||||||
expect(await res.text()).toBe('123 is valid!');
|
expect(await res.text()).toBe('123 is valid!')
|
||||||
});
|
})
|
||||||
|
|
||||||
it('Should return 400 response', async () => {
|
it('Should return 400 response', async () => {
|
||||||
const req = new Request('http://localhost/post', {
|
const req = new Request('http://localhost/post', {
|
||||||
|
@ -157,11 +157,11 @@ describe('With Hook', () => {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
const res = await app.request(req);
|
const res = await app.request(req)
|
||||||
expect(res).not.toBeNull();
|
expect(res).not.toBeNull()
|
||||||
expect(res.status).toBe(400);
|
expect(res.status).toBe(400)
|
||||||
});
|
})
|
||||||
|
|
||||||
it('Should return 400 response and error array', async () => {
|
it('Should return 400 response and error array', async () => {
|
||||||
const req = new Request('http://localhost/errorTest', {
|
const req = new Request('http://localhost/errorTest', {
|
||||||
|
@ -172,17 +172,17 @@ describe('With Hook', () => {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
const res = await app.request(req);
|
const res = await app.request(req)
|
||||||
expect(res).not.toBeNull();
|
expect(res).not.toBeNull()
|
||||||
expect(res.status).toBe(400);
|
expect(res.status).toBe(400)
|
||||||
|
|
||||||
const { errors, success } = (await res.json()) as {
|
const { errors, success } = (await res.json()) as {
|
||||||
success: boolean;
|
success: boolean
|
||||||
errors: ErrorObject[];
|
errors: ErrorObject[]
|
||||||
};
|
}
|
||||||
expect(success).toBe(false);
|
expect(success).toBe(false)
|
||||||
expect(Array.isArray(errors)).toBe(true);
|
expect(Array.isArray(errors)).toBe(true)
|
||||||
expect(
|
expect(
|
||||||
errors.map((e: ErrorObject) => ({
|
errors.map((e: ErrorObject) => ({
|
||||||
keyword: e.keyword,
|
keyword: e.keyword,
|
||||||
|
@ -195,6 +195,6 @@ describe('With Hook', () => {
|
||||||
instancePath: '',
|
instancePath: '',
|
||||||
message: "must have required property 'title'",
|
message: "must have required property 'title'",
|
||||||
},
|
},
|
||||||
]);
|
])
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { type, type Type, type ArkErrors } from 'arktype'
|
import { type } from 'arktype'
|
||||||
|
import type { Type, ArkErrors } from 'arktype'
|
||||||
import type { Context, MiddlewareHandler, Env, ValidationTargets, TypedResponse } from 'hono'
|
import type { Context, MiddlewareHandler, Env, ValidationTargets, TypedResponse } from 'hono'
|
||||||
import { validator } from 'hono/validator'
|
import { validator } from 'hono/validator'
|
||||||
|
|
||||||
|
|
|
@ -163,8 +163,7 @@ export function now() {
|
||||||
|
|
||||||
export function parseUrl(url?: string) {
|
export function parseUrl(url?: string) {
|
||||||
const defaultUrl = 'http://localhost:3000/api/auth'
|
const defaultUrl = 'http://localhost:3000/api/auth'
|
||||||
const parsedUrl = new URL(url?.startsWith('http') ? url : `https://${url}` || defaultUrl)
|
const parsedUrl = new URL(url ? (url.startsWith('http') ? url : `https://${url}`) : defaultUrl)
|
||||||
|
|
||||||
const path = parsedUrl.pathname === '/' ? '/api/auth' : parsedUrl.pathname.replace(/\/$/, '')
|
const path = parsedUrl.pathname === '/' ? '/api/auth' : parsedUrl.pathname.replace(/\/$/, '')
|
||||||
const base = `${parsedUrl.origin}${path}`
|
const base = `${parsedUrl.origin}${path}`
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
import type { AuthConfig as AuthConfigCore } from '@auth/core'
|
import type { AuthConfig as AuthConfigCore } from '@auth/core'
|
||||||
import { Auth } from '@auth/core'
|
import { Auth, setEnvDefaults as coreSetEnvDefaults } from '@auth/core'
|
||||||
import type { AdapterUser } from '@auth/core/adapters'
|
import type { AdapterUser } from '@auth/core/adapters'
|
||||||
import type { JWT } from '@auth/core/jwt'
|
import type { JWT } from '@auth/core/jwt'
|
||||||
import type { Session } from '@auth/core/types'
|
import type { Session } from '@auth/core/types'
|
||||||
import type { Context, MiddlewareHandler } from 'hono'
|
import type { Context, MiddlewareHandler } from 'hono'
|
||||||
import { env } from 'hono/adapter'
|
import { env } from 'hono/adapter'
|
||||||
import { HTTPException } from 'hono/http-exception'
|
import { HTTPException } from 'hono/http-exception'
|
||||||
import { setEnvDefaults as coreSetEnvDefaults } from '@auth/core'
|
|
||||||
|
|
||||||
declare module 'hono' {
|
declare module 'hono' {
|
||||||
interface ContextVariableMap {
|
interface ContextVariableMap {
|
||||||
|
@ -43,7 +42,9 @@ export function reqWithEnvUrl(req: Request, authUrl?: string) {
|
||||||
const authUrlObj = new URL(authUrl)
|
const authUrlObj = new URL(authUrl)
|
||||||
const props = ['hostname', 'protocol', 'port', 'password', 'username'] as const
|
const props = ['hostname', 'protocol', 'port', 'password', 'username'] as const
|
||||||
for (const prop of props) {
|
for (const prop of props) {
|
||||||
if (authUrlObj[prop]) reqUrlObj[prop] = authUrlObj[prop]
|
if (authUrlObj[prop]) {
|
||||||
|
reqUrlObj[prop] = authUrlObj[prop]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return new Request(reqUrlObj.href, req)
|
return new Request(reqUrlObj.href, req)
|
||||||
}
|
}
|
||||||
|
@ -51,12 +52,17 @@ export function reqWithEnvUrl(req: Request, authUrl?: string) {
|
||||||
const newReq = new Request(url.href, req)
|
const newReq = new Request(url.href, req)
|
||||||
const proto = newReq.headers.get('x-forwarded-proto')
|
const proto = newReq.headers.get('x-forwarded-proto')
|
||||||
const host = newReq.headers.get('x-forwarded-host') ?? newReq.headers.get('host')
|
const host = newReq.headers.get('x-forwarded-host') ?? newReq.headers.get('host')
|
||||||
if (proto != null) url.protocol = proto.endsWith(':') ? proto : `${proto}:`
|
if (proto != null) {
|
||||||
|
url.protocol = proto.endsWith(':') ? proto : `${proto}:`
|
||||||
|
}
|
||||||
if (host != null) {
|
if (host != null) {
|
||||||
url.host = host
|
url.host = host
|
||||||
const portMatch = host.match(/:(\d+)$/)
|
const portMatch = host.match(/:(\d+)$/)
|
||||||
if (portMatch) url.port = portMatch[1]
|
if (portMatch) {
|
||||||
else url.port = ''
|
url.port = portMatch[1]
|
||||||
|
} else {
|
||||||
|
url.port = ''
|
||||||
|
}
|
||||||
newReq.headers.delete('x-forwarded-host')
|
newReq.headers.delete('x-forwarded-host')
|
||||||
newReq.headers.delete('Host')
|
newReq.headers.delete('Host')
|
||||||
newReq.headers.set('Host', host)
|
newReq.headers.set('Host', host)
|
||||||
|
|
|
@ -1,28 +1,24 @@
|
||||||
|
import type { BuiltInProviderType, RedirectableProviderType } from '@auth/core/providers'
|
||||||
|
import type { LoggerInstance, Session } from '@auth/core/types'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import {
|
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
||||||
type AuthClientConfig,
|
import { ClientSessionError, fetchData, now, parseUrl, useOnline } from './client'
|
||||||
ClientSessionError,
|
import type {
|
||||||
fetchData,
|
|
||||||
now,
|
|
||||||
parseUrl,
|
|
||||||
useOnline,
|
|
||||||
type SessionContextValue,
|
|
||||||
type SessionProviderProps,
|
|
||||||
type GetSessionParams,
|
|
||||||
type UseSessionOptions,
|
|
||||||
type LiteralUnion,
|
|
||||||
type SignInOptions,
|
|
||||||
type SignInAuthorizationParams,
|
|
||||||
type SignInResponse,
|
|
||||||
type ClientSafeProvider,
|
|
||||||
type SignOutParams,
|
|
||||||
type SignOutResponse,
|
|
||||||
WindowProps,
|
WindowProps,
|
||||||
AuthState,
|
AuthState,
|
||||||
|
AuthClientConfig,
|
||||||
|
SessionContextValue,
|
||||||
|
SessionProviderProps,
|
||||||
|
GetSessionParams,
|
||||||
|
UseSessionOptions,
|
||||||
|
LiteralUnion,
|
||||||
|
SignInOptions,
|
||||||
|
SignInAuthorizationParams,
|
||||||
|
SignInResponse,
|
||||||
|
ClientSafeProvider,
|
||||||
|
SignOutParams,
|
||||||
|
SignOutResponse,
|
||||||
} from './client'
|
} from './client'
|
||||||
import type { LoggerInstance, Session } from '@auth/core/types'
|
|
||||||
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
|
||||||
import type { BuiltInProviderType, RedirectableProviderType } from '@auth/core/providers'
|
|
||||||
|
|
||||||
const logger: LoggerInstance = {
|
const logger: LoggerInstance = {
|
||||||
debug: console.debug,
|
debug: console.debug,
|
||||||
|
@ -431,7 +427,9 @@ export const useOauthPopupLogin = (
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleMessage = (event: MessageEvent<AuthState>) => {
|
const handleMessage = (event: MessageEvent<AuthState>) => {
|
||||||
if (event.origin !== window.location.origin) return
|
if (event.origin !== window.location.origin) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (event.data.status) {
|
if (event.data.status) {
|
||||||
setState(event.data)
|
setState(event.data)
|
||||||
if (event.data.status === 'success') {
|
if (event.data.status === 'success') {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { webcrypto } from 'node:crypto'
|
|
||||||
import { skipCSRFCheck } from '@auth/core'
|
import { skipCSRFCheck } from '@auth/core'
|
||||||
import type { Adapter } from '@auth/core/adapters'
|
import type { Adapter } from '@auth/core/adapters'
|
||||||
import Credentials from '@auth/core/providers/credentials'
|
import Credentials from '@auth/core/providers/credentials'
|
||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
import { webcrypto } from 'node:crypto'
|
||||||
import type { AuthConfig } from '../src'
|
import type { AuthConfig } from '../src'
|
||||||
import { authHandler, verifyAuth, initAuthConfig, reqWithEnvUrl } from '../src'
|
import { authHandler, verifyAuth, initAuthConfig, reqWithEnvUrl } from '../src'
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { decode } from 'hono/jwt'
|
|
||||||
import type { Enforcer } from 'casbin'
|
import type { Enforcer } from 'casbin'
|
||||||
import type { Context } from 'hono'
|
import type { Context } from 'hono'
|
||||||
|
import { decode } from 'hono/jwt'
|
||||||
import type { JWTPayload } from 'hono/utils/jwt/types'
|
import type { JWTPayload } from 'hono/utils/jwt/types'
|
||||||
|
|
||||||
export const jwtAuthorizer = async (
|
export const jwtAuthorizer = async (
|
||||||
|
@ -14,10 +14,14 @@ export const jwtAuthorizer = async (
|
||||||
|
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
const credentials = c.req.header('Authorization')
|
const credentials = c.req.header('Authorization')
|
||||||
if (!credentials) return false
|
if (!credentials) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
const parts = credentials.split(/\s+/)
|
const parts = credentials.split(/\s+/)
|
||||||
if (parts.length !== 2 || parts[0] !== 'Bearer') return false
|
if (parts.length !== 2 || parts[0] !== 'Bearer') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
const token = parts[1]
|
const token = parts[1]
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Enforcer } from 'casbin'
|
import { Enforcer } from 'casbin'
|
||||||
import { type Context, MiddlewareHandler } from 'hono'
|
import type { MiddlewareHandler, type Context } from 'hono'
|
||||||
|
|
||||||
interface CasbinOptions {
|
interface CasbinOptions {
|
||||||
newEnforcer: Promise<Enforcer>
|
newEnforcer: Promise<Enforcer>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import { describe, it, expect } from 'vitest'
|
|
||||||
import { Hono } from 'hono'
|
|
||||||
import { newEnforcer } from 'casbin'
|
import { newEnforcer } from 'casbin'
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
import { basicAuth } from 'hono/basic-auth'
|
||||||
|
import { jwt, sign } from 'hono/jwt'
|
||||||
|
import { describe, it, expect } from 'vitest'
|
||||||
import { casbin } from '../src'
|
import { casbin } from '../src'
|
||||||
import { basicAuthorizer, jwtAuthorizer } from '../src/helper'
|
import { basicAuthorizer, jwtAuthorizer } from '../src/helper'
|
||||||
import { jwt, sign } from 'hono/jwt'
|
|
||||||
import { basicAuth } from 'hono/basic-auth'
|
|
||||||
|
|
||||||
describe('Casbin Middleware Tests', () => {
|
describe('Casbin Middleware Tests', () => {
|
||||||
describe('BasicAuthorizer', () => {
|
describe('BasicAuthorizer', () => {
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
|
import type { ClassConstructor, ClassTransformOptions } from 'class-transformer'
|
||||||
|
import { plainToClass } from 'class-transformer'
|
||||||
|
import type { ValidationError } from 'class-validator'
|
||||||
|
import { validate } from 'class-validator'
|
||||||
|
import type { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono'
|
||||||
import { validator } from 'hono/validator'
|
import { validator } from 'hono/validator'
|
||||||
import { ClassConstructor, ClassTransformOptions, plainToClass } from 'class-transformer'
|
|
||||||
import { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono'
|
|
||||||
import { ValidationError, validate } from 'class-validator'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hono middleware that validates incoming data using class-validator(https://github.com/typestack/class-validator).
|
* Hono middleware that validates incoming data using class-validator(https://github.com/typestack/class-validator).
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import { Hono } from 'hono'
|
|
||||||
import { classValidator } from '../src'
|
|
||||||
import type { Equal, Expect } from 'hono/utils/types'
|
|
||||||
import { IsInt, IsString, ValidateNested, ValidationError } from 'class-validator'
|
|
||||||
import { ExtractSchema } from 'hono/types'
|
|
||||||
import { Type } from 'class-transformer'
|
import { Type } from 'class-transformer'
|
||||||
|
import type { ValidationError } from 'class-validator'
|
||||||
|
import { IsInt, IsString, ValidateNested } from 'class-validator'
|
||||||
|
import { Hono } from 'hono'
|
||||||
|
import type { ExtractSchema } from 'hono/types'
|
||||||
|
import type { Equal, Expect } from 'hono/utils/types'
|
||||||
|
import { classValidator } from '../src'
|
||||||
|
|
||||||
describe('Basic', () => {
|
describe('Basic', () => {
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { type ClerkClient, type ClerkOptions, createClerkClient } from '@clerk/backend'
|
import { createClerkClient } from '@clerk/backend'
|
||||||
|
import type { ClerkClient, ClerkOptions } from '@clerk/backend'
|
||||||
import type { Context, MiddlewareHandler } from 'hono'
|
import type { Context, MiddlewareHandler } from 'hono'
|
||||||
import { env } from 'hono/adapter'
|
import { env } from 'hono/adapter'
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
import { cloudflareAccess } from '../src'
|
|
||||||
import { describe, expect, it, vi } from 'vitest'
|
import { describe, expect, it, vi } from 'vitest'
|
||||||
|
|
||||||
import crypto from 'crypto';
|
import crypto from 'crypto'
|
||||||
import { promisify } from 'util';
|
import { promisify } from 'util'
|
||||||
|
import { cloudflareAccess } from '../src'
|
||||||
|
|
||||||
const generateKeyPair = promisify(crypto.generateKeyPair);
|
const generateKeyPair = promisify(crypto.generateKeyPair)
|
||||||
|
|
||||||
interface KeyPairResult {
|
interface KeyPairResult {
|
||||||
publicKey: string;
|
publicKey: string
|
||||||
privateKey: string;
|
privateKey: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface JWK {
|
interface JWK {
|
||||||
kid: string;
|
kid: string
|
||||||
kty: string;
|
kty: string
|
||||||
alg: string;
|
alg: string
|
||||||
use: string;
|
use: string
|
||||||
e: string;
|
e: string
|
||||||
n: string;
|
n: string
|
||||||
}
|
}
|
||||||
|
|
||||||
async function generateJWTKeyPair(): Promise<KeyPairResult> {
|
async function generateJWTKeyPair(): Promise<KeyPairResult> {
|
||||||
|
@ -27,38 +27,38 @@ async function generateJWTKeyPair(): Promise<KeyPairResult> {
|
||||||
modulusLength: 2048,
|
modulusLength: 2048,
|
||||||
publicKeyEncoding: {
|
publicKeyEncoding: {
|
||||||
type: 'spki',
|
type: 'spki',
|
||||||
format: 'pem'
|
format: 'pem',
|
||||||
},
|
},
|
||||||
privateKeyEncoding: {
|
privateKeyEncoding: {
|
||||||
type: 'pkcs8',
|
type: 'pkcs8',
|
||||||
format: 'pem'
|
format: 'pem',
|
||||||
}
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
publicKey,
|
publicKey,
|
||||||
privateKey
|
privateKey,
|
||||||
};
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`Failed to generate key pair: ${(error as Error).message}`);
|
throw new Error(`Failed to generate key pair: ${(error as Error).message}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateKeyThumbprint(modulusBase64: string): string {
|
function generateKeyThumbprint(modulusBase64: string): string {
|
||||||
const hash = crypto.createHash('sha256');
|
const hash = crypto.createHash('sha256')
|
||||||
hash.update(Buffer.from(modulusBase64, 'base64'));
|
hash.update(Buffer.from(modulusBase64, 'base64'))
|
||||||
return hash.digest('hex');
|
return hash.digest('hex')
|
||||||
}
|
}
|
||||||
|
|
||||||
function publicKeyToJWK(publicKey: string): JWK {
|
function publicKeyToJWK(publicKey: string): JWK {
|
||||||
// Convert PEM to key object
|
// Convert PEM to key object
|
||||||
const keyObject = crypto.createPublicKey(publicKey);
|
const keyObject = crypto.createPublicKey(publicKey)
|
||||||
|
|
||||||
// Export the key in JWK format
|
// Export the key in JWK format
|
||||||
const jwk = keyObject.export({ format: 'jwk' });
|
const jwk = keyObject.export({ format: 'jwk' })
|
||||||
|
|
||||||
// Generate key ID using the modulus
|
// Generate key ID using the modulus
|
||||||
const kid = generateKeyThumbprint(jwk.n as string);
|
const kid = generateKeyThumbprint(jwk.n as string)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
kid,
|
kid,
|
||||||
|
@ -67,66 +67,65 @@ function publicKeyToJWK(publicKey: string): JWK {
|
||||||
use: 'sig',
|
use: 'sig',
|
||||||
e: jwk.e as string,
|
e: jwk.e as string,
|
||||||
n: jwk.n as string,
|
n: jwk.n as string,
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function base64URLEncode(str: string): string {
|
function base64URLEncode(str: string): string {
|
||||||
return Buffer.from(str)
|
return Buffer.from(str)
|
||||||
.toString('base64')
|
.toString('base64')
|
||||||
.replace(/\+/g, '-')
|
.replace(/\+/g, '-')
|
||||||
.replace(/\//g, '_')
|
.replace(/\//g, '_')
|
||||||
.replace(/=/g, '');
|
.replace(/=/g, '')
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateJWT(privateKey: string, payload: Record<string, any>, expiresIn: number = 3600): string {
|
function generateJWT(
|
||||||
|
privateKey: string,
|
||||||
|
payload: Record<string, any>,
|
||||||
|
expiresIn: number = 3600
|
||||||
|
): string {
|
||||||
// Create header
|
// Create header
|
||||||
const header = {
|
const header = {
|
||||||
alg: 'RS256',
|
alg: 'RS256',
|
||||||
typ: 'JWT'
|
typ: 'JWT',
|
||||||
};
|
}
|
||||||
|
|
||||||
// Add expiration to payload
|
// Add expiration to payload
|
||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.floor(Date.now() / 1000)
|
||||||
const fullPayload = {
|
const fullPayload = {
|
||||||
...payload,
|
...payload,
|
||||||
iat: now,
|
iat: now,
|
||||||
exp: now + expiresIn
|
exp: now + expiresIn,
|
||||||
};
|
|
||||||
|
|
||||||
// Encode header and payload
|
|
||||||
const encodedHeader = base64URLEncode(JSON.stringify(header));
|
|
||||||
const encodedPayload = base64URLEncode(JSON.stringify(fullPayload));
|
|
||||||
|
|
||||||
// Create signature
|
|
||||||
const signatureInput = `${encodedHeader}.${encodedPayload}`;
|
|
||||||
const signer = crypto.createSign('RSA-SHA256');
|
|
||||||
signer.update(signatureInput);
|
|
||||||
const signature = signer.sign(privateKey);
|
|
||||||
// @ts-ignore
|
|
||||||
const encodedSignature = base64URLEncode(signature);
|
|
||||||
|
|
||||||
// Combine all parts
|
|
||||||
return `${encodedHeader}.${encodedPayload}.${encodedSignature}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Encode header and payload
|
||||||
|
const encodedHeader = base64URLEncode(JSON.stringify(header))
|
||||||
|
const encodedPayload = base64URLEncode(JSON.stringify(fullPayload))
|
||||||
|
|
||||||
|
// Create signature
|
||||||
|
const signatureInput = `${encodedHeader}.${encodedPayload}`
|
||||||
|
const signer = crypto.createSign('RSA-SHA256')
|
||||||
|
signer.update(signatureInput)
|
||||||
|
const signature = signer.sign(privateKey)
|
||||||
|
// @ts-expect-error signature is not typed correctly
|
||||||
|
const encodedSignature = base64URLEncode(signature)
|
||||||
|
|
||||||
|
// Combine all parts
|
||||||
|
return `${encodedHeader}.${encodedPayload}.${encodedSignature}`
|
||||||
|
}
|
||||||
|
|
||||||
describe('Cloudflare Access middleware', async () => {
|
describe('Cloudflare Access middleware', async () => {
|
||||||
const keyPair1 = await generateJWTKeyPair();
|
const keyPair1 = await generateJWTKeyPair()
|
||||||
const keyPair2 = await generateJWTKeyPair();
|
const keyPair2 = await generateJWTKeyPair()
|
||||||
const keyPair3 = await generateJWTKeyPair();
|
const keyPair3 = await generateJWTKeyPair()
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks()
|
||||||
vi.stubGlobal('fetch', async () => {
|
vi.stubGlobal('fetch', async () => {
|
||||||
return Response.json({
|
return Response.json({
|
||||||
keys: [
|
keys: [publicKeyToJWK(keyPair1.publicKey), publicKeyToJWK(keyPair2.publicKey)],
|
||||||
publicKeyToJWK(keyPair1.publicKey),
|
})
|
||||||
publicKeyToJWK(keyPair2.publicKey),
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
});
|
|
||||||
|
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
|
|
||||||
|
@ -135,9 +134,12 @@ describe('Cloudflare Access middleware', async () => {
|
||||||
app.get('/access-payload', (c) => c.json(c.get('accessPayload')))
|
app.get('/access-payload', (c) => c.json(c.get('accessPayload')))
|
||||||
|
|
||||||
app.onError((err, c) => {
|
app.onError((err, c) => {
|
||||||
return c.json({
|
return c.json(
|
||||||
|
{
|
||||||
err: err.toString(),
|
err: err.toString(),
|
||||||
}, 500)
|
},
|
||||||
|
500
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should be throw Missing bearer token when nothing is sent', async () => {
|
it('Should be throw Missing bearer token when nothing is sent', async () => {
|
||||||
|
@ -150,8 +152,8 @@ describe('Cloudflare Access middleware', async () => {
|
||||||
it('Should be throw Unable to decode Bearer token when sending garbage', async () => {
|
it('Should be throw Unable to decode Bearer token when sending garbage', async () => {
|
||||||
const res = await app.request('http://localhost/hello-behind-access', {
|
const res = await app.request('http://localhost/hello-behind-access', {
|
||||||
headers: {
|
headers: {
|
||||||
'cf-access-jwt-assertion': 'asdasdasda'
|
'cf-access-jwt-assertion': 'asdasdasda',
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
expect(res).not.toBeNull()
|
expect(res).not.toBeNull()
|
||||||
expect(res.status).toBe(401)
|
expect(res.status).toBe(401)
|
||||||
|
@ -159,14 +161,18 @@ describe('Cloudflare Access middleware', async () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should be throw Token is expired when sending expired token', async () => {
|
it('Should be throw Token is expired when sending expired token', async () => {
|
||||||
const token = generateJWT(keyPair1.privateKey, {
|
const token = generateJWT(
|
||||||
|
keyPair1.privateKey,
|
||||||
|
{
|
||||||
sub: '1234567890',
|
sub: '1234567890',
|
||||||
}, -3600);
|
},
|
||||||
|
-3600
|
||||||
|
)
|
||||||
|
|
||||||
const res = await app.request('http://localhost/hello-behind-access', {
|
const res = await app.request('http://localhost/hello-behind-access', {
|
||||||
headers: {
|
headers: {
|
||||||
'cf-access-jwt-assertion': token
|
'cf-access-jwt-assertion': token,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
expect(res).not.toBeNull()
|
expect(res).not.toBeNull()
|
||||||
expect(res.status).toBe(401)
|
expect(res.status).toBe(401)
|
||||||
|
@ -177,28 +183,30 @@ describe('Cloudflare Access middleware', async () => {
|
||||||
const token = generateJWT(keyPair1.privateKey, {
|
const token = generateJWT(keyPair1.privateKey, {
|
||||||
sub: '1234567890',
|
sub: '1234567890',
|
||||||
iss: 'https://different-team.cloudflareaccess.com',
|
iss: 'https://different-team.cloudflareaccess.com',
|
||||||
});
|
})
|
||||||
|
|
||||||
const res = await app.request('http://localhost/hello-behind-access', {
|
const res = await app.request('http://localhost/hello-behind-access', {
|
||||||
headers: {
|
headers: {
|
||||||
'cf-access-jwt-assertion': token
|
'cf-access-jwt-assertion': token,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
expect(res).not.toBeNull()
|
expect(res).not.toBeNull()
|
||||||
expect(res.status).toBe(401)
|
expect(res.status).toBe(401)
|
||||||
expect(await res.text()).toBe('Authentication error: Expected team name https://my-cool-team-name.cloudflareaccess.com, but received https://different-team.cloudflareaccess.com')
|
expect(await res.text()).toBe(
|
||||||
|
'Authentication error: Expected team name https://my-cool-team-name.cloudflareaccess.com, but received https://different-team.cloudflareaccess.com'
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should be throw Invalid token when sending token signed with private key not in the allowed list', async () => {
|
it('Should be throw Invalid token when sending token signed with private key not in the allowed list', async () => {
|
||||||
const token = generateJWT(keyPair3.privateKey, {
|
const token = generateJWT(keyPair3.privateKey, {
|
||||||
sub: '1234567890',
|
sub: '1234567890',
|
||||||
iss: 'https://my-cool-team-name.cloudflareaccess.com',
|
iss: 'https://my-cool-team-name.cloudflareaccess.com',
|
||||||
});
|
})
|
||||||
|
|
||||||
const res = await app.request('http://localhost/hello-behind-access', {
|
const res = await app.request('http://localhost/hello-behind-access', {
|
||||||
headers: {
|
headers: {
|
||||||
'cf-access-jwt-assertion': token
|
'cf-access-jwt-assertion': token,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
expect(res).not.toBeNull()
|
expect(res).not.toBeNull()
|
||||||
expect(res.status).toBe(401)
|
expect(res.status).toBe(401)
|
||||||
|
@ -209,12 +217,12 @@ describe('Cloudflare Access middleware', async () => {
|
||||||
const token = generateJWT(keyPair1.privateKey, {
|
const token = generateJWT(keyPair1.privateKey, {
|
||||||
sub: '1234567890',
|
sub: '1234567890',
|
||||||
iss: 'https://my-cool-team-name.cloudflareaccess.com',
|
iss: 'https://my-cool-team-name.cloudflareaccess.com',
|
||||||
});
|
})
|
||||||
|
|
||||||
const res = await app.request('http://localhost/hello-behind-access', {
|
const res = await app.request('http://localhost/hello-behind-access', {
|
||||||
headers: {
|
headers: {
|
||||||
'cf-access-jwt-assertion': token
|
'cf-access-jwt-assertion': token,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
expect(res).not.toBeNull()
|
expect(res).not.toBeNull()
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
|
@ -225,12 +233,12 @@ describe('Cloudflare Access middleware', async () => {
|
||||||
const token = generateJWT(keyPair2.privateKey, {
|
const token = generateJWT(keyPair2.privateKey, {
|
||||||
sub: '1234567890',
|
sub: '1234567890',
|
||||||
iss: 'https://my-cool-team-name.cloudflareaccess.com',
|
iss: 'https://my-cool-team-name.cloudflareaccess.com',
|
||||||
});
|
})
|
||||||
|
|
||||||
const res = await app.request('http://localhost/hello-behind-access', {
|
const res = await app.request('http://localhost/hello-behind-access', {
|
||||||
headers: {
|
headers: {
|
||||||
'cf-access-jwt-assertion': token
|
'cf-access-jwt-assertion': token,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
expect(res).not.toBeNull()
|
expect(res).not.toBeNull()
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
|
@ -241,20 +249,20 @@ describe('Cloudflare Access middleware', async () => {
|
||||||
const token = generateJWT(keyPair1.privateKey, {
|
const token = generateJWT(keyPair1.privateKey, {
|
||||||
sub: '1234567890',
|
sub: '1234567890',
|
||||||
iss: 'https://my-cool-team-name.cloudflareaccess.com',
|
iss: 'https://my-cool-team-name.cloudflareaccess.com',
|
||||||
});
|
})
|
||||||
|
|
||||||
const res = await app.request('http://localhost/access-payload', {
|
const res = await app.request('http://localhost/access-payload', {
|
||||||
headers: {
|
headers: {
|
||||||
'cf-access-jwt-assertion': token
|
'cf-access-jwt-assertion': token,
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
expect(res).not.toBeNull()
|
expect(res).not.toBeNull()
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
expect(await res.json()).toEqual({
|
expect(await res.json()).toEqual({
|
||||||
"sub":"1234567890",
|
sub: '1234567890',
|
||||||
"iss":"https://my-cool-team-name.cloudflareaccess.com",
|
iss: 'https://my-cool-team-name.cloudflareaccess.com',
|
||||||
"iat":expect.any(Number),
|
iat: expect.any(Number),
|
||||||
"exp":expect.any(Number)
|
exp: expect.any(Number),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -265,12 +273,14 @@ describe('Cloudflare Access middleware', async () => {
|
||||||
|
|
||||||
const res = await app.request('http://localhost/hello-behind-access', {
|
const res = await app.request('http://localhost/hello-behind-access', {
|
||||||
headers: {
|
headers: {
|
||||||
'cf-access-jwt-assertion': 'asdads'
|
'cf-access-jwt-assertion': 'asdads',
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
expect(res).not.toBeNull()
|
expect(res).not.toBeNull()
|
||||||
expect(res.status).toBe(500)
|
expect(res.status).toBe(500)
|
||||||
expect(await res.json()).toEqual({"err":"Error: Authentication error: The Access Organization 'my-cool-team-name' does not exist"})
|
expect(await res.json()).toEqual({
|
||||||
|
err: "Error: Authentication error: The Access Organization 'my-cool-team-name' does not exist",
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should throw an error, if the access certs url is unavailable', async () => {
|
it('Should throw an error, if the access certs url is unavailable', async () => {
|
||||||
|
@ -280,11 +290,13 @@ describe('Cloudflare Access middleware', async () => {
|
||||||
|
|
||||||
const res = await app.request('http://localhost/hello-behind-access', {
|
const res = await app.request('http://localhost/hello-behind-access', {
|
||||||
headers: {
|
headers: {
|
||||||
'cf-access-jwt-assertion': 'asdads'
|
'cf-access-jwt-assertion': 'asdads',
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
expect(res).not.toBeNull()
|
expect(res).not.toBeNull()
|
||||||
expect(res.status).toBe(500)
|
expect(res.status).toBe(500)
|
||||||
expect(await res.json()).toEqual({"err":"Error: Authentication error: Received unexpected HTTP code 500 from Cloudflare Access"})
|
expect(await res.json()).toEqual({
|
||||||
|
err: 'Error: Authentication error: Received unexpected HTTP code 500 from Cloudflare Access',
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
|
import type { Context } from 'hono'
|
||||||
import { createMiddleware } from 'hono/factory'
|
import { createMiddleware } from 'hono/factory'
|
||||||
import { Context } from 'hono'
|
|
||||||
import { HTTPException } from 'hono/http-exception'
|
import { HTTPException } from 'hono/http-exception'
|
||||||
|
|
||||||
export type CloudflareAccessPayload = {
|
export type CloudflareAccessPayload = {
|
||||||
aud: string[],
|
aud: string[]
|
||||||
email: string,
|
email: string
|
||||||
exp: number,
|
exp: number
|
||||||
iat: number,
|
iat: number
|
||||||
nbf: number,
|
nbf: number
|
||||||
iss: string,
|
iss: string
|
||||||
type: string,
|
type: string
|
||||||
identity_nonce: string,
|
identity_nonce: string
|
||||||
sub: string,
|
sub: string
|
||||||
country: string,
|
country: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CloudflareAccessVariables = {
|
export type CloudflareAccessVariables = {
|
||||||
|
@ -39,7 +39,9 @@ export const cloudflareAccess = (accessTeamName: string) => {
|
||||||
|
|
||||||
return createMiddleware(async (c, next) => {
|
return createMiddleware(async (c, next) => {
|
||||||
const encodedToken = getJwt(c)
|
const encodedToken = getJwt(c)
|
||||||
if (encodedToken === null) return c.text('Authentication error: Missing bearer token', 401)
|
if (encodedToken === null) {
|
||||||
|
return c.text('Authentication error: Missing bearer token', 401)
|
||||||
|
}
|
||||||
|
|
||||||
// Load jwt keys if they are not in memory or already expired
|
// Load jwt keys if they are not in memory or already expired
|
||||||
if (Object.keys(cacheKeys).length === 0 || Math.floor(Date.now() / 1000) < cacheExpiration) {
|
if (Object.keys(cacheKeys).length === 0 || Math.floor(Date.now() / 1000) < cacheExpiration) {
|
||||||
|
@ -59,19 +61,23 @@ export const cloudflareAccess = (accessTeamName: string) => {
|
||||||
// Is the token expired?
|
// Is the token expired?
|
||||||
const expiryDate = new Date(token.payload.exp * 1000)
|
const expiryDate = new Date(token.payload.exp * 1000)
|
||||||
const currentDate = new Date(Date.now())
|
const currentDate = new Date(Date.now())
|
||||||
if (expiryDate <= currentDate) return c.text('Authentication error: Token is expired', 401)
|
if (expiryDate <= currentDate) {
|
||||||
|
return c.text('Authentication error: Token is expired', 401)
|
||||||
|
}
|
||||||
|
|
||||||
// Check is token is valid against at least one public key?
|
// Check is token is valid against at least one public key?
|
||||||
if (!(await isValidJwtSignature(token, cacheKeys)))
|
if (!(await isValidJwtSignature(token, cacheKeys))) {
|
||||||
return c.text('Authentication error: Invalid Token', 401)
|
return c.text('Authentication error: Invalid Token', 401)
|
||||||
|
}
|
||||||
|
|
||||||
// Is signed from the correct team?
|
// Is signed from the correct team?
|
||||||
const expectedIss = `https://${accessTeamName}.cloudflareaccess.com`
|
const expectedIss = `https://${accessTeamName}.cloudflareaccess.com`
|
||||||
if (token.payload?.iss !== expectedIss)
|
if (token.payload?.iss !== expectedIss) {
|
||||||
return c.text(
|
return c.text(
|
||||||
`Authentication error: Expected team name ${expectedIss}, but received ${token.payload?.iss}`,
|
`Authentication error: Expected team name ${expectedIss}, but received ${token.payload?.iss}`,
|
||||||
401
|
401
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
c.set('accessPayload', token.payload)
|
c.set('accessPayload', token.payload)
|
||||||
await next()
|
await next()
|
||||||
|
@ -83,7 +89,6 @@ async function getPublicKeys(accessTeamName: string) {
|
||||||
|
|
||||||
const result = await fetch(jwtUrl, {
|
const result = await fetch(jwtUrl, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
// @ts-ignore
|
|
||||||
cf: {
|
cf: {
|
||||||
// Dont cache error responses
|
// Dont cache error responses
|
||||||
cacheTtlByStatus: { '200-299': 30, '300-599': 0 },
|
cacheTtlByStatus: { '200-299': 30, '300-599': 0 },
|
||||||
|
@ -92,16 +97,20 @@ async function getPublicKeys(accessTeamName: string) {
|
||||||
|
|
||||||
if (!result.ok) {
|
if (!result.ok) {
|
||||||
if (result.status === 404) {
|
if (result.status === 404) {
|
||||||
throw new HTTPException(500, { message: `Authentication error: The Access Organization '${accessTeamName}' does not exist` })
|
throw new HTTPException(500, {
|
||||||
|
message: `Authentication error: The Access Organization '${accessTeamName}' does not exist`,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new HTTPException(500, { message: `Authentication error: Received unexpected HTTP code ${result.status} from Cloudflare Access` })
|
throw new HTTPException(500, {
|
||||||
|
message: `Authentication error: Received unexpected HTTP code ${result.status} from Cloudflare Access`,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: any = await result.json()
|
const data: any = await result.json()
|
||||||
|
|
||||||
// Because we keep CryptoKey's in memory between requests, we need to make sure they are refreshed once in a while
|
// Because we keep CryptoKey's in memory between requests, we need to make sure they are refreshed once in a while
|
||||||
let cacheExpiration = Math.floor(Date.now() / 1000) + 3600 // 1h
|
const cacheExpiration = Math.floor(Date.now() / 1000) + 3600 // 1h
|
||||||
|
|
||||||
const importedKeys: Record<string, CryptoKey> = {}
|
const importedKeys: Record<string, CryptoKey> = {}
|
||||||
for (const key of data.keys) {
|
for (const key of data.keys) {
|
||||||
|
@ -158,7 +167,9 @@ async function isValidJwtSignature(token: DecodedToken, keys: Record<string, Cry
|
||||||
for (const key of Object.values(keys)) {
|
for (const key of Object.values(keys)) {
|
||||||
const isValid = await validateSingleKey(key, signature, data)
|
const isValid = await validateSingleKey(key, signature, data)
|
||||||
|
|
||||||
if (isValid) return true
|
if (isValid) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
import type { Context, Env, Input as HonoInput, MiddlewareHandler, ValidationTargets } from 'hono'
|
|
||||||
import type { Submission } from '@conform-to/dom'
|
import type { Submission } from '@conform-to/dom'
|
||||||
|
import type { Context, Env, Input as HonoInput, MiddlewareHandler, ValidationTargets } from 'hono'
|
||||||
import { getFormDataFromContext } from './utils'
|
import { getFormDataFromContext } from './utils'
|
||||||
|
|
||||||
type FormTargetValue = ValidationTargets['form']['string']
|
type FormTargetValue = ValidationTargets['form']['string']
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
import { parseWithZod } from '@conform-to/zod'
|
||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { parseWithZod } from '@conform-to/zod'
|
|
||||||
import { conformValidator } from '../src'
|
import { conformValidator } from '../src'
|
||||||
|
|
||||||
describe('Validate common processing', () => {
|
describe('Validate common processing', () => {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import * as z from 'zod'
|
import { parseWithZod } from '@conform-to/zod'
|
||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
import { hc } from 'hono/client'
|
import { hc } from 'hono/client'
|
||||||
import { parseWithZod } from '@conform-to/zod'
|
|
||||||
import { conformValidator } from '../src'
|
|
||||||
import { vi } from 'vitest'
|
import { vi } from 'vitest'
|
||||||
|
import * as z from 'zod'
|
||||||
|
import { conformValidator } from '../src'
|
||||||
|
|
||||||
describe('Validate the hook option processing', () => {
|
describe('Validate the hook option processing', () => {
|
||||||
const app = new Hono()
|
const app = new Hono()
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import type { ExtractSchema, ParsedFormValue } from 'hono/types'
|
import { parseWithValibot } from 'conform-to-valibot'
|
||||||
import type { Equal, Expect } from 'hono/utils/types'
|
|
||||||
import type { StatusCode } from 'hono/utils/http-status'
|
|
||||||
import * as v from 'valibot'
|
|
||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
import { hc } from 'hono/client'
|
import { hc } from 'hono/client'
|
||||||
import { parseWithValibot } from 'conform-to-valibot'
|
import type { ExtractSchema, ParsedFormValue } from 'hono/types'
|
||||||
|
import type { StatusCode } from 'hono/utils/http-status'
|
||||||
|
import type { Equal, Expect } from 'hono/utils/types'
|
||||||
|
import * as v from 'valibot'
|
||||||
import { conformValidator } from '../src'
|
import { conformValidator } from '../src'
|
||||||
|
|
||||||
describe('Validate requests using a Valibot schema', () => {
|
describe('Validate requests using a Valibot schema', () => {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import type { ExtractSchema, ParsedFormValue } from 'hono/types'
|
import { parseWithYup } from '@conform-to/yup'
|
||||||
import type { Equal, Expect } from 'hono/utils/types'
|
|
||||||
import type { StatusCode } from 'hono/utils/http-status'
|
|
||||||
import * as y from 'yup'
|
|
||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
import { hc } from 'hono/client'
|
import { hc } from 'hono/client'
|
||||||
import { parseWithYup } from '@conform-to/yup'
|
import type { ExtractSchema, ParsedFormValue } from 'hono/types'
|
||||||
|
import type { StatusCode } from 'hono/utils/http-status'
|
||||||
|
import type { Equal, Expect } from 'hono/utils/types'
|
||||||
|
import * as y from 'yup'
|
||||||
import { conformValidator } from '../src'
|
import { conformValidator } from '../src'
|
||||||
|
|
||||||
describe('Validate requests using a Yup schema', () => {
|
describe('Validate requests using a Yup schema', () => {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import type { ExtractSchema, ParsedFormValue } from 'hono/types'
|
import { parseWithZod } from '@conform-to/zod'
|
||||||
import type { Equal, Expect } from 'hono/utils/types'
|
|
||||||
import type { StatusCode } from 'hono/utils/http-status'
|
|
||||||
import * as z from 'zod'
|
|
||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
import { hc } from 'hono/client'
|
import { hc } from 'hono/client'
|
||||||
import { parseWithZod } from '@conform-to/zod'
|
import type { ExtractSchema, ParsedFormValue } from 'hono/types'
|
||||||
|
import type { StatusCode } from 'hono/utils/http-status'
|
||||||
|
import type { Equal, Expect } from 'hono/utils/types'
|
||||||
|
import * as z from 'zod'
|
||||||
import { conformValidator } from '../src'
|
import { conformValidator } from '../src'
|
||||||
|
|
||||||
describe('Validate requests using a Zod schema', () => {
|
describe('Validate requests using a Zod schema', () => {
|
||||||
|
|
|
@ -1,19 +1,18 @@
|
||||||
interface CloseEventInit extends EventInit {
|
interface CloseEventInit extends EventInit {
|
||||||
code?: number;
|
code?: number
|
||||||
reason?: string;
|
reason?: string
|
||||||
wasClean?: boolean;
|
wasClean?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
* @link https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
||||||
*/
|
*/
|
||||||
export const CloseEvent = globalThis.CloseEvent ?? class extends Event {
|
export const CloseEvent =
|
||||||
|
globalThis.CloseEvent ??
|
||||||
|
class extends Event {
|
||||||
#eventInitDict
|
#eventInitDict
|
||||||
|
|
||||||
constructor(
|
constructor(type: string, eventInitDict: CloseEventInit = {}) {
|
||||||
type: string,
|
|
||||||
eventInitDict: CloseEventInit = {}
|
|
||||||
) {
|
|
||||||
super(type, eventInitDict)
|
super(type, eventInitDict)
|
||||||
this.#eventInitDict = eventInitDict
|
this.#eventInitDict = eventInitDict
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { serve } from '@hono/node-server'
|
import { serve } from '@hono/node-server'
|
||||||
import type { ServerType } from '@hono/node-server/dist/types'
|
import type { ServerType } from '@hono/node-server/dist/types'
|
||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
|
import type { WSMessageReceive } from 'hono/ws'
|
||||||
import { WebSocket } from 'ws'
|
import { WebSocket } from 'ws'
|
||||||
import { createNodeWebSocket } from '.'
|
import { createNodeWebSocket } from '.'
|
||||||
import type { WSMessageReceive } from 'hono/ws'
|
|
||||||
|
|
||||||
describe('WebSocket helper', () => {
|
describe('WebSocket helper', () => {
|
||||||
let app: Hono
|
let app: Hono
|
||||||
|
@ -13,7 +13,6 @@ describe('WebSocket helper', () => {
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
app = new Hono()
|
app = new Hono()
|
||||||
|
|
||||||
;({ injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app }))
|
;({ injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app }))
|
||||||
|
|
||||||
server = await new Promise<ServerType>((resolve) => {
|
server = await new Promise<ServerType>((resolve) => {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import type { Server } from 'node:http'
|
|
||||||
import type { Http2SecureServer, Http2Server } from 'node:http2'
|
|
||||||
import type { Hono } from 'hono'
|
import type { Hono } from 'hono'
|
||||||
import type { UpgradeWebSocket, WSContext } from 'hono/ws'
|
import type { UpgradeWebSocket, WSContext } from 'hono/ws'
|
||||||
import type { WebSocket } from 'ws'
|
import type { WebSocket } from 'ws'
|
||||||
import { WebSocketServer } from 'ws'
|
import { WebSocketServer } from 'ws'
|
||||||
import type { IncomingMessage } from 'http'
|
import type { IncomingMessage } from 'http'
|
||||||
|
import type { Server } from 'node:http'
|
||||||
|
import type { Http2SecureServer, Http2Server } from 'node:http2'
|
||||||
import { CloseEvent } from './events'
|
import { CloseEvent } from './events'
|
||||||
|
|
||||||
export interface NodeWebSocket {
|
export interface NodeWebSocket {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { MiddlewareHandler } from 'hono'
|
import type { MiddlewareHandler } from 'hono'
|
||||||
import { getCookie, setCookie } from 'hono/cookie'
|
|
||||||
import { env } from 'hono/adapter'
|
import { env } from 'hono/adapter'
|
||||||
|
import { getCookie, setCookie } from 'hono/cookie'
|
||||||
import { HTTPException } from 'hono/http-exception'
|
import { HTTPException } from 'hono/http-exception'
|
||||||
|
|
||||||
import { getRandomState } from '../../utils/getRandomState'
|
import { getRandomState } from '../../utils/getRandomState'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { MiddlewareHandler } from 'hono'
|
import type { MiddlewareHandler } from 'hono'
|
||||||
import { getCookie, setCookie } from 'hono/cookie'
|
|
||||||
import { env } from 'hono/adapter'
|
import { env } from 'hono/adapter'
|
||||||
|
import { getCookie, setCookie } from 'hono/cookie'
|
||||||
import { HTTPException } from 'hono/http-exception'
|
import { HTTPException } from 'hono/http-exception'
|
||||||
|
|
||||||
import { getRandomState } from '../../utils/getRandomState'
|
import { getRandomState } from '../../utils/getRandomState'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { MiddlewareHandler } from 'hono'
|
import type { MiddlewareHandler } from 'hono'
|
||||||
import { getCookie, setCookie } from 'hono/cookie'
|
|
||||||
import { env } from 'hono/adapter'
|
import { env } from 'hono/adapter'
|
||||||
|
import { getCookie, setCookie } from 'hono/cookie'
|
||||||
import { HTTPException } from 'hono/http-exception'
|
import { HTTPException } from 'hono/http-exception'
|
||||||
|
|
||||||
import { getRandomState } from '../../utils/getRandomState'
|
import { getRandomState } from '../../utils/getRandomState'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { MiddlewareHandler } from 'hono'
|
import type { MiddlewareHandler } from 'hono'
|
||||||
import { getCookie, setCookie } from 'hono/cookie'
|
|
||||||
import { env } from 'hono/adapter'
|
import { env } from 'hono/adapter'
|
||||||
|
import { getCookie, setCookie } from 'hono/cookie'
|
||||||
import { HTTPException } from 'hono/http-exception'
|
import { HTTPException } from 'hono/http-exception'
|
||||||
|
|
||||||
import { getRandomState } from '../../utils/getRandomState'
|
import { getRandomState } from '../../utils/getRandomState'
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { MiddlewareHandler } from 'hono'
|
import type { MiddlewareHandler } from 'hono'
|
||||||
import { getCookie, setCookie } from 'hono/cookie'
|
|
||||||
import { env } from 'hono/adapter'
|
import { env } from 'hono/adapter'
|
||||||
|
import { getCookie, setCookie } from 'hono/cookie'
|
||||||
import { HTTPException } from 'hono/http-exception'
|
import { HTTPException } from 'hono/http-exception'
|
||||||
|
|
||||||
import { getCodeChallenge } from '../../utils/getCodeChallenge'
|
import { getCodeChallenge } from '../../utils/getCodeChallenge'
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
"hono": ">=3.*"
|
"hono": ">=3.*"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@jest/globals": "^29.7.0",
|
||||||
"@types/jest": "^29.5.11",
|
"@types/jest": "^29.5.11",
|
||||||
"@types/jsonwebtoken": "^9.0.5",
|
"@types/jsonwebtoken": "^9.0.5",
|
||||||
"hono": "^4.0.1",
|
"hono": "^4.0.1",
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import crypto from 'node:crypto'
|
|
||||||
import { jest } from '@jest/globals'
|
import { jest } from '@jest/globals'
|
||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
import * as oauth2 from 'oauth4webapi'
|
import * as oauth2 from 'oauth4webapi'
|
||||||
|
import crypto from 'node:crypto'
|
||||||
|
|
||||||
const MOCK_ISSUER = 'https://accounts.google.com'
|
const MOCK_ISSUER = 'https://accounts.google.com'
|
||||||
const MOCK_CLIENT_ID = 'CLIENT_ID_001'
|
const MOCK_CLIENT_ID = 'CLIENT_ID_001'
|
||||||
|
@ -387,7 +387,9 @@ describe('processOAuthCallback()', () => {
|
||||||
expect(res).not.toBeNull()
|
expect(res).not.toBeNull()
|
||||||
expect(res.status).toBe(302)
|
expect(res.status).toBe(302)
|
||||||
expect(res.headers.get('set-cookie')).toMatch(
|
expect(res.headers.get('set-cookie')).toMatch(
|
||||||
new RegExp(`${MOCK_COOKIE_NAME}=[^;]+; Path=${process.env.OIDC_COOKIE_PATH}; HttpOnly; Secure`)
|
new RegExp(
|
||||||
|
`${MOCK_COOKIE_NAME}=[^;]+; Path=${process.env.OIDC_COOKIE_PATH}; HttpOnly; Secure`
|
||||||
|
)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
test('Should return an error if the state parameter does not match', async () => {
|
test('Should return an error if the state parameter does not match', async () => {
|
||||||
|
|
|
@ -2,11 +2,8 @@ import type { Context } from 'hono'
|
||||||
import { createMiddleware } from 'hono/factory'
|
import { createMiddleware } from 'hono/factory'
|
||||||
import type { DefaultMetricsCollectorConfiguration, RegistryContentType } from 'prom-client'
|
import type { DefaultMetricsCollectorConfiguration, RegistryContentType } from 'prom-client'
|
||||||
import { Registry, collectDefaultMetrics as promCollectDefaultMetrics } from 'prom-client'
|
import { Registry, collectDefaultMetrics as promCollectDefaultMetrics } from 'prom-client'
|
||||||
import {
|
import { createStandardMetrics } from './standardMetrics'
|
||||||
type MetricOptions,
|
import type { MetricOptions, CustomMetricsOptions } from './standardMetrics'
|
||||||
type CustomMetricsOptions,
|
|
||||||
createStandardMetrics,
|
|
||||||
} from './standardMetrics'
|
|
||||||
|
|
||||||
interface PrometheusOptions {
|
interface PrometheusOptions {
|
||||||
registry?: Registry
|
registry?: Registry
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { Context } from 'hono'
|
import type { Context } from 'hono'
|
||||||
import type { CounterConfiguration, HistogramConfiguration, Metric } from 'prom-client'
|
import { Counter, Histogram } from 'prom-client'
|
||||||
import { Counter, Histogram, type Registry } from 'prom-client'
|
import type { CounterConfiguration, HistogramConfiguration, Metric, Registry } from 'prom-client'
|
||||||
|
|
||||||
export type MetricOptions = {
|
export type MetricOptions = {
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
|
|
|
@ -13,7 +13,6 @@ import {
|
||||||
import type { MiddlewareHandler } from 'hono'
|
import type { MiddlewareHandler } from 'hono'
|
||||||
|
|
||||||
export const qwikMiddleware = (opts: ServerRenderOptions): MiddlewareHandler => {
|
export const qwikMiddleware = (opts: ServerRenderOptions): MiddlewareHandler => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-extra-semi
|
|
||||||
;(globalThis as any).TextEncoderStream = TextEncoderStream
|
;(globalThis as any).TextEncoderStream = TextEncoderStream
|
||||||
const qwikSerializer = {
|
const qwikSerializer = {
|
||||||
_deserializeData,
|
_deserializeData,
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import type { Context } from 'hono'
|
import type { Context } from 'hono'
|
||||||
import type { Env, MiddlewareHandler } from 'hono/types'
|
import type { Env, MiddlewareHandler } from 'hono/types'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { renderToString, type RenderToReadableStreamOptions } from 'react-dom/server'
|
import { renderToString } from 'react-dom/server'
|
||||||
|
import type { RenderToReadableStreamOptions } from 'react-dom/server'
|
||||||
import type { Props } from '.'
|
import type { Props } from '.'
|
||||||
|
|
||||||
type RendererOptions = {
|
type RendererOptions = {
|
||||||
|
|
|
@ -69,7 +69,7 @@ const SwaggerUI = (options: SwaggerUIOptions) => {
|
||||||
const middleware =
|
const middleware =
|
||||||
<E extends Env>(options: SwaggerUIOptions): MiddlewareHandler<E> =>
|
<E extends Env>(options: SwaggerUIOptions): MiddlewareHandler<E> =>
|
||||||
async (c) => {
|
async (c) => {
|
||||||
const title = options?.title ?? "SwaggerUI"
|
const title = options?.title ?? 'SwaggerUI'
|
||||||
return c.html(/* html */ `
|
return c.html(/* html */ `
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'reflect-metadata'
|
import 'reflect-metadata'
|
||||||
import { injectable, inject } from 'tsyringe'
|
|
||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
|
import { injectable, inject } from 'tsyringe'
|
||||||
import { tsyringe } from '../src'
|
import { tsyringe } from '../src'
|
||||||
|
|
||||||
class Config {
|
class Config {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { container, DependencyContainer, InjectionToken } from 'tsyringe'
|
|
||||||
import type { Context, MiddlewareHandler } from 'hono'
|
import type { Context, MiddlewareHandler } from 'hono'
|
||||||
import { createMiddleware } from 'hono/factory'
|
import { createMiddleware } from 'hono/factory'
|
||||||
|
import type { DependencyContainer, InjectionToken } from 'tsyringe'
|
||||||
|
import { container } from 'tsyringe'
|
||||||
|
|
||||||
declare module 'hono' {
|
declare module 'hono' {
|
||||||
interface ContextVariableMap {
|
interface ContextVariableMap {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { TSchema, Static, TypeGuard, ValueGuard } from '@sinclair/typebox'
|
import type { TSchema, Static } from '@sinclair/typebox'
|
||||||
import { Value, type ValueError } from '@sinclair/typebox/value'
|
import { TypeGuard, ValueGuard } from '@sinclair/typebox'
|
||||||
|
import { Value } from '@sinclair/typebox/value'
|
||||||
|
import type { ValueError } from '@sinclair/typebox/value'
|
||||||
import type { Context, Env, MiddlewareHandler, ValidationTargets } from 'hono'
|
import type { Context, Env, MiddlewareHandler, ValidationTargets } from 'hono'
|
||||||
import { validator } from 'hono/validator'
|
import { validator } from 'hono/validator'
|
||||||
import IsObject = ValueGuard.IsObject
|
import IsObject = ValueGuard.IsObject
|
||||||
|
@ -7,7 +9,7 @@ import IsArray = ValueGuard.IsArray
|
||||||
|
|
||||||
export type Hook<T, E extends Env, P extends string> = (
|
export type Hook<T, E extends Env, P extends string> = (
|
||||||
result: { success: true; data: T } | { success: false; errors: ValueError[] },
|
result: { success: true; data: T } | { success: false; errors: ValueError[] },
|
||||||
c: Context<E, P>,
|
c: Context<E, P>
|
||||||
) => Response | Promise<Response> | void
|
) => Response | Promise<Response> | void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -61,11 +63,18 @@ export function tbValidator<
|
||||||
E extends Env,
|
E extends Env,
|
||||||
P extends string,
|
P extends string,
|
||||||
V extends { in: { [K in Target]: Static<T> }; out: { [K in Target]: Static<T> } }
|
V extends { in: { [K in Target]: Static<T> }; out: { [K in Target]: Static<T> } }
|
||||||
>(target: Target, schema: T, hook?: Hook<Static<T>, E, P>, stripNonSchemaItems?: boolean): MiddlewareHandler<E, P, V> {
|
>(
|
||||||
|
target: Target,
|
||||||
|
schema: T,
|
||||||
|
hook?: Hook<Static<T>, E, P>,
|
||||||
|
stripNonSchemaItems?: boolean
|
||||||
|
): MiddlewareHandler<E, P, V> {
|
||||||
// Compile the provided schema once rather than per validation. This could be optimized further using a shared schema
|
// Compile the provided schema once rather than per validation. This could be optimized further using a shared schema
|
||||||
// compilation pool similar to the Fastify implementation.
|
// compilation pool similar to the Fastify implementation.
|
||||||
return validator(target, (unprocessedData, c) => {
|
return validator(target, (unprocessedData, c) => {
|
||||||
const data = stripNonSchemaItems ? removeNonSchemaItems(schema, unprocessedData) : unprocessedData
|
const data = stripNonSchemaItems
|
||||||
|
? removeNonSchemaItems(schema, unprocessedData)
|
||||||
|
: unprocessedData
|
||||||
|
|
||||||
if (Value.Check(schema, data)) {
|
if (Value.Check(schema, data)) {
|
||||||
if (hook) {
|
if (hook) {
|
||||||
|
@ -90,7 +99,9 @@ export function tbValidator<
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeNonSchemaItems<T extends TSchema>(schema: T, obj: any): Static<T> {
|
function removeNonSchemaItems<T extends TSchema>(schema: T, obj: any): Static<T> {
|
||||||
if (typeof obj !== 'object' || obj === null) return obj
|
if (typeof obj !== 'object' || obj === null) {
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
return obj.map((item) => removeNonSchemaItems(schema.items, item))
|
return obj.map((item) => removeNonSchemaItems(schema.items, item))
|
||||||
|
@ -98,12 +109,9 @@ function removeNonSchemaItems<T extends TSchema>(schema: T, obj: any): Static<T>
|
||||||
|
|
||||||
const result: any = {}
|
const result: any = {}
|
||||||
for (const key in schema.properties) {
|
for (const key in schema.properties) {
|
||||||
if (obj.hasOwnProperty(key)) {
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||||
const propertySchema = schema.properties[key]
|
const propertySchema = schema.properties[key]
|
||||||
if (
|
if (IsObject(propertySchema) && !IsArray(propertySchema)) {
|
||||||
IsObject(propertySchema) &&
|
|
||||||
!IsArray(propertySchema)
|
|
||||||
) {
|
|
||||||
result[key] = removeNonSchemaItems(propertySchema as unknown as TSchema, obj[key])
|
result[key] = removeNonSchemaItems(propertySchema as unknown as TSchema, obj[key])
|
||||||
} else {
|
} else {
|
||||||
result[key] = obj[key]
|
result[key] = obj[key]
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Type as T } from '@sinclair/typebox'
|
import { Type as T } from '@sinclair/typebox'
|
||||||
|
import type { ValueError } from '@sinclair/typebox/value'
|
||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
import type { Equal, Expect } from 'hono/utils/types'
|
import type { Equal, Expect } from 'hono/utils/types'
|
||||||
import { tbValidator } from '../src'
|
import { tbValidator } from '../src'
|
||||||
import { ValueError } from '@sinclair/typebox/value'
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
type ExtractSchema<T> = T extends Hono<infer _, infer S> ? S : never
|
type ExtractSchema<T> = T extends Hono<infer _, infer S> ? S : never
|
||||||
|
@ -91,7 +91,8 @@ describe('With Hook', () => {
|
||||||
title: T.String(),
|
title: T.String(),
|
||||||
})
|
})
|
||||||
|
|
||||||
app.post(
|
app
|
||||||
|
.post(
|
||||||
'/post',
|
'/post',
|
||||||
tbValidator('json', schema, (result, c) => {
|
tbValidator('json', schema, (result, c) => {
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
|
@ -106,8 +107,9 @@ describe('With Hook', () => {
|
||||||
success: true,
|
success: true,
|
||||||
message: `${data.id} is ${data.title}`,
|
message: `${data.id} is ${data.title}`,
|
||||||
})
|
})
|
||||||
},
|
}
|
||||||
).post(
|
)
|
||||||
|
.post(
|
||||||
'/errorTest',
|
'/errorTest',
|
||||||
tbValidator('json', schema, (result, c) => {
|
tbValidator('json', schema, (result, c) => {
|
||||||
return c.json(result, 400)
|
return c.json(result, 400)
|
||||||
|
@ -118,7 +120,7 @@ describe('With Hook', () => {
|
||||||
success: true,
|
success: true,
|
||||||
message: `${data.id} is ${data.title}`,
|
message: `${data.id} is ${data.title}`,
|
||||||
})
|
})
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
it('Should return 200 response', async () => {
|
it('Should return 200 response', async () => {
|
||||||
|
@ -171,20 +173,22 @@ describe('With Hook', () => {
|
||||||
const { errors, success } = (await res.json()) as { success: boolean; errors: any[] }
|
const { errors, success } = (await res.json()) as { success: boolean; errors: any[] }
|
||||||
expect(success).toBe(false)
|
expect(success).toBe(false)
|
||||||
expect(Array.isArray(errors)).toBe(true)
|
expect(Array.isArray(errors)).toBe(true)
|
||||||
expect(errors.map((e: ValueError) => ({
|
expect(
|
||||||
'type': e?.schema?.type,
|
errors.map((e: ValueError) => ({
|
||||||
|
type: e?.schema?.type,
|
||||||
path: e?.path,
|
path: e?.path,
|
||||||
message: e?.message,
|
message: e?.message,
|
||||||
}))).toEqual([
|
}))
|
||||||
|
).toEqual([
|
||||||
{
|
{
|
||||||
'type': 'string',
|
type: 'string',
|
||||||
'path': '/title',
|
path: '/title',
|
||||||
'message': 'Required property',
|
message: 'Required property',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'type': 'string',
|
type: 'string',
|
||||||
'path': '/title',
|
path: '/title',
|
||||||
'message': 'Expected string',
|
message: 'Expected string',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
@ -207,25 +211,19 @@ describe('Remove non schema items', () => {
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
app.post(
|
app
|
||||||
'/stripValuesNested',
|
.post('/stripValuesNested', tbValidator('json', nestedSchema, undefined, true), (c) => {
|
||||||
tbValidator('json', nestedSchema, undefined, true),
|
|
||||||
(c) => {
|
|
||||||
return c.json({
|
return c.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: c.req.valid('json'),
|
message: c.req.valid('json'),
|
||||||
})
|
})
|
||||||
},
|
})
|
||||||
).post(
|
.post('/stripValuesArray', tbValidator('json', T.Array(schema), undefined, true), (c) => {
|
||||||
'/stripValuesArray',
|
|
||||||
tbValidator('json', T.Array(schema), undefined, true),
|
|
||||||
(c) => {
|
|
||||||
return c.json({
|
return c.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: c.req.valid('json'),
|
message: c.req.valid('json'),
|
||||||
})
|
})
|
||||||
},
|
})
|
||||||
)
|
|
||||||
|
|
||||||
it('Should remove all the values in the nested object and return a 200 response', async () => {
|
it('Should remove all the values in the nested object and return a 200 response', async () => {
|
||||||
const req = new Request('http://localhost/stripValuesNested', {
|
const req = new Request('http://localhost/stripValuesNested', {
|
||||||
|
@ -274,20 +272,21 @@ describe('Remove non schema items', () => {
|
||||||
|
|
||||||
const { message, success } = (await res.json()) as { success: boolean; message: any }
|
const { message, success } = (await res.json()) as { success: boolean; message: any }
|
||||||
expect(success).toBe(true)
|
expect(success).toBe(true)
|
||||||
expect(message).toEqual(
|
expect(message).toEqual({
|
||||||
|
id: 123,
|
||||||
|
itemArray: [
|
||||||
|
{ id: 123, title: 'Hello' },
|
||||||
{
|
{
|
||||||
'id': 123,
|
id: 123,
|
||||||
'itemArray': [{ 'id': 123, 'title': 'Hello' }, {
|
title: 'Hello',
|
||||||
'id': 123,
|
|
||||||
'title': 'Hello',
|
|
||||||
}],
|
|
||||||
'item': { 'id': 123, 'title': 'Hello' },
|
|
||||||
'itemObject': {
|
|
||||||
'item1': { 'id': 123, 'title': 'Hello' },
|
|
||||||
'item2': { 'id': 123, 'title': 'Hello' },
|
|
||||||
},
|
},
|
||||||
|
],
|
||||||
|
item: { id: 123, title: 'Hello' },
|
||||||
|
itemObject: {
|
||||||
|
item1: { id: 123, title: 'Hello' },
|
||||||
|
item2: { id: 123, title: 'Hello' },
|
||||||
},
|
},
|
||||||
)
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should remove all the values in the array and return a 200 response', async () => {
|
it('Should remove all the values in the array and return a 200 response', async () => {
|
||||||
|
@ -303,7 +302,8 @@ describe('Remove non schema items', () => {
|
||||||
title: 'Hello 2',
|
title: 'Hello 2',
|
||||||
nonExistentKey: 'error',
|
nonExistentKey: 'error',
|
||||||
},
|
},
|
||||||
]), method: 'POST',
|
]),
|
||||||
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
|
@ -313,11 +313,12 @@ describe('Remove non schema items', () => {
|
||||||
const { message, success } = (await res.json()) as { success: boolean; message: Array<any> }
|
const { message, success } = (await res.json()) as { success: boolean; message: Array<any> }
|
||||||
expect(res.status).toBe(200)
|
expect(res.status).toBe(200)
|
||||||
expect(success).toBe(true)
|
expect(success).toBe(true)
|
||||||
expect(message).toEqual([{ 'id': 123, 'title': 'Hello' }, {
|
expect(message).toEqual([
|
||||||
'id': 123,
|
{ id: 123, title: 'Hello' },
|
||||||
'title': 'Hello 2',
|
{
|
||||||
}],
|
id: 123,
|
||||||
)
|
title: 'Hello 2',
|
||||||
|
},
|
||||||
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -129,7 +129,7 @@ export const typiaValidator: TypiaValidator = (
|
||||||
validate: (input: any) => IValidation<any>,
|
validate: (input: any) => IValidation<any>,
|
||||||
hook?: Hook<any, any, any>
|
hook?: Hook<any, any, any>
|
||||||
): MiddlewareHandler => {
|
): MiddlewareHandler => {
|
||||||
if (target === 'query' || target === 'header')
|
if (target === 'query' || target === 'header') {
|
||||||
return async (c, next) => {
|
return async (c, next) => {
|
||||||
let value: any
|
let value: any
|
||||||
if (target === 'query') {
|
if (target === 'query') {
|
||||||
|
@ -140,15 +140,20 @@ export const typiaValidator: TypiaValidator = (
|
||||||
} satisfies IReadableURLSearchParams
|
} satisfies IReadableURLSearchParams
|
||||||
} else {
|
} else {
|
||||||
value = Object.create(null)
|
value = Object.create(null)
|
||||||
for (const [key, headerValue] of c.req.raw.headers) value[key.toLowerCase()] = headerValue
|
for (const [key, headerValue] of c.req.raw.headers) {
|
||||||
if (c.req.raw.headers.has('Set-Cookie'))
|
value[key.toLowerCase()] = headerValue
|
||||||
|
}
|
||||||
|
if (c.req.raw.headers.has('Set-Cookie')) {
|
||||||
value['Set-Cookie'] = c.req.raw.headers.getSetCookie()
|
value['Set-Cookie'] = c.req.raw.headers.getSetCookie()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
const result = validate(value)
|
const result = validate(value)
|
||||||
|
|
||||||
if (hook) {
|
if (hook) {
|
||||||
const res = await hook(result as never, c)
|
const res = await hook(result as never, c)
|
||||||
if (res instanceof Response) return res
|
if (res instanceof Response) {
|
||||||
|
return res
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
return c.json({ success: false, error: result.errors }, 400)
|
return c.json({ success: false, error: result.errors }, 400)
|
||||||
|
@ -157,6 +162,7 @@ export const typiaValidator: TypiaValidator = (
|
||||||
|
|
||||||
await next()
|
await next()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return validator(target, async (value, c) => {
|
return validator(target, async (value, c) => {
|
||||||
const result = validate(value)
|
const result = validate(value)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
import type { Equal, Expect } from 'hono/utils/types'
|
import type { Equal, Expect } from 'hono/utils/types'
|
||||||
import typia, { tags } from 'typia'
|
import type { tags } from 'typia'
|
||||||
|
import typia from 'typia'
|
||||||
import { typiaValidator } from '../src/http'
|
import { typiaValidator } from '../src/http'
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
import type { Context, Env, Input as HonoInput, MiddlewareHandler, ValidationTargets } from 'hono'
|
import type { Context, Env, Input as HonoInput, MiddlewareHandler, ValidationTargets } from 'hono'
|
||||||
import { validator } from 'hono/validator'
|
import { validator } from 'hono/validator'
|
||||||
import type { GenericSchema, GenericSchemaAsync, InferInput, InferOutput, SafeParseResult } from 'valibot'
|
import type {
|
||||||
|
GenericSchema,
|
||||||
|
GenericSchemaAsync,
|
||||||
|
InferInput,
|
||||||
|
InferOutput,
|
||||||
|
SafeParseResult,
|
||||||
|
} from 'valibot'
|
||||||
import { safeParseAsync } from 'valibot'
|
import { safeParseAsync } from 'valibot'
|
||||||
|
|
||||||
export type Hook<T extends GenericSchema | GenericSchemaAsync, E extends Env, P extends string> = (
|
export type Hook<T extends GenericSchema | GenericSchemaAsync, E extends Env, P extends string> = (
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
|
import type { StatusCode } from 'hono/utils/http-status'
|
||||||
import type { Equal, Expect } from 'hono/utils/types'
|
import type { Equal, Expect } from 'hono/utils/types'
|
||||||
import { number, object, objectAsync, optional, optionalAsync, string } from 'valibot'
|
import { number, object, objectAsync, optional, optionalAsync, string } from 'valibot'
|
||||||
import { vValidator } from '../src'
|
import { vValidator } from '../src'
|
||||||
import { StatusCode } from 'hono/utils/http-status'
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
type ExtractSchema<T> = T extends Hono<infer _, infer S> ? S : never
|
type ExtractSchema<T> = T extends Hono<infer _, infer S> ? S : never
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { defineConfig } from "tsup";
|
import { defineConfig } from 'tsup'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
entryPoints: ["src/index.ts"],
|
entryPoints: ['src/index.ts'],
|
||||||
format: ["cjs", "esm"],
|
format: ['cjs', 'esm'],
|
||||||
dts: true,
|
dts: true,
|
||||||
outDir: "dist",
|
outDir: 'dist',
|
||||||
clean: true,
|
clean: true,
|
||||||
});
|
})
|
||||||
|
|
|
@ -26,7 +26,6 @@ import type {
|
||||||
ValidationTargets,
|
ValidationTargets,
|
||||||
} from 'hono'
|
} from 'hono'
|
||||||
import type { MergePath, MergeSchemaPath } from 'hono/types'
|
import type { MergePath, MergeSchemaPath } from 'hono/types'
|
||||||
import type { JSONParsed, JSONValue, RemoveBlankRecord, SimplifyDeepArray } from 'hono/utils/types'
|
|
||||||
import type {
|
import type {
|
||||||
ClientErrorStatusCode,
|
ClientErrorStatusCode,
|
||||||
InfoStatusCode,
|
InfoStatusCode,
|
||||||
|
@ -35,6 +34,7 @@ import type {
|
||||||
StatusCode,
|
StatusCode,
|
||||||
SuccessStatusCode,
|
SuccessStatusCode,
|
||||||
} from 'hono/utils/http-status'
|
} from 'hono/utils/http-status'
|
||||||
|
import type { JSONParsed, JSONValue, RemoveBlankRecord, SimplifyDeepArray } from 'hono/utils/types'
|
||||||
import { mergePath } from 'hono/utils/url'
|
import { mergePath } from 'hono/utils/url'
|
||||||
import type { ZodError, ZodSchema } from 'zod'
|
import type { ZodError, ZodSchema } from 'zod'
|
||||||
import { ZodType, z } from 'zod'
|
import { ZodType, z } from 'zod'
|
||||||
|
@ -657,7 +657,6 @@ export class OpenAPIHono<
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
return this as any
|
return this as any
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { assertType, describe, it } from 'vitest'
|
|
||||||
import { MiddlewareToHandlerType, OfHandlerType, OpenAPIHono, createRoute, z } from '../src/index'
|
|
||||||
import { createMiddleware } from 'hono/factory'
|
import { createMiddleware } from 'hono/factory'
|
||||||
import type { ExtractSchema } from 'hono/types'
|
import type { ExtractSchema } from 'hono/types'
|
||||||
import type { Equal, Expect } from 'hono/utils/types'
|
import type { Equal, Expect } from 'hono/utils/types'
|
||||||
|
import { assertType, describe, it } from 'vitest'
|
||||||
|
import { OpenAPIHono, createRoute, z } from '../src/index'
|
||||||
|
import type { MiddlewareToHandlerType, OfHandlerType } from '../src/index'
|
||||||
|
|
||||||
describe('Types', () => {
|
describe('Types', () => {
|
||||||
const RequestSchema = z.object({
|
const RequestSchema = z.object({
|
||||||
|
@ -276,7 +277,7 @@ describe('Middleware', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should infer Env from router middleware', async () => {
|
it('Should infer Env from router middleware', async () => {
|
||||||
const app = new OpenAPIHono<{ Variables: { too: Symbol } }>()
|
const app = new OpenAPIHono<{ Variables: { too: symbol } }>()
|
||||||
app.openapi(
|
app.openapi(
|
||||||
createRoute({
|
createRoute({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
|
@ -308,7 +309,7 @@ describe('Middleware', () => {
|
||||||
|
|
||||||
type verifyFoo = Expect<Equal<typeof c.var.foo, string>>
|
type verifyFoo = Expect<Equal<typeof c.var.foo, string>>
|
||||||
type verifyBar = Expect<Equal<typeof c.var.bar, number>>
|
type verifyBar = Expect<Equal<typeof c.var.bar, number>>
|
||||||
type verifyToo = Expect<Equal<typeof c.var.too, Symbol>>
|
type verifyToo = Expect<Equal<typeof c.var.too, symbol>>
|
||||||
|
|
||||||
return c.json({})
|
return c.json({})
|
||||||
}
|
}
|
||||||
|
@ -316,7 +317,7 @@ describe('Middleware', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('Should infer Env root when no middleware provided', async () => {
|
it('Should infer Env root when no middleware provided', async () => {
|
||||||
const app = new OpenAPIHono<{ Variables: { too: Symbol } }>()
|
const app = new OpenAPIHono<{ Variables: { too: symbol } }>()
|
||||||
app.openapi(
|
app.openapi(
|
||||||
createRoute({
|
createRoute({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
|
@ -331,7 +332,7 @@ describe('Middleware', () => {
|
||||||
(c) => {
|
(c) => {
|
||||||
c.var.too
|
c.var.too
|
||||||
|
|
||||||
type verify = Expect<Equal<typeof c.var.too, Symbol>>
|
type verify = Expect<Equal<typeof c.var.too, symbol>>
|
||||||
|
|
||||||
return c.json({})
|
return c.json({})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import type { RouteConfig } from '@asteasolutions/zod-to-openapi'
|
import type { RouteConfig } from '@asteasolutions/zod-to-openapi'
|
||||||
import type { Context, TypedResponse } from 'hono'
|
import type { Context, TypedResponse } from 'hono'
|
||||||
|
import { accepts } from 'hono/accepts'
|
||||||
import { bearerAuth } from 'hono/bearer-auth'
|
import { bearerAuth } from 'hono/bearer-auth'
|
||||||
import { hc } from 'hono/client'
|
import { hc } from 'hono/client'
|
||||||
|
import type { ServerErrorStatusCode } from 'hono/utils/http-status'
|
||||||
|
import type { Equal, Expect } from 'hono/utils/types'
|
||||||
import { describe, expect, expectTypeOf, it, vi } from 'vitest'
|
import { describe, expect, expectTypeOf, it, vi } from 'vitest'
|
||||||
|
import { stringify } from 'yaml'
|
||||||
import type { RouteConfigToTypedResponse } from '../src/index'
|
import type { RouteConfigToTypedResponse } from '../src/index'
|
||||||
import { OpenAPIHono, createRoute, z } from '../src/index'
|
import { OpenAPIHono, createRoute, z } from '../src/index'
|
||||||
import type { Equal, Expect } from 'hono/utils/types'
|
|
||||||
import type { ServerErrorStatusCode } from 'hono/utils/http-status'
|
|
||||||
import { stringify } from 'yaml'
|
|
||||||
import { accepts } from 'hono/accepts'
|
|
||||||
|
|
||||||
describe('Constructor', () => {
|
describe('Constructor', () => {
|
||||||
it('Should not require init object', () => {
|
it('Should not require init object', () => {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import type { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono'
|
import type { Context, Env, Input, MiddlewareHandler, TypedResponse, ValidationTargets } from 'hono'
|
||||||
import { validator } from 'hono/validator'
|
import { validator } from 'hono/validator'
|
||||||
import { ZodObject, type ZodError, type ZodSchema, type z } from 'zod'
|
import { ZodObject } from 'zod'
|
||||||
|
import type { ZodError, ZodSchema, z } from 'zod'
|
||||||
|
|
||||||
export type Hook<
|
export type Hook<
|
||||||
T,
|
T,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Hono } from 'hono'
|
import { Hono } from 'hono'
|
||||||
import type { Equal, Expect } from 'hono/utils/types'
|
import type { Equal, Expect } from 'hono/utils/types'
|
||||||
|
import { vi } from 'vitest'
|
||||||
import { z } from 'zod'
|
import { z } from 'zod'
|
||||||
import { zValidator } from '../src'
|
import { zValidator } from '../src'
|
||||||
import { vi } from 'vitest'
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
type ExtractSchema<T> = T extends Hono<infer _, infer S> ? S : never
|
type ExtractSchema<T> = T extends Hono<infer _, infer S> ? S : never
|
||||||
|
|
197
yarn.lock
197
yarn.lock
|
@ -2221,6 +2221,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@eslint-community/regexpp@npm:^4.12.1":
|
||||||
|
version: 4.12.1
|
||||||
|
resolution: "@eslint-community/regexpp@npm:4.12.1"
|
||||||
|
checksum: a03d98c246bcb9109aec2c08e4d10c8d010256538dcb3f56610191607214523d4fb1b00aa81df830b6dffb74c5fa0be03642513a289c567949d3e550ca11cdf6
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@eslint-community/regexpp@npm:^4.4.0":
|
"@eslint-community/regexpp@npm:^4.4.0":
|
||||||
version: 4.11.1
|
version: 4.11.1
|
||||||
resolution: "@eslint-community/regexpp@npm:4.11.1"
|
resolution: "@eslint-community/regexpp@npm:4.11.1"
|
||||||
|
@ -2246,6 +2253,26 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@eslint/config-array@npm:^0.19.0":
|
||||||
|
version: 0.19.1
|
||||||
|
resolution: "@eslint/config-array@npm:0.19.1"
|
||||||
|
dependencies:
|
||||||
|
"@eslint/object-schema": "npm:^2.1.5"
|
||||||
|
debug: "npm:^4.3.1"
|
||||||
|
minimatch: "npm:^3.1.2"
|
||||||
|
checksum: 43b01f596ddad404473beae5cf95c013d29301c72778d0f5bf8a6699939c8a9a5663dbd723b53c5f476b88b0c694f76ea145d1aa9652230d140fe1161e4a4b49
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@eslint/core@npm:^0.9.0":
|
||||||
|
version: 0.9.1
|
||||||
|
resolution: "@eslint/core@npm:0.9.1"
|
||||||
|
dependencies:
|
||||||
|
"@types/json-schema": "npm:^7.0.15"
|
||||||
|
checksum: 638104b1b5833a9bbf2329f0c0ddf322e4d6c0410b149477e02cd2b78c04722be90c14b91b8ccdef0d63a2404dff72a17b6b412ce489ea429ae6a8fcb8abff28
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@eslint/eslintrc@npm:^2.1.4":
|
"@eslint/eslintrc@npm:^2.1.4":
|
||||||
version: 2.1.4
|
version: 2.1.4
|
||||||
resolution: "@eslint/eslintrc@npm:2.1.4"
|
resolution: "@eslint/eslintrc@npm:2.1.4"
|
||||||
|
@ -2280,6 +2307,23 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@eslint/eslintrc@npm:^3.2.0":
|
||||||
|
version: 3.2.0
|
||||||
|
resolution: "@eslint/eslintrc@npm:3.2.0"
|
||||||
|
dependencies:
|
||||||
|
ajv: "npm:^6.12.4"
|
||||||
|
debug: "npm:^4.3.2"
|
||||||
|
espree: "npm:^10.0.1"
|
||||||
|
globals: "npm:^14.0.0"
|
||||||
|
ignore: "npm:^5.2.0"
|
||||||
|
import-fresh: "npm:^3.2.1"
|
||||||
|
js-yaml: "npm:^4.1.0"
|
||||||
|
minimatch: "npm:^3.1.2"
|
||||||
|
strip-json-comments: "npm:^3.1.1"
|
||||||
|
checksum: 43867a07ff9884d895d9855edba41acf325ef7664a8df41d957135a81a477ff4df4196f5f74dc3382627e5cc8b7ad6b815c2cea1b58f04a75aced7c43414ab8b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@eslint/js@npm:8.57.0":
|
"@eslint/js@npm:8.57.0":
|
||||||
version: 8.57.0
|
version: 8.57.0
|
||||||
resolution: "@eslint/js@npm:8.57.0"
|
resolution: "@eslint/js@npm:8.57.0"
|
||||||
|
@ -2294,6 +2338,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@eslint/js@npm:9.17.0":
|
||||||
|
version: 9.17.0
|
||||||
|
resolution: "@eslint/js@npm:9.17.0"
|
||||||
|
checksum: a0fda8657a01c60aa540f95397754267ba640ffb126e011b97fd65c322a94969d161beeaef57c1441c495da2f31167c34bd38209f7c146c7225072378c3a933d
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@eslint/object-schema@npm:^2.1.4":
|
"@eslint/object-schema@npm:^2.1.4":
|
||||||
version: 2.1.4
|
version: 2.1.4
|
||||||
resolution: "@eslint/object-schema@npm:2.1.4"
|
resolution: "@eslint/object-schema@npm:2.1.4"
|
||||||
|
@ -2301,6 +2352,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@eslint/object-schema@npm:^2.1.5":
|
||||||
|
version: 2.1.5
|
||||||
|
resolution: "@eslint/object-schema@npm:2.1.5"
|
||||||
|
checksum: 5320691ed41ecd09a55aff40ce8e56596b4eb81f3d4d6fe530c50fdd6552d88102d1c1a29d970ae798ce30849752a708772de38ded07a6f25b3da32ebea081d8
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@eslint/plugin-kit@npm:^0.1.0":
|
"@eslint/plugin-kit@npm:^0.1.0":
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
resolution: "@eslint/plugin-kit@npm:0.1.0"
|
resolution: "@eslint/plugin-kit@npm:0.1.0"
|
||||||
|
@ -2310,6 +2368,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@eslint/plugin-kit@npm:^0.2.3":
|
||||||
|
version: 0.2.4
|
||||||
|
resolution: "@eslint/plugin-kit@npm:0.2.4"
|
||||||
|
dependencies:
|
||||||
|
levn: "npm:^0.4.1"
|
||||||
|
checksum: 1bcfc0a30b1df891047c1d8b3707833bded12a057ba01757a2a8591fdc8d8fe0dbb8d51d4b0b61b2af4ca1d363057abd7d2fb4799f1706b105734f4d3fa0dbf1
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@fastify/busboy@npm:^2.0.0":
|
"@fastify/busboy@npm:^2.0.0":
|
||||||
version: 2.1.0
|
version: 2.1.0
|
||||||
resolution: "@fastify/busboy@npm:2.1.0"
|
resolution: "@fastify/busboy@npm:2.1.0"
|
||||||
|
@ -2719,6 +2786,7 @@ __metadata:
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@hono/oidc-auth@workspace:packages/oidc-auth"
|
resolution: "@hono/oidc-auth@workspace:packages/oidc-auth"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
"@jest/globals": "npm:^29.7.0"
|
||||||
"@types/jest": "npm:^29.5.11"
|
"@types/jest": "npm:^29.5.11"
|
||||||
"@types/jsonwebtoken": "npm:^9.0.5"
|
"@types/jsonwebtoken": "npm:^9.0.5"
|
||||||
hono: "npm:^4.0.1"
|
hono: "npm:^4.0.1"
|
||||||
|
@ -2958,6 +3026,23 @@ __metadata:
|
||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
|
"@humanfs/core@npm:^0.19.1":
|
||||||
|
version: 0.19.1
|
||||||
|
resolution: "@humanfs/core@npm:0.19.1"
|
||||||
|
checksum: aa4e0152171c07879b458d0e8a704b8c3a89a8c0541726c6b65b81e84fd8b7564b5d6c633feadc6598307d34564bd53294b533491424e8e313d7ab6c7bc5dc67
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@humanfs/node@npm:^0.16.6":
|
||||||
|
version: 0.16.6
|
||||||
|
resolution: "@humanfs/node@npm:0.16.6"
|
||||||
|
dependencies:
|
||||||
|
"@humanfs/core": "npm:^0.19.1"
|
||||||
|
"@humanwhocodes/retry": "npm:^0.3.0"
|
||||||
|
checksum: 8356359c9f60108ec204cbd249ecd0356667359b2524886b357617c4a7c3b6aace0fd5a369f63747b926a762a88f8a25bc066fa1778508d110195ce7686243e1
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@humanwhocodes/config-array@npm:^0.11.14":
|
"@humanwhocodes/config-array@npm:^0.11.14":
|
||||||
version: 0.11.14
|
version: 0.11.14
|
||||||
resolution: "@humanwhocodes/config-array@npm:0.11.14"
|
resolution: "@humanwhocodes/config-array@npm:0.11.14"
|
||||||
|
@ -2990,6 +3075,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@humanwhocodes/retry@npm:^0.4.1":
|
||||||
|
version: 0.4.1
|
||||||
|
resolution: "@humanwhocodes/retry@npm:0.4.1"
|
||||||
|
checksum: be7bb6841c4c01d0b767d9bb1ec1c9359ee61421ce8ba66c249d035c5acdfd080f32d55a5c9e859cdd7868788b8935774f65b2caf24ec0b7bd7bf333791f063b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@iarna/toml@npm:^2.2.5":
|
"@iarna/toml@npm:^2.2.5":
|
||||||
version: 2.2.5
|
version: 2.2.5
|
||||||
resolution: "@iarna/toml@npm:2.2.5"
|
resolution: "@iarna/toml@npm:2.2.5"
|
||||||
|
@ -4908,7 +5000,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/estree@npm:1.0.6":
|
"@types/estree@npm:1.0.6, @types/estree@npm:^1.0.6":
|
||||||
version: 1.0.6
|
version: 1.0.6
|
||||||
resolution: "@types/estree@npm:1.0.6"
|
resolution: "@types/estree@npm:1.0.6"
|
||||||
checksum: cdfd751f6f9065442cd40957c07fd80361c962869aa853c1c2fd03e101af8b9389d8ff4955a43a6fcfa223dd387a089937f95be0f3eec21ca527039fd2d9859a
|
checksum: cdfd751f6f9065442cd40957c07fd80361c962869aa853c1c2fd03e101af8b9389d8ff4955a43a6fcfa223dd387a089937f95be0f3eec21ca527039fd2d9859a
|
||||||
|
@ -5002,7 +5094,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.6, @types/json-schema@npm:^7.0.9":
|
"@types/json-schema@npm:*, @types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.6, @types/json-schema@npm:^7.0.9":
|
||||||
version: 7.0.15
|
version: 7.0.15
|
||||||
resolution: "@types/json-schema@npm:7.0.15"
|
resolution: "@types/json-schema@npm:7.0.15"
|
||||||
checksum: a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db
|
checksum: a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db
|
||||||
|
@ -6105,6 +6197,15 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"acorn@npm:^8.14.0":
|
||||||
|
version: 8.14.0
|
||||||
|
resolution: "acorn@npm:8.14.0"
|
||||||
|
bin:
|
||||||
|
acorn: bin/acorn
|
||||||
|
checksum: 6d4ee461a7734b2f48836ee0fbb752903606e576cc100eb49340295129ca0b452f3ba91ddd4424a1d4406a98adfb2ebb6bd0ff4c49d7a0930c10e462719bbfd7
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"agent-base@npm:6":
|
"agent-base@npm:6":
|
||||||
version: 6.0.2
|
version: 6.0.2
|
||||||
resolution: "agent-base@npm:6.0.2"
|
resolution: "agent-base@npm:6.0.2"
|
||||||
|
@ -8011,6 +8112,17 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"cross-spawn@npm:^7.0.6":
|
||||||
|
version: 7.0.6
|
||||||
|
resolution: "cross-spawn@npm:7.0.6"
|
||||||
|
dependencies:
|
||||||
|
path-key: "npm:^3.1.0"
|
||||||
|
shebang-command: "npm:^2.0.0"
|
||||||
|
which: "npm:^2.0.1"
|
||||||
|
checksum: 053ea8b2135caff68a9e81470e845613e374e7309a47731e81639de3eaeb90c3d01af0e0b44d2ab9d50b43467223b88567dfeb3262db942dc063b9976718ffc1
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"crypto-random-string@npm:^2.0.0":
|
"crypto-random-string@npm:^2.0.0":
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
resolution: "crypto-random-string@npm:2.0.0"
|
resolution: "crypto-random-string@npm:2.0.0"
|
||||||
|
@ -9695,6 +9807,16 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"eslint-scope@npm:^8.2.0":
|
||||||
|
version: 8.2.0
|
||||||
|
resolution: "eslint-scope@npm:8.2.0"
|
||||||
|
dependencies:
|
||||||
|
esrecurse: "npm:^4.3.0"
|
||||||
|
estraverse: "npm:^5.2.0"
|
||||||
|
checksum: 8d2d58e2136d548ac7e0099b1a90d9fab56f990d86eb518de1247a7066d38c908be2f3df477a79cf60d70b30ba18735d6c6e70e9914dca2ee515a729975d70d6
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3":
|
"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3":
|
||||||
version: 3.4.3
|
version: 3.4.3
|
||||||
resolution: "eslint-visitor-keys@npm:3.4.3"
|
resolution: "eslint-visitor-keys@npm:3.4.3"
|
||||||
|
@ -9709,6 +9831,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"eslint-visitor-keys@npm:^4.2.0":
|
||||||
|
version: 4.2.0
|
||||||
|
resolution: "eslint-visitor-keys@npm:4.2.0"
|
||||||
|
checksum: 2ed81c663b147ca6f578312919483eb040295bbab759e5a371953456c636c5b49a559883e2677112453728d66293c0a4c90ab11cab3428cf02a0236d2e738269
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"eslint@npm:^8.57.0":
|
"eslint@npm:^8.57.0":
|
||||||
version: 8.57.0
|
version: 8.57.0
|
||||||
resolution: "eslint@npm:8.57.0"
|
resolution: "eslint@npm:8.57.0"
|
||||||
|
@ -9806,6 +9935,55 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"eslint@npm:^9.17.0":
|
||||||
|
version: 9.17.0
|
||||||
|
resolution: "eslint@npm:9.17.0"
|
||||||
|
dependencies:
|
||||||
|
"@eslint-community/eslint-utils": "npm:^4.2.0"
|
||||||
|
"@eslint-community/regexpp": "npm:^4.12.1"
|
||||||
|
"@eslint/config-array": "npm:^0.19.0"
|
||||||
|
"@eslint/core": "npm:^0.9.0"
|
||||||
|
"@eslint/eslintrc": "npm:^3.2.0"
|
||||||
|
"@eslint/js": "npm:9.17.0"
|
||||||
|
"@eslint/plugin-kit": "npm:^0.2.3"
|
||||||
|
"@humanfs/node": "npm:^0.16.6"
|
||||||
|
"@humanwhocodes/module-importer": "npm:^1.0.1"
|
||||||
|
"@humanwhocodes/retry": "npm:^0.4.1"
|
||||||
|
"@types/estree": "npm:^1.0.6"
|
||||||
|
"@types/json-schema": "npm:^7.0.15"
|
||||||
|
ajv: "npm:^6.12.4"
|
||||||
|
chalk: "npm:^4.0.0"
|
||||||
|
cross-spawn: "npm:^7.0.6"
|
||||||
|
debug: "npm:^4.3.2"
|
||||||
|
escape-string-regexp: "npm:^4.0.0"
|
||||||
|
eslint-scope: "npm:^8.2.0"
|
||||||
|
eslint-visitor-keys: "npm:^4.2.0"
|
||||||
|
espree: "npm:^10.3.0"
|
||||||
|
esquery: "npm:^1.5.0"
|
||||||
|
esutils: "npm:^2.0.2"
|
||||||
|
fast-deep-equal: "npm:^3.1.3"
|
||||||
|
file-entry-cache: "npm:^8.0.0"
|
||||||
|
find-up: "npm:^5.0.0"
|
||||||
|
glob-parent: "npm:^6.0.2"
|
||||||
|
ignore: "npm:^5.2.0"
|
||||||
|
imurmurhash: "npm:^0.1.4"
|
||||||
|
is-glob: "npm:^4.0.0"
|
||||||
|
json-stable-stringify-without-jsonify: "npm:^1.0.1"
|
||||||
|
lodash.merge: "npm:^4.6.2"
|
||||||
|
minimatch: "npm:^3.1.2"
|
||||||
|
natural-compare: "npm:^1.4.0"
|
||||||
|
optionator: "npm:^0.9.3"
|
||||||
|
peerDependencies:
|
||||||
|
jiti: "*"
|
||||||
|
peerDependenciesMeta:
|
||||||
|
jiti:
|
||||||
|
optional: true
|
||||||
|
bin:
|
||||||
|
eslint: bin/eslint.js
|
||||||
|
checksum: 9edd8dd782b4ae2eb00a158ed4708194835d4494d75545fa63a51f020ed17f865c49b4ae1914a2ecbc7fdb262bd8059e811aeef9f0bae63dced9d3293be1bbdd
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"espree@npm:^10.0.1, espree@npm:^10.1.0":
|
"espree@npm:^10.0.1, espree@npm:^10.1.0":
|
||||||
version: 10.1.0
|
version: 10.1.0
|
||||||
resolution: "espree@npm:10.1.0"
|
resolution: "espree@npm:10.1.0"
|
||||||
|
@ -9817,6 +9995,17 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"espree@npm:^10.3.0":
|
||||||
|
version: 10.3.0
|
||||||
|
resolution: "espree@npm:10.3.0"
|
||||||
|
dependencies:
|
||||||
|
acorn: "npm:^8.14.0"
|
||||||
|
acorn-jsx: "npm:^5.3.2"
|
||||||
|
eslint-visitor-keys: "npm:^4.2.0"
|
||||||
|
checksum: 272beeaca70d0a1a047d61baff64db04664a33d7cfb5d144f84bc8a5c6194c6c8ebe9cc594093ca53add88baa23e59b01e69e8a0160ab32eac570482e165c462
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"espree@npm:^9.0.0, espree@npm:^9.6.0, espree@npm:^9.6.1":
|
"espree@npm:^9.0.0, espree@npm:^9.6.0, espree@npm:^9.6.1":
|
||||||
version: 9.6.1
|
version: 9.6.1
|
||||||
resolution: "espree@npm:9.6.1"
|
resolution: "espree@npm:9.6.1"
|
||||||
|
@ -11398,9 +11587,7 @@ __metadata:
|
||||||
"@types/node": "npm:^20.10.4"
|
"@types/node": "npm:^20.10.4"
|
||||||
"@typescript-eslint/eslint-plugin": "npm:^8.7.0"
|
"@typescript-eslint/eslint-plugin": "npm:^8.7.0"
|
||||||
"@typescript-eslint/parser": "npm:^8.7.0"
|
"@typescript-eslint/parser": "npm:^8.7.0"
|
||||||
eslint: "npm:^8.57.0"
|
eslint: "npm:^9.17.0"
|
||||||
eslint-plugin-import-x: "npm:^4.1.1"
|
|
||||||
eslint-plugin-n: "npm:^17.10.2"
|
|
||||||
jest: "npm:^29.5.0"
|
jest: "npm:^29.5.0"
|
||||||
jest-environment-miniflare: "npm:^2.14.1"
|
jest-environment-miniflare: "npm:^2.14.1"
|
||||||
npm-run-all2: "npm:^6.2.2"
|
npm-run-all2: "npm:^6.2.2"
|
||||||
|
|
Loading…
Reference in New Issue