mirror of
https://github.com/logto-io/logto.git
synced 2025-03-31 22:51:25 -05:00
feat(core,schemas): add post custom ui assets api (#6118)
* feat(core,schemas): add post custom ui assets api
This commit is contained in:
parent
84ac935c80
commit
ce3a62bc7a
24 changed files with 547 additions and 79 deletions
|
@ -96,6 +96,7 @@
|
|||
"@logto/cloud": "0.2.5-3046fa6",
|
||||
"@silverhand/eslint-config": "6.0.1",
|
||||
"@silverhand/ts-config": "6.0.0",
|
||||
"@types/adm-zip": "^0.5.5",
|
||||
"@types/debug": "^4.1.7",
|
||||
"@types/etag": "^1.8.1",
|
||||
"@types/jest": "^29.4.0",
|
||||
|
@ -113,6 +114,7 @@
|
|||
"@types/semver": "^7.3.12",
|
||||
"@types/sinon": "^17.0.0",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"adm-zip": "^0.5.14",
|
||||
"eslint": "^8.56.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-matcher-specific-error": "^1.0.0",
|
||||
|
|
|
@ -92,7 +92,7 @@ export const mockSignInExperience: SignInExperience = {
|
|||
customCss: null,
|
||||
customContent: {},
|
||||
agreeToTermsPolicy: AgreeToTermsPolicy.Automatic,
|
||||
customUiAssetId: null,
|
||||
customUiAssets: null,
|
||||
passwordPolicy: {},
|
||||
mfa: {
|
||||
policy: MfaPolicy.UserControlled,
|
||||
|
|
|
@ -32,6 +32,7 @@ describe('sign-in-experience query', () => {
|
|||
signUp: JSON.stringify(mockSignInExperience.signUp),
|
||||
socialSignInConnectorTargets: JSON.stringify(mockSignInExperience.socialSignInConnectorTargets),
|
||||
customContent: JSON.stringify(mockSignInExperience.customContent),
|
||||
customUiAssets: JSON.stringify(mockSignInExperience.customUiAssets),
|
||||
passwordPolicy: JSON.stringify(mockSignInExperience.passwordPolicy),
|
||||
mfa: JSON.stringify(mockSignInExperience.mfa),
|
||||
socialSignIn: JSON.stringify(mockSignInExperience.socialSignIn),
|
||||
|
@ -40,7 +41,7 @@ describe('sign-in-experience query', () => {
|
|||
it('findDefaultSignInExperience', async () => {
|
||||
/* eslint-disable sql/no-unsafe-query */
|
||||
const expectSql = `
|
||||
select "tenant_id", "id", "color", "branding", "language_info", "terms_of_use_url", "privacy_policy_url", "agree_to_terms_policy", "sign_in", "sign_up", "social_sign_in", "social_sign_in_connector_targets", "sign_in_mode", "custom_css", "custom_content", "custom_ui_asset_id", "password_policy", "mfa", "single_sign_on_enabled"
|
||||
select "tenant_id", "id", "color", "branding", "language_info", "terms_of_use_url", "privacy_policy_url", "agree_to_terms_policy", "sign_in", "sign_up", "social_sign_in", "social_sign_in_connector_targets", "sign_in_mode", "custom_css", "custom_content", "custom_ui_assets", "password_policy", "mfa", "single_sign_on_enabled"
|
||||
from "sign_in_experiences"
|
||||
where "id"=$1
|
||||
`;
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
type UserAssets,
|
||||
userAssetsGuard,
|
||||
adminTenantId,
|
||||
uploadFileGuard,
|
||||
} from '@logto/schemas';
|
||||
import { generateStandardId } from '@logto/shared';
|
||||
import { format } from 'date-fns';
|
||||
|
@ -18,7 +19,6 @@ import koaGuard from '#src/middleware/koa-guard.js';
|
|||
import type { RouterInitArgs } from '#src/routes/types.js';
|
||||
import SystemContext from '#src/tenants/SystemContext.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import { uploadFileGuard } from '#src/utils/storage/consts.js';
|
||||
import { buildUploadFile } from '#src/utils/storage/index.js';
|
||||
|
||||
import type { AuthedMeRouter } from './types.js';
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"tags": [
|
||||
{
|
||||
"name": "Custom UI assets",
|
||||
"description": "Endpoints for uploading custom UI assets for the sign-in experience. Users can upload a zip file containing custom HTML, CSS, and JavaScript files to replace and fully customize the sign-in experience."
|
||||
},
|
||||
{ "name": "Cloud only" },
|
||||
{ "name": "Dev feature" }
|
||||
],
|
||||
"paths": {
|
||||
"/api/sign-in-exp/default/custom-ui-assets": {
|
||||
"post": {
|
||||
"summary": "Upload custom UI assets",
|
||||
"description": "Upload a zip file containing custom web assets such as HTML, CSS, and JavaScript files, then replace the default sign-in experience with the custom UI assets.",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"multipart/form-data": {
|
||||
"schema": {
|
||||
"properties": {
|
||||
"file": {
|
||||
"description": "The zip file containing custom web assets such as HTML, CSS, and JavaScript files."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "An JSON object containing the custom UI assets ID."
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request. The request body is invalid."
|
||||
},
|
||||
"500": {
|
||||
"description": "Failed to unzip or upload the custom UI assets to storage provider."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { Readable } from 'node:stream';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { StorageProvider } from '@logto/schemas';
|
||||
import { generateStandardId } from '@logto/shared';
|
||||
import { createMockUtils, pickDefault } from '@logto/shared/esm';
|
||||
import AdmZip from 'adm-zip';
|
||||
import pRetry from 'p-retry';
|
||||
import { type Response } from 'supertest';
|
||||
|
||||
import SystemContext from '#src/tenants/SystemContext.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
import { createRequester } from '#src/utils/test-utils.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
const { mockEsmWithActual } = createMockUtils(jest);
|
||||
|
||||
const experienceZipsProviderConfig = {
|
||||
provider: StorageProvider.AzureStorage,
|
||||
connectionString: 'connectionString',
|
||||
container: 'container',
|
||||
} satisfies {
|
||||
provider: StorageProvider.AzureStorage;
|
||||
connectionString: string;
|
||||
container: string;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutation
|
||||
SystemContext.shared.experienceZipsProviderConfig = experienceZipsProviderConfig;
|
||||
|
||||
const mockedIsFileExisted = jest.fn(async (filename: string) => false);
|
||||
const mockedDownloadFile = jest.fn();
|
||||
|
||||
await mockEsmWithActual('#src/utils/storage/azure-storage.js', () => ({
|
||||
buildAzureStorage: () => ({
|
||||
uploadFile: jest.fn(async () => 'https://fake.url'),
|
||||
downloadFile: mockedDownloadFile,
|
||||
isFileExisted: mockedIsFileExisted,
|
||||
}),
|
||||
}));
|
||||
|
||||
await mockEsmWithActual('#src/utils/tenant.js', () => ({
|
||||
getTenantId: jest.fn().mockResolvedValue(['default']),
|
||||
}));
|
||||
|
||||
await mockEsmWithActual('p-retry', () => ({
|
||||
// Stub pRetry by overriding the default "exponential backoff",
|
||||
// in order to make the test run faster.
|
||||
default: async (input: <T>(retries: number) => T | PromiseLike<T>) =>
|
||||
pRetry(input, { factor: 0 }),
|
||||
}));
|
||||
|
||||
const mockedGenerateStandardId = jest.fn(generateStandardId);
|
||||
|
||||
await mockEsmWithActual('@logto/shared', () => ({
|
||||
generateStandardId: mockedGenerateStandardId,
|
||||
}));
|
||||
|
||||
const tenantContext = new MockTenant();
|
||||
|
||||
const signInExperiencesRoutes = await pickDefault(import('./index.js'));
|
||||
const signInExperienceRequester = createRequester({
|
||||
authedRoutes: signInExperiencesRoutes,
|
||||
tenantContext,
|
||||
});
|
||||
|
||||
const currentPath = path.dirname(fileURLToPath(import.meta.url));
|
||||
const testFilesPath = path.join(currentPath, 'test-files');
|
||||
const pathToZip = path.join(testFilesPath, 'assets.zip');
|
||||
|
||||
const uploadCustomUiAssets = async (filePath: string): Promise<Response> => {
|
||||
const response = await signInExperienceRequester
|
||||
.post('/sign-in-exp/default/custom-ui-assets')
|
||||
.field('name', 'file')
|
||||
.attach('file', filePath);
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
describe('POST /sign-in-exp/default/custom-ui-assets', () => {
|
||||
beforeAll(async () => {
|
||||
await fs.mkdir(testFilesPath);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
void fs.rm(testFilesPath, { force: true, recursive: true });
|
||||
});
|
||||
|
||||
it('should fail if upload file is not a zip', async () => {
|
||||
const pathToTxt = path.join(testFilesPath, 'foo.txt');
|
||||
await fs.writeFile(pathToTxt, 'foo');
|
||||
const response = await uploadCustomUiAssets(pathToTxt);
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
});
|
||||
|
||||
it('should upload custom ui assets', async () => {
|
||||
mockedGenerateStandardId.mockReturnValue('custom-ui-asset-id');
|
||||
const zip = new AdmZip();
|
||||
zip.addFile('index.html', Buffer.from('<html></html>'));
|
||||
await zip.writeZipPromise(pathToZip);
|
||||
const response = await uploadCustomUiAssets(pathToZip);
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.body.customUiAssetId).toBe('custom-ui-asset-id');
|
||||
});
|
||||
|
||||
it('should fail if the error.log file exists', async () => {
|
||||
mockedIsFileExisted.mockImplementation(async (filename: string) =>
|
||||
filename.endsWith('error.log')
|
||||
);
|
||||
mockedDownloadFile.mockImplementation(async () => ({
|
||||
readableStreamBody: Readable.from('Failed to unzip files!'),
|
||||
}));
|
||||
const response = await uploadCustomUiAssets(pathToZip);
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.text).toBe('Failed to upload file to the storage provider.');
|
||||
});
|
||||
|
||||
it('should fail if the upload zip always persists (unzipping azure function does not trigger)', async () => {
|
||||
mockedIsFileExisted.mockImplementation(async (filename) => filename.endsWith('assets.zip'));
|
||||
const response = await uploadCustomUiAssets(pathToZip);
|
||||
expect(response.status).toBe(500);
|
||||
expect(response.text).toBe('Failed to upload file to the storage provider.');
|
||||
expect(mockedIsFileExisted).toHaveBeenCalledTimes(10);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,115 @@
|
|||
import { readFile } from 'node:fs/promises';
|
||||
|
||||
import { uploadFileGuard, maxUploadFileSize } from '@logto/schemas';
|
||||
import { generateStandardId } from '@logto/shared';
|
||||
import pRetry, { AbortError } from 'p-retry';
|
||||
import { object, z } from 'zod';
|
||||
|
||||
import { EnvSet } from '#src/env-set/index.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import koaGuard from '#src/middleware/koa-guard.js';
|
||||
import SystemContext from '#src/tenants/SystemContext.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import { getConsoleLogFromContext } from '#src/utils/console.js';
|
||||
import { streamToString } from '#src/utils/file.js';
|
||||
import { buildAzureStorage } from '#src/utils/storage/azure-storage.js';
|
||||
import { getTenantId } from '#src/utils/tenant.js';
|
||||
|
||||
import { type ManagementApiRouter, type RouterInitArgs } from '../../types.js';
|
||||
|
||||
const maxRetryCount = 5;
|
||||
|
||||
export default function customUiAssetsRoutes<T extends ManagementApiRouter>(
|
||||
...[router]: RouterInitArgs<T>
|
||||
) {
|
||||
// TODO: Remove
|
||||
if (!EnvSet.values.isDevFeaturesEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
router.post(
|
||||
'/sign-in-exp/default/custom-ui-assets',
|
||||
koaGuard({
|
||||
files: object({
|
||||
file: uploadFileGuard.array().min(1).max(1),
|
||||
}),
|
||||
response: z.object({
|
||||
customUiAssetId: z.string(),
|
||||
}),
|
||||
status: [200, 400, 500],
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { file: bodyFiles } = ctx.guard.files;
|
||||
const file = bodyFiles[0];
|
||||
|
||||
assertThat(file, 'guard.invalid_input');
|
||||
assertThat(file.size <= maxUploadFileSize, 'guard.file_size_exceeded');
|
||||
assertThat(file.mimetype === 'application/zip', 'guard.mime_type_not_allowed');
|
||||
|
||||
const { experienceZipsProviderConfig } = SystemContext.shared;
|
||||
assertThat(
|
||||
experienceZipsProviderConfig?.provider === 'AzureStorage',
|
||||
'storage.not_configured'
|
||||
);
|
||||
const { connectionString, container } = experienceZipsProviderConfig;
|
||||
|
||||
const { uploadFile, downloadFile, isFileExisted } = buildAzureStorage(
|
||||
connectionString,
|
||||
container
|
||||
);
|
||||
|
||||
const [tenantId] = await getTenantId(ctx.URL);
|
||||
assertThat(tenantId, 'guard.can_not_get_tenant_id');
|
||||
|
||||
const customUiAssetId = generateStandardId(8);
|
||||
const objectKey = `${tenantId}/${customUiAssetId}/assets.zip`;
|
||||
const errorLogObjectKey = `${tenantId}/${customUiAssetId}/error.log`;
|
||||
|
||||
try {
|
||||
// Upload the zip file to `experience-zips` container, in which a blob trigger is configured,
|
||||
// and an azure function will be executed automatically to unzip the file on blob received.
|
||||
// If the unzipping process succeeds, the zip file will be removed and assets will be stored in
|
||||
// `experience-blobs` container. If it fails, the error message will be written to `error.log` file.
|
||||
await uploadFile(await readFile(file.filepath), objectKey, {
|
||||
contentType: file.mimetype,
|
||||
});
|
||||
|
||||
const hasUnzipCompleted = async (retryTimes: number) => {
|
||||
if (retryTimes > maxRetryCount) {
|
||||
throw new AbortError('Unzip timeout. Max retry count reached.');
|
||||
}
|
||||
const [hasZip, hasError] = await Promise.all([
|
||||
isFileExisted(objectKey),
|
||||
isFileExisted(errorLogObjectKey),
|
||||
]);
|
||||
if (hasZip) {
|
||||
throw new Error('Unzip in progress...');
|
||||
}
|
||||
if (hasError) {
|
||||
const errorLogBlob = await downloadFile(errorLogObjectKey);
|
||||
const errorLog = await streamToString(errorLogBlob.readableStreamBody);
|
||||
throw new AbortError(errorLog || 'Unzipping failed.');
|
||||
}
|
||||
};
|
||||
|
||||
await pRetry(hasUnzipCompleted, {
|
||||
retries: maxRetryCount,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
getConsoleLogFromContext(ctx).error(error);
|
||||
throw new RequestError(
|
||||
{
|
||||
code: 'storage.upload_error',
|
||||
status: 500,
|
||||
},
|
||||
{
|
||||
details: error instanceof Error ? error.message : String(error),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
ctx.body = { customUiAssetId };
|
||||
return next();
|
||||
}
|
||||
);
|
||||
}
|
|
@ -8,9 +8,12 @@ import koaGuard from '#src/middleware/koa-guard.js';
|
|||
|
||||
import type { ManagementApiRouter, RouterInitArgs } from '../types.js';
|
||||
|
||||
import customUiAssetsRoutes from './custom-ui-assets/index.js';
|
||||
|
||||
export default function signInExperiencesRoutes<T extends ManagementApiRouter>(
|
||||
...[router, { queries, libraries, connectors }]: RouterInitArgs<T>
|
||||
...args: RouterInitArgs<T>
|
||||
) {
|
||||
const [router, { queries, libraries, connectors }] = args;
|
||||
const { findDefaultSignInExperience, updateDefaultSignInExperience } = queries.signInExperiences;
|
||||
const { deleteConnectorById } = queries.connectors;
|
||||
const {
|
||||
|
@ -118,4 +121,6 @@ export default function signInExperiencesRoutes<T extends ManagementApiRouter>(
|
|||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
customUiAssetsRoutes(...args);
|
||||
}
|
||||
|
|
|
@ -156,6 +156,7 @@ const additionalTags = Object.freeze(
|
|||
condArray<string>(
|
||||
'Organization applications',
|
||||
EnvSet.values.isDevFeaturesEnabled && 'Subject tokens',
|
||||
EnvSet.values.isDevFeaturesEnabled && 'Custom UI assets',
|
||||
'Organization users'
|
||||
)
|
||||
);
|
||||
|
|
|
@ -27,6 +27,8 @@ type RouteDictionary = Record<`${OpenAPIV3.HttpMethods} ${string}`, string>;
|
|||
const devFeatureCustomRoutes: RouteDictionary = Object.freeze({
|
||||
// Subject tokens
|
||||
'post /subject-tokens': 'CreateSubjectToken',
|
||||
// Custom UI assets
|
||||
'post /sign-in-exp/default/custom-ui-assets': 'UploadCustomUiAssets',
|
||||
});
|
||||
|
||||
export const customRoutes: Readonly<RouteDictionary> = Object.freeze({
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
userAssetsServiceStatusGuard,
|
||||
allowUploadMimeTypes,
|
||||
maxUploadFileSize,
|
||||
uploadFileGuard,
|
||||
} from '@logto/schemas';
|
||||
import { generateStandardId } from '@logto/shared';
|
||||
import { format } from 'date-fns';
|
||||
|
@ -16,7 +17,6 @@ import koaGuard from '#src/middleware/koa-guard.js';
|
|||
import SystemContext from '#src/tenants/SystemContext.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
import { getConsoleLogFromContext } from '#src/utils/console.js';
|
||||
import { uploadFileGuard } from '#src/utils/storage/consts.js';
|
||||
import { buildUploadFile } from '#src/utils/storage/index.js';
|
||||
import { getTenantId } from '#src/utils/tenant.js';
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ import { devConsole } from '#src/utils/console.js';
|
|||
export default class SystemContext {
|
||||
static shared = new SystemContext();
|
||||
public storageProviderConfig?: StorageProviderData;
|
||||
public experienceBlobsProviderConfig?: StorageProviderData;
|
||||
public experienceZipsProviderConfig?: StorageProviderData;
|
||||
public hostnameProviderConfig?: HostnameProviderData;
|
||||
public protectedAppConfigProviderConfig?: ProtectedAppConfigProviderData;
|
||||
public protectedAppHostnameProviderConfig?: HostnameProviderData;
|
||||
|
@ -31,6 +33,20 @@ export default class SystemContext {
|
|||
storageProviderDataGuard
|
||||
);
|
||||
})(),
|
||||
(async () => {
|
||||
this.experienceBlobsProviderConfig = await this.loadConfig(
|
||||
pool,
|
||||
StorageProviderKey.ExperienceBlobsProvider,
|
||||
storageProviderDataGuard
|
||||
);
|
||||
})(),
|
||||
(async () => {
|
||||
this.experienceZipsProviderConfig = await this.loadConfig(
|
||||
pool,
|
||||
StorageProviderKey.ExperienceZipsProvider,
|
||||
storageProviderDataGuard
|
||||
);
|
||||
})(),
|
||||
(async () => {
|
||||
this.hostnameProviderConfig = await this.loadConfig(
|
||||
pool,
|
||||
|
|
16
packages/core/src/utils/file.test.ts
Normal file
16
packages/core/src/utils/file.test.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { Readable } from 'node:stream';
|
||||
|
||||
import { streamToString } from './file.js';
|
||||
|
||||
describe('streamToString()', () => {
|
||||
it('should return an empty string if the stream is empty', async () => {
|
||||
const result = await streamToString();
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should return the stream content as a string', async () => {
|
||||
const stream = Readable.from(['Hello', ' ', 'world', '!']);
|
||||
const result = await streamToString(stream);
|
||||
expect(result).toBe('Hello world!');
|
||||
});
|
||||
});
|
17
packages/core/src/utils/file.ts
Normal file
17
packages/core/src/utils/file.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Read a Readable stream to a string
|
||||
* @param stream - The Readable stream to read from
|
||||
* @returns A promise that resolves to a string containing the stream's data
|
||||
*/
|
||||
export async function streamToString(stream?: NodeJS.ReadableStream): Promise<string> {
|
||||
if (!stream) {
|
||||
return '';
|
||||
}
|
||||
const chunks: Uint8Array[] = [];
|
||||
for await (const chunk of stream) {
|
||||
// eslint-disable-next-line @silverhand/fp/no-mutating-methods
|
||||
chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);
|
||||
}
|
||||
const buffer = Buffer.concat(chunks);
|
||||
return buffer.toString('utf8');
|
||||
}
|
|
@ -1,10 +1,17 @@
|
|||
import { BlobServiceClient } from '@azure/storage-blob';
|
||||
import { type BlobDownloadResponseParsed, BlobServiceClient } from '@azure/storage-blob';
|
||||
|
||||
import type { UploadFile } from './types.js';
|
||||
|
||||
const defaultPublicDomain = 'blob.core.windows.net';
|
||||
|
||||
export const buildAzureStorage = (connectionString: string, container: string) => {
|
||||
export const buildAzureStorage = (
|
||||
connectionString: string,
|
||||
container: string
|
||||
): {
|
||||
uploadFile: UploadFile;
|
||||
downloadFile: (objectKey: string) => Promise<BlobDownloadResponseParsed>;
|
||||
isFileExisted: (objectKey: string) => Promise<boolean>;
|
||||
} => {
|
||||
const blobServiceClient = BlobServiceClient.fromConnectionString(connectionString);
|
||||
const containerClient = blobServiceClient.getContainerClient(container);
|
||||
|
||||
|
@ -24,5 +31,15 @@ export const buildAzureStorage = (connectionString: string, container: string) =
|
|||
};
|
||||
};
|
||||
|
||||
return { uploadFile };
|
||||
const downloadFile = async (objectKey: string) => {
|
||||
const blockBlobClient = containerClient.getBlockBlobClient(objectKey);
|
||||
return blockBlobClient.download();
|
||||
};
|
||||
|
||||
const isFileExisted = async (objectKey: string) => {
|
||||
const blockBlobClient = containerClient.getBlockBlobClient(objectKey);
|
||||
return blockBlobClient.exists();
|
||||
};
|
||||
|
||||
return { uploadFile, downloadFile, isFileExisted };
|
||||
};
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
import { number, object, string } from 'zod';
|
||||
|
||||
export const uploadFileGuard = object({
|
||||
filepath: string(),
|
||||
mimetype: string(),
|
||||
originalFilename: string(),
|
||||
size: number(),
|
||||
});
|
|
@ -106,7 +106,7 @@ export const mockSignInExperience: SignInExperience = {
|
|||
customCss: null,
|
||||
customContent: {},
|
||||
agreeToTermsPolicy: AgreeToTermsPolicy.ManualRegistrationOnly,
|
||||
customUiAssetId: null,
|
||||
customUiAssets: null,
|
||||
passwordPolicy: {},
|
||||
mfa: {
|
||||
policy: MfaPolicy.UserControlled,
|
||||
|
@ -140,7 +140,7 @@ export const mockSignInExperienceSettings: SignInExperienceResponse = {
|
|||
customCss: null,
|
||||
customContent: {},
|
||||
agreeToTermsPolicy: mockSignInExperience.agreeToTermsPolicy,
|
||||
customUiAssetId: null,
|
||||
customUiAssets: null,
|
||||
passwordPolicy: {},
|
||||
mfa: {
|
||||
policy: MfaPolicy.UserControlled,
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import { sql } from '@silverhand/slonik';
|
||||
|
||||
import type { AlterationScript } from '../lib/types/alteration.js';
|
||||
|
||||
const alteration: AlterationScript = {
|
||||
up: async (pool) => {
|
||||
await pool.query(sql`
|
||||
alter table sign_in_experiences drop column custom_ui_asset_id;
|
||||
alter table sign_in_experiences add column custom_ui_assets jsonb;
|
||||
`);
|
||||
},
|
||||
down: async (pool) => {
|
||||
await pool.query(sql`
|
||||
alter table sign_in_experiences add column custom_ui_asset_id varchar(21);
|
||||
alter table sign_in_experiences drop column custom_ui_assets;
|
||||
`);
|
||||
},
|
||||
};
|
||||
|
||||
export default alteration;
|
|
@ -109,3 +109,10 @@ export const mfaGuard = z.object({
|
|||
});
|
||||
|
||||
export type Mfa = z.infer<typeof mfaGuard>;
|
||||
|
||||
export const customUiAssetsGuard = z.object({
|
||||
id: z.string(),
|
||||
createdAt: z.number(),
|
||||
});
|
||||
|
||||
export type CustomUiAssets = z.infer<typeof customUiAssetsGuard>;
|
||||
|
|
|
@ -49,7 +49,7 @@ export const createDefaultSignInExperience = (
|
|||
signInMode: SignInMode.SignInAndRegister,
|
||||
customCss: null,
|
||||
customContent: {},
|
||||
customUiAssetId: null,
|
||||
customUiAssets: null,
|
||||
passwordPolicy: {},
|
||||
mfa: {
|
||||
factors: [],
|
||||
|
|
|
@ -61,16 +61,22 @@ export type StorageProviderData = z.infer<typeof storageProviderDataGuard>;
|
|||
|
||||
export enum StorageProviderKey {
|
||||
StorageProvider = 'storageProvider',
|
||||
ExperienceBlobsProvider = 'experienceBlobsProvider',
|
||||
ExperienceZipsProvider = 'experienceZipsProvider',
|
||||
}
|
||||
|
||||
export type StorageProviderType = {
|
||||
[StorageProviderKey.StorageProvider]: StorageProviderData;
|
||||
[StorageProviderKey.ExperienceBlobsProvider]: StorageProviderData;
|
||||
[StorageProviderKey.ExperienceZipsProvider]: StorageProviderData;
|
||||
};
|
||||
|
||||
export const storageProviderGuard: Readonly<{
|
||||
[key in StorageProviderKey]: ZodType<StorageProviderType[key]>;
|
||||
}> = Object.freeze({
|
||||
[StorageProviderKey.StorageProvider]: storageProviderDataGuard,
|
||||
[StorageProviderKey.ExperienceBlobsProvider]: storageProviderDataGuard,
|
||||
[StorageProviderKey.ExperienceZipsProvider]: storageProviderDataGuard,
|
||||
});
|
||||
|
||||
// Email service provider
|
||||
|
|
|
@ -32,3 +32,10 @@ export const userAssetsGuard = z.object({
|
|||
});
|
||||
|
||||
export type UserAssets = z.infer<typeof userAssetsGuard>;
|
||||
|
||||
export const uploadFileGuard = z.object({
|
||||
filepath: z.string(),
|
||||
mimetype: z.string(),
|
||||
originalFilename: z.string(),
|
||||
size: z.number(),
|
||||
});
|
||||
|
|
|
@ -19,7 +19,7 @@ create table sign_in_experiences (
|
|||
sign_in_mode sign_in_mode not null default 'SignInAndRegister',
|
||||
custom_css text,
|
||||
custom_content jsonb /* @use CustomContent */ not null default '{}'::jsonb,
|
||||
custom_ui_asset_id varchar(21),
|
||||
custom_ui_assets jsonb /* @use CustomUiAssets */,
|
||||
password_policy jsonb /* @use PartialPasswordPolicy */ not null default '{}'::jsonb,
|
||||
mfa jsonb /* @use Mfa */ not null default '{}'::jsonb,
|
||||
single_sign_on_enabled boolean not null default false,
|
||||
|
|
187
pnpm-lock.yaml
generated
187
pnpm-lock.yaml
generated
|
@ -3365,6 +3365,9 @@ importers:
|
|||
'@silverhand/ts-config':
|
||||
specifier: 6.0.0
|
||||
version: 6.0.0(typescript@5.3.3)
|
||||
'@types/adm-zip':
|
||||
specifier: ^0.5.5
|
||||
version: 0.5.5
|
||||
'@types/debug':
|
||||
specifier: ^4.1.7
|
||||
version: 4.1.7
|
||||
|
@ -3416,12 +3419,15 @@ importers:
|
|||
'@types/supertest':
|
||||
specifier: ^6.0.2
|
||||
version: 6.0.2
|
||||
adm-zip:
|
||||
specifier: ^0.5.14
|
||||
version: 0.5.14
|
||||
eslint:
|
||||
specifier: ^8.56.0
|
||||
version: 8.57.0
|
||||
jest:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0(@types/node@20.10.4)
|
||||
version: 29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))
|
||||
jest-matcher-specific-error:
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
|
@ -3676,7 +3682,7 @@ importers:
|
|||
version: 3.0.0
|
||||
jest:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0(@types/node@20.12.7)
|
||||
version: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3))
|
||||
jest-environment-jsdom:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0
|
||||
|
@ -3685,7 +3691,7 @@ importers:
|
|||
version: 2.0.0
|
||||
jest-transformer-svg:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(jest@29.7.0(@types/node@20.12.7))(react@18.2.0)
|
||||
version: 2.0.0(jest@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3)))(react@18.2.0)
|
||||
js-base64:
|
||||
specifier: ^3.7.5
|
||||
version: 3.7.5
|
||||
|
@ -3824,7 +3830,7 @@ importers:
|
|||
version: 10.0.0
|
||||
jest:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0(@types/node@20.10.4)
|
||||
version: 29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))
|
||||
jest-matcher-specific-error:
|
||||
specifier: ^1.0.0
|
||||
version: 1.0.0
|
||||
|
@ -6403,6 +6409,9 @@ packages:
|
|||
'@types/acorn@4.0.6':
|
||||
resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==}
|
||||
|
||||
'@types/adm-zip@0.5.5':
|
||||
resolution: {integrity: sha512-YCGstVMjc4LTY5uK9/obvxBya93axZOVOyf2GSUulADzmLhYE45u2nAssCs/fWBs1Ifq5Vat75JTPwd5XZoPJw==}
|
||||
|
||||
'@types/aria-query@5.0.1':
|
||||
resolution: {integrity: sha512-XTIieEY+gvJ39ChLcB4If5zHtPxt3Syj5rgZR+e1ctpmK8NjPf0zFqsz4JpLJT0xla9GFDKjy8Cpu331nrmE1Q==}
|
||||
|
||||
|
@ -6874,6 +6883,10 @@ packages:
|
|||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
adm-zip@0.5.14:
|
||||
resolution: {integrity: sha512-DnyqqifT4Jrcvb8USYjp6FHtBpEIz1mnXu6pTRHZ0RL69LbQYiO+0lDFg5+OKA7U29oWSs3a/i8fhn8ZcceIWg==}
|
||||
engines: {node: '>=12.0'}
|
||||
|
||||
agent-base@6.0.2:
|
||||
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||
engines: {node: '>= 6.0.0'}
|
||||
|
@ -14581,6 +14594,41 @@ snapshots:
|
|||
- supports-color
|
||||
- ts-node
|
||||
|
||||
'@jest/core@29.7.0(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))':
|
||||
dependencies:
|
||||
'@jest/console': 29.7.0
|
||||
'@jest/reporters': 29.7.0
|
||||
'@jest/test-result': 29.7.0
|
||||
'@jest/transform': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
'@types/node': 20.12.7
|
||||
ansi-escapes: 4.3.2
|
||||
chalk: 4.1.2
|
||||
ci-info: 3.8.0
|
||||
exit: 0.1.2
|
||||
graceful-fs: 4.2.11
|
||||
jest-changed-files: 29.7.0
|
||||
jest-config: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))
|
||||
jest-haste-map: 29.7.0
|
||||
jest-message-util: 29.7.0
|
||||
jest-regex-util: 29.6.3
|
||||
jest-resolve: 29.7.0
|
||||
jest-resolve-dependencies: 29.7.0
|
||||
jest-runner: 29.7.0
|
||||
jest-runtime: 29.7.0
|
||||
jest-snapshot: 29.7.0
|
||||
jest-util: 29.7.0
|
||||
jest-validate: 29.7.0
|
||||
jest-watcher: 29.7.0
|
||||
micromatch: 4.0.5
|
||||
pretty-format: 29.7.0
|
||||
slash: 3.0.0
|
||||
strip-ansi: 6.0.1
|
||||
transitivePeerDependencies:
|
||||
- babel-plugin-macros
|
||||
- supports-color
|
||||
- ts-node
|
||||
|
||||
'@jest/create-cache-key-function@27.5.1':
|
||||
dependencies:
|
||||
'@jest/types': 27.5.1
|
||||
|
@ -15902,10 +15950,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.3.3))(eslint@8.57.0)(typescript@5.3.3))(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint@8.57.0)(typescript@5.3.3)
|
||||
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0)
|
||||
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint@8.57.0))(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.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
|
||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0))(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)
|
||||
|
@ -16518,6 +16566,10 @@ snapshots:
|
|||
dependencies:
|
||||
'@types/estree': 1.0.5
|
||||
|
||||
'@types/adm-zip@0.5.5':
|
||||
dependencies:
|
||||
'@types/node': 20.12.7
|
||||
|
||||
'@types/aria-query@5.0.1': {}
|
||||
|
||||
'@types/babel__core@7.1.19':
|
||||
|
@ -17149,6 +17201,8 @@ snapshots:
|
|||
|
||||
acorn@8.11.3: {}
|
||||
|
||||
adm-zip@0.5.14: {}
|
||||
|
||||
agent-base@6.0.2:
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
|
@ -17913,13 +17967,13 @@ snapshots:
|
|||
dependencies:
|
||||
lodash.get: 4.4.2
|
||||
|
||||
create-jest@29.7.0(@types/node@20.10.4):
|
||||
create-jest@29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3)):
|
||||
dependencies:
|
||||
'@jest/types': 29.6.3
|
||||
chalk: 4.1.2
|
||||
exit: 0.1.2
|
||||
graceful-fs: 4.2.11
|
||||
jest-config: 29.7.0(@types/node@20.10.4)
|
||||
jest-config: 29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))
|
||||
jest-util: 29.7.0
|
||||
prompts: 2.4.2
|
||||
transitivePeerDependencies:
|
||||
|
@ -18727,13 +18781,13 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0):
|
||||
eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0):
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
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.3.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.3.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.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0)
|
||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.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.3.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.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.3.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
|
||||
fast-glob: 3.3.2
|
||||
get-tsconfig: 4.7.3
|
||||
is-core-module: 2.13.1
|
||||
|
@ -18744,14 +18798,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.3.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.3.3))(eslint-plugin-import@2.29.1)(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.3.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.3.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint@8.57.0))(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.3.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.3.3))(eslint-plugin-import@2.29.1)(eslint@8.57.0)
|
||||
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
@ -18773,7 +18827,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.3.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0):
|
||||
eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0):
|
||||
dependencies:
|
||||
array-includes: 3.1.8
|
||||
array.prototype.findlastindex: 1.2.5
|
||||
|
@ -18783,7 +18837,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.3.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.3.3))(eslint-plugin-import@2.29.1)(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.3.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.3.3))(eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.7.0(eslint@8.57.0)(typescript@5.3.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0)
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.13.1
|
||||
is-glob: 4.0.3
|
||||
|
@ -20244,35 +20298,16 @@ snapshots:
|
|||
- babel-plugin-macros
|
||||
- supports-color
|
||||
|
||||
jest-cli@29.7.0(@types/node@20.10.4):
|
||||
jest-cli@29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3)):
|
||||
dependencies:
|
||||
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3))
|
||||
'@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))
|
||||
'@jest/test-result': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
chalk: 4.1.2
|
||||
create-jest: 29.7.0(@types/node@20.10.4)
|
||||
create-jest: 29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))
|
||||
exit: 0.1.2
|
||||
import-local: 3.1.0
|
||||
jest-config: 29.7.0(@types/node@20.10.4)
|
||||
jest-util: 29.7.0
|
||||
jest-validate: 29.7.0
|
||||
yargs: 17.7.2
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- babel-plugin-macros
|
||||
- supports-color
|
||||
- ts-node
|
||||
|
||||
jest-cli@29.7.0(@types/node@20.12.7):
|
||||
dependencies:
|
||||
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3))
|
||||
'@jest/test-result': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
chalk: 4.1.2
|
||||
create-jest: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3))
|
||||
exit: 0.1.2
|
||||
import-local: 3.1.0
|
||||
jest-config: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3))
|
||||
jest-config: 29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))
|
||||
jest-util: 29.7.0
|
||||
jest-validate: 29.7.0
|
||||
yargs: 17.7.2
|
||||
|
@ -20301,7 +20336,7 @@ snapshots:
|
|||
- supports-color
|
||||
- ts-node
|
||||
|
||||
jest-config@29.7.0(@types/node@20.10.4):
|
||||
jest-config@29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3)):
|
||||
dependencies:
|
||||
'@babel/core': 7.24.4
|
||||
'@jest/test-sequencer': 29.7.0
|
||||
|
@ -20327,6 +20362,7 @@ snapshots:
|
|||
strip-json-comments: 3.1.1
|
||||
optionalDependencies:
|
||||
'@types/node': 20.10.4
|
||||
ts-node: 10.9.2(@types/node@20.10.4)(typescript@5.3.3)
|
||||
transitivePeerDependencies:
|
||||
- babel-plugin-macros
|
||||
- supports-color
|
||||
|
@ -20362,6 +20398,37 @@ snapshots:
|
|||
- babel-plugin-macros
|
||||
- supports-color
|
||||
|
||||
jest-config@29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3)):
|
||||
dependencies:
|
||||
'@babel/core': 7.24.4
|
||||
'@jest/test-sequencer': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
babel-jest: 29.7.0(@babel/core@7.24.4)
|
||||
chalk: 4.1.2
|
||||
ci-info: 3.8.0
|
||||
deepmerge: 4.3.1
|
||||
glob: 7.2.3
|
||||
graceful-fs: 4.2.11
|
||||
jest-circus: 29.7.0
|
||||
jest-environment-node: 29.7.0
|
||||
jest-get-type: 29.6.3
|
||||
jest-regex-util: 29.6.3
|
||||
jest-resolve: 29.7.0
|
||||
jest-runner: 29.7.0
|
||||
jest-util: 29.7.0
|
||||
jest-validate: 29.7.0
|
||||
micromatch: 4.0.5
|
||||
parse-json: 5.2.0
|
||||
pretty-format: 29.7.0
|
||||
slash: 3.0.0
|
||||
strip-json-comments: 3.1.1
|
||||
optionalDependencies:
|
||||
'@types/node': 20.12.7
|
||||
ts-node: 10.9.2(@types/node@20.10.4)(typescript@5.3.3)
|
||||
transitivePeerDependencies:
|
||||
- babel-plugin-macros
|
||||
- supports-color
|
||||
|
||||
jest-dev-server@10.0.0:
|
||||
dependencies:
|
||||
chalk: 4.1.2
|
||||
|
@ -20618,11 +20685,6 @@ snapshots:
|
|||
jest: 29.7.0(@types/node@20.12.7)(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3))
|
||||
react: 18.2.0
|
||||
|
||||
jest-transformer-svg@2.0.0(jest@29.7.0(@types/node@20.12.7))(react@18.2.0):
|
||||
dependencies:
|
||||
jest: 29.7.0(@types/node@20.12.7)
|
||||
react: 18.2.0
|
||||
|
||||
jest-util@29.5.0:
|
||||
dependencies:
|
||||
'@jest/types': 29.6.3
|
||||
|
@ -20675,24 +20737,12 @@ snapshots:
|
|||
merge-stream: 2.0.0
|
||||
supports-color: 8.1.1
|
||||
|
||||
jest@29.7.0(@types/node@20.10.4):
|
||||
jest@29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3)):
|
||||
dependencies:
|
||||
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3))
|
||||
'@jest/core': 29.7.0(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))
|
||||
'@jest/types': 29.6.3
|
||||
import-local: 3.1.0
|
||||
jest-cli: 29.7.0(@types/node@20.10.4)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- babel-plugin-macros
|
||||
- supports-color
|
||||
- ts-node
|
||||
|
||||
jest@29.7.0(@types/node@20.12.7):
|
||||
dependencies:
|
||||
'@jest/core': 29.7.0(ts-node@10.9.2(@swc/core@1.3.52(@swc/helpers@0.5.1))(@types/node@20.12.7)(typescript@5.3.3))
|
||||
'@jest/types': 29.6.3
|
||||
import-local: 3.1.0
|
||||
jest-cli: 29.7.0(@types/node@20.12.7)
|
||||
jest-cli: 29.7.0(@types/node@20.10.4)(ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3))
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- babel-plugin-macros
|
||||
|
@ -24186,6 +24236,25 @@ snapshots:
|
|||
optionalDependencies:
|
||||
'@swc/core': 1.3.52(@swc/helpers@0.5.1)
|
||||
|
||||
ts-node@10.9.2(@types/node@20.10.4)(typescript@5.3.3):
|
||||
dependencies:
|
||||
'@cspotcode/source-map-support': 0.8.1
|
||||
'@tsconfig/node10': 1.0.9
|
||||
'@tsconfig/node12': 1.0.11
|
||||
'@tsconfig/node14': 1.0.3
|
||||
'@tsconfig/node16': 1.0.4
|
||||
'@types/node': 20.10.4
|
||||
acorn: 8.10.0
|
||||
acorn-walk: 8.2.0
|
||||
arg: 4.1.3
|
||||
create-require: 1.1.1
|
||||
diff: 4.0.2
|
||||
make-error: 1.3.6
|
||||
typescript: 5.3.3
|
||||
v8-compile-cache-lib: 3.0.1
|
||||
yn: 3.1.1
|
||||
optional: true
|
||||
|
||||
tsconfig-paths@3.15.0:
|
||||
dependencies:
|
||||
'@types/json5': 0.0.29
|
||||
|
|
Loading…
Add table
Reference in a new issue