wip
This commit is contained in:
		| @@ -3,7 +3,7 @@ import Ajv from 'ajv'; | |||||||
| import type { LocalUser } from '@/models/entities/User.js'; | import type { LocalUser } from '@/models/entities/User.js'; | ||||||
| import type { AccessToken } from '@/models/entities/AccessToken.js'; | import type { AccessToken } from '@/models/entities/AccessToken.js'; | ||||||
| import { ApiError } from './error.js'; | import { ApiError } from './error.js'; | ||||||
| import { endpoints } from 'misskey-js/built/endpoints.js'; | import { endpoints, getEndpointSchema } from 'misskey-js/built/endpoints.js'; | ||||||
| import type { IEndpointMeta, ResponseOf, SchemaOrUndefined } from 'misskey-js/built/endpoints.types.js'; | import type { IEndpointMeta, ResponseOf, SchemaOrUndefined } from 'misskey-js/built/endpoints.types.js'; | ||||||
| import type { Endpoints } from 'misskey-js'; | import type { Endpoints } from 'misskey-js'; | ||||||
| import { WeakSerialized } from 'schema-type'; | import { WeakSerialized } from 'schema-type'; | ||||||
| @@ -50,7 +50,8 @@ export abstract class Endpoint<E extends keyof Endpoints, T extends IEndpointMet | |||||||
|  |  | ||||||
| 	constructor(cb: Executor<T>) { | 	constructor(cb: Executor<T>) { | ||||||
| 		this.meta = endpoints[this.name]; | 		this.meta = endpoints[this.name]; | ||||||
| 		const validate = ajv.compile({ oneOf: this.meta.defines.map(d => d.req) }); | 		const req = getEndpointSchema('req', this.name); | ||||||
|  | 		const validate = req ? ajv.compile(req) : null; | ||||||
|  |  | ||||||
| 		this.exec = (params, user, token, file, ip, headers) => { | 		this.exec = (params, user, token, file, ip, headers) => { | ||||||
| 			let cleanup: undefined | (() => void) = undefined; | 			let cleanup: undefined | (() => void) = undefined; | ||||||
| @@ -66,21 +67,27 @@ export abstract class Endpoint<E extends keyof Endpoints, T extends IEndpointMet | |||||||
| 					id: '4267801e-70d1-416a-b011-4ee502885d8b', | 					id: '4267801e-70d1-416a-b011-4ee502885d8b', | ||||||
| 				})); | 				})); | ||||||
| 			} | 			} | ||||||
| 	 |  | ||||||
| 			const valid = validate(params); | 			if (validate) { | ||||||
| 			if (!valid) { | 				const valid = validate(params); | ||||||
| 				if (file) cleanup!(); |  | ||||||
| 	 | 				if (!valid) { | ||||||
| 				const errors = validate.errors!; | 					if (file) cleanup!(); | ||||||
| 				const err = new ApiError({ | 		 | ||||||
| 					message: 'Invalid param.', | 					const errors = validate.errors!; | ||||||
| 					code: 'INVALID_PARAM', | 					const err = new ApiError({ | ||||||
| 					id: '3d81ceae-475f-4600-b2a8-2bc116157532', | 						message: 'Invalid param.', | ||||||
| 				}, { | 						code: 'INVALID_PARAM', | ||||||
| 					param: errors[0].schemaPath, | 						id: '3d81ceae-475f-4600-b2a8-2bc116157532', | ||||||
| 					reason: errors[0].message, | 					}, { | ||||||
| 				}); | 						param: errors[0].schemaPath, | ||||||
| 				return Promise.reject(err); | 						reason: errors[0].message, | ||||||
|  | 					}); | ||||||
|  | 					return Promise.reject(err); | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				// validateがnullである場合、paramsがnullや空オブジェクトであるべきではあるが、 | ||||||
|  | 				// 特にチェックはしない | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			return cb(params as any, user as any, token, file, cleanup, ip, headers); | 			return cb(params as any, user as any, token, file, cleanup, ip, headers); | ||||||
|   | |||||||
| @@ -1,7 +1,8 @@ | |||||||
| import type { Config } from '@/config.js'; | import type { Config } from '@/config.js'; | ||||||
| import endpoints from '../endpoints.js'; | import { endpoints, getEndpointSchema } from 'misskey-js/built/endpoints.js'; | ||||||
| import { errors as basicErrors } from './errors.js'; | import { errors as basicErrors } from './errors.js'; | ||||||
| import { schemas, convertSchemaToOpenApiSchema } from './schemas.js'; | import { schemas } from './schemas.js'; | ||||||
|  | import { Endpoints } from 'misskey-js'; | ||||||
|  |  | ||||||
| export function genOpenapiSpec(config: Config) { | export function genOpenapiSpec(config: Config) { | ||||||
| 	const spec = { | 	const spec = { | ||||||
| @@ -37,11 +38,11 @@ export function genOpenapiSpec(config: Config) { | |||||||
| 		}, | 		}, | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	for (const endpoint of endpoints.filter(ep => !ep.meta.secure)) { | 	for (const [name, endpoint] of Object.entries(endpoints).filter(([name, ep]) => !ep.secure)) { | ||||||
| 		const errors = {} as any; | 		const errors = {} as any; | ||||||
|  |  | ||||||
| 		if (endpoint.meta.errors) { | 		if ('errors' in endpoint && endpoint.errors) { | ||||||
| 			for (const e of Object.values(endpoint.meta.errors)) { | 			for (const e of Object.values(endpoint.errors)) { | ||||||
| 				errors[e.code] = { | 				errors[e.code] = { | ||||||
| 					value: { | 					value: { | ||||||
| 						error: e, | 						error: e, | ||||||
| @@ -50,42 +51,30 @@ export function genOpenapiSpec(config: Config) { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res) : {}; | 		const resSchema = getEndpointSchema('res', name as keyof Endpoints); | ||||||
|  |  | ||||||
| 		let desc = (endpoint.meta.description ? endpoint.meta.description : 'No description provided.') + '\n\n'; | 		let desc = ('description' in endpoint ? endpoint.description : 'No description provided.') + '\n\n'; | ||||||
| 		desc += `**Credential required**: *${endpoint.meta.requireCredential ? 'Yes' : 'No'}*`; | 		desc += `**Credential required**: *${('requireCredential' in endpoint && endpoint.requireCredential) ? 'Yes' : 'No'}*`; | ||||||
| 		if (endpoint.meta.kind) { | 		if ('kind' in endpoint && endpoint.kind) { | ||||||
| 			const kind = endpoint.meta.kind; | 			const kind = endpoint.kind; | ||||||
| 			desc += ` / **Permission**: *${kind}*`; | 			desc += ` / **Permission**: *${kind}*`; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		const requestType = endpoint.meta.requireFile ? 'multipart/form-data' : 'application/json'; | 		const requestType = ('requireFile' in endpoint && endpoint.requireFile) ? 'multipart/form-data' : 'application/json'; | ||||||
| 		const schema = { ...endpoint.params }; | 		const schema = getEndpointSchema('req', name as keyof Endpoints) ?? {}; | ||||||
|  |  | ||||||
| 		if (endpoint.meta.requireFile) { |  | ||||||
| 			schema.properties = { |  | ||||||
| 				...schema.properties, |  | ||||||
| 				file: { |  | ||||||
| 					type: 'string', |  | ||||||
| 					format: 'binary', |  | ||||||
| 					description: 'The file contents.', |  | ||||||
| 				}, |  | ||||||
| 			}; |  | ||||||
| 			schema.required = [...schema.required ?? [], 'file']; |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		const info = { | 		const info = { | ||||||
| 			operationId: endpoint.name, | 			operationId: name, | ||||||
| 			summary: endpoint.name, | 			summary: name, | ||||||
| 			description: desc, | 			description: desc, | ||||||
| 			externalDocs: { | 			externalDocs: { | ||||||
| 				description: 'Source code', | 				description: 'Source code', | ||||||
| 				url: `https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/${endpoint.name}.ts`, | 				url: `https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/${name}.ts`, | ||||||
| 			}, | 			}, | ||||||
| 			...(endpoint.meta.tags ? { | 			...(('tags' in endpoint && endpoint.tags) ? { | ||||||
| 				tags: [endpoint.meta.tags[0]], | 				tags: [endpoint.tags[0]], | ||||||
| 			} : {}), | 			} : {}), | ||||||
| 			...(endpoint.meta.requireCredential ? { | 			...('requireCredential' in endpoint && endpoint.requireCredential ? { | ||||||
| 				security: [{ | 				security: [{ | ||||||
| 					ApiKeyAuth: [], | 					ApiKeyAuth: [], | ||||||
| 				}], | 				}], | ||||||
| @@ -99,7 +88,7 @@ export function genOpenapiSpec(config: Config) { | |||||||
| 				}, | 				}, | ||||||
| 			}, | 			}, | ||||||
| 			responses: { | 			responses: { | ||||||
| 				...(endpoint.meta.res ? { | 				...(resSchema ? { | ||||||
| 					'200': { | 					'200': { | ||||||
| 						description: 'OK (with results)', | 						description: 'OK (with results)', | ||||||
| 						content: { | 						content: { | ||||||
| @@ -157,7 +146,7 @@ export function genOpenapiSpec(config: Config) { | |||||||
| 						}, | 						}, | ||||||
| 					}, | 					}, | ||||||
| 				}, | 				}, | ||||||
| 				...(endpoint.meta.limit ? { | 				...(('limit' in endpoint && endpoint.limit) ? { | ||||||
| 					'429': { | 					'429': { | ||||||
| 						description: 'To many requests', | 						description: 'To many requests', | ||||||
| 						content: { | 						content: { | ||||||
| @@ -184,7 +173,7 @@ export function genOpenapiSpec(config: Config) { | |||||||
| 			}, | 			}, | ||||||
| 		}; | 		}; | ||||||
|  |  | ||||||
| 		spec.paths['/' + endpoint.name] = { | 		spec.paths['/' + name] = { | ||||||
| 			post: info, | 			post: info, | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -1,31 +1,4 @@ | |||||||
| import type { JSONSchema7 } from 'schema-type'; | import { refs } from 'misskey-js/built/schemas.js'; | ||||||
| import { refs } from 'misskey-js'; |  | ||||||
|  |  | ||||||
| export function convertSchemaToOpenApiSchema(schema: JSONSchema7) { |  | ||||||
| 	const res: any = schema; |  | ||||||
|  |  | ||||||
| 	if (schema.type === 'object' && schema.properties) { |  | ||||||
| 		res.required = Object.entries(schema.properties).filter(([k, v]) => !v.optional).map(([k]) => k); |  | ||||||
|  |  | ||||||
| 		for (const k of Object.keys(schema.properties)) { |  | ||||||
| 			res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k]); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (schema.type === 'array' && schema.items) { |  | ||||||
| 		res.items = convertSchemaToOpenApiSchema(schema.items); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if (schema.anyOf) res.anyOf = schema.anyOf.map(convertSchemaToOpenApiSchema); |  | ||||||
| 	if (schema.oneOf) res.oneOf = schema.oneOf.map(convertSchemaToOpenApiSchema); |  | ||||||
| 	if (schema.allOf) res.allOf = schema.allOf.map(convertSchemaToOpenApiSchema); |  | ||||||
|  |  | ||||||
| 	if (schema.ref) { |  | ||||||
| 		res.$ref = `#/components/schemas/${schema.ref}`; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return res; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export const schemas = { | export const schemas = { | ||||||
| 	Error: { | 	Error: { | ||||||
| @@ -55,7 +28,5 @@ export const schemas = { | |||||||
| 		required: ['error'], | 		required: ['error'], | ||||||
| 	}, | 	}, | ||||||
|  |  | ||||||
| 	...Object.fromEntries( | 	...refs, | ||||||
| 		Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema)]), |  | ||||||
| 	), |  | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| module.exports = { | module.exports = { | ||||||
| 	parserOptions: { | 	parserOptions: { | ||||||
| 		tsconfigRootDir: __dirname, | 		tsconfigRootDir: __dirname, | ||||||
| 		project: ['./tsconfig.json'], | 		project: ['./tsconfig.json', './test/tsconfig.json'], | ||||||
| 	}, | 	}, | ||||||
| 	extends: [ | 	extends: [ | ||||||
| 		'../shared/.eslintrc.js', | 		'../shared/.eslintrc.js', | ||||||
|   | |||||||
| @@ -26,6 +26,7 @@ | |||||||
| 		"@types/node": "18.16.3", | 		"@types/node": "18.16.3", | ||||||
| 		"@typescript-eslint/eslint-plugin": "5.59.5", | 		"@typescript-eslint/eslint-plugin": "5.59.5", | ||||||
| 		"@typescript-eslint/parser": "5.59.5", | 		"@typescript-eslint/parser": "5.59.5", | ||||||
|  | 		"ajv": "8.12.0", | ||||||
| 		"eslint": "8.40.0", | 		"eslint": "8.40.0", | ||||||
| 		"jest": "29.5.0", | 		"jest": "29.5.0", | ||||||
| 		"jest-fetch-mock": "3.0.3", | 		"jest-fetch-mock": "3.0.3", | ||||||
|   | |||||||
| @@ -446,3 +446,17 @@ export const endpoints = { | |||||||
| 		}], | 		}], | ||||||
| 	}, | 	}, | ||||||
| } as const satisfies { [x: string]: IEndpointMeta; }; | } as const satisfies { [x: string]: IEndpointMeta; }; | ||||||
|  |  | ||||||
|  | export function getEndpointSchema(reqres: 'req' | 'res', key: keyof typeof endpoints) { | ||||||
|  | 	const endpoint = endpoints[key]; | ||||||
|  | 	const schemas = endpoint.defines.map(d => d[reqres]).filter(d => d !== undefined); | ||||||
|  | 	if (schemas.length === 0) { | ||||||
|  | 		return null; | ||||||
|  | 	} | ||||||
|  | 	if (schemas.length === 1) { | ||||||
|  | 		return schemas[0]; | ||||||
|  | 	} | ||||||
|  | 	return { | ||||||
|  | 		oneOf: schemas, | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ import Stream, { Connection } from './streaming.js'; | |||||||
| import { Channels } from './streaming.types.js'; | import { Channels } from './streaming.types.js'; | ||||||
| import { Acct } from './acct.js'; | import { Acct } from './acct.js'; | ||||||
| import type { Packed, Def } from './schemas.js'; | import type { Packed, Def } from './schemas.js'; | ||||||
| import { refs as _refs } from './schemas.js'; |  | ||||||
| import * as consts from './consts.js'; | import * as consts from './consts.js'; | ||||||
|  |  | ||||||
| export { | export { | ||||||
| @@ -15,8 +14,6 @@ export { | |||||||
| 	Packed, Def, | 	Packed, Def, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const refs = _refs; |  | ||||||
|  |  | ||||||
| export const permissions = consts.permissions; | export const permissions = consts.permissions; | ||||||
| export const notificationTypes = consts.notificationTypes; | export const notificationTypes = consts.notificationTypes; | ||||||
| export const obsoleteNotificationTypes = consts.obsoleteNotificationTypes; | export const obsoleteNotificationTypes = consts.obsoleteNotificationTypes; | ||||||
|   | |||||||
| @@ -1,5 +1,31 @@ | |||||||
| import { enableFetchMocks } from 'jest-fetch-mock'; | import { enableFetchMocks } from 'jest-fetch-mock'; | ||||||
| import { APIClient, isAPIError } from '../src/api'; | import { APIClient, isAPIError } from '../src/api'; | ||||||
|  | import Ajv from 'ajv'; | ||||||
|  | import { endpoints, getEndpointSchema } from '../src/endpoints'; | ||||||
|  | import { Endpoints } from '@/endpoints.types'; | ||||||
|  |  | ||||||
|  | describe('schemas', () => { | ||||||
|  |     describe.each(Object.keys(endpoints))('validate schema of %s', async (key) => { | ||||||
|  | 		const ajv = new Ajv({ | ||||||
|  | 			useDefaults: true, | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		ajv.addFormat('misskey:id', /^[a-zA-Z0-9]+$/); | ||||||
|  |  | ||||||
|  | 		const endpoint = (endpoints as any)[key] as unknown as Endpoints[keyof Endpoints]; | ||||||
|  | 		test('each schemas', async () => { | ||||||
|  | 			for (const def of endpoint.defines) { | ||||||
|  | 				if (def.res === undefined) continue; | ||||||
|  | 				ajv.compile(def.req); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		test('jointed schema (oneOf)', () => { | ||||||
|  | 			const req = getEndpointSchema('req', key as keyof Endpoints); | ||||||
|  | 			if (req) ajv.compile(req); | ||||||
|  | 		}); | ||||||
|  |     }); | ||||||
|  | }); | ||||||
|  |  | ||||||
| enableFetchMocks(); | enableFetchMocks(); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										42
									
								
								packages/misskey-js/test/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								packages/misskey-js/test/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,42 @@ | |||||||
|  | { | ||||||
|  | 	"compilerOptions": { | ||||||
|  | 		"allowJs": true, | ||||||
|  | 		"noEmitOnError": false, | ||||||
|  | 		"noImplicitAny": true, | ||||||
|  | 		"noImplicitReturns": true, | ||||||
|  | 		"noUnusedParameters": false, | ||||||
|  | 		"noUnusedLocals": true, | ||||||
|  | 		"noFallthroughCasesInSwitch": true, | ||||||
|  | 		"declaration": false, | ||||||
|  | 		"sourceMap": true, | ||||||
|  | 		"target": "es2021", | ||||||
|  | 		"module": "es2020", | ||||||
|  | 		"moduleResolution": "node", | ||||||
|  | 		"allowSyntheticDefaultImports": true, | ||||||
|  | 		"removeComments": false, | ||||||
|  | 		"noLib": false, | ||||||
|  | 		"strict": true, | ||||||
|  | 		"strictNullChecks": true, | ||||||
|  | 		"strictPropertyInitialization": false, | ||||||
|  | 		"experimentalDecorators": true, | ||||||
|  | 		"emitDecoratorMetadata": true, | ||||||
|  | 		"resolveJsonModule": true, | ||||||
|  | 		"isolatedModules": true, | ||||||
|  | 		"baseUrl": "./", | ||||||
|  | 		"paths": { | ||||||
|  | 			"@/*": ["../src/*"] | ||||||
|  | 		}, | ||||||
|  | 		"typeRoots": [ | ||||||
|  | 			"../node_modules/@types", | ||||||
|  | 			"../src/@types" | ||||||
|  | 		], | ||||||
|  | 		"lib": [ | ||||||
|  | 			"esnext" | ||||||
|  | 		], | ||||||
|  | 		"types": ["jest", "node"] | ||||||
|  | 	}, | ||||||
|  | 	"compileOnSave": false, | ||||||
|  | 	"include": [ | ||||||
|  | 		"./**/*.ts", | ||||||
|  | 	] | ||||||
|  | } | ||||||
							
								
								
									
										6
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							| @@ -1049,6 +1049,9 @@ importers: | |||||||
|       '@typescript-eslint/parser': |       '@typescript-eslint/parser': | ||||||
|         specifier: 5.59.5 |         specifier: 5.59.5 | ||||||
|         version: 5.59.5(eslint@8.40.0)(typescript@5.0.4) |         version: 5.59.5(eslint@8.40.0)(typescript@5.0.4) | ||||||
|  |       ajv: | ||||||
|  |         specifier: 8.12.0 | ||||||
|  |         version: 8.12.0 | ||||||
|       eslint: |       eslint: | ||||||
|         specifier: 8.40.0 |         specifier: 8.40.0 | ||||||
|         version: 8.40.0 |         version: 8.40.0 | ||||||
| @@ -7796,7 +7799,6 @@ packages: | |||||||
|       json-schema-traverse: 1.0.0 |       json-schema-traverse: 1.0.0 | ||||||
|       require-from-string: 2.0.2 |       require-from-string: 2.0.2 | ||||||
|       uri-js: 4.4.1 |       uri-js: 4.4.1 | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /alphanum-sort@1.0.2: |   /alphanum-sort@1.0.2: | ||||||
|     resolution: {integrity: sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ==} |     resolution: {integrity: sha512-0FcBfdcmaumGPQ0qPn7Q5qTgz/ooXgIyp1rf8ik5bGX8mpE2YHjC0P/eyQvxu1GURYQgq9ozf2mteQ5ZD9YiyQ==} | ||||||
| @@ -13932,7 +13934,6 @@ packages: | |||||||
|  |  | ||||||
|   /json-schema-traverse@1.0.0: |   /json-schema-traverse@1.0.0: | ||||||
|     resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} |     resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /json-schema@0.4.0: |   /json-schema@0.4.0: | ||||||
|     resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} |     resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} | ||||||
| @@ -17386,7 +17387,6 @@ packages: | |||||||
|   /require-from-string@2.0.2: |   /require-from-string@2.0.2: | ||||||
|     resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} |     resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} | ||||||
|     engines: {node: '>=0.10.0'} |     engines: {node: '>=0.10.0'} | ||||||
|     dev: false |  | ||||||
|  |  | ||||||
|   /require-main-filename@1.0.1: |   /require-main-filename@1.0.1: | ||||||
|     resolution: {integrity: sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==} |     resolution: {integrity: sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 tamaina
					tamaina