diff --git a/packages/connectors/connector-http-email/README.md b/packages/connectors/connector-http-email/README.md new file mode 100644 index 000000000..4479c45a0 --- /dev/null +++ b/packages/connectors/connector-http-email/README.md @@ -0,0 +1,3 @@ +# HTTP email connector + +TBD diff --git a/packages/connectors/connector-http-email/logo-dark.svg b/packages/connectors/connector-http-email/logo-dark.svg new file mode 100644 index 000000000..c721488d7 --- /dev/null +++ b/packages/connectors/connector-http-email/logo-dark.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/connectors/connector-http-email/logo.svg b/packages/connectors/connector-http-email/logo.svg new file mode 100644 index 000000000..c721488d7 --- /dev/null +++ b/packages/connectors/connector-http-email/logo.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/packages/connectors/connector-http-email/package.json b/packages/connectors/connector-http-email/package.json new file mode 100644 index 000000000..61a59714c --- /dev/null +++ b/packages/connectors/connector-http-email/package.json @@ -0,0 +1,68 @@ +{ + "name": "@logto/connector-http-email", + "version": "0.1.0", + "description": "Email connector to send email via HTTP call.", + "author": "Silverhand Inc. ", + "dependencies": { + "@logto/connector-kit": "workspace:^4.0.0", + "@silverhand/essentials": "^2.9.1", + "got": "^14.0.0", + "zod": "^3.23.8" + }, + "main": "./lib/index.js", + "module": "./lib/index.js", + "exports": "./lib/index.js", + "license": "MPL-2.0", + "type": "module", + "files": [ + "lib", + "docs", + "logo.svg", + "logo-dark.svg" + ], + "scripts": { + "precommit": "lint-staged", + "check": "tsc --noEmit", + "build": "tsup", + "dev": "tsup --watch", + "lint": "eslint --ext .ts src", + "lint:report": "pnpm lint --format json --output-file report.json", + "test": "vitest src", + "test:ci": "pnpm run test --silent --coverage", + "prepublishOnly": "pnpm build" + }, + "engines": { + "node": "^20.9.0" + }, + "eslintConfig": { + "extends": "@silverhand", + "settings": { + "import/core-modules": [ + "@silverhand/essentials", + "got", + "nock", + "snakecase-keys", + "zod" + ] + } + }, + "prettier": "@silverhand/eslint-config/.prettierrc", + "publishConfig": { + "access": "public" + }, + "devDependencies": { + "@silverhand/eslint-config": "6.0.1", + "@silverhand/ts-config": "6.0.0", + "@types/node": "^20.11.20", + "@types/supertest": "^6.0.2", + "@vitest/coverage-v8": "^2.0.0", + "eslint": "^8.56.0", + "lint-staged": "^15.0.2", + "nock": "^13.3.1", + "prettier": "^3.0.0", + "supertest": "^7.0.0", + "tsup": "^8.1.0", + "typescript": "^5.5.3", + "vitest": "^2.0.0" + } +} diff --git a/packages/connectors/connector-http-email/src/constant.ts b/packages/connectors/connector-http-email/src/constant.ts new file mode 100644 index 000000000..37adc10ce --- /dev/null +++ b/packages/connectors/connector-http-email/src/constant.ts @@ -0,0 +1,33 @@ +import type { ConnectorMetadata } from '@logto/connector-kit'; +import { ConnectorConfigFormItemType } from '@logto/connector-kit'; + +export const defaultMetadata: ConnectorMetadata = { + id: 'http-email-service', + target: 'http-mail', + platform: null, + name: { + en: 'HTTP Email', + }, + logo: './logo.svg', + logoDark: null, + description: { + en: 'Send email via HTTP call.', + }, + readme: './README.md', + formItems: [ + { + key: 'endpoint', + label: 'Endpoint', + type: ConnectorConfigFormItemType.Text, + required: true, + placeholder: '', + }, + { + key: 'authorization', + label: 'Authorization Header', + type: ConnectorConfigFormItemType.Text, + required: false, + placeholder: '', + }, + ], +}; diff --git a/packages/connectors/connector-http-email/src/index.test.ts b/packages/connectors/connector-http-email/src/index.test.ts new file mode 100644 index 000000000..516c66c56 --- /dev/null +++ b/packages/connectors/connector-http-email/src/index.test.ts @@ -0,0 +1,47 @@ +import nock from 'nock'; + +import { TemplateType } from '@logto/connector-kit'; + +import createConnector from './index.js'; +import { mockedConfig } from './mock.js'; + +const getConfig = vi.fn().mockResolvedValue(mockedConfig); + +describe('HTTP email connector', () => { + afterEach(() => { + nock.cleanAll(); + }); + + it('should init without throwing errors', async () => { + await expect(createConnector({ getConfig })).resolves.not.toThrow(); + }); + + it('should call endpoint with correct parameters', async () => { + const url = new URL(mockedConfig.endpoint); + const mockPost = nock(url.origin) + .post(url.pathname, (body) => { + expect(body).toMatchObject({ + to: 'foo@logto.io', + type: TemplateType.SignIn, + payload: { + code: '123456', + }, + }); + return true; + }) + .reply(200, { + message: 'Email sent successfully', + }); + + const connector = await createConnector({ getConfig }); + await connector.sendMessage({ + to: 'foo@logto.io', + type: TemplateType.SignIn, + payload: { + code: '123456', + }, + }); + + expect(mockPost.isDone()).toBe(true); + }); +}); diff --git a/packages/connectors/connector-http-email/src/index.ts b/packages/connectors/connector-http-email/src/index.ts new file mode 100644 index 000000000..a42a7aa19 --- /dev/null +++ b/packages/connectors/connector-http-email/src/index.ts @@ -0,0 +1,70 @@ +import { assert } from '@silverhand/essentials'; +import { got, HTTPError } from 'got'; + +import type { + GetConnectorConfig, + SendMessageFunction, + CreateConnector, + EmailConnector, +} from '@logto/connector-kit'; +import { + ConnectorError, + ConnectorErrorCodes, + validateConfig, + ConnectorType, +} from '@logto/connector-kit'; + +import { defaultMetadata } from './constant.js'; +import { httpMailConfigGuard } from './types.js'; + +const sendMessage = + (getConfig: GetConnectorConfig): SendMessageFunction => + async (data, inputConfig) => { + const { to, type, payload } = data; + const config = inputConfig ?? (await getConfig(defaultMetadata.id)); + validateConfig(config, httpMailConfigGuard); + const { endpoint, authorization } = config; + + try { + return await got.post(endpoint, { + headers: { + Authorization: authorization, + 'Content-Type': 'application/json', + }, + json: { + to, + type, + payload, + }, + }); + } catch (error: unknown) { + if (error instanceof HTTPError) { + const { + response: { body: rawBody }, + } = error; + + assert( + typeof rawBody === 'string', + new ConnectorError( + ConnectorErrorCodes.InvalidResponse, + `Invalid response raw body type: ${typeof rawBody}` + ) + ); + + throw new ConnectorError(ConnectorErrorCodes.General, rawBody); + } + + throw new ConnectorError(ConnectorErrorCodes.General, error); + } + }; + +const createHttpMailConnector: CreateConnector = async ({ getConfig }) => { + return { + metadata: defaultMetadata, + type: ConnectorType.Email, + configGuard: httpMailConfigGuard, + sendMessage: sendMessage(getConfig), + }; +}; + +export default createHttpMailConnector; diff --git a/packages/connectors/connector-http-email/src/mock.ts b/packages/connectors/connector-http-email/src/mock.ts new file mode 100644 index 000000000..4d38f6563 --- /dev/null +++ b/packages/connectors/connector-http-email/src/mock.ts @@ -0,0 +1,6 @@ +import type { HttpMailConfig } from './types.js'; + +export const mockedConfig: HttpMailConfig = { + endpoint: 'https://example.com/your-http-endpoint', + authorization: 'SampleToken', +}; diff --git a/packages/connectors/connector-http-email/src/types.ts b/packages/connectors/connector-http-email/src/types.ts new file mode 100644 index 000000000..4968573c4 --- /dev/null +++ b/packages/connectors/connector-http-email/src/types.ts @@ -0,0 +1,8 @@ +import { z } from 'zod'; + +export const httpMailConfigGuard = z.object({ + endpoint: z.string(), + authorization: z.string().optional(), +}); + +export type HttpMailConfig = z.infer; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b41e4492c..4c6e85fcd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1076,6 +1076,61 @@ importers: specifier: ^2.0.0 version: 2.0.0(@types/node@20.11.20)(happy-dom@14.12.3)(jsdom@20.0.2)(lightningcss@1.25.1)(sass@1.77.8) + packages/connectors/connector-http-email: + dependencies: + '@logto/connector-kit': + specifier: workspace:^4.0.0 + version: link:../../toolkit/connector-kit + '@silverhand/essentials': + specifier: ^2.9.1 + version: 2.9.1 + got: + specifier: ^14.0.0 + version: 14.0.0 + zod: + specifier: ^3.23.8 + version: 3.23.8 + devDependencies: + '@silverhand/eslint-config': + specifier: 6.0.1 + version: 6.0.1(eslint@8.57.0)(prettier@3.0.0)(typescript@5.5.3) + '@silverhand/ts-config': + specifier: 6.0.0 + version: 6.0.0(typescript@5.5.3) + '@types/node': + specifier: ^20.11.20 + version: 20.12.7 + '@types/supertest': + specifier: ^6.0.2 + version: 6.0.2 + '@vitest/coverage-v8': + specifier: ^2.0.0 + version: 2.0.0(vitest@2.0.0(@types/node@20.12.7)(happy-dom@14.12.3)(jsdom@20.0.2)(lightningcss@1.25.1)(sass@1.77.8)) + eslint: + specifier: ^8.56.0 + version: 8.57.0 + lint-staged: + specifier: ^15.0.2 + version: 15.0.2 + nock: + specifier: ^13.3.1 + version: 13.3.1 + prettier: + specifier: ^3.0.0 + version: 3.0.0 + supertest: + specifier: ^7.0.0 + version: 7.0.0 + tsup: + specifier: ^8.1.0 + version: 8.1.0(@swc/core@1.3.52(@swc/helpers@0.5.1))(postcss@8.4.39)(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.5.3))(typescript@5.5.3) + typescript: + specifier: ^5.5.3 + version: 5.5.3 + vitest: + specifier: ^2.0.0 + version: 2.0.0(@types/node@20.12.7)(happy-dom@14.12.3)(jsdom@20.0.2)(lightningcss@1.25.1)(sass@1.77.8) + packages/connectors/connector-huggingface: dependencies: '@logto/connector-kit': @@ -5536,6 +5591,7 @@ packages: '@koa/router@12.0.1': resolution: {integrity: sha512-ribfPYfHb+Uw3b27Eiw6NPqjhIhTpVFzEWLwyc/1Xp+DCdwRRyIlAUODX+9bPARF6aQtUu1+/PHzdNvRzcs/+Q==} engines: {node: '>= 12'} + deprecated: Use v12.0.2 or higher to fix the vulnerability issue '@levischuck/tiny-cbor@0.2.2': resolution: {integrity: sha512-f5CnPw997Y2GQ8FAvtuVVC19FX8mwNNC+1XJcIi16n/LTJifKO6QBgGLgN3YEmqtGMk17SKSuoWES3imJVxAVw==} @@ -7575,7 +7631,7 @@ packages: engines: {node: '>= 0.6'} concat-map@0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} confusing-browser-globals@1.0.11: resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} @@ -8174,7 +8230,7 @@ packages: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} ee-first@1.1.1: - resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=} + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} electron-to-chromium@1.5.0: resolution: {integrity: sha512-Vb3xHHYnLseK8vlMJQKJYXJ++t4u1/qJ3vykuVrVjvdiOEhYyT1AuP4x03G8EnPmYvYOhe9T+dADTmthjRQMkA==} @@ -9195,7 +9251,7 @@ packages: engines: {node: '>=16.17.0'} humanize-number@0.0.2: - resolution: {integrity: sha1-EcCvakcWQ2M1iFiASPF5lUFInBg=} + resolution: {integrity: sha512-un3ZAcNQGI7RzaWGZzQDH47HETM4Wrj6z6E4TId8Yeq9w5ZKUVB1nrT2jwFheTUjEmqcgTjXDc959jum+ai1kQ==} husky@9.0.7: resolution: {integrity: sha512-vWdusw+y12DUEeoZqW1kplOFqk3tedGV8qlga8/SF6a3lOiWLqGZZQvfWvY0fQYdfiRi/u1DFNpudTSV9l1aCg==} @@ -10415,7 +10471,7 @@ packages: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} media-typer@0.3.0: - resolution: {integrity: sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=} + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} meow@10.1.5: @@ -12411,7 +12467,7 @@ packages: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} stubs@3.0.0: - resolution: {integrity: sha1-6NK6H6nJBXAwPAMLaQD31fiavls=} + resolution: {integrity: sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==} style-search@0.1.0: resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==} @@ -13014,7 +13070,7 @@ packages: optional: true void-elements@3.1.0: - resolution: {integrity: sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=} + resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} engines: {node: '>=0.10.0'} w3c-xmlserializer@3.0.0: @@ -15684,10 +15740,10 @@ snapshots: eslint-config-prettier: 9.1.0(eslint@8.57.0) eslint-config-xo: 0.44.0(eslint@8.57.0) eslint-config-xo-typescript: 4.0.0(@typescript-eslint/eslint-plugin@7.7.0(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3))(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0)(typescript@5.5.3) - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0) eslint-plugin-consistent-default-export-name: 0.0.15 eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) eslint-plugin-n: 17.2.1(eslint@8.57.0) eslint-plugin-no-use-extend-native: 0.5.0 eslint-plugin-prettier: 5.1.3(eslint-config-prettier@9.1.0(eslint@8.57.0))(eslint@8.57.0)(prettier@3.0.0) @@ -18651,13 +18707,13 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0): + eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0): dependencies: debug: 4.3.5 enhanced-resolve: 5.16.0 eslint: 8.57.0 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.3 is-core-module: 2.13.1 @@ -18668,14 +18724,14 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.8.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-module-utils@2.8.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0): dependencies: debug: 3.2.7(supports-color@5.5.0) optionalDependencies: '@typescript-eslint/parser': 7.7.0(eslint@8.57.0)(typescript@5.5.3) eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0) + eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0) transitivePeerDependencies: - supports-color @@ -18697,7 +18753,7 @@ snapshots: eslint: 8.57.0 ignore: 5.3.1 - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5 @@ -18707,7 +18763,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) + eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0) hasown: 2.0.2 is-core-module: 2.13.1 is-glob: 4.0.3