mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor(core,toolkit): extract connector loading code to toolkit (#3400)
This commit is contained in:
parent
1d1cd46ccf
commit
2bd5f36fc3
28 changed files with 265 additions and 243 deletions
|
@ -43,6 +43,7 @@
|
|||
"url": "https://github.com/logto-io/logto/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@logto/connector-kit": "workspace:1.0.0-rc.2",
|
||||
"@logto/core-kit": "workspace:*",
|
||||
"@logto/schemas": "workspace:*",
|
||||
"@logto/shared": "workspace:*",
|
||||
|
|
53
packages/cli/src/connector/factories.ts
Normal file
53
packages/cli/src/connector/factories.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
import chalk from 'chalk';
|
||||
|
||||
import { notImplemented } from './consts.js';
|
||||
import { loadConnector } from './loader.js';
|
||||
import type { ConnectorFactory, ConnectorPackage } from './types.js';
|
||||
import { parseMetadata, validateConnectorModule } from './utils.js';
|
||||
|
||||
// eslint-disable-next-line @silverhand/fp/no-let
|
||||
let cachedConnectorFactories: ConnectorFactory[] | undefined;
|
||||
|
||||
export const loadConnectorFactories = async (
|
||||
connectorPackages: ConnectorPackage[],
|
||||
ignoreVersionMismatch: boolean
|
||||
) => {
|
||||
if (cachedConnectorFactories) {
|
||||
return cachedConnectorFactories;
|
||||
}
|
||||
const connectorFactories = await Promise.all(
|
||||
connectorPackages.map(async ({ path: packagePath, name }) => {
|
||||
try {
|
||||
const createConnector = await loadConnector(packagePath, ignoreVersionMismatch);
|
||||
const rawConnector = await createConnector({ getConfig: notImplemented });
|
||||
validateConnectorModule(rawConnector);
|
||||
|
||||
return {
|
||||
metadata: await parseMetadata(rawConnector.metadata, packagePath),
|
||||
type: rawConnector.type,
|
||||
createConnector,
|
||||
path: packagePath,
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.log(
|
||||
`${chalk.red(
|
||||
`[load-connector] skip ${chalk.bold(name)} due to error: ${error.message}`
|
||||
)}`
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||
cachedConnectorFactories = connectorFactories.filter(
|
||||
(connectorFactory): connectorFactory is ConnectorFactory => connectorFactory !== undefined
|
||||
);
|
||||
|
||||
return cachedConnectorFactories;
|
||||
};
|
4
packages/cli/src/connector/index.ts
Normal file
4
packages/cli/src/connector/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export * from './factories.js';
|
||||
export * from './types.js';
|
||||
export * from './consts.js';
|
||||
export * from './utils.js';
|
|
@ -2,15 +2,14 @@ import path from 'path';
|
|||
|
||||
import type { AllConnector, CreateConnector } from '@logto/connector-kit';
|
||||
import connectorKitMeta from '@logto/connector-kit/package.json' assert { type: 'json' };
|
||||
import { isKeyInObject } from '@logto/shared';
|
||||
import { satisfies } from 'semver';
|
||||
|
||||
import { EnvSet } from '#src/env-set/index.js';
|
||||
import { isKeyInObject } from './utils.js';
|
||||
|
||||
const connectorKit = '@logto/connector-kit';
|
||||
const { version: currentVersion } = connectorKitMeta;
|
||||
|
||||
const checkConnectorKitVersion = (dependencies: unknown) => {
|
||||
const checkConnectorKitVersion = (dependencies: unknown, ignoreVersionMismatch: boolean) => {
|
||||
if (isKeyInObject(dependencies, connectorKit)) {
|
||||
const value = dependencies[connectorKit];
|
||||
|
||||
|
@ -21,7 +20,7 @@ const checkConnectorKitVersion = (dependencies: unknown) => {
|
|||
|
||||
const message = `Connector requires ${connectorKit} to be ${value}, but the version here is ${currentVersion}.`;
|
||||
|
||||
if (EnvSet.values.isIntegrationTest || EnvSet.values.ignoreConnectorVersionCheck) {
|
||||
if (ignoreVersionMismatch) {
|
||||
console.warn(`[warn] ${message}\n\nThis is highly discouraged in production.`);
|
||||
|
||||
return;
|
||||
|
@ -35,7 +34,8 @@ const checkConnectorKitVersion = (dependencies: unknown) => {
|
|||
};
|
||||
|
||||
export const loadConnector = async (
|
||||
connectorPath: string
|
||||
connectorPath: string,
|
||||
ignoreVersionMismatch: boolean
|
||||
): Promise<CreateConnector<AllConnector>> => {
|
||||
const {
|
||||
default: { dependencies },
|
||||
|
@ -44,7 +44,7 @@ export const loadConnector = async (
|
|||
assert: { type: 'json' },
|
||||
})) as { default: Record<string, unknown> };
|
||||
|
||||
checkConnectorKitVersion(dependencies);
|
||||
checkConnectorKitVersion(dependencies, ignoreVersionMismatch);
|
||||
|
||||
const loaded: unknown = await import(path.join(connectorPath, 'lib/index.js'));
|
||||
|
17
packages/cli/src/connector/types.ts
Normal file
17
packages/cli/src/connector/types.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import type { AllConnector, CreateConnector } from '@logto/connector-kit';
|
||||
|
||||
/**
|
||||
* Dynamic loaded connector type.
|
||||
*/
|
||||
export type ConnectorFactory<T extends AllConnector = AllConnector> = Pick<
|
||||
T,
|
||||
'type' | 'metadata'
|
||||
> & {
|
||||
createConnector: CreateConnector<AllConnector>;
|
||||
path: string;
|
||||
};
|
||||
|
||||
export type ConnectorPackage = {
|
||||
name: string;
|
||||
path: string;
|
||||
};
|
85
packages/cli/src/connector/utils.ts
Normal file
85
packages/cli/src/connector/utils.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import { existsSync } from 'fs';
|
||||
import { readFile } from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
import type { AllConnector, BaseConnector, GetConnectorConfig } from '@logto/connector-kit';
|
||||
import { ConnectorError, ConnectorErrorCodes, ConnectorType } from '@logto/connector-kit';
|
||||
|
||||
import { notImplemented } from './consts.js';
|
||||
import type { ConnectorFactory } from './types.js';
|
||||
|
||||
export function validateConnectorModule(
|
||||
connector: Partial<BaseConnector<ConnectorType>>
|
||||
): asserts connector is BaseConnector<ConnectorType> {
|
||||
if (!connector.metadata) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidMetadata);
|
||||
}
|
||||
|
||||
if (!connector.configGuard) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidConfigGuard);
|
||||
}
|
||||
|
||||
if (!connector.type || !Object.values(ConnectorType).includes(connector.type)) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.UnexpectedType);
|
||||
}
|
||||
}
|
||||
|
||||
export const readUrl = async (
|
||||
url: string,
|
||||
baseUrl: string,
|
||||
type: 'text' | 'svg'
|
||||
): Promise<string> => {
|
||||
if (!url) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (type !== 'text' && url.startsWith('http')) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (!existsSync(path.join(baseUrl, url))) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (type === 'svg') {
|
||||
const data = await readFile(path.join(baseUrl, url));
|
||||
|
||||
return `data:image/svg+xml;base64,${data.toString('base64')}`;
|
||||
}
|
||||
|
||||
return readFile(path.join(baseUrl, url), 'utf8');
|
||||
};
|
||||
|
||||
export const parseMetadata = async (
|
||||
metadata: AllConnector['metadata'],
|
||||
packagePath: string
|
||||
): Promise<AllConnector['metadata']> => {
|
||||
return {
|
||||
...metadata,
|
||||
logo: await readUrl(metadata.logo, packagePath, 'svg'),
|
||||
logoDark: metadata.logoDark && (await readUrl(metadata.logoDark, packagePath, 'svg')),
|
||||
readme: await readUrl(metadata.readme, packagePath, 'text'),
|
||||
configTemplate:
|
||||
metadata.configTemplate && (await readUrl(metadata.configTemplate, packagePath, 'text')),
|
||||
};
|
||||
};
|
||||
|
||||
export const buildRawConnector = async (
|
||||
connectorFactory: ConnectorFactory,
|
||||
getConnectorConfig?: GetConnectorConfig
|
||||
) => {
|
||||
const { createConnector, path: packagePath } = connectorFactory;
|
||||
const rawConnector = await createConnector({
|
||||
getConfig: getConnectorConfig ?? notImplemented,
|
||||
});
|
||||
validateConnectorModule(rawConnector);
|
||||
const rawMetadata = await parseMetadata(rawConnector.metadata, packagePath);
|
||||
|
||||
return { rawConnector, rawMetadata };
|
||||
};
|
||||
|
||||
export const isKeyInObject = <Key extends string>(
|
||||
object: unknown,
|
||||
key: Key
|
||||
): object is Record<string, unknown> & Record<Key, unknown> =>
|
||||
object !== null && typeof object === 'object' && key in object;
|
|
@ -1,9 +1,10 @@
|
|||
import type { ConnectorFactory } from '@logto/cli/lib/connector/index.js';
|
||||
import { ConnectorPlatform } from '@logto/connector-kit';
|
||||
import type { Connector } from '@logto/schemas';
|
||||
import { ConnectorType } from '@logto/schemas';
|
||||
import { any } from 'zod';
|
||||
|
||||
import type { LogtoConnector, ConnectorFactory } from '#src/utils/connectors/types.js';
|
||||
import type { LogtoConnector } from '#src/utils/connectors/types.js';
|
||||
|
||||
import {
|
||||
mockConnector0,
|
||||
|
|
|
@ -9,7 +9,7 @@ import SystemContext from './tenants/SystemContext.js';
|
|||
dotenv.config({ path: await findUp('.env', {}) });
|
||||
|
||||
// Import after env has been configured
|
||||
const { loadConnectorFactories } = await import('./utils/connectors/factories.js');
|
||||
const { loadConnectorFactories } = await import('./utils/connectors/index.js');
|
||||
const { EnvSet } = await import('./env-set/index.js');
|
||||
const { default: initI18n } = await import('./i18n/init.js');
|
||||
const { tenantPool, checkRowLevelSecurity } = await import('./tenants/index.js');
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import { buildRawConnector, defaultConnectorMethods } from '@logto/cli/lib/connector/index.js';
|
||||
import type { AllConnector } from '@logto/connector-kit';
|
||||
import { validateConfig } from '@logto/connector-kit';
|
||||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import type Queries from '#src/tenants/Queries.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import { defaultConnectorMethods } from '#src/utils/connectors/consts.js';
|
||||
import { loadConnectorFactories } from '#src/utils/connectors/factories.js';
|
||||
import { buildRawConnector } from '#src/utils/connectors/index.js';
|
||||
import { loadConnectorFactories } from '#src/utils/connectors/index.js';
|
||||
import type { LogtoConnector } from '#src/utils/connectors/types.js';
|
||||
|
||||
export type ConnectorLibrary = ReturnType<typeof createConnectorLibrary>;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { defaultConnectorMethods } from '@logto/cli/lib/connector/index.js';
|
||||
import { ConnectorType, VerificationCodeType } from '@logto/connector-kit';
|
||||
import { Passcode } from '@logto/schemas';
|
||||
import { any } from 'zod';
|
||||
|
@ -5,7 +6,6 @@ import { any } from 'zod';
|
|||
import { mockConnector, mockMetadata } from '#src/__mocks__/index.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { MockQueries } from '#src/test-utils/tenant.js';
|
||||
import { defaultConnectorMethods } from '#src/utils/connectors/consts.js';
|
||||
|
||||
import {
|
||||
createPasscodeLibrary,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { notImplemented } from '@logto/cli/lib/connector/index.js';
|
||||
import { ConnectorType } from '@logto/schemas';
|
||||
import { has } from '@silverhand/essentials';
|
||||
import { object, record, string, unknown } from 'zod';
|
||||
|
@ -5,7 +6,6 @@ import { object, record, string, unknown } from 'zod';
|
|||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import koaGuard from '#src/middleware/koa-guard.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import { notImplemented } from '#src/utils/connectors/consts.js';
|
||||
import { transpileLogtoConnector } from '#src/utils/connectors/index.js';
|
||||
|
||||
import type { RouterInitArgs } from '../routes/types.js';
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
/* eslint-disable max-lines */
|
||||
import type { EmailConnector, SmsConnector } from '@logto/connector-kit';
|
||||
import { defaultConnectorMethods } from '@logto/cli/lib/connector/index.js';
|
||||
import {
|
||||
ConnectorError,
|
||||
ConnectorErrorCodes,
|
||||
ConnectorPlatform,
|
||||
VerificationCodeType,
|
||||
} from '@logto/connector-kit';
|
||||
import type { EmailConnector, SmsConnector } from '@logto/connector-kit';
|
||||
import type { Connector } from '@logto/schemas';
|
||||
import { ConnectorType } from '@logto/schemas';
|
||||
import { pickDefault, createMockUtils } from '@logto/shared/esm';
|
||||
|
@ -26,7 +27,6 @@ import RequestError from '#src/errors/RequestError/index.js';
|
|||
import Queries from '#src/tenants/Queries.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import { defaultConnectorMethods } from '#src/utils/connectors/consts.js';
|
||||
import type { LogtoConnector } from '#src/utils/connectors/types.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
|
@ -57,11 +57,14 @@ const {
|
|||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
const getLogtoConnectors = jest.fn<Promise<LogtoConnector[]>, []>();
|
||||
|
||||
const { loadConnectorFactories } = mockEsm('#src/utils/connectors/factories.js', () => ({
|
||||
loadConnectorFactories: jest.fn(),
|
||||
}));
|
||||
const { loadConnectorFactories } = await mockEsmWithActual(
|
||||
'#src/utils/connectors/index.js',
|
||||
() => ({
|
||||
loadConnectorFactories: jest.fn(),
|
||||
})
|
||||
);
|
||||
|
||||
const { buildRawConnector } = await mockEsmWithActual('#src/utils/connectors/index.js', () => ({
|
||||
const { buildRawConnector } = await mockEsmWithActual('@logto/cli/lib/connector/index.js', () => ({
|
||||
buildRawConnector: jest.fn(),
|
||||
}));
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { buildRawConnector } from '@logto/cli/lib/connector/index.js';
|
||||
import { VerificationCodeType, validateConfig } from '@logto/connector-kit';
|
||||
import { emailRegEx, phoneRegEx, buildIdGenerator } from '@logto/core-kit';
|
||||
import { arbitraryObjectGuard, Connectors, ConnectorType } from '@logto/schemas';
|
||||
|
@ -7,9 +8,8 @@ import { string, object } from 'zod';
|
|||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import koaGuard from '#src/middleware/koa-guard.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import { loadConnectorFactories } from '#src/utils/connectors/factories.js';
|
||||
import {
|
||||
buildRawConnector,
|
||||
loadConnectorFactories,
|
||||
transpileConnectorFactory,
|
||||
transpileLogtoConnector,
|
||||
} from '#src/utils/connectors/index.js';
|
||||
|
|
|
@ -1,91 +0,0 @@
|
|||
import { existsSync } from 'fs';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import path from 'path';
|
||||
|
||||
import { connectorDirectory } from '@logto/cli/lib/constants.js';
|
||||
import { getConnectorPackagesFromDirectory } from '@logto/cli/lib/utils.js';
|
||||
import { findPackage } from '@logto/shared';
|
||||
import { deduplicate } from '@silverhand/essentials';
|
||||
import chalk from 'chalk';
|
||||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import type { ConnectorFactory } from '#src/utils/connectors/types.js';
|
||||
|
||||
import { notImplemented } from './consts.js';
|
||||
import { parseMetadata, validateConnectorModule } from './index.js';
|
||||
import { loadConnector } from './loader.js';
|
||||
|
||||
const checkDuplicateConnectorFactoriesId = (connectorFactories: ConnectorFactory[]) => {
|
||||
const connectorFactoryIds = connectorFactories.map(({ metadata }) => metadata.id);
|
||||
const deduplicatedConnectorFactoryIds = deduplicate(connectorFactoryIds);
|
||||
|
||||
if (connectorFactoryIds.length !== deduplicatedConnectorFactoryIds.length) {
|
||||
const duplicatedConnectorFactoryIds = deduplicatedConnectorFactoryIds.filter(
|
||||
(deduplicateId) => connectorFactoryIds.filter((id) => id === deduplicateId).length > 1
|
||||
);
|
||||
throw new RequestError({
|
||||
code: 'connector.more_than_one_connector_factory',
|
||||
status: 422,
|
||||
connectorIds: duplicatedConnectorFactoryIds.map((id) => `${id}`).join(', '),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @silverhand/fp/no-let
|
||||
let cachedConnectorFactories: ConnectorFactory[] | undefined;
|
||||
|
||||
export const loadConnectorFactories = async () => {
|
||||
if (cachedConnectorFactories) {
|
||||
checkDuplicateConnectorFactoriesId(cachedConnectorFactories);
|
||||
|
||||
return cachedConnectorFactories;
|
||||
}
|
||||
|
||||
const currentDirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const coreDirectory = await findPackage(currentDirname);
|
||||
const directory = coreDirectory && path.join(coreDirectory, connectorDirectory);
|
||||
|
||||
if (!directory || !existsSync(directory)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const connectorPackages = await getConnectorPackagesFromDirectory(directory);
|
||||
|
||||
const connectorFactories = await Promise.all(
|
||||
connectorPackages.map(async ({ path: packagePath, name }) => {
|
||||
try {
|
||||
const createConnector = await loadConnector(packagePath);
|
||||
const rawConnector = await createConnector({ getConfig: notImplemented });
|
||||
validateConnectorModule(rawConnector);
|
||||
|
||||
return {
|
||||
metadata: await parseMetadata(rawConnector.metadata, packagePath),
|
||||
type: rawConnector.type,
|
||||
createConnector,
|
||||
path: packagePath,
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
console.log(
|
||||
`${chalk.red(
|
||||
`[load-connector] skip ${chalk.bold(name)} due to error: ${error.message}`
|
||||
)}`
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||
cachedConnectorFactories = connectorFactories.filter(
|
||||
(connectorFactory): connectorFactory is ConnectorFactory => connectorFactory !== undefined
|
||||
);
|
||||
|
||||
checkDuplicateConnectorFactoriesId(cachedConnectorFactories);
|
||||
|
||||
return cachedConnectorFactories;
|
||||
};
|
|
@ -1,84 +1,19 @@
|
|||
import { existsSync } from 'fs';
|
||||
import { readFile } from 'fs/promises';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
import type { AllConnector, BaseConnector, GetConnectorConfig } from '@logto/connector-kit';
|
||||
import { ConnectorError, ConnectorErrorCodes, ConnectorType } from '@logto/connector-kit';
|
||||
import type { ConnectorFactory } from '@logto/cli/lib/connector/index.js';
|
||||
import { loadConnectorFactories as _loadConnectorFactories } from '@logto/cli/lib/connector/index.js';
|
||||
import { connectorDirectory } from '@logto/cli/lib/constants.js';
|
||||
import { getConnectorPackagesFromDirectory } from '@logto/cli/lib/utils.js';
|
||||
import type { ConnectorFactoryResponse, ConnectorResponse } from '@logto/schemas';
|
||||
import { pick } from '@silverhand/essentials';
|
||||
import { findPackage } from '@logto/shared';
|
||||
import { deduplicate, pick } from '@silverhand/essentials';
|
||||
|
||||
import { notImplemented } from './consts.js';
|
||||
import type { ConnectorFactory, LogtoConnector } from './types.js';
|
||||
import { EnvSet } from '#src/env-set/index.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
|
||||
export function validateConnectorModule(
|
||||
connector: Partial<BaseConnector<ConnectorType>>
|
||||
): asserts connector is BaseConnector<ConnectorType> {
|
||||
if (!connector.metadata) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidMetadata);
|
||||
}
|
||||
|
||||
if (!connector.configGuard) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.InvalidConfigGuard);
|
||||
}
|
||||
|
||||
if (!connector.type || !Object.values(ConnectorType).includes(connector.type)) {
|
||||
throw new ConnectorError(ConnectorErrorCodes.UnexpectedType);
|
||||
}
|
||||
}
|
||||
|
||||
export const readUrl = async (
|
||||
url: string,
|
||||
baseUrl: string,
|
||||
type: 'text' | 'svg'
|
||||
): Promise<string> => {
|
||||
if (!url) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (type !== 'text' && url.startsWith('http')) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (!existsSync(path.join(baseUrl, url))) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (type === 'svg') {
|
||||
const data = await readFile(path.join(baseUrl, url));
|
||||
|
||||
return `data:image/svg+xml;base64,${data.toString('base64')}`;
|
||||
}
|
||||
|
||||
return readFile(path.join(baseUrl, url), 'utf8');
|
||||
};
|
||||
|
||||
export const parseMetadata = async (
|
||||
metadata: AllConnector['metadata'],
|
||||
packagePath: string
|
||||
): Promise<AllConnector['metadata']> => {
|
||||
return {
|
||||
...metadata,
|
||||
logo: await readUrl(metadata.logo, packagePath, 'svg'),
|
||||
logoDark: metadata.logoDark && (await readUrl(metadata.logoDark, packagePath, 'svg')),
|
||||
readme: await readUrl(metadata.readme, packagePath, 'text'),
|
||||
configTemplate:
|
||||
metadata.configTemplate && (await readUrl(metadata.configTemplate, packagePath, 'text')),
|
||||
};
|
||||
};
|
||||
|
||||
export const buildRawConnector = async (
|
||||
connectorFactory: ConnectorFactory,
|
||||
getConnectorConfig?: GetConnectorConfig
|
||||
) => {
|
||||
const { createConnector, path: packagePath } = connectorFactory;
|
||||
const rawConnector = await createConnector({
|
||||
getConfig: getConnectorConfig ?? notImplemented,
|
||||
});
|
||||
validateConnectorModule(rawConnector);
|
||||
const rawMetadata = await parseMetadata(rawConnector.metadata, packagePath);
|
||||
|
||||
return { rawConnector, rawMetadata };
|
||||
};
|
||||
import type { LogtoConnector } from './types.js';
|
||||
|
||||
export const transpileLogtoConnector = ({
|
||||
dbEntry,
|
||||
|
@ -98,3 +33,41 @@ export const transpileConnectorFactory = ({
|
|||
}: ConnectorFactory): ConnectorFactoryResponse => {
|
||||
return { type, ...metadata };
|
||||
};
|
||||
|
||||
const checkDuplicateConnectorFactoriesId = (connectorFactories: ConnectorFactory[]) => {
|
||||
const connectorFactoryIds = connectorFactories.map(({ metadata }) => metadata.id);
|
||||
const deduplicatedConnectorFactoryIds = deduplicate(connectorFactoryIds);
|
||||
|
||||
if (connectorFactoryIds.length !== deduplicatedConnectorFactoryIds.length) {
|
||||
const duplicatedConnectorFactoryIds = deduplicatedConnectorFactoryIds.filter(
|
||||
(deduplicateId) => connectorFactoryIds.filter((id) => id === deduplicateId).length > 1
|
||||
);
|
||||
throw new RequestError({
|
||||
code: 'connector.more_than_one_connector_factory',
|
||||
status: 422,
|
||||
connectorIds: duplicatedConnectorFactoryIds.map((id) => `${id}`).join(', '),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const loadConnectorFactories = async () => {
|
||||
const currentDirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const cliDirectory = await findPackage(currentDirname);
|
||||
const coreDirectory = cliDirectory && path.join(cliDirectory, '../core');
|
||||
const directory = coreDirectory && path.join(coreDirectory, connectorDirectory);
|
||||
|
||||
if (!directory || !existsSync(directory)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const connectorPackages = await getConnectorPackagesFromDirectory(directory);
|
||||
|
||||
const connectorFactories = await _loadConnectorFactories(
|
||||
connectorPackages,
|
||||
EnvSet.values.isIntegrationTest || EnvSet.values.ignoreConnectorVersionCheck
|
||||
);
|
||||
|
||||
checkDuplicateConnectorFactoriesId(connectorFactories);
|
||||
|
||||
return connectorFactories;
|
||||
};
|
||||
|
|
|
@ -1,21 +1,10 @@
|
|||
import type { AllConnector, CreateConnector, VerificationCodeType } from '@logto/connector-kit';
|
||||
import type { AllConnector, VerificationCodeType } from '@logto/connector-kit';
|
||||
import type { Connector } from '@logto/schemas';
|
||||
|
||||
export { ConnectorType } from '@logto/schemas';
|
||||
|
||||
export type TemplateType = VerificationCodeType;
|
||||
|
||||
/**
|
||||
* Dynamic loaded connector type.
|
||||
*/
|
||||
export type ConnectorFactory<T extends AllConnector = AllConnector> = Pick<
|
||||
T,
|
||||
'type' | 'metadata'
|
||||
> & {
|
||||
createConnector: CreateConnector<AllConnector>;
|
||||
path: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* The connector type with full context.
|
||||
*/
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
"url": "https://github.com/logto-io/logto/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@logto/core-kit": "workspace:*",
|
||||
"@logto/language-kit": "workspace:*",
|
||||
"@silverhand/essentials": "2.4.0",
|
||||
"zod": "^3.20.2"
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { fallback } from '@logto/core-kit';
|
||||
import type { LanguageTag } from '@logto/language-kit';
|
||||
import { languages } from '@logto/language-kit';
|
||||
import { languages, fallback } from '@logto/language-kit';
|
||||
import type { NormalizeKeyPaths } from '@silverhand/essentials';
|
||||
import { z } from 'zod';
|
||||
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
"url": "https://github.com/logto-io/logto/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@logto/core-kit": "workspace:*",
|
||||
"@logto/language-kit": "workspace:*",
|
||||
"@silverhand/essentials": "2.4.0",
|
||||
"zod": "^3.20.2"
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { fallback } from '@logto/core-kit';
|
||||
import type { LanguageTag } from '@logto/language-kit';
|
||||
import { languages } from '@logto/language-kit';
|
||||
import { languages, fallback } from '@logto/language-kit';
|
||||
import type { NormalizeKeyPaths } from '@silverhand/essentials';
|
||||
import { z } from 'zod';
|
||||
|
||||
|
|
|
@ -32,7 +32,6 @@
|
|||
"prepack": "pnpm build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@logto/core-kit": "workspace:*",
|
||||
"@logto/language-kit": "workspace:*",
|
||||
"@silverhand/essentials": "2.4.0"
|
||||
},
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
export * from './color.js';
|
||||
export * from './id.js';
|
||||
export * from './zod.js';
|
||||
export * from './url.js';
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
import { number, ZodError } from 'zod';
|
||||
|
||||
import { fallback } from './zod.js';
|
||||
|
||||
describe('fallback', () => {
|
||||
it('should fallback to default value', () => {
|
||||
const schema = number();
|
||||
const tolerant = schema.or(fallback(-1));
|
||||
|
||||
expect(() => schema.parse('foo')).toThrow(ZodError);
|
||||
expect(tolerant.parse('foo')).toBe(-1);
|
||||
});
|
||||
});
|
|
@ -1,15 +0,0 @@
|
|||
import { any } from 'zod';
|
||||
|
||||
/**
|
||||
* https://github.com/colinhacks/zod/issues/316#issuecomment-850906479
|
||||
* Create a schema matches anything and returns a value. Use it with `or`:
|
||||
*
|
||||
* const schema = zod.number();
|
||||
* const tolerant = schema.or(fallback(-1));
|
||||
*
|
||||
* schema.parse('foo') // => ZodError
|
||||
* tolerant.parse('foo') // -1
|
||||
*/
|
||||
export function fallback<T>(value: T) {
|
||||
return any().transform(() => value);
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
import { isLanguageTag, languageTagGuard } from './utility.js';
|
||||
import { number, ZodError } from 'zod';
|
||||
|
||||
import { fallback, isLanguageTag, languageTagGuard } from './utility.js';
|
||||
|
||||
describe('isLanguageTag', () => {
|
||||
it('should pass when input is a valid language key', () => {
|
||||
|
@ -25,3 +27,13 @@ describe('languageTagGuard', () => {
|
|||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('fallback', () => {
|
||||
it('should fallback to default value', () => {
|
||||
const schema = number();
|
||||
const tolerant = schema.or(fallback(-1));
|
||||
|
||||
expect(() => schema.parse('foo')).toThrow(ZodError);
|
||||
expect(tolerant.parse('foo')).toBe(-1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { z } from 'zod';
|
||||
import { z, any } from 'zod';
|
||||
|
||||
import { languages } from './const.js';
|
||||
import type { LanguageTag } from './type.js';
|
||||
|
@ -9,3 +9,17 @@ export const isLanguageTag = (value: unknown): value is LanguageTag =>
|
|||
export const languageTagGuard: z.ZodType<LanguageTag> = z
|
||||
.any()
|
||||
.refine((value: unknown) => isLanguageTag(value));
|
||||
|
||||
/**
|
||||
* https://github.com/colinhacks/zod/issues/316#issuecomment-850906479
|
||||
* Create a schema matches anything and returns a value. Use it with `or`:
|
||||
*
|
||||
* const schema = zod.number();
|
||||
* const tolerant = schema.or(fallback(-1));
|
||||
*
|
||||
* schema.parse('foo') // => ZodError
|
||||
* tolerant.parse('foo') // -1
|
||||
*/
|
||||
export function fallback<T>(value: T) {
|
||||
return any().transform(() => value);
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ importers:
|
|||
|
||||
packages/cli:
|
||||
specifiers:
|
||||
'@logto/connector-kit': workspace:1.0.0-rc.2
|
||||
'@logto/core-kit': workspace:*
|
||||
'@logto/schemas': workspace:*
|
||||
'@logto/shared': workspace:*
|
||||
|
@ -66,6 +67,7 @@ importers:
|
|||
yargs: ^17.6.0
|
||||
zod: ^3.20.2
|
||||
dependencies:
|
||||
'@logto/connector-kit': link:../toolkit/connector-kit
|
||||
'@logto/core-kit': link:../toolkit/core-kit
|
||||
'@logto/schemas': link:../schemas
|
||||
'@logto/shared': link:../shared
|
||||
|
@ -619,7 +621,6 @@ importers:
|
|||
|
||||
packages/phrases:
|
||||
specifiers:
|
||||
'@logto/core-kit': workspace:*
|
||||
'@logto/language-kit': workspace:*
|
||||
'@silverhand/eslint-config': 2.0.1
|
||||
'@silverhand/essentials': 2.4.0
|
||||
|
@ -630,7 +631,6 @@ importers:
|
|||
typescript: ^4.9.4
|
||||
zod: ^3.20.2
|
||||
dependencies:
|
||||
'@logto/core-kit': link:../toolkit/core-kit
|
||||
'@logto/language-kit': link:../toolkit/language-kit
|
||||
'@silverhand/essentials': 2.4.0
|
||||
zod: 3.20.2
|
||||
|
@ -644,7 +644,6 @@ importers:
|
|||
|
||||
packages/phrases-ui:
|
||||
specifiers:
|
||||
'@logto/core-kit': workspace:*
|
||||
'@logto/language-kit': workspace:*
|
||||
'@silverhand/eslint-config': 2.0.1
|
||||
'@silverhand/essentials': 2.4.0
|
||||
|
@ -656,7 +655,6 @@ importers:
|
|||
typescript: ^4.9.4
|
||||
zod: ^3.20.2
|
||||
dependencies:
|
||||
'@logto/core-kit': link:../toolkit/core-kit
|
||||
'@logto/language-kit': link:../toolkit/language-kit
|
||||
'@silverhand/essentials': 2.4.0
|
||||
zod: 3.20.2
|
||||
|
@ -765,7 +763,6 @@ importers:
|
|||
|
||||
packages/toolkit/connector-kit:
|
||||
specifiers:
|
||||
'@logto/core-kit': workspace:*
|
||||
'@logto/language-kit': workspace:*
|
||||
'@silverhand/eslint-config': 2.0.1
|
||||
'@silverhand/essentials': 2.4.0
|
||||
|
@ -778,7 +775,6 @@ importers:
|
|||
typescript: ^4.9.4
|
||||
zod: ^3.20.2
|
||||
dependencies:
|
||||
'@logto/core-kit': link:../core-kit
|
||||
'@logto/language-kit': link:../language-kit
|
||||
'@silverhand/essentials': 2.4.0
|
||||
optionalDependencies:
|
||||
|
|
Loading…
Reference in a new issue