CMS providers update (#309)

* CMS providers update

* Strapi provider update

* Add channel listing update issue note to readme

* Update provider operations logs

* Update contribution guide and fields instructions

* Fix external links opening
This commit is contained in:
Dawid 2023-03-20 12:21:19 +01:00 committed by GitHub
parent b80df176e5
commit ab0dec5814
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 581 additions and 41 deletions

View file

@ -17,11 +17,11 @@ CMS Hub will:
If you want to add a provider for a new CMS, here is what you have to do: If you want to add a provider for a new CMS, here is what you have to do:
1. Go to `/src/lib/cms/config.ts`. 1. Go to `/src/lib/cms/config/providers.ts`.
2. Update the `providersConfig` variable with basic information about your provider: `name`, `label` and `tokens`: 2. Update the `providersConfig` variable with basic information about your provider: `name`, `label` and `tokens`:
```ts ```ts
// src/lib/cms/config.ts // src/lib/cms/config/providers.ts
export const providersConfig = { export const providersConfig = {
contentful: { contentful: {
... ...

View file

@ -29,6 +29,10 @@ Currently, the CMS Hub does not support mapping Saleor fields to your CMS fields
- strings fields: `saleor_id`, `name`, `product_id`, `product_name`, `product_slug`, - strings fields: `saleor_id`, `name`, `product_id`, `product_name`, `product_slug`,
- JSON fileds: `channels`. - JSON fileds: `channels`.
### Known issues
CMS Hub updates product variants in CMS providers on create, update or delete product variant webook events. They are triggered on product variant channel listing added or updated, but they currently don't trigger on deleting channel listing due to the [core issue #12247](https://github.com/saleor/saleor/issues/12247). To make it working on deleting channel listing, you can additionally modify other product variant field e.g. `name`, so webhook handlers receive the updated channel listings as well.
## How to use it? ## How to use it?
1. Install the application in your Dashboard and open it. 1. Install the application in your Dashboard and open it.

View file

@ -7,6 +7,6 @@ DatoCMS integration requires several configuration tokens. You should enter them
Here is the list of the tokens and instructions on how to obtain them Here is the list of the tokens and instructions on how to obtain them
- `baseUrl`: the optional URL to your DatoCMS project. If you leave this blank, this URL will be inferred from your API Token. - `baseUrl`: the optional URL to your DatoCMS project. If you leave this blank, this URL will be inferred from your API Token.
- `token`: the API token with access to Content Management API. You can find this in your DatoCMS project settings. - `token`: the API token with access to Content Management API. You can find this in your DatoCMS project settings. More instructions of how to create it available at [DatoCMS "Authentication" documentation](https://www.datocms.com/docs/content-management-api/authentication).
- `itemTypeId`: item type ID (number). You can find this as Model ID in your DatoCMS product variant model settings, by clicking "Edit model". - `itemTypeId`: item type ID (number). You can find this as Model ID in your DatoCMS product variant model settings, by clicking "Edit model".
- `environment`: optional environment name. If you leave this blank, default environment will be used. You can find this in your DatoCMS project settings. - `environment`: optional environment name. If you leave this blank, default environment will be used. You can find this in your DatoCMS project settings.

View file

@ -8,3 +8,4 @@ Here is the list of the tokens and instructions on how to obtain them
- `baseUrl`: the API URL. It's the address of your Strapi API. For local Strapi development it will be: `http://localhost:XXXX`. - `baseUrl`: the API URL. It's the address of your Strapi API. For local Strapi development it will be: `http://localhost:XXXX`.
- `token`: the authorization token. For instructions on how to create one for CMS Hub, please go to the [Strapi "Managing API tokens" documentation](https://docs.strapi.io/user-docs/latest/settings/managing-global-settings.html#managing-api-tokens). - `token`: the authorization token. For instructions on how to create one for CMS Hub, please go to the [Strapi "Managing API tokens" documentation](https://docs.strapi.io/user-docs/latest/settings/managing-global-settings.html#managing-api-tokens).
- `contentTypeId`: the content type id. You can find this in your Strapi project, go to Content-Type Builder > select content type > click Edit > use API ID (Plural). For more unstruction of how to get content type id, please go to [Strapi "Editing content types" documentation](https://docs.strapi.io/user-docs/content-type-builder/managing-content-types#editing-content-types).

View file

@ -29,9 +29,11 @@
"graphql-tag": "^2.12.6", "graphql-tag": "^2.12.6",
"next": "13.2", "next": "13.2",
"pino": "^8.8.0", "pino": "^8.8.0",
"pino-pretty": "^9.1.1",
"react": "18.2.0", "react": "18.2.0",
"react-dom": "18.2.0", "react-dom": "18.2.0",
"react-hook-form": "^7.39.1", "react-hook-form": "^7.39.1",
"react-markdown": "^8.0.5",
"urql": "^3.0.3", "urql": "^3.0.3",
"usehooks-ts": "^2.9.1", "usehooks-ts": "^2.9.1",
"uuid": "^9.0.0", "uuid": "^9.0.0",

View file

@ -65,6 +65,7 @@ describe("CMS Clients Operations", () => {
name: "First provider", name: "First provider",
token: "token", token: "token",
baseUrl: "baseUrl", baseUrl: "baseUrl",
contentTypeId: "contentTypeId",
id: "first-provider", id: "first-provider",
providerName: "strapi", providerName: "strapi",
}, },
@ -72,6 +73,7 @@ describe("CMS Clients Operations", () => {
name: "Second provider", name: "Second provider",
token: "token", token: "token",
baseUrl: "baseUrl", baseUrl: "baseUrl",
contentTypeId: "contentTypeId",
id: "second-provider", id: "second-provider",
providerName: "strapi", providerName: "strapi",
}, },
@ -79,6 +81,7 @@ describe("CMS Clients Operations", () => {
name: "Third provider", name: "Third provider",
token: "token", token: "token",
baseUrl: "baseUrl", baseUrl: "baseUrl",
contentTypeId: "contentTypeId",
id: "third-provider", id: "third-provider",
providerName: "strapi", providerName: "strapi",
}, },
@ -134,6 +137,7 @@ describe("CMS Clients Operations", () => {
name: "First provider", name: "First provider",
token: "token", token: "token",
baseUrl: "baseUrl", baseUrl: "baseUrl",
contentTypeId: "contentTypeId",
id: "first-provider", id: "first-provider",
providerName: "strapi", providerName: "strapi",
}, },
@ -141,6 +145,7 @@ describe("CMS Clients Operations", () => {
name: "Second provider", name: "Second provider",
token: "token", token: "token",
baseUrl: "baseUrl", baseUrl: "baseUrl",
contentTypeId: "contentTypeId",
id: "second-provider", id: "second-provider",
providerName: "strapi", providerName: "strapi",
}, },
@ -148,6 +153,7 @@ describe("CMS Clients Operations", () => {
name: "Third provider", name: "Third provider",
token: "token", token: "token",
baseUrl: "baseUrl", baseUrl: "baseUrl",
contentTypeId: "contentTypeId",
id: "third-provider", id: "third-provider",
providerName: "strapi", providerName: "strapi",
}, },
@ -203,6 +209,7 @@ describe("CMS Clients Operations", () => {
name: "First provider", name: "First provider",
token: "token", token: "token",
baseUrl: "baseUrl", baseUrl: "baseUrl",
contentTypeId: "contentTypeId",
id: "first-provider", id: "first-provider",
providerName: "strapi", providerName: "strapi",
}, },
@ -210,6 +217,7 @@ describe("CMS Clients Operations", () => {
name: "Second provider", name: "Second provider",
token: "token", token: "token",
baseUrl: "baseUrl", baseUrl: "baseUrl",
contentTypeId: "contentTypeId",
id: "second-provider", id: "second-provider",
providerName: "strapi", providerName: "strapi",
}, },
@ -217,6 +225,7 @@ describe("CMS Clients Operations", () => {
name: "Third provider", name: "Third provider",
token: "token", token: "token",
baseUrl: "baseUrl", baseUrl: "baseUrl",
contentTypeId: "contentTypeId",
id: "third-provider", id: "third-provider",
providerName: "strapi", providerName: "strapi",
}, },

View file

@ -15,7 +15,6 @@ import { getCmsIdFromSaleorItemKey } from "./metadata";
type WebhookContext = Parameters<NextWebhookApiHandler>["2"]; type WebhookContext = Parameters<NextWebhookApiHandler>["2"];
// todo: add support for multiple providers at once
export const createCmsOperations = async ({ export const createCmsOperations = async ({
context, context,
productVariantChannels, productVariantChannels,

View file

@ -13,7 +13,7 @@ export const createCmsKeyForSaleorItem = (cmsProviderInstanceId: string) => {
}; };
export const getCmsIdFromSaleorItemKey = (key: string) => { export const getCmsIdFromSaleorItemKey = (key: string) => {
return key.split("_")[2]; return key.split("_")[1];
}; };
export const getCmsIdFromSaleorItem = ( export const getCmsIdFromSaleorItem = (

View file

@ -25,40 +25,46 @@ export const providersConfig = {
icon: ContentfulIcon, icon: ContentfulIcon,
tokens: [ tokens: [
{ {
name: "baseUrl", required: true,
label: "Base URL",
helpText: "CDN API URL of your Contentful project, e.g. https://cdn.contentful.com.",
},
{
name: "token", name: "token",
label: "Token", label: "Token",
helpText: helpText:
"You can find this in your Contentful project, go to Settings > API keys > Content management tokens > Generate personal token.", 'You can find this in your Contentful project, go to Settings > API keys > Content management tokens > Generate personal token. More instructions at [Contentful "Authentication" documentation](https://www.contentful.com/developers/docs/references/authentication/).',
}, },
{ {
required: true,
name: "environment", name: "environment",
label: "Environment", label: "Environment",
helpText: helpText:
"Environment of your content, e.g. master. You can find this in your Contentful project, go to Settings > Environments.", "Environment of your content, e.g. master. You can find this in your Contentful project, go to Settings > Environments.",
}, },
{ {
required: true,
name: "spaceId", name: "spaceId",
label: "Space ID", label: "Space ID",
helpText: helpText:
"You can find this in your Contentful project, go to settings > general settings.", "You can find this in your Contentful project, go to settings > general settings.",
}, },
{ {
required: true,
name: "contentId", name: "contentId",
label: "Content ID", label: "Content ID",
helpText: helpText:
"You can find this in your Contentful project, go to Content model > select model > Content type id.", "You can find this in your Contentful project, go to Content model > select model > Content type id.",
}, },
{ {
required: true,
name: "locale", name: "locale",
label: "Locale", label: "Locale",
helpText: helpText:
"Locale of your content, e.g. en-US. You can find this in your Contentful project, go to Settings > Locales.", "Locale of your content, e.g. en-US. You can find this in your Contentful project, go to Settings > Locales.",
}, },
{
name: "baseUrl",
label: "Base URL",
helpText:
"Optional content management API URL of your Contentful project. If you leave this blank, default https://api.contentful.com will be used.",
},
], ],
}, },
strapi: { strapi: {
@ -70,14 +76,21 @@ export const providersConfig = {
required: true, required: true,
name: "baseUrl", name: "baseUrl",
label: "Base URL", label: "Base URL",
helpText: "API URL of your Strapi project.", helpText: "API URL of your Strapi project. E.g. https://your-strapi-project/api.",
}, },
{ {
required: true, required: true,
name: "token", name: "token",
label: "API Token (with full access)", label: "API Token (with full access)",
helpText: helpText:
"You can find this in your Strapi project settings, go to Settings > API Tokens and use full access token or create new one.", 'You can find this in your Strapi project settings, go to Settings > API Tokens and use full access token or create new one. More instructions at [Strapi "Managing API tokens" documentation](https://docs.strapi.io/user-docs/latest/settings/managing-global-settings.html#managing-api-tokens).',
},
{
required: true,
name: "contentTypeId",
label: "Content Type ID",
helpText:
'You can find this in your Strapi project, go to Content-Type Builder > select content type > click Edit > use API ID (Plural). More instructions at [Strapi "Editing content types" documentation](https://docs.strapi.io/user-docs/content-type-builder/managing-content-types#editing-content-types).',
}, },
], ],
}, },
@ -90,7 +103,8 @@ export const providersConfig = {
required: true, required: true,
name: "token", name: "token",
label: "API Token (with access to Content Management API)", label: "API Token (with access to Content Management API)",
helpText: "You can find this in your DatoCMS project settings.", helpText:
'You can find this in your DatoCMS project settings. More instructions at [DatoCMS "Authentication" documentation](https://www.datocms.com/docs/content-management-api/authentication).',
}, },
{ {
required: true, required: true,
@ -123,24 +137,25 @@ export const strapiConfigSchema = z.object({
name: z.string().min(1), name: z.string().min(1),
token: z.string().min(1), token: z.string().min(1),
baseUrl: z.string().min(1), baseUrl: z.string().min(1),
contentTypeId: z.string().min(1),
}); });
export const contentfulConfigSchema = z.object({ export const contentfulConfigSchema = z.object({
name: z.string().min(1), name: z.string().min(1),
token: z.string(), token: z.string().min(1),
environment: z.string().min(1),
spaceId: z.string().min(1),
locale: z.string().min(1),
contentId: z.string().min(1),
baseUrl: z.string(), baseUrl: z.string(),
environment: z.string(),
spaceId: z.string(),
locale: z.string(),
contentId: z.string(),
}); });
export const datocmsConfigSchema = z.object({ export const datocmsConfigSchema = z.object({
name: z.string().min(1), name: z.string().min(1),
token: z.string().min(1), token: z.string().min(1),
itemTypeId: z.string().min(1),
baseUrl: z.string(), baseUrl: z.string(),
environment: z.string(), environment: z.string(),
itemTypeId: z.string().min(1),
}); });
export const providerCommonSchema = z.object({ export const providerCommonSchema = z.object({

View file

@ -1,11 +1,13 @@
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import { ContentfulConfig, contentfulConfigSchema } from "../config"; import { ContentfulConfig, contentfulConfigSchema } from "../config";
import { logger as pinoLogger } from "../../logger";
import { CreateOperations, CreateProductResponse, ProductInput } from "../types"; import { CreateOperations, CreateProductResponse, ProductInput } from "../types";
import { createProvider } from "./create"; import { createProvider } from "./create";
const contentfulFetch = (endpoint: string, config: ContentfulConfig, options?: RequestInit) => { const contentfulFetch = (endpoint: string, config: ContentfulConfig, options?: RequestInit) => {
const { baseUrl, token } = config; const baseUrl = config.baseUrl || "https://api.contentful.com";
const token = config.token;
return fetch(`${baseUrl}${endpoint}`, { return fetch(`${baseUrl}${endpoint}`, {
...options, ...options,
@ -55,11 +57,8 @@ const transformInputToBody = ({
[locale]: input.productName, [locale]: input.productName,
}, },
channels: { channels: {
[locale]: JSON.stringify(input.channels), [locale]: input.channels,
}, },
// image: {
// [locale]: input.image,
// },
}, },
}; };
return body; return body;
@ -92,7 +91,9 @@ const getEntryEndpoint = ({
}): string => `/spaces/${spaceId}/environments/${environment}/entries/${resourceId}`; }): string => `/spaces/${spaceId}/environments/${environment}/entries/${resourceId}`;
const contentfulOperations: CreateOperations<ContentfulConfig> = (config) => { const contentfulOperations: CreateOperations<ContentfulConfig> = (config) => {
const { baseUrl, token, environment, spaceId, contentId, locale } = config; const logger = pinoLogger.child({ cms: "strapi" });
const { environment, spaceId, contentId, locale } = config;
return { return {
createProduct: async (params) => { createProduct: async (params) => {
@ -104,6 +105,7 @@ const contentfulOperations: CreateOperations<ContentfulConfig> = (config) => {
environment, environment,
spaceId, spaceId,
}); });
const response = await contentfulFetch(endpoint, config, { const response = await contentfulFetch(endpoint, config, {
method: "PUT", method: "PUT",
body: JSON.stringify(body), body: JSON.stringify(body),
@ -111,7 +113,10 @@ const contentfulOperations: CreateOperations<ContentfulConfig> = (config) => {
"X-Contentful-Content-Type": contentId, "X-Contentful-Content-Type": contentId,
}, },
}); });
logger.debug("createProduct response", { response });
const result = await response.json(); const result = await response.json();
logger.debug("createProduct result", { result });
return transformCreateProductResponse(result); return transformCreateProductResponse(result);
}, },
updateProduct: async ({ id, input }) => { updateProduct: async ({ id, input }) => {
@ -121,8 +126,12 @@ const contentfulOperations: CreateOperations<ContentfulConfig> = (config) => {
environment, environment,
spaceId, spaceId,
}); });
const getEntryResponse = await contentfulFetch(endpoint, config, { method: "GET" }); const getEntryResponse = await contentfulFetch(endpoint, config, { method: "GET" });
logger.debug("updateProduct getEntryResponse", { getEntryResponse });
const entry = await getEntryResponse.json(); const entry = await getEntryResponse.json();
logger.debug("updateProduct entry", { entry });
const response = await contentfulFetch(endpoint, config, { const response = await contentfulFetch(endpoint, config, {
method: "PUT", method: "PUT",
body: JSON.stringify(body), body: JSON.stringify(body),
@ -130,12 +139,19 @@ const contentfulOperations: CreateOperations<ContentfulConfig> = (config) => {
"X-Contentful-Version": entry.sys.version, "X-Contentful-Version": entry.sys.version,
}, },
}); });
logger.debug("updateProduct response", { response });
const result = await response.json(); const result = await response.json();
logger.debug("updateProduct result", { result });
return result; return result;
}, },
deleteProduct: ({ id }) => { deleteProduct: async ({ id }) => {
const endpoint = getEntryEndpoint({ resourceId: id, environment, spaceId }); const endpoint = getEntryEndpoint({ resourceId: id, environment, spaceId });
return contentfulFetch(endpoint, config, { method: "DELETE" });
const response = await contentfulFetch(endpoint, config, { method: "DELETE" });
logger.debug("deleteProduct response", { response });
return response;
}, },
}; };
}; };

View file

@ -1,5 +1,6 @@
import { createProvider } from "./create"; import { createProvider } from "./create";
import { CreateOperations, CreateProductResponse } from "../types"; import { CreateOperations, CreateProductResponse } from "../types";
import { logger as pinoLogger } from "../../logger";
import { ApiError, buildClient, SimpleSchemaTypes } from "@datocms/cma-client-node"; import { ApiError, buildClient, SimpleSchemaTypes } from "@datocms/cma-client-node";
import { DatocmsConfig, datocmsConfigSchema } from "../config"; import { DatocmsConfig, datocmsConfigSchema } from "../config";
@ -41,6 +42,8 @@ const transformResponseItem = (item: SimpleSchemaTypes.Item): CreateProductRespo
}; };
const datocmsOperations: CreateOperations<DatocmsConfig> = (config) => { const datocmsOperations: CreateOperations<DatocmsConfig> = (config) => {
const logger = pinoLogger.child({ cms: "strapi" });
return { return {
createProduct: async ({ input }) => { createProduct: async ({ input }) => {
const client = datocmsClient(config); const client = datocmsClient(config);
@ -58,6 +61,8 @@ const datocmsOperations: CreateOperations<DatocmsConfig> = (config) => {
product_name: input.productName, product_name: input.productName,
product_slug: input.productSlug, product_slug: input.productSlug,
}); });
logger.debug("createProduct response", { item });
return transformResponseItem(item); return transformResponseItem(item);
} catch (error) { } catch (error) {
return transformResponseError(error); return transformResponseError(error);
@ -66,7 +71,7 @@ const datocmsOperations: CreateOperations<DatocmsConfig> = (config) => {
updateProduct: async ({ id, input }) => { updateProduct: async ({ id, input }) => {
const client = datocmsClient(config); const client = datocmsClient(config);
await client.items.update(id, { const item = await client.items.update(id, {
saleor_id: input.saleorId, saleor_id: input.saleorId,
name: input.name, name: input.name,
channels: JSON.stringify(input.channels), channels: JSON.stringify(input.channels),
@ -74,11 +79,13 @@ const datocmsOperations: CreateOperations<DatocmsConfig> = (config) => {
product_name: input.productName, product_name: input.productName,
product_slug: input.productSlug, product_slug: input.productSlug,
}); });
logger.debug("updateProduct response", { item });
}, },
deleteProduct: async ({ id }) => { deleteProduct: async ({ id }) => {
const client = datocmsClient(config); const client = datocmsClient(config);
await client.items.destroy(id); const item = await client.items.destroy(id);
logger.debug("deleteProduct response", { item });
}, },
}; };
}; };

View file

@ -1,9 +1,11 @@
import { StrapiConfig, strapiConfigSchema } from "../config"; import { StrapiConfig, strapiConfigSchema } from "../config";
import { CmsOperations, CreateOperations, CreateProductResponse, ProductInput } from "../types"; import { CmsOperations, CreateOperations, CreateProductResponse, ProductInput } from "../types";
import { createProvider } from "./create"; import { createProvider } from "./create";
import { logger as pinoLogger } from "../../logger";
const strapiFetch = (endpoint: string, config: StrapiConfig, options?: RequestInit) => { const strapiFetch = async (endpoint: string, config: StrapiConfig, options?: RequestInit) => {
const { baseUrl, token } = config; const { baseUrl, token } = config;
return fetch(`${baseUrl}${endpoint}`, { return fetch(`${baseUrl}${endpoint}`, {
...options, ...options,
headers: { headers: {
@ -23,7 +25,7 @@ const transformInputToBody = ({ input }: { input: ProductInput }): StrapiBody =>
data: { data: {
saleor_id: input.saleorId, saleor_id: input.saleorId,
name: input.name, name: input.name,
channels: JSON.stringify(input.channels), channels: input.channels,
product_id: input.productId, product_id: input.productId,
product_name: input.productName, product_name: input.productName,
product_slug: input.productSlug, product_slug: input.productSlug,
@ -72,24 +74,39 @@ const transformCreateProductResponse = (response: StrapiResponse): CreateProduct
type CreateStrapiOperations = CreateOperations<StrapiConfig>; type CreateStrapiOperations = CreateOperations<StrapiConfig>;
export const strapiOperations: CreateStrapiOperations = (config): CmsOperations => { export const strapiOperations: CreateStrapiOperations = (config): CmsOperations => {
const logger = pinoLogger.child({ cms: "strapi" });
const { contentTypeId } = config;
return { return {
createProduct: async (params) => { createProduct: async (params) => {
const body = transformInputToBody(params); const body = transformInputToBody(params);
const response = await strapiFetch("/products", config, { const response = await strapiFetch(`/${contentTypeId}`, config, {
method: "POST", method: "POST",
body: JSON.stringify(body), body: JSON.stringify(body),
}); });
logger.debug("createProduct response", { response });
const result = await response.json(); const result = await response.json();
logger.debug("createProduct result", { result });
return transformCreateProductResponse(result); return transformCreateProductResponse(result);
}, },
updateProduct: ({ id, input }) => { updateProduct: async ({ id, input }) => {
const body = transformInputToBody({ input }); const body = transformInputToBody({ input });
return strapiFetch(`/products/${id}`, config, { method: "PUT", body: JSON.stringify(body) }); const response = await strapiFetch(`/${contentTypeId}/${id}`, config, {
method: "PUT",
body: JSON.stringify(body),
});
logger.debug("updateProduct response", { response });
return response;
}, },
deleteProduct: ({ id }) => { deleteProduct: async ({ id }) => {
return strapiFetch(`/products/${id}`, config, { method: "DELETE" }); const response = await strapiFetch(`/${contentTypeId}/${id}`, config, { method: "DELETE" });
logger.debug("deleteProduct response", { response });
return response;
}, },
}; };
}; };

View file

@ -1,8 +1,9 @@
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { Checkbox, FormControlLabel, Grid, TextField, Typography } from "@material-ui/core"; import { Grid, TextField, Typography } from "@material-ui/core";
import { Button, makeStyles } from "@saleor/macaw-ui"; import { Button, makeStyles } from "@saleor/macaw-ui";
import React from "react"; import React from "react";
import { Controller, DeepRequired, FieldErrorsImpl, Path, useForm } from "react-hook-form"; import ReactMarkdown from "react-markdown";
import { Path, useForm } from "react-hook-form";
import { z } from "zod"; import { z } from "zod";
import { import {
providersConfig, providersConfig,
@ -13,6 +14,7 @@ import {
ProviderInstanceSchema, ProviderInstanceSchema,
} from "../../../lib/cms/config"; } from "../../../lib/cms/config";
import { Provider } from "../../providers/config"; import { Provider } from "../../providers/config";
import { AppMarkdownText } from "../../ui/app-markdown-text";
const useStyles = makeStyles((theme) => ({ const useStyles = makeStyles((theme) => ({
footer: { footer: {
@ -132,7 +134,7 @@ const ProviderInstanceConfigurationForm = <TProvider extends CMSProviderSchema>(
helperText={ helperText={
<> <>
{errors[token.name as Path<ProvidersSchema[TProvider]>]?.message || {errors[token.name as Path<ProvidersSchema[TProvider]>]?.message ||
("helpText" in token && token.helpText)} ("helpText" in token && <AppMarkdownText>{token.helpText}</AppMarkdownText>)}
</> </>
} }
/> />

View file

@ -0,0 +1,35 @@
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
import ReactMarkdown from "react-markdown";
import { ReactMarkdownOptions } from "react-markdown/lib/react-markdown";
export const AppMarkdownText = ({ children, components, ...rest }: ReactMarkdownOptions) => {
const { appBridge } = useAppBridge();
const onClickHelpTextLink = (
event: React.MouseEvent<HTMLAnchorElement, MouseEvent>,
href?: string
) => {
event.preventDefault();
if (href) {
appBridge?.dispatch(
actions.Redirect({
to: href,
newContext: true,
})
);
}
};
return (
<ReactMarkdown
{...rest}
components={{
...components,
a: (props) => <a {...props} onClick={(event) => onClickHelpTextLink(event, props.href)} />,
}}
>
{children}
</ReactMarkdown>
);
};

View file

@ -56,10 +56,12 @@ importers:
jsdom: ^20.0.3 jsdom: ^20.0.3
next: '13.2' next: '13.2'
pino: ^8.8.0 pino: ^8.8.0
pino-pretty: ^9.1.1
prettier: ^2.7.1 prettier: ^2.7.1
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0 react-dom: 18.2.0
react-hook-form: ^7.39.1 react-hook-form: ^7.39.1
react-markdown: ^8.0.5
typescript: '4.9' typescript: '4.9'
urql: ^3.0.3 urql: ^3.0.3
usehooks-ts: ^2.9.1 usehooks-ts: ^2.9.1
@ -82,9 +84,11 @@ importers:
graphql-tag: 2.12.6_graphql@16.6.0 graphql-tag: 2.12.6_graphql@16.6.0
next: 13.2.4_biqbaboplfbrettd7655fr4n2y next: 13.2.4_biqbaboplfbrettd7655fr4n2y
pino: 8.9.0 pino: 8.9.0
pino-pretty: 9.1.1
react: 18.2.0 react: 18.2.0
react-dom: 18.2.0_react@18.2.0 react-dom: 18.2.0_react@18.2.0
react-hook-form: 7.43.1_react@18.2.0 react-hook-form: 7.43.1_react@18.2.0
react-markdown: 8.0.5_3stiutgnnbnfnf3uowm5cip22i
urql: 3.0.3_onqnqwb3ubg5opvemcqf7c2qhy urql: 3.0.3_onqnqwb3ubg5opvemcqf7c2qhy
usehooks-ts: 2.9.1_biqbaboplfbrettd7655fr4n2y usehooks-ts: 2.9.1_biqbaboplfbrettd7655fr4n2y
uuid: 9.0.0 uuid: 9.0.0
@ -5703,6 +5707,12 @@ packages:
resolution: {integrity: sha512-Wtl6PUL26jEbC1NBqJi7uoyYZo1/I3EDCd9pZk9EN6ZDvKaO28M5+nIQGyYomzvkMpMHnfywpTzalhwr76/oAg==} resolution: {integrity: sha512-Wtl6PUL26jEbC1NBqJi7uoyYZo1/I3EDCd9pZk9EN6ZDvKaO28M5+nIQGyYomzvkMpMHnfywpTzalhwr76/oAg==}
dev: false dev: false
/@types/hast/2.3.4:
resolution: {integrity: sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g==}
dependencies:
'@types/unist': 2.0.6
dev: false
/@types/hogan.js/3.0.1: /@types/hogan.js/3.0.1:
resolution: {integrity: sha512-D03i/2OY7kGyMq9wdQ7oD8roE49z/ZCZThe/nbahtvuqCNZY9T2MfedOWyeBdbEpY2W8Gnh/dyJLdFtUCOkYbg==} resolution: {integrity: sha512-D03i/2OY7kGyMq9wdQ7oD8roE49z/ZCZThe/nbahtvuqCNZY9T2MfedOWyeBdbEpY2W8Gnh/dyJLdFtUCOkYbg==}
dev: false dev: false
@ -5759,6 +5769,12 @@ packages:
resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==} resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
dev: false dev: false
/@types/mdast/3.0.10:
resolution: {integrity: sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA==}
dependencies:
'@types/unist': 2.0.6
dev: false
/@types/minimatch/3.0.5: /@types/minimatch/3.0.5:
resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==} resolution: {integrity: sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==}
dev: true dev: true
@ -5877,6 +5893,10 @@ packages:
resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==} resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==}
dev: true dev: true
/@types/unist/2.0.6:
resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
dev: false
/@types/uuid/8.3.4: /@types/uuid/8.3.4:
resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==}
dev: true dev: true
@ -6927,6 +6947,10 @@ packages:
- supports-color - supports-color
dev: true dev: true
/bail/2.0.2:
resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
dev: false
/balanced-match/1.0.2: /balanced-match/1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@ -7283,6 +7307,10 @@ packages:
tslib: 2.5.0 tslib: 2.5.0
dev: true dev: true
/character-entities/2.0.2:
resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
dev: false
/chardet/0.7.0: /chardet/0.7.0:
resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
@ -7480,6 +7508,10 @@ packages:
resolution: {integrity: sha512-+1dlx0aY5Jo1vHy/tSsIGpSkN4tS9rZSW8FIhG0JH/crs9wwweswIo/POr451r7bZww3hFbPAKnTpimzL/mm4Q==} resolution: {integrity: sha512-+1dlx0aY5Jo1vHy/tSsIGpSkN4tS9rZSW8FIhG0JH/crs9wwweswIo/POr451r7bZww3hFbPAKnTpimzL/mm4Q==}
dev: false dev: false
/comma-separated-tokens/2.0.3:
resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==}
dev: false
/commander/2.20.3: /commander/2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
dev: false dev: false
@ -7805,6 +7837,12 @@ packages:
/decimal.js/10.4.3: /decimal.js/10.4.3:
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
/decode-named-character-reference/1.0.2:
resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
dependencies:
character-entities: 2.0.2
dev: false
/decompress-response/6.0.0: /decompress-response/6.0.0:
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -7885,6 +7923,11 @@ packages:
engines: {node: '>= 0.6.0'} engines: {node: '>= 0.6.0'}
dev: true dev: true
/dequal/2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
dev: false
/detect-indent/6.1.0: /detect-indent/6.1.0:
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -10190,6 +10233,10 @@ packages:
dependencies: dependencies:
function-bind: 1.1.1 function-bind: 1.1.1
/hast-util-whitespace/2.0.1:
resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==}
dev: false
/he/1.2.0: /he/1.2.0:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true hasBin: true
@ -10445,6 +10492,10 @@ packages:
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
dev: false dev: false
/inline-style-parser/0.1.1:
resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==}
dev: false
/inline-style-prefixer/6.0.4: /inline-style-prefixer/6.0.4:
resolution: {integrity: sha512-FwXmZC2zbeeS7NzGjJ6pAiqRhXR0ugUShSNb6GApMl6da0/XGc4MOJsoWAywia52EEWbXNSy0pzkwz/+Y+swSg==} resolution: {integrity: sha512-FwXmZC2zbeeS7NzGjJ6pAiqRhXR0ugUShSNb6GApMl6da0/XGc4MOJsoWAywia52EEWbXNSy0pzkwz/+Y+swSg==}
dependencies: dependencies:
@ -10555,6 +10606,11 @@ packages:
call-bind: 1.0.2 call-bind: 1.0.2
has-tostringtag: 1.0.0 has-tostringtag: 1.0.0
/is-buffer/2.0.5:
resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==}
engines: {node: '>=4'}
dev: false
/is-callable/1.2.7: /is-callable/1.2.7:
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
@ -10652,6 +10708,11 @@ packages:
resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
/is-plain-obj/4.1.0:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
dev: false
/is-potential-custom-element-name/1.0.1: /is-potential-custom-element-name/1.0.1:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
@ -11504,6 +11565,52 @@ packages:
css-mediaquery: 0.1.2 css-mediaquery: 0.1.2
dev: false dev: false
/mdast-util-definitions/5.1.2:
resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==}
dependencies:
'@types/mdast': 3.0.10
'@types/unist': 2.0.6
unist-util-visit: 4.1.2
dev: false
/mdast-util-from-markdown/1.3.0:
resolution: {integrity: sha512-HN3W1gRIuN/ZW295c7zi7g9lVBllMgZE40RxCX37wrTPWXCWtpvOZdfnuK+1WNpvZje6XuJeI3Wnb4TJEUem+g==}
dependencies:
'@types/mdast': 3.0.10
'@types/unist': 2.0.6
decode-named-character-reference: 1.0.2
mdast-util-to-string: 3.1.1
micromark: 3.1.0
micromark-util-decode-numeric-character-reference: 1.0.0
micromark-util-decode-string: 1.0.2
micromark-util-normalize-identifier: 1.0.0
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
unist-util-stringify-position: 3.0.3
uvu: 0.5.6
transitivePeerDependencies:
- supports-color
dev: false
/mdast-util-to-hast/12.3.0:
resolution: {integrity: sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==}
dependencies:
'@types/hast': 2.3.4
'@types/mdast': 3.0.10
mdast-util-definitions: 5.1.2
micromark-util-sanitize-uri: 1.1.0
trim-lines: 3.0.1
unist-util-generated: 2.0.1
unist-util-position: 4.0.4
unist-util-visit: 4.1.2
dev: false
/mdast-util-to-string/3.1.1:
resolution: {integrity: sha512-tGvhT94e+cVnQt8JWE9/b3cUQZWS732TJxXHktvP+BYo62PpYD53Ls/6cC60rW21dW+txxiM4zMdc6abASvZKA==}
dependencies:
'@types/mdast': 3.0.10
dev: false
/mdn-data/2.0.14: /mdn-data/2.0.14:
resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
dev: false dev: false
@ -11573,6 +11680,182 @@ packages:
transliteration: 2.2.0 transliteration: 2.2.0
dev: false dev: false
/micromark-core-commonmark/1.0.6:
resolution: {integrity: sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==}
dependencies:
decode-named-character-reference: 1.0.2
micromark-factory-destination: 1.0.0
micromark-factory-label: 1.0.2
micromark-factory-space: 1.0.0
micromark-factory-title: 1.0.2
micromark-factory-whitespace: 1.0.0
micromark-util-character: 1.1.0
micromark-util-chunked: 1.0.0
micromark-util-classify-character: 1.0.0
micromark-util-html-tag-name: 1.1.0
micromark-util-normalize-identifier: 1.0.0
micromark-util-resolve-all: 1.0.0
micromark-util-subtokenize: 1.0.2
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
uvu: 0.5.6
dev: false
/micromark-factory-destination/1.0.0:
resolution: {integrity: sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==}
dependencies:
micromark-util-character: 1.1.0
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
dev: false
/micromark-factory-label/1.0.2:
resolution: {integrity: sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg==}
dependencies:
micromark-util-character: 1.1.0
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
uvu: 0.5.6
dev: false
/micromark-factory-space/1.0.0:
resolution: {integrity: sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew==}
dependencies:
micromark-util-character: 1.1.0
micromark-util-types: 1.0.2
dev: false
/micromark-factory-title/1.0.2:
resolution: {integrity: sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A==}
dependencies:
micromark-factory-space: 1.0.0
micromark-util-character: 1.1.0
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
uvu: 0.5.6
dev: false
/micromark-factory-whitespace/1.0.0:
resolution: {integrity: sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A==}
dependencies:
micromark-factory-space: 1.0.0
micromark-util-character: 1.1.0
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
dev: false
/micromark-util-character/1.1.0:
resolution: {integrity: sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg==}
dependencies:
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
dev: false
/micromark-util-chunked/1.0.0:
resolution: {integrity: sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g==}
dependencies:
micromark-util-symbol: 1.0.1
dev: false
/micromark-util-classify-character/1.0.0:
resolution: {integrity: sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA==}
dependencies:
micromark-util-character: 1.1.0
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
dev: false
/micromark-util-combine-extensions/1.0.0:
resolution: {integrity: sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA==}
dependencies:
micromark-util-chunked: 1.0.0
micromark-util-types: 1.0.2
dev: false
/micromark-util-decode-numeric-character-reference/1.0.0:
resolution: {integrity: sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w==}
dependencies:
micromark-util-symbol: 1.0.1
dev: false
/micromark-util-decode-string/1.0.2:
resolution: {integrity: sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q==}
dependencies:
decode-named-character-reference: 1.0.2
micromark-util-character: 1.1.0
micromark-util-decode-numeric-character-reference: 1.0.0
micromark-util-symbol: 1.0.1
dev: false
/micromark-util-encode/1.0.1:
resolution: {integrity: sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA==}
dev: false
/micromark-util-html-tag-name/1.1.0:
resolution: {integrity: sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA==}
dev: false
/micromark-util-normalize-identifier/1.0.0:
resolution: {integrity: sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg==}
dependencies:
micromark-util-symbol: 1.0.1
dev: false
/micromark-util-resolve-all/1.0.0:
resolution: {integrity: sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw==}
dependencies:
micromark-util-types: 1.0.2
dev: false
/micromark-util-sanitize-uri/1.1.0:
resolution: {integrity: sha512-RoxtuSCX6sUNtxhbmsEFQfWzs8VN7cTctmBPvYivo98xb/kDEoTCtJQX5wyzIYEmk/lvNFTat4hL8oW0KndFpg==}
dependencies:
micromark-util-character: 1.1.0
micromark-util-encode: 1.0.1
micromark-util-symbol: 1.0.1
dev: false
/micromark-util-subtokenize/1.0.2:
resolution: {integrity: sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA==}
dependencies:
micromark-util-chunked: 1.0.0
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
uvu: 0.5.6
dev: false
/micromark-util-symbol/1.0.1:
resolution: {integrity: sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ==}
dev: false
/micromark-util-types/1.0.2:
resolution: {integrity: sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w==}
dev: false
/micromark/3.1.0:
resolution: {integrity: sha512-6Mj0yHLdUZjHnOPgr5xfWIMqMWS12zDN6iws9SLuSz76W8jTtAv24MN4/CL7gJrl5vtxGInkkqDv/JIoRsQOvA==}
dependencies:
'@types/debug': 4.1.7
debug: 4.3.4
decode-named-character-reference: 1.0.2
micromark-core-commonmark: 1.0.6
micromark-factory-space: 1.0.0
micromark-util-character: 1.1.0
micromark-util-chunked: 1.0.0
micromark-util-combine-extensions: 1.0.0
micromark-util-decode-numeric-character-reference: 1.0.0
micromark-util-encode: 1.0.1
micromark-util-normalize-identifier: 1.0.0
micromark-util-resolve-all: 1.0.0
micromark-util-sanitize-uri: 1.1.0
micromark-util-subtokenize: 1.0.2
micromark-util-symbol: 1.0.1
micromark-util-types: 1.0.2
uvu: 0.5.6
transitivePeerDependencies:
- supports-color
dev: false
/micromatch/4.0.5: /micromatch/4.0.5:
resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
engines: {node: '>=8.6'} engines: {node: '>=8.6'}
@ -12053,7 +12336,6 @@ packages:
/mri/1.2.0: /mri/1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'} engines: {node: '>=4'}
dev: true
/ms/2.1.2: /ms/2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
@ -13233,6 +13515,10 @@ packages:
resolution: {integrity: sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==} resolution: {integrity: sha512-IJUkICM5dP5znhCckHSv30Q4b5/JA5enCtkRHYaOVOAocnH/1BQEYTC5NMfT3AVl/iXKdr3aqQbQn9DxyWknwA==}
dev: false dev: false
/property-information/6.2.0:
resolution: {integrity: sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg==}
dev: false
/proto-list/1.2.4: /proto-list/1.2.4:
resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==}
dev: false dev: false
@ -13490,6 +13776,33 @@ packages:
resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
dev: false dev: false
/react-markdown/8.0.5_3stiutgnnbnfnf3uowm5cip22i:
resolution: {integrity: sha512-jGJolWWmOWAvzf+xMdB9zwStViODyyFQhNB/bwCerbBKmrTmgmA599CGiOlP58OId1IMoIRsA8UdI1Lod4zb5A==}
peerDependencies:
'@types/react': '>=16'
react: '>=16'
dependencies:
'@types/hast': 2.3.4
'@types/prop-types': 15.7.5
'@types/react': 18.0.27
'@types/unist': 2.0.6
comma-separated-tokens: 2.0.3
hast-util-whitespace: 2.0.1
prop-types: 15.8.1
property-information: 6.2.0
react: 18.2.0
react-is: 18.2.0
remark-parse: 10.0.1
remark-rehype: 10.1.0
space-separated-tokens: 2.0.2
style-to-object: 0.4.1
unified: 10.1.2
unist-util-visit: 4.1.2
vfile: 5.3.7
transitivePeerDependencies:
- supports-color
dev: false
/react-modal/3.16.1_biqbaboplfbrettd7655fr4n2y: /react-modal/3.16.1_biqbaboplfbrettd7655fr4n2y:
resolution: {integrity: sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==} resolution: {integrity: sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -13797,6 +14110,25 @@ packages:
- encoding - encoding
dev: true dev: true
/remark-parse/10.0.1:
resolution: {integrity: sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==}
dependencies:
'@types/mdast': 3.0.10
mdast-util-from-markdown: 1.3.0
unified: 10.1.2
transitivePeerDependencies:
- supports-color
dev: false
/remark-rehype/10.1.0:
resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==}
dependencies:
'@types/hast': 2.3.4
'@types/mdast': 3.0.10
mdast-util-to-hast: 12.3.0
unified: 10.1.2
dev: false
/remedial/1.0.8: /remedial/1.0.8:
resolution: {integrity: sha512-/62tYiOe6DzS5BqVsNpH/nkGlX45C/Sp6V+NtiN6JQNS1Viay7cWkazmRkrQrdFj2eshDe96SIQNIoMxqhzBOg==} resolution: {integrity: sha512-/62tYiOe6DzS5BqVsNpH/nkGlX45C/Sp6V+NtiN6JQNS1Viay7cWkazmRkrQrdFj2eshDe96SIQNIoMxqhzBOg==}
dev: true dev: true
@ -13986,6 +14318,13 @@ packages:
dependencies: dependencies:
tslib: 2.5.0 tslib: 2.5.0
/sade/1.8.1:
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
engines: {node: '>=6'}
dependencies:
mri: 1.2.0
dev: false
/safe-buffer/5.1.2: /safe-buffer/5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
dev: false dev: false
@ -14248,6 +14587,10 @@ packages:
deprecated: Please use @jridgewell/sourcemap-codec instead deprecated: Please use @jridgewell/sourcemap-codec instead
dev: false dev: false
/space-separated-tokens/2.0.2:
resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
dev: false
/spawndamnit/2.0.0: /spawndamnit/2.0.0:
resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==} resolution: {integrity: sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==}
dependencies: dependencies:
@ -14504,6 +14847,12 @@ packages:
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
dev: false dev: false
/style-to-object/0.4.1:
resolution: {integrity: sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw==}
dependencies:
inline-style-parser: 0.1.1
dev: false
/styled-jsx/5.1.1: /styled-jsx/5.1.1:
resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
@ -14744,10 +15093,18 @@ packages:
resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==} resolution: {integrity: sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==}
dev: false dev: false
/trim-lines/3.0.1:
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
dev: false
/trim-newlines/3.0.1: /trim-newlines/3.0.1:
resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==}
engines: {node: '>=8'} engines: {node: '>=8'}
/trough/2.1.0:
resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==}
dev: false
/ts-easing/0.2.0: /ts-easing/0.2.0:
resolution: {integrity: sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==} resolution: {integrity: sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==}
dev: false dev: false
@ -15019,6 +15376,55 @@ packages:
tiny-inflate: 1.0.3 tiny-inflate: 1.0.3
dev: false dev: false
/unified/10.1.2:
resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==}
dependencies:
'@types/unist': 2.0.6
bail: 2.0.2
extend: 3.0.2
is-buffer: 2.0.5
is-plain-obj: 4.1.0
trough: 2.1.0
vfile: 5.3.7
dev: false
/unist-util-generated/2.0.1:
resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==}
dev: false
/unist-util-is/5.2.1:
resolution: {integrity: sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==}
dependencies:
'@types/unist': 2.0.6
dev: false
/unist-util-position/4.0.4:
resolution: {integrity: sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==}
dependencies:
'@types/unist': 2.0.6
dev: false
/unist-util-stringify-position/3.0.3:
resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==}
dependencies:
'@types/unist': 2.0.6
dev: false
/unist-util-visit-parents/5.1.3:
resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==}
dependencies:
'@types/unist': 2.0.6
unist-util-is: 5.2.1
dev: false
/unist-util-visit/4.1.2:
resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==}
dependencies:
'@types/unist': 2.0.6
unist-util-is: 5.2.1
unist-util-visit-parents: 5.1.3
dev: false
/universalify/0.1.2: /universalify/0.1.2:
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
engines: {node: '>= 4.0.0'} engines: {node: '>= 4.0.0'}
@ -15198,6 +15604,17 @@ packages:
hasBin: true hasBin: true
dev: false dev: false
/uvu/0.5.6:
resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==}
engines: {node: '>=8'}
hasBin: true
dependencies:
dequal: 2.0.3
diff: 5.1.0
kleur: 4.1.5
sade: 1.8.1
dev: false
/v8-compile-cache/2.3.0: /v8-compile-cache/2.3.0:
resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
dev: true dev: true
@ -15236,6 +15653,22 @@ packages:
extsprintf: 1.3.0 extsprintf: 1.3.0
dev: false dev: false
/vfile-message/3.1.4:
resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==}
dependencies:
'@types/unist': 2.0.6
unist-util-stringify-position: 3.0.3
dev: false
/vfile/5.3.7:
resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==}
dependencies:
'@types/unist': 2.0.6
is-buffer: 2.0.5
unist-util-stringify-position: 3.0.3
vfile-message: 3.1.4
dev: false
/vite-node/0.27.3_@types+node@18.13.0: /vite-node/0.27.3_@types+node@18.13.0:
resolution: {integrity: sha512-eyJYOO64o5HIp8poc4bJX+ZNBwMZeI3f6/JdiUmJgW02Mt7LnoCtDMRVmLaY9S05SIsjGe339ZK4uo2wQ+bF9g==} resolution: {integrity: sha512-eyJYOO64o5HIp8poc4bJX+ZNBwMZeI3f6/JdiUmJgW02Mt7LnoCtDMRVmLaY9S05SIsjGe339ZK4uo2wQ+bF9g==}
engines: {node: '>=v14.16.0'} engines: {node: '>=v14.16.0'}