Add Payload CMS (#905)
This commit is contained in:
parent
45ed9fb444
commit
6f1c5c9436
23 changed files with 723 additions and 69 deletions
5
.changeset/light-bobcats-prove.md
Normal file
5
.changeset/light-bobcats-prove.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-app-cms-v2": patch
|
||||
---
|
||||
|
||||
Fix styling of modal in the dark mode
|
5
.changeset/strong-pugs-cover.md
Normal file
5
.changeset/strong-pugs-cover.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"saleor-app-cms-v2": minor
|
||||
---
|
||||
|
||||
Added Payload CMS support.
|
|
@ -35,6 +35,7 @@
|
|||
"p-ratelimit": "1.0.1",
|
||||
"pino": "^8.14.1",
|
||||
"pino-pretty": "^10.0.0",
|
||||
"qs": "6.11.2",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-error-boundary": "4.0.10",
|
||||
|
@ -56,6 +57,7 @@
|
|||
"@graphql-typed-document-node/core": "3.2.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/react": "18.2.5",
|
||||
"@types/react-dom": "18.2.5",
|
||||
"eslint": "8.46.0",
|
||||
|
|
|
@ -2,11 +2,11 @@ import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
|||
import { EncryptedMetadataManagerFactory } from "@saleor/apps-shared";
|
||||
import { Client } from "urql";
|
||||
|
||||
const metadataManagerFactory = new EncryptedMetadataManagerFactory(process.env.SECRET_KEY!);
|
||||
|
||||
export const createSettingsManager = (
|
||||
client: Pick<Client, "query" | "mutation">,
|
||||
appId: string,
|
||||
): SettingsManager => {
|
||||
const metadataManagerFactory = new EncryptedMetadataManagerFactory(process.env.SECRET_KEY!);
|
||||
|
||||
return metadataManagerFactory.create(client, appId);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import { PayloadCMS } from "@/modules/providers/payloadcms/payloadcms";
|
||||
import { z } from "zod";
|
||||
import { SaleorProviderFieldsMappingSchema } from "./saleor-provider-fields-mapping.schema";
|
||||
|
||||
const InputSchema = z.object({
|
||||
type: z.literal(PayloadCMS.type),
|
||||
authToken: z.string(),
|
||||
configName: z.string().min(1),
|
||||
collectionName: z.string().min(1),
|
||||
productVariantFieldsMapping: SaleorProviderFieldsMappingSchema,
|
||||
payloadApiUrl: z.string().url(),
|
||||
authenticatedUserSlug: z.string(),
|
||||
});
|
||||
|
||||
const FullSchema = InputSchema.extend({
|
||||
id: z.string(),
|
||||
});
|
||||
|
||||
export namespace PayloadCmsProviderConfig {
|
||||
export type InputShape = z.infer<typeof InputSchema>;
|
||||
export type FullShape = z.infer<typeof FullSchema>;
|
||||
|
||||
export const Schema = {
|
||||
Input: InputSchema,
|
||||
Full: FullSchema,
|
||||
};
|
||||
}
|
|
@ -4,6 +4,7 @@ import { ContentfulProviderConfig } from "./contentful-provider.schema";
|
|||
import { BuilderIoProviderConfig } from "./builder-provider.schema";
|
||||
import { StrapiProviderConfig } from "./strapi-provider.schema";
|
||||
import { DatocmsProviderConfig } from "./datocms-provider.schema";
|
||||
import { PayloadCmsProviderConfig } from "./payloadcms-provider.schema";
|
||||
|
||||
export namespace ProvidersConfig {
|
||||
const AnyFull = z.union([
|
||||
|
@ -14,6 +15,7 @@ export namespace ProvidersConfig {
|
|||
DatocmsProviderConfig.Schema.Full,
|
||||
StrapiProviderConfig.Schema.Full,
|
||||
BuilderIoProviderConfig.Schema.Full,
|
||||
PayloadCmsProviderConfig.Schema.Full,
|
||||
]);
|
||||
|
||||
export const Schema = {
|
||||
|
@ -23,6 +25,7 @@ export namespace ProvidersConfig {
|
|||
DatocmsProviderConfig.Schema.Input,
|
||||
StrapiProviderConfig.Schema.Input,
|
||||
BuilderIoProviderConfig.Schema.Input,
|
||||
PayloadCmsProviderConfig.Schema.Input,
|
||||
]),
|
||||
AnyFullList: z.array(AnyFull),
|
||||
};
|
||||
|
|
|
@ -104,7 +104,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
|||
padding={2}
|
||||
>
|
||||
<Text variant="caption">Saleor Field</Text>
|
||||
<Text variant="caption">Contentful field</Text>
|
||||
<Text variant="caption">Builder.io field</Text>
|
||||
</Box>
|
||||
{SaleorProviderFieldsMappingKeys.map((saleorField) => (
|
||||
// todo extract this table to component
|
||||
|
@ -190,7 +190,7 @@ const EditFormVariant = (props: { configId: string }) => {
|
|||
},
|
||||
{
|
||||
enabled: !!props.configId,
|
||||
}
|
||||
},
|
||||
);
|
||||
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
||||
onSuccess() {
|
||||
|
|
|
@ -53,7 +53,7 @@ const PureForm = ({
|
|||
});
|
||||
notifyError(
|
||||
"Error",
|
||||
"Could not fetch content types from Contentful. Please check your credentials."
|
||||
"Could not fetch content types from Contentful. Please check your credentials.",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -76,7 +76,7 @@ const PureForm = ({
|
|||
});
|
||||
notifyError(
|
||||
"Error",
|
||||
"Could not fetch environments from Contentful. Please check your credentials."
|
||||
"Could not fetch environments from Contentful. Please check your credentials.",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -197,7 +197,7 @@ const PureForm = ({
|
|||
newTab
|
||||
size="small"
|
||||
href={`https://app.contentful.com/spaces/${getValues(
|
||||
"spaceId"
|
||||
"spaceId",
|
||||
)}/settings/environments`}
|
||||
>
|
||||
here
|
||||
|
@ -247,7 +247,7 @@ const PureForm = ({
|
|||
|
||||
<Box marginTop={4}>
|
||||
<Text as="p" variant="heading" size="small">
|
||||
Map fields from Saleor to your contentful schema.
|
||||
Map fields from Saleor to your Contentful schema.
|
||||
</Text>
|
||||
<Text as="p" marginTop={2} marginBottom={4}>
|
||||
All fields should be type of <Text variant="bodyStrong">Text</Text>. Channels should
|
||||
|
@ -357,7 +357,7 @@ const EditVariant = ({ configId }: { configId: string }) => {
|
|||
},
|
||||
{
|
||||
enabled: !!configId,
|
||||
}
|
||||
},
|
||||
);
|
||||
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
||||
onSuccess() {
|
||||
|
|
|
@ -43,7 +43,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
|||
|
||||
notifyError(
|
||||
"Error",
|
||||
"Could not fetch content types from DatoCMS. Please check your credentials."
|
||||
"Could not fetch content types from DatoCMS. Please check your credentials.",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -61,7 +61,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
|||
|
||||
notifyError(
|
||||
"Error",
|
||||
"Could not fetch content types from DatoCMS. Please check your credentials."
|
||||
"Could not fetch content types from DatoCMS. Please check your credentials.",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
@ -161,7 +161,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
|||
{fieldsData && (
|
||||
<React.Fragment>
|
||||
<Text as="p" variant="heading" size="small">
|
||||
Map fields from Saleor to your contentful schema.
|
||||
Map fields from Saleor to your DatoCMS schema.
|
||||
</Text>
|
||||
<Text as="p" marginTop={2} marginBottom={4}>
|
||||
All fields should be type of <Text variant="bodyStrong">Text</Text>. Channels should
|
||||
|
@ -177,7 +177,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
|||
padding={2}
|
||||
>
|
||||
<Text variant="caption">Saleor Field</Text>
|
||||
<Text variant="caption">Contentful field</Text>
|
||||
<Text variant="caption">DatoCMS field</Text>
|
||||
</Box>
|
||||
{SaleorProviderFieldsMappingKeys.map((saleorField) => (
|
||||
// todo extract this table to component
|
||||
|
@ -271,7 +271,7 @@ const EditFormVariant = (props: { configId: string }) => {
|
|||
},
|
||||
{
|
||||
enabled: !!props.configId,
|
||||
}
|
||||
},
|
||||
);
|
||||
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
||||
onSuccess() {
|
||||
|
|
BIN
apps/cms-v2/src/modules/providers/payloadcms/logo.png
Normal file
BIN
apps/cms-v2/src/modules/providers/payloadcms/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.4 KiB |
|
@ -0,0 +1,51 @@
|
|||
import { BulkImportProductFragment } from "../../../../generated/graphql";
|
||||
import { BulkSyncProcessor, BulkSyncProcessorHooks } from "../../bulk-sync/bulk-sync-processor";
|
||||
|
||||
import { PayloadCmsProviderConfig } from "@/modules/configuration/schemas/payloadcms-provider.schema";
|
||||
import { PayloadCMSClient } from "./payloadcms-client";
|
||||
|
||||
// todo CORS or proxy
|
||||
export class PayloadCmsBulkSyncProcessor implements BulkSyncProcessor {
|
||||
constructor(private config: PayloadCmsProviderConfig.FullShape) {}
|
||||
|
||||
async uploadProducts(
|
||||
products: BulkImportProductFragment[],
|
||||
hooks: BulkSyncProcessorHooks,
|
||||
): Promise<void> {
|
||||
const client = new PayloadCMSClient();
|
||||
|
||||
products.flatMap(
|
||||
(product) =>
|
||||
product.variants?.map((variant) => {
|
||||
if (hooks.onUploadStart) {
|
||||
hooks.onUploadStart({ variantId: variant.id });
|
||||
}
|
||||
|
||||
return client
|
||||
.upsertProductVariant({
|
||||
configuration: this.config,
|
||||
variant: {
|
||||
id: variant.id,
|
||||
name: variant.name,
|
||||
channelListings: variant.channelListings,
|
||||
product: {
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
slug: product.slug,
|
||||
},
|
||||
},
|
||||
})
|
||||
.then((r) => {
|
||||
if (hooks.onUploadSuccess) {
|
||||
hooks.onUploadSuccess({ variantId: variant.id });
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
if (hooks.onUploadError) {
|
||||
hooks.onUploadError({ variantId: variant.id, error: e });
|
||||
}
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
import { createLogger } from "@saleor/apps-shared";
|
||||
import { WebhookProductVariantFragment } from "../../../../generated/graphql";
|
||||
|
||||
import { PayloadCmsProviderConfig } from "@/modules/configuration/schemas/payloadcms-provider.schema";
|
||||
import { FieldsMapper } from "../fields-mapper";
|
||||
|
||||
import qs from "qs";
|
||||
import { z } from "zod";
|
||||
|
||||
type Context = {
|
||||
configuration: PayloadCmsProviderConfig.FullShape;
|
||||
variant: WebhookProductVariantFragment;
|
||||
};
|
||||
|
||||
/**
|
||||
* Client uses REST API with built-in query language
|
||||
* https://payloadcms.com/docs/queries/overview#rest-queries
|
||||
*/
|
||||
export class PayloadCMSClient {
|
||||
private logger = createLogger({ name: "PayloadCMSClient" });
|
||||
|
||||
private mapVariantToPayloadFields({ configuration, variant }: Context) {
|
||||
const fields = FieldsMapper.mapProductVariantToConfigurationFields({
|
||||
variant,
|
||||
configMapping: configuration.productVariantFieldsMapping,
|
||||
});
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
private constructCollectionUrl(config: PayloadCmsProviderConfig.FullShape) {
|
||||
return `${config.payloadApiUrl}/${config.collectionName}`;
|
||||
}
|
||||
|
||||
getItemsBySaleorVariantId(context: Context) {
|
||||
const queryString = qs.stringify(
|
||||
{
|
||||
where: {
|
||||
[context.configuration.productVariantFieldsMapping.variantId]: {
|
||||
equals: context.variant.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
addQueryPrefix: true,
|
||||
},
|
||||
);
|
||||
|
||||
return fetch(`${this.constructCollectionUrl(context.configuration)}${queryString}`, {
|
||||
headers: this.getHeaders(context),
|
||||
}).then((r) => r.json());
|
||||
}
|
||||
|
||||
async deleteProductVariant(context: Context) {
|
||||
const queryString = qs.stringify(
|
||||
{
|
||||
where: {
|
||||
[context.configuration.productVariantFieldsMapping.variantId]: {
|
||||
equals: context.variant.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
addQueryPrefix: true,
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
this.constructCollectionUrl(context.configuration) + queryString,
|
||||
{
|
||||
method: "DELETE",
|
||||
headers: this.getHeaders(context),
|
||||
},
|
||||
);
|
||||
|
||||
if (response.status >= 400) {
|
||||
throw new Error("Error while deleting product variant");
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(e);
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private getHeaders(context: Context) {
|
||||
const headers = new Headers({
|
||||
"Content-Type": "application/json",
|
||||
});
|
||||
|
||||
/**
|
||||
* https://payloadcms.com/docs/authentication/config#api-keys
|
||||
*/
|
||||
if (
|
||||
context.configuration.authToken.length > 0 &&
|
||||
context.configuration.authenticatedUserSlug.length > 0
|
||||
) {
|
||||
headers.append(
|
||||
"Authorization",
|
||||
`${context.configuration.authenticatedUserSlug} API-Key ${context.configuration.authToken}`,
|
||||
);
|
||||
}
|
||||
|
||||
return headers;
|
||||
}
|
||||
|
||||
uploadProductVariant(context: Context) {
|
||||
this.logger.debug("Trying to upload product variant");
|
||||
|
||||
return fetch(this.constructCollectionUrl(context.configuration), {
|
||||
method: "POST",
|
||||
body: JSON.stringify(this.mapVariantToPayloadFields(context)),
|
||||
headers: this.getHeaders(context),
|
||||
})
|
||||
.then((r) => {
|
||||
if (r.status >= 400) {
|
||||
throw new Error(`Error while uploading product variant: ${r.statusText}`);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
this.logger.error(e);
|
||||
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
|
||||
async updateProductVariant({ configuration, variant }: Context) {
|
||||
this.logger.debug("Trying to update product variant");
|
||||
|
||||
const queryString = qs.stringify(
|
||||
{
|
||||
where: {
|
||||
[configuration.productVariantFieldsMapping.variantId]: {
|
||||
equals: variant.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
addQueryPrefix: true,
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await fetch(this.constructCollectionUrl(configuration) + queryString, {
|
||||
method: "PATCH",
|
||||
body: JSON.stringify(this.mapVariantToPayloadFields({ configuration, variant })),
|
||||
headers: this.getHeaders({ configuration, variant }),
|
||||
});
|
||||
|
||||
if (response.status >= 400) {
|
||||
throw new Error("Error while updating product variant");
|
||||
}
|
||||
} catch (e) {
|
||||
this.logger.error(e);
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async upsertProductVariant(context: Context) {
|
||||
this.logger.debug("Trying to upsert product variant");
|
||||
|
||||
try {
|
||||
await this.uploadProductVariant(context);
|
||||
} catch (e) {
|
||||
this.logger.debug("Failed to upload, will try to update");
|
||||
|
||||
await this.updateProductVariant(context);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,263 @@
|
|||
import { SaleorProviderFieldsMappingKeys } from "@/modules/configuration";
|
||||
import { PayloadCmsProviderConfig } from "@/modules/configuration/schemas/payloadcms-provider.schema";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useDashboardNotification } from "@saleor/apps-shared";
|
||||
import { Box, Button, Text } from "@saleor/macaw-ui/next";
|
||||
import { Input, Select } from "@saleor/react-hook-form-macaw";
|
||||
import { useRouter } from "next/router";
|
||||
import React from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { printSaleorProductFields } from "../../configuration/print-saleor-product-fields";
|
||||
import { trpcClient } from "../../trpc/trpc-client";
|
||||
import { ButtonsBox } from "../../ui/buttons-box";
|
||||
import { TextLink } from "@saleor/apps-ui";
|
||||
|
||||
type FormShape = Omit<PayloadCmsProviderConfig.InputShape, "type">;
|
||||
|
||||
type PureFormProps = {
|
||||
defaultValues: FormShape;
|
||||
onSubmit(values: FormShape): void;
|
||||
onDelete?(): void;
|
||||
};
|
||||
|
||||
/*
|
||||
* todo react on token change, refresh mutation
|
||||
*/
|
||||
const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
||||
const { notifyError } = useDashboardNotification();
|
||||
|
||||
const { control, getValues, setValue, watch, handleSubmit, clearErrors, setError } = useForm({
|
||||
defaultValues: defaultValues,
|
||||
resolver: zodResolver(PayloadCmsProviderConfig.Schema.Input.omit({ type: true })),
|
||||
});
|
||||
|
||||
return (
|
||||
<Box
|
||||
as="form"
|
||||
display={"grid"}
|
||||
gap={4}
|
||||
onSubmit={handleSubmit((vals) => {
|
||||
onSubmit(vals);
|
||||
})}
|
||||
>
|
||||
<Input
|
||||
required
|
||||
control={control}
|
||||
name="configName"
|
||||
label="Configuration name"
|
||||
helperText="Meaningful name that will help you understand it later. E.g. 'staging' or 'prod' "
|
||||
/>
|
||||
<Box display={"grid"} gap={4} marginY={4}>
|
||||
<Text variant="heading">Provide connection details</Text>
|
||||
<Input
|
||||
required
|
||||
control={control}
|
||||
name="payloadApiUrl"
|
||||
type="url"
|
||||
label="API url"
|
||||
helperText="URL where Payload API is available. By default ends with /api"
|
||||
/>
|
||||
|
||||
<Box
|
||||
backgroundColor="surfaceNeutralHighlight"
|
||||
borderColor="neutralHighlight"
|
||||
borderWidth={1}
|
||||
borderStyle={"solid"}
|
||||
padding={4}
|
||||
borderRadius={4}
|
||||
>
|
||||
<Text variant="heading" as="h1" marginBottom={4}>
|
||||
Authorization
|
||||
</Text>
|
||||
<Text marginBottom={2} as="p">
|
||||
Payload can be configured to have open operations (not recommended) or to require an API
|
||||
key. Key can be generated per user. To authenticate, you need to provide both user slug
|
||||
and the key itself.{" "}
|
||||
</Text>
|
||||
<TextLink
|
||||
marginBottom={2}
|
||||
display="block"
|
||||
newTab
|
||||
href="https://payloadcms.com/docs/authentication/config"
|
||||
>
|
||||
Read more in Payload docs
|
||||
</TextLink>
|
||||
<Text as="p" marginBottom={4}>
|
||||
If your API is open (e.g. for development purposes) leave both fields empty.
|
||||
</Text>
|
||||
|
||||
<Input
|
||||
control={control}
|
||||
name="authenticatedUserSlug"
|
||||
label="Authenticated user slug"
|
||||
placeholder="e.g. apps"
|
||||
/>
|
||||
<Input control={control} name="authToken" type="password" label="User API Key" />
|
||||
</Box>
|
||||
</Box>
|
||||
<Box display={"grid"} gap={4} marginY={4}>
|
||||
<Text variant="heading">Configure fields mapping</Text>
|
||||
<Input
|
||||
label="Collection Slug"
|
||||
name="collectionName"
|
||||
control={control}
|
||||
helperText="Slug of your collection in Payload, e.g. 'saleorVariants'"
|
||||
/>
|
||||
|
||||
<React.Fragment>
|
||||
<Text as="p" variant="heading" size="small">
|
||||
Map fields from Saleor to your Payload schema.
|
||||
</Text>
|
||||
<Text as="p" marginTop={2} marginBottom={4}>
|
||||
All fields should be type of <Text variant="bodyStrong">Text</Text>. Channels should be
|
||||
type of <Text variant="bodyStrong">JSON</Text>.
|
||||
</Text>
|
||||
<Box
|
||||
marginBottom={4}
|
||||
display="grid"
|
||||
__gridTemplateColumns={"50% 50%"}
|
||||
borderBottomWidth={1}
|
||||
borderBottomStyle="solid"
|
||||
borderColor="neutralHighlight"
|
||||
padding={2}
|
||||
>
|
||||
<Text variant="caption">Saleor Field</Text>
|
||||
<Text variant="caption">Payload field</Text>
|
||||
</Box>
|
||||
{SaleorProviderFieldsMappingKeys.map((saleorField) => (
|
||||
// todo extract this table to component
|
||||
<Box
|
||||
display="grid"
|
||||
__gridTemplateColumns={"50% 50%"}
|
||||
padding={2}
|
||||
key={saleorField}
|
||||
alignItems="center"
|
||||
>
|
||||
<Box>
|
||||
<Text as="p" variant="bodyStrong">
|
||||
{printSaleorProductFields(saleorField)}
|
||||
</Text>
|
||||
<Text variant="caption">
|
||||
{saleorField === "channels" ? "JSON field" : "Text field"}
|
||||
</Text>
|
||||
</Box>
|
||||
<Input
|
||||
size="small"
|
||||
control={control}
|
||||
name={`productVariantFieldsMapping.${saleorField}`}
|
||||
label="CMS Field"
|
||||
/>
|
||||
</Box>
|
||||
))}
|
||||
</React.Fragment>
|
||||
</Box>
|
||||
|
||||
<ButtonsBox>
|
||||
{onDelete && (
|
||||
<Button onClick={onDelete} variant="tertiary">
|
||||
Delete
|
||||
</Button>
|
||||
)}
|
||||
<Button type="submit">Save</Button>
|
||||
</ButtonsBox>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const AddFormVariant = () => {
|
||||
const { push } = useRouter();
|
||||
const { notifySuccess } = useDashboardNotification();
|
||||
|
||||
const { mutate } = trpcClient.providersConfigs.addOne.useMutation({
|
||||
onSuccess() {
|
||||
notifySuccess("Success", "Added new configuration");
|
||||
push("/configuration");
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<PureForm
|
||||
onSubmit={(values) => {
|
||||
mutate({
|
||||
...values,
|
||||
type: "payloadcms",
|
||||
});
|
||||
}}
|
||||
defaultValues={{
|
||||
payloadApiUrl: "",
|
||||
authToken: "",
|
||||
configName: "",
|
||||
collectionName: "",
|
||||
authenticatedUserSlug: "",
|
||||
productVariantFieldsMapping: {
|
||||
channels: "",
|
||||
variantName: "",
|
||||
productId: "",
|
||||
productName: "",
|
||||
productSlug: "",
|
||||
variantId: "",
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const EditFormVariant = (props: { configId: string }) => {
|
||||
const { push } = useRouter();
|
||||
const { notifySuccess } = useDashboardNotification();
|
||||
|
||||
const { data } = trpcClient.providersConfigs.getOne.useQuery(
|
||||
{
|
||||
id: props.configId,
|
||||
},
|
||||
{
|
||||
enabled: !!props.configId,
|
||||
},
|
||||
);
|
||||
|
||||
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
||||
onSuccess() {
|
||||
notifySuccess("Success", "Updated configuration");
|
||||
push("/configuration");
|
||||
},
|
||||
});
|
||||
|
||||
const { mutate: deleteProvider } = trpcClient.providersConfigs.deleteOne.useMutation({
|
||||
onSuccess() {
|
||||
notifySuccess("Success", "Removed configuration");
|
||||
push("/configuration");
|
||||
},
|
||||
});
|
||||
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data.type !== "payloadcms") {
|
||||
throw new Error("Trying to fill Payload CMS form with non Payload CMS data");
|
||||
}
|
||||
|
||||
return (
|
||||
<PureForm
|
||||
onDelete={() => {
|
||||
deleteProvider({
|
||||
id: props.configId,
|
||||
});
|
||||
}}
|
||||
onSubmit={(values) => {
|
||||
mutate({
|
||||
...values,
|
||||
type: "payloadcms",
|
||||
id: props.configId,
|
||||
});
|
||||
}}
|
||||
defaultValues={data}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const PayloadCMSConfigForm = {
|
||||
PureVariant: PureForm,
|
||||
AddVariant: AddFormVariant,
|
||||
EditVariant: EditFormVariant,
|
||||
};
|
|
@ -0,0 +1,69 @@
|
|||
import {
|
||||
WebhookProductFragment,
|
||||
WebhookProductVariantFragment,
|
||||
} from "../../../../generated/graphql";
|
||||
|
||||
import { PayloadCmsProviderConfig } from "@/modules/configuration/schemas/payloadcms-provider.schema";
|
||||
import { createLogger } from "@saleor/apps-shared";
|
||||
import { ProductWebhooksProcessor } from "../../webhooks-operations/product-webhooks-processor";
|
||||
import { PayloadCMSClient } from "./payloadcms-client";
|
||||
|
||||
/*
|
||||
* todo error handling
|
||||
*/
|
||||
export class PayloadCmsWebhooksProcessor implements ProductWebhooksProcessor {
|
||||
private client = new PayloadCMSClient();
|
||||
|
||||
private logger = createLogger({ name: "PayloadCmsWebhooksProcessor" });
|
||||
|
||||
constructor(private providerConfig: PayloadCmsProviderConfig.FullShape) {}
|
||||
|
||||
async onProductVariantUpdated(productVariant: WebhookProductVariantFragment): Promise<void> {
|
||||
this.logger.trace("onProductVariantUpdated called");
|
||||
|
||||
await this.client.upsertProductVariant({
|
||||
configuration: this.providerConfig,
|
||||
variant: productVariant,
|
||||
});
|
||||
}
|
||||
|
||||
async onProductVariantCreated(productVariant: WebhookProductVariantFragment): Promise<void> {
|
||||
this.logger.trace("onProductVariantCreated called");
|
||||
|
||||
await this.client.uploadProductVariant({
|
||||
configuration: this.providerConfig,
|
||||
variant: productVariant,
|
||||
});
|
||||
}
|
||||
async onProductVariantDeleted(productVariant: WebhookProductVariantFragment): Promise<void> {
|
||||
this.logger.trace("onProductVariantDeleted called");
|
||||
|
||||
await this.client.deleteProductVariant({
|
||||
configuration: this.providerConfig,
|
||||
variant: productVariant,
|
||||
});
|
||||
}
|
||||
|
||||
async onProductUpdated(product: WebhookProductFragment): Promise<void> {
|
||||
this.logger.trace("onProductUpdated called");
|
||||
|
||||
const client = new PayloadCMSClient();
|
||||
|
||||
await Promise.all(
|
||||
(product.variants ?? []).map((variant) => {
|
||||
return client.upsertProductVariant({
|
||||
configuration: this.providerConfig,
|
||||
variant: {
|
||||
id: variant.id,
|
||||
name: variant.name,
|
||||
product: {
|
||||
id: product.id,
|
||||
name: product.name,
|
||||
slug: product.slug,
|
||||
},
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
29
apps/cms-v2/src/modules/providers/payloadcms/payloadcms.tsx
Normal file
29
apps/cms-v2/src/modules/providers/payloadcms/payloadcms.tsx
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { Box, Text } from "@saleor/macaw-ui/next";
|
||||
|
||||
import { CMSProviderMeta } from "../cms-provider-meta";
|
||||
import logo from "./logo.png";
|
||||
import { TextLink } from "@saleor/apps-ui";
|
||||
|
||||
export const PayloadCMS = {
|
||||
formSideInfo: (
|
||||
<Box>
|
||||
<Text as="p" marginBottom={2}>
|
||||
Configure the Payload CMS integration by providing required information.
|
||||
</Text>
|
||||
<Text as="p" marginBottom={2}>
|
||||
Fields are not validated - ensure you enter correct values.
|
||||
</Text>
|
||||
<Text as="p" marginBottom={2}>
|
||||
Consult{" "}
|
||||
<TextLink newTab href="https://docs.saleor.io/docs/3.x/developer/app-store/apps/cms">
|
||||
docs
|
||||
</TextLink>{" "}
|
||||
for more information how to set up Payload CMS.
|
||||
</Text>
|
||||
</Box>
|
||||
),
|
||||
type: "payloadcms" as const,
|
||||
logoUrl: logo.src,
|
||||
displayName: "Payload",
|
||||
description: "Open source, typescript first headless CMS. GraphQL included.",
|
||||
} satisfies CMSProviderMeta;
|
|
@ -1,12 +1,24 @@
|
|||
import { BuilderIo } from "./builder.io/builder-io";
|
||||
import { Contentful } from "./contentful/contentful";
|
||||
import { Datocms } from "./datocms/datocms";
|
||||
import { PayloadCMS } from "./payloadcms/payloadcms";
|
||||
import { Strapi } from "./strapi/strapi";
|
||||
|
||||
export type CMS = typeof Contentful | typeof Datocms | typeof Strapi | typeof BuilderIo;
|
||||
export type CMS =
|
||||
| typeof Contentful
|
||||
| typeof Datocms
|
||||
| typeof Strapi
|
||||
| typeof BuilderIo
|
||||
| typeof PayloadCMS;
|
||||
|
||||
export type CMSType = CMS["type"];
|
||||
|
||||
export const cmsTypes = [Contentful.type, Datocms.type, Strapi.type, BuilderIo.type] as const;
|
||||
export const cmsTypes = [
|
||||
Contentful.type,
|
||||
Datocms.type,
|
||||
Strapi.type,
|
||||
BuilderIo.type,
|
||||
PayloadCMS.type,
|
||||
] as const;
|
||||
|
||||
export const CMSProviders = [Contentful, Datocms, Strapi, BuilderIo] as const;
|
||||
export const CMSProviders = [Contentful, Datocms, Strapi, BuilderIo, PayloadCMS] as const;
|
||||
|
|
|
@ -22,6 +22,10 @@ import { DatocmsProviderConfig } from "../configuration/schemas/datocms-provider
|
|||
import { BuilderIo } from "./builder.io/builder-io";
|
||||
import { BuilderIoWebhooksProcessor } from "./builder.io/builder-io-webhooks-processor";
|
||||
import { BuilderIoBulkSyncProcessor } from "./builder.io/builder-io-bulk-sync-processor";
|
||||
import { PayloadCmsBulkSyncProcessor } from "./payloadcms/payloadcms-bulk-sync-processor";
|
||||
import { PayloadCmsProviderConfig } from "../configuration/schemas/payloadcms-provider.schema";
|
||||
import { PayloadCMS } from "./payloadcms/payloadcms";
|
||||
import { PayloadCmsWebhooksProcessor } from "./payloadcms/payloadcms-webhooks-processor";
|
||||
|
||||
/**
|
||||
* Almost-single source of new providers. Every time app will need to resolve a provider, it will use on of these factories.
|
||||
|
@ -39,9 +43,9 @@ export const ProvidersResolver = {
|
|||
case "builder.io": {
|
||||
return new BuilderIoBulkSyncProcessor(config);
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error(`Unknown provider`);
|
||||
case "payloadcms": {
|
||||
return new PayloadCmsBulkSyncProcessor(config);
|
||||
}
|
||||
}
|
||||
},
|
||||
getProviderInputSchema(type: CMSType) {
|
||||
|
@ -54,9 +58,8 @@ export const ProvidersResolver = {
|
|||
return StrapiProviderConfig.Schema.Input;
|
||||
case "builder.io":
|
||||
return BuilderIoProviderConfig.Schema.Input;
|
||||
default: {
|
||||
throw new Error("Failed to build input schema");
|
||||
}
|
||||
case "payloadcms":
|
||||
return PayloadCmsProviderConfig.Schema.Input;
|
||||
}
|
||||
},
|
||||
getProviderSchema(type: CMSType) {
|
||||
|
@ -69,12 +72,11 @@ export const ProvidersResolver = {
|
|||
return StrapiProviderConfig.Schema.Full;
|
||||
case "builder.io":
|
||||
return BuilderIoProviderConfig.Schema.Full;
|
||||
default: {
|
||||
throw new Error("Failed to build provdier schema");
|
||||
}
|
||||
case "payloadcms":
|
||||
return PayloadCmsProviderConfig.Schema.Full;
|
||||
}
|
||||
},
|
||||
createProviderMeta(type: CMSType | string): CMS {
|
||||
createProviderMeta(type: CMSType): CMS {
|
||||
switch (type) {
|
||||
case "contentful": {
|
||||
return Contentful;
|
||||
|
@ -88,8 +90,8 @@ export const ProvidersResolver = {
|
|||
case "builder.io": {
|
||||
return BuilderIo;
|
||||
}
|
||||
default: {
|
||||
throw new Error("Unknown provider");
|
||||
case "payloadcms": {
|
||||
return PayloadCMS;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -107,13 +109,13 @@ export const ProvidersResolver = {
|
|||
case "builder.io": {
|
||||
return new BuilderIoWebhooksProcessor(config);
|
||||
}
|
||||
default: {
|
||||
throw new Error("Failed to build webhook processor.");
|
||||
case "payloadcms": {
|
||||
return new PayloadCmsWebhooksProcessor(config);
|
||||
}
|
||||
}
|
||||
},
|
||||
getEditProviderFormComponent: (
|
||||
type: CMSType
|
||||
type: CMSType,
|
||||
): ComponentType<{
|
||||
configId: string;
|
||||
}> => {
|
||||
|
@ -121,33 +123,37 @@ export const ProvidersResolver = {
|
|||
case "contentful": {
|
||||
return dynamic(() =>
|
||||
import("./contentful/contentful-config-form").then(
|
||||
(module) => module.ContentfulConfigForm.EditVariant
|
||||
)
|
||||
(module) => module.ContentfulConfigForm.EditVariant,
|
||||
),
|
||||
);
|
||||
}
|
||||
case "datocms": {
|
||||
return dynamic(() =>
|
||||
import("./datocms/datocms-config-form").then(
|
||||
(module) => module.DatoCMSConfigForm.EditVariant
|
||||
)
|
||||
(module) => module.DatoCMSConfigForm.EditVariant,
|
||||
),
|
||||
);
|
||||
}
|
||||
case "strapi": {
|
||||
return dynamic(() =>
|
||||
import("./strapi/strapi-config-form").then(
|
||||
(module) => module.StrapiConfigForm.EditVariant
|
||||
)
|
||||
(module) => module.StrapiConfigForm.EditVariant,
|
||||
),
|
||||
);
|
||||
}
|
||||
case "builder.io": {
|
||||
return dynamic(() =>
|
||||
import("./builder.io/builder-io-config-form").then(
|
||||
(module) => module.BuilderIoConfigForm.EditVariant
|
||||
)
|
||||
(module) => module.BuilderIoConfigForm.EditVariant,
|
||||
),
|
||||
);
|
||||
}
|
||||
default: {
|
||||
throw new Error("Provider form not registered");
|
||||
case "payloadcms": {
|
||||
return dynamic(() =>
|
||||
import("./payloadcms/payloadcms-config-form").then(
|
||||
(module) => module.PayloadCMSConfigForm.EditVariant,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -156,31 +162,37 @@ export const ProvidersResolver = {
|
|||
case "contentful": {
|
||||
return dynamic(() =>
|
||||
import("./contentful/contentful-config-form").then(
|
||||
(module) => module.ContentfulConfigForm.AddVariant
|
||||
)
|
||||
(module) => module.ContentfulConfigForm.AddVariant,
|
||||
),
|
||||
);
|
||||
}
|
||||
case "datocms": {
|
||||
return dynamic(() =>
|
||||
import("./datocms/datocms-config-form").then(
|
||||
(module) => module.DatoCMSConfigForm.AddVariant
|
||||
)
|
||||
(module) => module.DatoCMSConfigForm.AddVariant,
|
||||
),
|
||||
);
|
||||
}
|
||||
case "strapi": {
|
||||
return dynamic(() =>
|
||||
import("./strapi/strapi-config-form").then((module) => module.StrapiConfigForm.AddVariant)
|
||||
import("./strapi/strapi-config-form").then(
|
||||
(module) => module.StrapiConfigForm.AddVariant,
|
||||
),
|
||||
);
|
||||
}
|
||||
case "builder.io": {
|
||||
return dynamic(() =>
|
||||
import("./builder.io/builder-io-config-form").then(
|
||||
(module) => module.BuilderIoConfigForm.AddVariant
|
||||
)
|
||||
(module) => module.BuilderIoConfigForm.AddVariant,
|
||||
),
|
||||
);
|
||||
}
|
||||
default: {
|
||||
throw new Error("Provider form not registered");
|
||||
case "payloadcms": {
|
||||
return dynamic(() =>
|
||||
import("./payloadcms/payloadcms-config-form").then(
|
||||
(module) => module.PayloadCMSConfigForm.AddVariant,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -86,7 +86,7 @@ const PureForm = ({ defaultValues, onSubmit, onDelete }: PureFormProps) => {
|
|||
padding={2}
|
||||
>
|
||||
<Text variant="caption">Saleor Field</Text>
|
||||
<Text variant="caption">Contentful field</Text>
|
||||
<Text variant="caption">Strapi field</Text>
|
||||
</Box>
|
||||
{SaleorProviderFieldsMappingKeys.map((saleorField) => (
|
||||
// todo extract this table to component
|
||||
|
@ -172,7 +172,7 @@ const EditFormVariant = (props: { configId: string }) => {
|
|||
},
|
||||
{
|
||||
enabled: !!props.configId,
|
||||
}
|
||||
},
|
||||
);
|
||||
const { mutate } = trpcClient.providersConfigs.updateOne.useMutation({
|
||||
onSuccess() {
|
||||
|
|
|
@ -9,7 +9,7 @@ dialog {
|
|||
|
||||
.dialog-overlay {
|
||||
z-index: 1;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
background: rgba(var(--mu-colors-background-plain), 0.8);
|
||||
backdrop-filter: blur(5px);
|
||||
content: "";
|
||||
position: fixed;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Box, BoxProps } from "@saleor/macaw-ui/next";
|
||||
import { forwardRef } from "react";
|
||||
|
||||
export const Modal = ({ onClose, ...rest }: { onClose(): void } & BoxProps) => {
|
||||
return (
|
||||
|
@ -12,6 +11,8 @@ export const Modal = ({ onClose, ...rest }: { onClose(): void } & BoxProps) => {
|
|||
as="dialog"
|
||||
__maxWidth="400px"
|
||||
boxShadow={"modal"}
|
||||
backgroundColor="surfaceNeutralPlain"
|
||||
color="textNeutralDefault"
|
||||
open
|
||||
{...rest}
|
||||
/>
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import { ContentfulConfigForm } from "@/modules/providers/contentful/contentful-config-form";
|
||||
import { DatoCMSConfigForm } from "@/modules/providers/datocms/datocms-config-form";
|
||||
import { CMSType } from "@/modules/providers/providers-registry";
|
||||
import { ProvidersResolver } from "@/modules/providers/providers-resolver";
|
||||
|
||||
import { StrapiConfigForm } from "@/modules/providers/strapi/strapi-config-form";
|
||||
import { AppHeader } from "@/modules/ui/app-header";
|
||||
import { AppSection } from "@/modules/ui/app-section";
|
||||
import { Breadcrumbs } from "@saleor/apps-ui";
|
||||
|
@ -16,7 +13,7 @@ const AddProviderPage: NextPage = () => {
|
|||
const { query } = useRouter();
|
||||
|
||||
const provider = useMemo(() => {
|
||||
return query.type ? ProvidersResolver.createProviderMeta(query.type as string) : null;
|
||||
return query.type ? ProvidersResolver.createProviderMeta(query.type as CMSType) : null;
|
||||
}, [query.type]);
|
||||
|
||||
if (!provider) return null;
|
||||
|
|
|
@ -2,11 +2,11 @@ import { SettingsManager } from "@saleor/app-sdk/settings-manager";
|
|||
import { EncryptedMetadataManagerFactory } from "@saleor/apps-shared";
|
||||
import { Client } from "urql";
|
||||
|
||||
const metadataManagerFactory = new EncryptedMetadataManagerFactory(process.env.SECRET_KEY!);
|
||||
|
||||
export const createSettingsManager = (
|
||||
client: Pick<Client, "query" | "mutation">,
|
||||
appId: string,
|
||||
): SettingsManager => {
|
||||
const metadataManagerFactory = new EncryptedMetadataManagerFactory(process.env.SECRET_KEY!);
|
||||
|
||||
return metadataManagerFactory.create(client, appId);
|
||||
};
|
||||
|
|
|
@ -106,6 +106,9 @@ importers:
|
|||
pino-pretty:
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0
|
||||
qs:
|
||||
specifier: 6.11.2
|
||||
version: 6.11.2
|
||||
react:
|
||||
specifier: 18.2.0
|
||||
version: 18.2.0
|
||||
|
@ -164,6 +167,9 @@ importers:
|
|||
'@testing-library/react-hooks':
|
||||
specifier: ^8.0.1
|
||||
version: 8.0.1(@types/react@18.2.5)(react-dom@18.2.0)(react@18.2.0)
|
||||
'@types/qs':
|
||||
specifier: ^6.9.7
|
||||
version: 6.9.7
|
||||
'@types/react':
|
||||
specifier: 18.2.5
|
||||
version: 18.2.5
|
||||
|
@ -5799,7 +5805,7 @@ packages:
|
|||
/@changesets/apply-release-plan@6.1.4:
|
||||
resolution: {integrity: sha512-FMpKF1fRlJyCZVYHr3CbinpZZ+6MwvOtWUuO8uo+svcATEoc1zRDcj23pAurJ2TZ/uVz1wFHH6K3NlACy0PLew==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.6
|
||||
'@babel/runtime': 7.22.10
|
||||
'@changesets/config': 2.3.1
|
||||
'@changesets/get-version-range-type': 0.3.2
|
||||
'@changesets/git': 2.0.0
|
||||
|
@ -5816,7 +5822,7 @@ packages:
|
|||
/@changesets/assemble-release-plan@5.2.4:
|
||||
resolution: {integrity: sha512-xJkWX+1/CUaOUWTguXEbCDTyWJFECEhmdtbkjhn5GVBGxdP/JwaHBIU9sW3FR6gD07UwZ7ovpiPclQZs+j+mvg==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.6
|
||||
'@babel/runtime': 7.22.10
|
||||
'@changesets/errors': 0.1.4
|
||||
'@changesets/get-dependents-graph': 1.3.6
|
||||
'@changesets/types': 5.2.1
|
||||
|
@ -5894,7 +5900,7 @@ packages:
|
|||
/@changesets/get-release-plan@3.0.17:
|
||||
resolution: {integrity: sha512-6IwKTubNEgoOZwDontYc2x2cWXfr6IKxP3IhKeK+WjyD6y3M4Gl/jdQvBw+m/5zWILSOCAaGLu2ZF6Q+WiPniw==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.6
|
||||
'@babel/runtime': 7.22.10
|
||||
'@changesets/assemble-release-plan': 5.2.4
|
||||
'@changesets/config': 2.3.1
|
||||
'@changesets/pre': 1.0.14
|
||||
|
@ -5908,7 +5914,7 @@ packages:
|
|||
/@changesets/git@2.0.0:
|
||||
resolution: {integrity: sha512-enUVEWbiqUTxqSnmesyJGWfzd51PY4H7mH9yUw0hPVpZBJ6tQZFMU3F3mT/t9OJ/GjyiM4770i+sehAn6ymx6A==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.6
|
||||
'@babel/runtime': 7.22.10
|
||||
'@changesets/errors': 0.1.4
|
||||
'@changesets/types': 5.2.1
|
||||
'@manypkg/get-packages': 1.1.3
|
||||
|
@ -5930,7 +5936,7 @@ packages:
|
|||
/@changesets/pre@1.0.14:
|
||||
resolution: {integrity: sha512-dTsHmxQWEQekHYHbg+M1mDVYFvegDh9j/kySNuDKdylwfMEevTeDouR7IfHNyVodxZXu17sXoJuf2D0vi55FHQ==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.6
|
||||
'@babel/runtime': 7.22.10
|
||||
'@changesets/errors': 0.1.4
|
||||
'@changesets/types': 5.2.1
|
||||
'@manypkg/get-packages': 1.1.3
|
||||
|
@ -5939,7 +5945,7 @@ packages:
|
|||
/@changesets/read@0.5.9:
|
||||
resolution: {integrity: sha512-T8BJ6JS6j1gfO1HFq50kU3qawYxa4NTbI/ASNVVCBTsKquy2HYwM9r7ZnzkiMe8IEObAJtUVGSrePCOxAK2haQ==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.6
|
||||
'@babel/runtime': 7.22.10
|
||||
'@changesets/git': 2.0.0
|
||||
'@changesets/logger': 0.0.5
|
||||
'@changesets/parse': 0.3.16
|
||||
|
@ -5957,7 +5963,7 @@ packages:
|
|||
/@changesets/write@0.2.3:
|
||||
resolution: {integrity: sha512-Dbamr7AIMvslKnNYsLFafaVORx4H0pvCA2MHqgtNCySMe1blImEyAEOzDmcgKAkgz4+uwoLz7demIrX+JBr/Xw==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.6
|
||||
'@babel/runtime': 7.22.10
|
||||
'@changesets/types': 5.2.1
|
||||
fs-extra: 7.0.1
|
||||
human-id: 1.0.2
|
||||
|
@ -7698,7 +7704,7 @@ packages:
|
|||
/@manypkg/get-packages@1.1.3:
|
||||
resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==}
|
||||
dependencies:
|
||||
'@babel/runtime': 7.22.6
|
||||
'@babel/runtime': 7.22.10
|
||||
'@changesets/types': 4.1.0
|
||||
'@manypkg/find-root': 1.1.0
|
||||
fs-extra: 8.1.0
|
||||
|
|
Loading…
Reference in a new issue