Add prettier on pre-commit & reformat codebase (#137)

* Run prettier on project

* Install lint-staged

* Add Husky
This commit is contained in:
Lukasz Ostrowski 2023-02-10 11:13:59 +01:00 committed by GitHub
parent 9f843b2d31
commit 081d15e168
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 140 additions and 119 deletions

1
.husky/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
_

4
.husky/pre-commit Executable file
View file

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged

View file

@ -2,3 +2,4 @@
**/pnpm-lock.yaml
**/graphql/schema.graphql
**/generated
.changeset/

View file

@ -29,7 +29,7 @@
This repository serves as a starting point in the exploration of Saleor apps.
> _Saleor apps are separate applications that use GraphQL to talk to the Saleor server and receive webhooks with event notifications from Saleor._
>
>
> [docs.saleor.io](https://docs.saleor.io/docs/3.x/developer/extending/apps/key-concepts)
From here, you can visit:

View file

@ -2,4 +2,4 @@
# Saleor App: Data Importer
Description TBD
Description TBD

View file

@ -1,5 +1,5 @@
import { SaleorApp } from "@saleor/app-sdk/saleor-app";
import {APL, FileAPL, SaleorCloudAPL, UpstashAPL, VercelAPL} from "@saleor/app-sdk/APL";
import { APL, FileAPL, SaleorCloudAPL, UpstashAPL, VercelAPL } from "@saleor/app-sdk/APL";
/**
* By default auth data are stored in the `.auth-data.json` (FileAPL).

View file

@ -46,8 +46,8 @@ const nuvoSettings: SettingsAPI = {
color: "#fff",
},
},
loader:{
loadAnimationColor: '#000'
loader: {
loadAnimationColor: "#000",
},
header: {
description: {

View file

@ -13,7 +13,9 @@ export const CustomersImportingResults = ({
return (
<div>
<Typography paragraph variant="h3">Customers rows from imported file</Typography>
<Typography paragraph variant="h3">
Customers rows from imported file
</Typography>
<Typography paragraph>
Lines will be imported one by one. Failed imports can be retried, but performed operations
@ -33,7 +35,7 @@ export const CustomersImportingResults = ({
</Button>
)}
<Table style={{marginTop: 50}}>
<Table style={{ marginTop: 50 }}>
<TableBody>
{importedLines.map((row) => (
<CustomerImportingRow

View file

@ -7,7 +7,6 @@ import GraphQLProvider from "../providers/GraphQLProvider";
import { actions, useAppBridge } from "@saleor/app-sdk/app-bridge";
import { AppIcon, TitleBar } from "@saleor/apps-shared";
type Tab = "customers";
const useStyles = makeStyles((theme: SaleorTheme) => ({
@ -36,7 +35,7 @@ const ImporterPage: NextPage = () => {
<div className={styles.wrapper}>
<TitleBar
bottomMargin
icon={<AppIcon theme="rgb(58, 86, 199)" text="DI"/>}
icon={<AppIcon theme="rgb(58, 86, 199)" text="DI" />}
name="Data Importer"
author="By Saleor Commerce"
rightColumnContent={

View file

@ -3,4 +3,4 @@
*
* https://vitest.dev/config/#setupfiles
*/
export {}
export {};

View file

@ -71,28 +71,31 @@ pnpm dev
### Without CLI
1. Install the dependencies by running:
```
pnpm install
```
2. Start the local server with:
```
pnpm dev
```
3. Expose local environment using tunnel:
Use tunneling tools like [localtunnel](https://github.com/localtunnel/localtunnel) or [ngrok](https://ngrok.com/).
Use tunneling tools like [localtunnel](https://github.com/localtunnel/localtunnel) or [ngrok](https://ngrok.com/).
4. Install aplication at your dashboard:
If you use Saleor Cloud or your local server is exposed, you can install your app by following this link:
```
[YOUR_SALEOR_DASHBOARD_URL]/apps/install?manifestUrl=[YOUR_APP_TUNNEL_MANIFEST_URL]
```
This template host manifest at `/api/manifest`
You can also install application using GQL or command line. Follow the guide [how to install your app](https://docs.saleor.io/docs/3.x/developer/extending/apps/installing-apps#installation-using-graphql-api) to learn more.
You can also install application using GQL or command line. Follow the guide [how to install your app](https://docs.saleor.io/docs/3.x/developer/extending/apps/installing-apps#installation-using-graphql-api) to learn more.
### Generated schema and typings

View file

@ -1,49 +1,49 @@
import {describe, it, expect} from "vitest";
import {appConfigInputSchema} from "./app-config-input-schema";
import {AppConfig, SellerShopConfig} from "./app-config";
import {getMockAddress} from "../../fixtures/mock-address";
import { describe, it, expect } from "vitest";
import { appConfigInputSchema } from "./app-config-input-schema";
import { AppConfig, SellerShopConfig } from "./app-config";
import { getMockAddress } from "../../fixtures/mock-address";
describe("appConfigInputSchema", () => {
it('Passes with no channels at all', () => {
expect(() =>
appConfigInputSchema.parse({
shopConfigPerChannel: {}
} satisfies AppConfig)
).not.to.throw()
})
it("Passes with no channels at all", () => {
expect(() =>
appConfigInputSchema.parse({
shopConfigPerChannel: {},
} satisfies AppConfig)
).not.to.throw();
});
it('Passes with all address fields empty', () => {
expect(() =>
appConfigInputSchema.parse({
shopConfigPerChannel: {
channel: {
address: {
city: "",
cityArea: "",
companyName: "",
country: "",
countryArea: "",
firstName: "",
lastName: "",
postalCode: "",
streetAddress1: "",
streetAddress2: ""
it("Passes with all address fields empty", () => {
expect(() =>
appConfigInputSchema.parse({
shopConfigPerChannel: {
channel: {
address: {
city: "",
cityArea: "",
companyName: "",
country: "",
countryArea: "",
firstName: "",
lastName: "",
postalCode: "",
streetAddress1: "",
streetAddress2: "",
},
},
},
} satisfies AppConfig)
).not.to.throw();
});
}
}
}
} satisfies AppConfig)
).not.to.throw()
})
it('Passes with partial address', () => {
expect(() =>
appConfigInputSchema.parse({
shopConfigPerChannel: {
channel: {
address: getMockAddress() }
}
} satisfies AppConfig)
).not.to.throw()
})
})
it("Passes with partial address", () => {
expect(() =>
appConfigInputSchema.parse({
shopConfigPerChannel: {
channel: {
address: getMockAddress(),
},
},
} satisfies AppConfig)
).not.to.throw();
});
});

View file

@ -1,19 +1,22 @@
import {OrderPayloadFragment} from "../../../generated/graphql";
import { OrderPayloadFragment } from "../../../generated/graphql";
interface IInvoiceNumberGenerationStrategy {
(order: OrderPayloadFragment): string;
(order: OrderPayloadFragment): string;
}
export const InvoiceNumberGenerationStrategy = {
localizedDate: (locale: string) => (order: Pick<OrderPayloadFragment, 'created'>) => {
const orderCreatedDate = new Date(order.created);
localizedDate: (locale: string) => (order: Pick<OrderPayloadFragment, "created">) => {
const orderCreatedDate = new Date(order.created);
return Intl.DateTimeFormat(locale,).format(orderCreatedDate)
}
} satisfies Record<string, (...args: any[]) => IInvoiceNumberGenerationStrategy>
return Intl.DateTimeFormat(locale).format(orderCreatedDate);
},
} satisfies Record<string, (...args: any[]) => IInvoiceNumberGenerationStrategy>;
export class InvoiceNumberGenerator {
generateFromOrder(order: OrderPayloadFragment, strategy: IInvoiceNumberGenerationStrategy): string {
return strategy(order)
}
}
generateFromOrder(
order: OrderPayloadFragment,
strategy: IInvoiceNumberGenerationStrategy
): string {
return strategy(order);
}
}

View file

@ -39,7 +39,7 @@ switch (aplType) {
if (!process.env.SECRET_KEY && process.env.NODE_ENV === "production") {
throw new Error(
"For production deployment SECRET_KEY is mandatory to use EncryptedSettingsManager.",
"For production deployment SECRET_KEY is mandatory to use EncryptedSettingsManager."
);
}

View file

@ -80,7 +80,7 @@ export const AlgoliaConfigurationCard = () => {
},
});
},
},
}
);
const onFormSubmit = handleSubmit(async (conf) => mutate(conf));

View file

@ -1,4 +1,4 @@
import {Card, CardContent, CardHeader} from "@material-ui/core";
import { Card, CardContent, CardHeader } from "@material-ui/core";
import { ImportProductsToAlgolia } from "./ImportProductsToAlgolia";
import AlgoliaConfigurationCard from "./AlgoliaConfigurationCard";
import { makeStyles, PageTab, PageTabs } from "@saleor/macaw-ui";

View file

@ -19,7 +19,7 @@ export const ImportProductsToAlgolia = () => {
const { appBridgeState } = useAppBridge();
const algoliaConfiguration = useConfiguration(
appBridgeState?.saleorApiUrl,
appBridgeState?.token,
appBridgeState?.token
);
const searchProvider = useMemo(() => {

View file

@ -5,28 +5,28 @@ import { useSearchBox } from "react-instantsearch-hooks-web";
import styles from "../styles/search.module.css";
export function SearchBox() {
const { query, refine } = useSearchBox();
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
refine(e.target.value);
};
return (
<div className={styles.textFieldContainer}>
<TextField
fullWidth
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
value={query}
onChange={handleChange}
placeholder={"Search products..."}
inputProps={{ style: { padding: "16px" } }}
className={styles.textField}
/>
</div>
);
}
const { query, refine } = useSearchBox();
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
refine(e.target.value);
};
return (
<div className={styles.textFieldContainer}>
<TextField
fullWidth
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
value={query}
onChange={handleChange}
placeholder={"Search products..."}
inputProps={{ style: { padding: "16px" } }}
className={styles.textField}
/>
</div>
);
}

View file

@ -35,7 +35,7 @@ export class AlgoliaSearchProvider implements SearchProvider {
Object.entries(groupedByIndex).map(([indexName, objects]) => {
const index = this.#algolia.initIndex(indexName);
return index.saveObjects(objects);
}),
})
);
}
@ -46,7 +46,7 @@ export class AlgoliaSearchProvider implements SearchProvider {
Object.entries(groupedByIndex).map(([indexName, objects]) => {
const index = this.#algolia.initIndex(indexName);
return index.deleteObjects(objects.map((o) => o.objectID));
}),
})
);
}
@ -141,7 +141,7 @@ const groupVariantByIndexName = (
{
visibleInListings,
indexNamePrefix,
}: { visibleInListings: true | false | null; indexNamePrefix: string | undefined },
}: { visibleInListings: true | false | null; indexNamePrefix: string | undefined }
) => {
if (!productVariant.product.channelListings) {
return null;
@ -150,7 +150,7 @@ const groupVariantByIndexName = (
const objectsToSaveByIndexName = productVariant.product.channelListings
.filter((channelListing) =>
// don't filter if `visibleInListings` is null
visibleInListings === null ? true : channelListing.visibleInListings === visibleInListings,
visibleInListings === null ? true : channelListing.visibleInListings === visibleInListings
)
.map((channelListing) => {
const object = productAndVariantToAlgolia({
@ -176,7 +176,7 @@ const groupProductsByIndexName = (
{
visibleInListings,
indexNamePrefix,
}: { visibleInListings: true | false | null; indexNamePrefix: string | undefined },
}: { visibleInListings: true | false | null; indexNamePrefix: string | undefined }
) => {
debug(`groupProductsByIndexName called`);
const batchesAndIndices = productsBatch

View file

@ -10,7 +10,7 @@ type PartialChannelListing = {
export function channelListingToAlgoliaIndexId(
channelListing: PartialChannelListing,
indexNamePrefix: string | undefined,
indexNamePrefix: string | undefined
) {
const nameSegments = [
indexNamePrefix,

View file

@ -51,7 +51,7 @@ export const nextClient = (url: string, getAuth: AuthConfig<IAuthState>["getAuth
url,
exchanges: getExchanges(getAuth),
},
false,
false
);
};

View file

@ -37,7 +37,7 @@ export async function mutateMetadata(client: Client, metadata: MetadataEntry[])
if (idQueryError) {
console.debug("Could not fetch the app id: ", idQueryError);
throw new Error(
"Could not fetch the app id. Please check if auth data for the client are valid.",
"Could not fetch the app id. Please check if auth data for the client are valid."
);
}

View file

@ -20,7 +20,7 @@ const sendResponse = async (
res: NextApiResponse<SettingsApiResponse>,
statusCode: number,
settings: SettingsManager,
domain: string,
domain: string
) => {
res.status(statusCode).json({
success: statusCode === 200,
@ -36,7 +36,7 @@ const sendResponse = async (
export const handler = async (
req: NextApiRequest,
res: NextApiResponse,
ctx: ProtectedHandlerContext,
ctx: ProtectedHandlerContext
) => {
debug("Configuration handler received request");
@ -54,7 +54,7 @@ export const handler = async (
} else if (req.method === "POST") {
debug("Updating the configuration");
const { appId, searchKey, secretKey, indexNamePrefix } = JSON.parse(
req.body,
req.body
) as AlgoliaConfigurationFields;
await settings.set([
{ key: "secretKey", value: secretKey || "", domain },

View file

@ -11,15 +11,15 @@ const debug = createDebug("Webhooks handler");
export const handler: NextWebhookApiHandler<ProductEditedSubscription["event"]> = async (
req,
res,
context,
context
) => {
const { event, authData } = context;
debug(
`New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!`,
`New event ${event} (${context.payload?.__typename}) from the ${authData.domain} domain has been received!`
);
const client = createClient(authData.saleorApiUrl, async () =>
Promise.resolve({ token: authData.token }),
Promise.resolve({ token: authData.token })
);
const settings = createSettingsManager(client);

View file

@ -37,7 +37,7 @@ function Search() {
const [indexName, setIndexName] = useState<string>();
const algoliaConfiguration = useConfiguration(
appBridgeState?.saleorApiUrl,
appBridgeState?.token,
appBridgeState?.token
);
const searchClient = useMemo(() => {

View file

@ -13,7 +13,7 @@ function GraphQLProvider(props: PropsWithChildren<{}>) {
}
const client = createClient(saleorApiUrl, async () =>
Promise.resolve({ token: appBridgeState?.token! }),
Promise.resolve({ token: appBridgeState?.token! })
);
return <Provider value={client} {...props} />;

View file

@ -14,17 +14,23 @@
"test": "turbo run test",
"test:ci": "turbo run test:ci",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"generate": "turbo run generate"
"generate": "turbo run generate",
"prepare": "husky install"
},
"devDependencies": {
"@changesets/cli": "^2.26.0",
"eslint-config-saleor": "workspace:*",
"prettier": "^2.8.3",
"turbo": "^1.7.4",
"eslint": "^8.33.0"
"eslint": "^8.33.0",
"husky": "^8.0.3"
},
"engines": {
"node": ">=18.0.0"
},
"packageManager": "pnpm@7.26.2"
"packageManager": "pnpm@7.26.2",
"lint-staged": {
"*.{js,ts,tsx}": "eslint --cache --fix",
"*.{ts,tsx,md}": "prettier --write"
}
}

View file

@ -4,4 +4,4 @@ export function isInIframe() {
} catch (e) {
return true;
}
}
}

View file

@ -80,4 +80,4 @@ export function TitleBar({
);
}
TitleBar.height = height;
TitleBar.height = height;

View file

@ -7,12 +7,14 @@ importers:
'@changesets/cli': ^2.26.0
eslint: ^8.33.0
eslint-config-saleor: workspace:*
husky: ^8.0.3
prettier: ^2.8.3
turbo: ^1.7.4
devDependencies:
'@changesets/cli': 2.26.0
eslint: 8.33.0
eslint-config-saleor: link:packages/eslint-config-saleor
husky: 8.0.3
prettier: 2.8.3
turbo: 1.7.4