boilderplate for orders export

This commit is contained in:
Lukasz Ostrowski 2023-09-07 17:21:45 +02:00
parent 7e0755ec9e
commit 489e110e74
41 changed files with 31348 additions and 6 deletions

View file

@ -0,0 +1,18 @@
# The key used for metadata encryption. Required for production builds
SECRET_KEY=
# APL Config
# https://github.com/saleor/saleor-app-sdk/blob/main/docs/apl.md
APL=file
REST_APL_ENDPOINT=
REST_APL_TOKEN=
APP_LOG_LEVEL=info
# Local development variables. When developped locally with Saleor inside docker, these can be set to:
# APP_IFRAME_BASE_URL = http://localhost:3000, so Dashboard on host can access iframe
# APP_API_BASE_URL=http://host.docker.internal:3000 - so Saleor can reach App running on host, from the container.
# If developped with tunnels, set this empty, it will fallback to default Next's localhost:3000
# https://docs.saleor.io/docs/3.x/developer/extending/apps/local-app-development
APP_IFRAME_BASE_URL=
APP_API_BASE_URL=

View file

@ -0,0 +1,4 @@
{
"root": true,
"extends": ["saleor"]
}

View file

@ -0,0 +1,19 @@
schema: graphql/schema.graphql
documents: [graphql/**/*.graphql, src/**/*.ts, src/**/*.tsx]
extensions:
codegen:
overwrite: true
generates:
generated/graphql.ts:
config:
dedupeFragments: true
plugins:
- typescript
- typescript-operations
- typescript-urql:
documentVariablePrefix: "Untyped"
fragmentVariablePrefix: "Untyped"
- typed-document-node
generated/schema.graphql:
plugins:
- schema-ast

View file

@ -0,0 +1,31 @@
fragment OrderBase on Order {
id
user {
id
email
}
channel {
id
slug
name
}
userEmail
shippingMethodName
total {
gross {
amount
currency
}
net {
currency
amount
}
}
lines {
id
productVariantId
productSku
variantName
}
number
}

View file

@ -0,0 +1,10 @@
mutation UpdateAppMetadata($id: ID!, $input: [MetadataInput!]!) {
updatePrivateMetadata(id: $id, input: $input) {
item {
privateMetadata {
key
value
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
fragment OrderCancelledSubscriptionPayload on OrderCancelled {
order {
...OrderBase
}
}
subscription OrderCancelled {
event {
...OrderCancelledSubscriptionPayload
}
}

View file

@ -0,0 +1,11 @@
fragment OrderCreatedSubscriptionPayload on OrderCreated {
order {
...OrderBase
}
}
subscription OrderCreated {
event {
...OrderCreatedSubscriptionPayload
}
}

View file

@ -0,0 +1,11 @@
fragment OrderFullyPaidSubscriptionPayload on OrderFullyPaid {
order {
...OrderBase
}
}
subscription OrderFullyPaid {
event {
...OrderFullyPaidSubscriptionPayload
}
}

View file

@ -0,0 +1,11 @@
fragment OrderRefundedSubscriptionPayload on OrderRefunded {
order {
...OrderBase
}
}
subscription OrderRefunded {
event {
...OrderRefundedSubscriptionPayload
}
}

View file

@ -0,0 +1,11 @@
fragment OrderUpdatedSubscriptionPayload on OrderUpdated {
order {
...OrderBase
}
}
subscription OrderUpdated {
event {
...OrderUpdatedSubscriptionPayload
}
}

5
apps/orders-export/next-env.d.ts vendored Normal file
View file

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View file

@ -0,0 +1,44 @@
const { z } = require("zod");
const { withSentryConfig } = require("@sentry/nextjs");
const RequiredEnvs = z.object({
APL: z.string().min(1),
});
/** @type {import('next').NextConfig} */
const nextConfig = () => {
try {
RequiredEnvs.parse(process.env);
} catch (e) {
console.error("🚫 Missing required env variables, see message below");
console.error(e.issues);
process.exit(1);
}
return {
reactStrictMode: true,
// TODO Infer names dynamically from disk
transpilePackages: ["@saleor/apps-shared", "@saleor/apps-ui", "@saleor/react-hook-form-macaw", "@saleor/trpc"],
};
};
const isSentryPropertiesInEnvironment =
process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_PROJECT && process.env.SENTRY_ORG;
const configWithSentry = withSentryConfig(
nextConfig,
{
silent: true,
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
},
{
widenClientFileUpload: true,
transpileClientSDK: true,
tunnelRoute: "/monitoring",
hideSourceMaps: true,
disableLogger: true,
}
);
module.exports = isSentryPropertiesInEnvironment ? configWithSentry : nextConfig;

View file

@ -0,0 +1,67 @@
{
"name": "saleor-app-orders-export",
"version": "0.0.0",
"scripts": {
"build": "pnpm generate && next build",
"dev": "pnpm generate && NODE_OPTIONS='--inspect' next dev",
"fetch-schema": "curl https://raw.githubusercontent.com/saleor/saleor/${npm_package_saleor_schemaVersion}/saleor/graphql/schema.graphql > graphql/schema.graphql",
"generate": "graphql-codegen",
"lint": "next lint",
"lint:fix": "eslint --fix .",
"start": "next start",
"test": "vitest"
},
"dependencies": {
"@hookform/resolvers": "^3.1.0",
"@saleor/app-sdk": "0.43.1",
"@saleor/apps-shared": "workspace:*",
"@saleor/apps-ui": "workspace:*",
"@saleor/macaw-ui": "0.8.0-pre.127",
"@saleor/react-hook-form-macaw": "workspace:*",
"@saleor/trpc": "workspace:*",
"@sentry/nextjs": "7.67.0",
"@tanstack/react-query": "^4.29.19",
"@trpc/client": "10.38.1",
"@trpc/next": "10.38.1",
"@trpc/react-query": "10.38.1",
"@trpc/server": "10.38.1",
"@urql/exchange-auth": "^2.1.4",
"@vitejs/plugin-react": "4.0.4",
"graphql": "16.7.1",
"graphql-tag": "^2.12.6",
"jsdom": "^20.0.3",
"next": "13.4.8",
"pino": "^8.14.1",
"pino-pretty": "^10.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-error-boundary": "4.0.10",
"react-hook-form": "^7.43.9",
"urql": "^4.0.4",
"usehooks-ts": "^2.9.1",
"vite": "4.4.8",
"vitest": "0.34.1",
"zod": "3.21.4"
},
"devDependencies": {
"@graphql-codegen/cli": "4.0.1",
"@graphql-codegen/introspection": "4.0.0",
"@graphql-codegen/typed-document-node": "5.0.1",
"@graphql-codegen/typescript": "4.0.1",
"@graphql-codegen/typescript-operations": "4.0.1",
"@graphql-codegen/typescript-urql": "3.7.3",
"@graphql-typed-document-node/core": "3.2.0",
"@testing-library/react": "^14.0.0",
"@testing-library/react-hooks": "^8.0.1",
"@types/react": "18.2.5",
"@types/react-dom": "18.2.5",
"eslint": "8.46.0",
"eslint-config-saleor": "workspace:*",
"node-mocks-http": "^1.12.2",
"typescript": "5.1.6"
},
"private": true,
"saleor": {
"schemaVersion": "3.14"
}
}

View file

@ -0,0 +1,37 @@
/*
* This file configures the initialization of Sentry on the client.
* The config you add here will be used whenever a users loads a page in their browser.
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
import * as Sentry from "@sentry/nextjs";
import pkg from "./package.json";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 0.5,
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
replaysOnErrorSampleRate: 1.0,
/*
* This sets the sample rate to be 10%. You may want this to be 100% while
* in development and sample at a lower rate in production
*/
replaysSessionSampleRate: 0.1,
// You can remove this option if you're not planning to use the Sentry Session Replay feature:
integrations: [
new Sentry.Replay({
// Additional Replay configuration goes in here, for example:
maskAllText: true,
blockAllMedia: true,
}),
],
environment: process.env.SENTRY_ENVIRONMENT,
release: `${pkg.name}@${pkg.version}`,
});

View file

@ -0,0 +1,21 @@
/*
* This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
* The config you add here will be used whenever one of the edge features is loaded.
* Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
import * as Sentry from "@sentry/nextjs";
import pkg from "./package.json";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 0.5,
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
environment: process.env.SENTRY_ENVIRONMENT,
release: `${pkg.name}@${pkg.version}`,
});

View file

@ -0,0 +1,20 @@
/*
* This file configures the initialization of Sentry on the server.
* The config you add here will be used whenever the server handles a request.
* https://docs.sentry.io/platforms/javascript/guides/nextjs/
*/
import * as Sentry from "@sentry/nextjs";
import pkg from "./package.json";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 0.5,
// Setting this option to true will print useful information to the console while you're setting up Sentry.
debug: false,
environment: process.env.SENTRY_ENVIRONMENT,
release: `${pkg.name}@${pkg.version}`,
});

View file

@ -0,0 +1,36 @@
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
import { AppConfig } from "./app-config";
import { createSettingsManager } from "./metadata-manager";
import { createGraphQLClient } from "@saleor/apps-shared";
import { AuthData } from "@saleor/app-sdk/APL";
export class AppConfigMetadataManager {
public readonly metadataKey = "app-config-v1";
constructor(private mm: SettingsManager) {}
async get() {
const metadata = await this.mm.get(this.metadataKey);
return metadata ? AppConfig.parse(metadata) : new AppConfig();
}
set(config: AppConfig) {
return this.mm.set({
key: this.metadataKey,
value: config.serialize(),
});
}
static createFromAuthData(authData: AuthData): AppConfigMetadataManager {
const settingsManager = createSettingsManager(
createGraphQLClient({
saleorApiUrl: authData.saleorApiUrl,
token: authData.token,
}),
authData.appId,
);
return new AppConfigMetadataManager(settingsManager);
}
}

View file

@ -0,0 +1,24 @@
import { z } from "zod";
import { RootConfig } from "./schemas/root-config.schema";
export class AppConfig {
private rootData: RootConfig.Shape = {};
constructor(initialData?: RootConfig.Shape) {
if (initialData) {
this.rootData = RootConfig.Schema.parse(initialData);
}
}
static parse(serializedSchema: string) {
return new AppConfig(JSON.parse(serializedSchema));
}
serialize() {
return JSON.stringify(this.rootData);
}
getConfig() {
return this.rootData;
}
}

View file

@ -0,0 +1,6 @@
import { createLogger } from "@saleor/apps-shared";
import { router } from "../trpc/trpc-server";
const logger = createLogger({ name: "configuration.router" });
export const configurationRouter = router({});

View file

@ -0,0 +1,12 @@
import { SettingsManager } from "@saleor/app-sdk/settings-manager";
import { EncryptedMetadataManagerFactory } from "@saleor/apps-shared";
import { Client } from "urql";
export const createSettingsManager = (
client: Pick<Client, "query" | "mutation">,
appId: string,
): SettingsManager => {
const metadataManagerFactory = new EncryptedMetadataManagerFactory(process.env.SECRET_KEY!);
return metadataManagerFactory.create(client, appId);
};

View file

@ -0,0 +1,12 @@
import { z } from "zod";
export namespace RootConfig {
/**
* Store entire app config in single file
* - Only one request
* - Always transactional
*/
export const Schema = z.object({});
export type Shape = z.infer<typeof Schema>;
}

View file

@ -0,0 +1,117 @@
import { verifyJWT } from "@saleor/app-sdk/verify-jwt";
import { middleware, procedure } from "./trpc-server";
import { TRPCError } from "@trpc/server";
import { ProtectedHandlerError } from "@saleor/app-sdk/handlers/next";
import { saleorApp } from "../../saleor-app";
import { createGraphQLClient, logger } from "@saleor/apps-shared";
const attachAppToken = middleware(async ({ ctx, next }) => {
logger.debug("attachAppToken middleware");
if (!ctx.saleorApiUrl) {
logger.debug("ctx.saleorApiUrl not found, throwing");
throw new TRPCError({
code: "BAD_REQUEST",
message: "Missing saleorApiUrl in request",
});
}
const authData = await saleorApp.apl.get(ctx.saleorApiUrl);
if (!authData) {
logger.debug("authData not found, throwing 401");
throw new TRPCError({
code: "UNAUTHORIZED",
message: "Missing auth data",
});
}
return next({
ctx: {
appToken: authData.token,
saleorApiUrl: authData.saleorApiUrl,
appId: authData.appId,
},
});
});
const validateClientToken = middleware(async ({ ctx, next, meta }) => {
logger.debug(
{
permissions: meta?.requiredClientPermissions,
},
"Calling validateClientToken middleware with permissions required",
);
if (!ctx.token) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Missing token in request. This middleware can be used only in frontend",
});
}
if (!ctx.appId) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Missing appId in request. This middleware can be used after auth is attached",
});
}
if (!ctx.saleorApiUrl) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message:
"Missing saleorApiUrl in request. This middleware can be used after auth is attached",
});
}
if (!ctx.ssr) {
try {
logger.debug("trying to verify JWT token from frontend");
logger.debug({ token: ctx.token ? `${ctx.token[0]}...` : undefined });
await verifyJWT({
appId: ctx.appId,
token: ctx.token,
saleorApiUrl: ctx.saleorApiUrl,
requiredPermissions: meta?.requiredClientPermissions ?? [],
});
} catch (e) {
logger.debug("JWT verification failed, throwing");
throw new ProtectedHandlerError("JWT verification failed: ", "JWT_VERIFICATION_FAILED");
}
}
return next({
ctx: {
...ctx,
saleorApiUrl: ctx.saleorApiUrl,
},
});
});
/**
* Construct common graphQL client and attach it to the context
*
* Can be used only if called from the frontend (react-query),
* otherwise jwks validation will fail (if createCaller used)
*
* TODO Rethink middleware composition to enable safe server-side router calls
*/
export const protectedClientProcedure = procedure
.use(attachAppToken)
.use(validateClientToken)
.use(async ({ ctx, next }) => {
const client = createGraphQLClient({ saleorApiUrl: ctx.saleorApiUrl, token: ctx.appToken });
return next({
ctx: {
apiClient: client,
appToken: ctx.appToken,
saleorApiUrl: ctx.saleorApiUrl,
appId: ctx.appId!,
},
});
});

View file

@ -0,0 +1,8 @@
import { configurationRouter } from "../configuration/configuration.router";
import { router } from "./trpc-server";
export const appRouter = router({
configuration: configurationRouter,
});
export type AppRouter = typeof appRouter;

View file

@ -0,0 +1,15 @@
import { createTRPCNext } from "@trpc/next";
import { createHttpBatchLink } from "@saleor/trpc";
import { appBridgeInstance } from "../../pages/_app";
import { AppRouter } from "./trpc-app-router";
export const trpcClient = createTRPCNext<AppRouter>({
config() {
return {
links: [createHttpBatchLink(appBridgeInstance)],
queryClientConfig: { defaultOptions: { queries: { refetchOnWindowFocus: false } } },
};
},
ssr: false,
});

View file

@ -0,0 +1,31 @@
import { initTRPC } from "@trpc/server";
import { TrpcContext } from "@saleor/trpc";
import { Permission } from "@saleor/app-sdk/types";
import { ZodError } from "zod";
interface Meta {
requiredClientPermissions?: Permission[];
updateWebhooks?: boolean;
}
const t = initTRPC
.context<TrpcContext>()
.meta<Meta>()
.create({
errorFormatter({ shape, error }) {
return {
...shape,
data: {
...shape.data,
zodError:
error.code === "BAD_REQUEST" && error.cause instanceof ZodError
? error.cause.flatten()
: null,
},
};
},
});
export const router = t.router;
export const procedure = t.procedure;
export const middleware = t.middleware;

View file

@ -0,0 +1,45 @@
import "@saleor/macaw-ui/next/style";
import { trpcClient } from "@/modules/trpc/trpc-client";
import { AppBridge, AppBridgeProvider } from "@saleor/app-sdk/app-bridge";
import { RoutePropagator } from "@saleor/app-sdk/app-bridge/next";
import { GraphQLProvider, NoSSRWrapper, ThemeSynchronizer } from "@saleor/apps-shared";
import { Box, ThemeProvider } from "@saleor/macaw-ui/next";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { AppProps } from "next/app";
/**
* Ensure instance is a singleton.
* TODO: This is React 18 issue, consider hiding this workaround inside app-sdk
*/
export const appBridgeInstance = typeof window !== "undefined" ? new AppBridge() : undefined;
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
},
},
});
function NextApp({ Component, pageProps }: AppProps) {
return (
<NoSSRWrapper>
<AppBridgeProvider appBridgeInstance={appBridgeInstance}>
<GraphQLProvider>
<ThemeProvider>
<ThemeSynchronizer />
<RoutePropagator />
<QueryClientProvider client={queryClient}>
<Box padding={10}>
<Component {...pageProps} />
</Box>
</QueryClientProvider>
</ThemeProvider>
</GraphQLProvider>
</AppBridgeProvider>
</NoSSRWrapper>
);
}
export default trpcClient.withTRPC(NextApp);

View file

@ -0,0 +1,13 @@
import { Html, Head, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}

View file

@ -0,0 +1,45 @@
import { createManifestHandler } from "@saleor/app-sdk/handlers/next";
import { AppManifest } from "@saleor/app-sdk/types";
import packageJson from "../../../package.json";
export default createManifestHandler({
async manifestFactory({ appBaseUrl }) {
const iframeBaseUrl = process.env.APP_IFRAME_BASE_URL ?? appBaseUrl;
const apiBaseURL = process.env.APP_API_BASE_URL ?? appBaseUrl;
const manifest: AppManifest = {
about: "Export Saleor orders to spreadsheets",
appUrl: iframeBaseUrl,
author: "Saleor Commerce",
/*
* brand: {
* logo: {
* default: `${apiBaseURL}/logo.png`,
* },
* },
*/
dataPrivacyUrl: "https://saleor.io/legal/privacy/",
extensions: [
/**
* Optionally, extend Dashboard with custom UIs
* https://docs.saleor.io/docs/3.x/developer/extending/apps/extending-dashboard-with-apps
*/
],
homepageUrl: "https://github.com/saleor/apps",
id: "saleor.app.orders-export",
name: "Orders Export",
permissions: ["MANAGE_ORDERS"],
requiredSaleorVersion: ">=3.14 <4",
supportUrl: "https://github.com/saleor/apps/discussions",
tokenTargetUrl: `${apiBaseURL}/api/register`,
version: packageJson.version,
/*
* TODO Add webhooks disabled and enable then when configured
*/
webhooks: [],
};
return manifest;
},
});

View file

@ -0,0 +1,23 @@
import { saleorApp } from "@/saleor-app";
import { createAppRegisterHandler } from "@saleor/app-sdk/handlers/next";
const allowedUrlsPattern = process.env.ALLOWED_DOMAIN_PATTERN;
/**
* Required endpoint, called by Saleor to install app.
* It will exchange tokens with app, so saleorApp.apl will contain token
*/
export default createAppRegisterHandler({
apl: saleorApp.apl,
allowedSaleorUrls: [
(url) => {
if (allowedUrlsPattern) {
const regex = new RegExp(allowedUrlsPattern);
return regex.test(url);
}
return true;
},
],
});

View file

@ -0,0 +1,9 @@
import * as trpcNext from "@trpc/server/adapters/next";
import { appRouter } from "../../../modules/trpc/trpc-app-router";
import { createTrpcContext } from "@saleor/trpc";
export default trpcNext.createNextApiHandler({
router: appRouter,
createContext: createTrpcContext,
});

View file

@ -0,0 +1,28 @@
import { AppHeader } from "@/modules/ui/app-header";
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
import { Layout } from "@saleor/apps-ui";
import { Box, Text } from "@saleor/macaw-ui/next";
import { NextPage } from "next";
const ConfigurationPage: NextPage = () => {
const { appBridgeState } = useAppBridge();
if (!appBridgeState) {
return null;
}
if (appBridgeState.user?.permissions.includes("MANAGE_APPS") === false) {
return <Text>You do not have permission to access this page.</Text>;
}
return (
<Box>
<AppHeader />
<Layout.AppSection marginBottom={14} heading="todo">
todo
</Layout.AppSection>
</Box>
);
};
export default ConfigurationPage;

View file

@ -0,0 +1,30 @@
import { NextPage } from "next";
import { useAppBridge } from "@saleor/app-sdk/app-bridge";
import { useEffect } from "react";
import { useIsMounted } from "usehooks-ts";
import { useRouter } from "next/router";
import { isInIframe } from "@saleor/apps-shared";
const IndexPage: NextPage = () => {
const { appBridgeState } = useAppBridge();
const isMounted = useIsMounted();
const { replace } = useRouter();
useEffect(() => {
if (isMounted() && appBridgeState?.ready) {
replace("/configuration");
}
}, [isMounted, appBridgeState?.ready, replace]);
if (isInIframe()) {
return <p>Loading</p>;
}
return (
<div>
<h1>Saleor App - Orders Export</h1>
</div>
);
};
export default IndexPage;

View file

@ -0,0 +1,35 @@
import { APL, FileAPL, SaleorCloudAPL, UpstashAPL } from "@saleor/app-sdk/APL";
import { SaleorApp } from "@saleor/app-sdk/saleor-app";
const aplType = process.env.APL ?? "file";
export let apl: APL;
switch (aplType) {
case "upstash":
apl = new UpstashAPL();
break;
case "file":
apl = new FileAPL();
break;
case "saleor-cloud": {
if (!process.env.REST_APL_ENDPOINT || !process.env.REST_APL_TOKEN) {
throw new Error("Rest APL is not configured - missing env variables. Check saleor-app.ts");
}
apl = new SaleorCloudAPL({
resourceUrl: process.env.REST_APL_ENDPOINT,
token: process.env.REST_APL_TOKEN,
});
break;
}
default: {
throw new Error("Invalid APL config, ");
}
}
export const saleorApp = new SaleorApp({
apl,
});

View file

@ -0,0 +1 @@
export {};

View file

@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

View file

@ -0,0 +1,28 @@
{
"extends": ["//"],
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"env": [
"APL",
"APP_DEBUG",
"NODE_ENV",
"SECRET_KEY",
"ALLOWED_DOMAIN_PATTERN",
"REST_APL_ENDPOINT",
"REST_APL_TOKEN",
"NEXT_PUBLIC_VERCEL_ENV",
"VERCEL_URL",
"PORT",
"SENTRY_ORG",
"SENTRY_PROJECT",
"SENTRY_DSN",
"SENTRY_AUTH_TOKEN",
"NEXT_PUBLIC_SENTRY_DSN",
"SENTRY_ENVIRONMENT",
"APP_IFRAME_BASE_URL",
"APP_API_BASE_URL"
]
}
}
}

View file

@ -0,0 +1,17 @@
import react from "@vitejs/plugin-react";
import { defineConfig } from "vitest/config";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
test: {
passWithNoTests: true,
environment: "jsdom",
setupFiles: "./src/setup-tests.ts",
css: false,
alias: {
"@": "./src",
},
},
});

View file

@ -1,5 +1,9 @@
lockfileVersion: '6.0' lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
overrides: overrides:
'@saleor/app-sdk': 0.43.1 '@saleor/app-sdk': 0.43.1
@ -896,6 +900,145 @@ importers:
specifier: 5.1.6 specifier: 5.1.6
version: 5.1.6 version: 5.1.6
apps/orders-export:
dependencies:
'@hookform/resolvers':
specifier: ^3.1.0
version: 3.1.0(react-hook-form@7.44.3)
'@saleor/app-sdk':
specifier: 0.43.1
version: 0.43.1(graphql@16.7.1)(next@13.4.8)(react-dom@18.2.0)(react@18.2.0)
'@saleor/apps-shared':
specifier: workspace:*
version: link:../../packages/shared
'@saleor/apps-ui':
specifier: workspace:*
version: link:../../packages/ui
'@saleor/macaw-ui':
specifier: 0.8.0-pre.127
version: 0.8.0-pre.127(@types/react-dom@18.2.5)(@types/react@18.2.5)(react-dom@18.2.0)(react@18.2.0)
'@saleor/react-hook-form-macaw':
specifier: workspace:*
version: link:../../packages/react-hook-form-macaw
'@saleor/trpc':
specifier: workspace:*
version: link:../../packages/trpc
'@sentry/nextjs':
specifier: 7.67.0
version: 7.67.0(next@13.4.8)(react@18.2.0)
'@tanstack/react-query':
specifier: ^4.29.19
version: 4.29.19(react-dom@18.2.0)(react@18.2.0)
'@trpc/client':
specifier: 10.38.1
version: 10.38.1(@trpc/server@10.38.1)
'@trpc/next':
specifier: 10.38.1
version: 10.38.1(@tanstack/react-query@4.29.19)(@trpc/client@10.38.1)(@trpc/react-query@10.38.1)(@trpc/server@10.38.1)(next@13.4.8)(react-dom@18.2.0)(react@18.2.0)
'@trpc/react-query':
specifier: 10.38.1
version: 10.38.1(@tanstack/react-query@4.29.19)(@trpc/client@10.38.1)(@trpc/server@10.38.1)(react-dom@18.2.0)(react@18.2.0)
'@trpc/server':
specifier: 10.38.1
version: 10.38.1
'@urql/exchange-auth':
specifier: ^2.1.4
version: 2.1.4(graphql@16.7.1)
'@vitejs/plugin-react':
specifier: 4.0.4
version: 4.0.4(vite@4.4.8)
graphql:
specifier: 16.7.1
version: 16.7.1
graphql-tag:
specifier: ^2.12.6
version: 2.12.6(graphql@16.7.1)
jsdom:
specifier: ^20.0.3
version: 20.0.3
next:
specifier: 13.4.8
version: 13.4.8(@babel/core@7.22.11)(react-dom@18.2.0)(react@18.2.0)
pino:
specifier: ^8.14.1
version: 8.14.1
pino-pretty:
specifier: ^10.0.0
version: 10.0.0
react:
specifier: 18.2.0
version: 18.2.0
react-dom:
specifier: 18.2.0
version: 18.2.0(react@18.2.0)
react-error-boundary:
specifier: 4.0.10
version: 4.0.10(react@18.2.0)
react-hook-form:
specifier: ^7.43.9
version: 7.44.3(react@18.2.0)
urql:
specifier: ^4.0.4
version: 4.0.4(graphql@16.7.1)(react@18.2.0)
usehooks-ts:
specifier: ^2.9.1
version: 2.9.1(react-dom@18.2.0)(react@18.2.0)
vite:
specifier: 4.4.8
version: 4.4.8(@types/node@18.15.3)
vitest:
specifier: 0.34.1
version: 0.34.1(jsdom@20.0.3)
zod:
specifier: 3.21.4
version: 3.21.4
devDependencies:
'@graphql-codegen/cli':
specifier: 4.0.1
version: 4.0.1(@babel/core@7.22.11)(@types/node@18.15.3)(graphql@16.7.1)
'@graphql-codegen/introspection':
specifier: 4.0.0
version: 4.0.0(graphql@16.7.1)
'@graphql-codegen/typed-document-node':
specifier: 5.0.1
version: 5.0.1(graphql@16.7.1)
'@graphql-codegen/typescript':
specifier: 4.0.1
version: 4.0.1(graphql@16.7.1)
'@graphql-codegen/typescript-operations':
specifier: 4.0.1
version: 4.0.1(graphql@16.7.1)
'@graphql-codegen/typescript-urql':
specifier: 3.7.3
version: 3.7.3(graphql-tag@2.12.6)(graphql@16.7.1)
'@graphql-typed-document-node/core':
specifier: 3.2.0
version: 3.2.0(graphql@16.7.1)
'@testing-library/react':
specifier: ^14.0.0
version: 14.0.0(react-dom@18.2.0)(react@18.2.0)
'@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/react':
specifier: 18.2.5
version: 18.2.5
'@types/react-dom':
specifier: 18.2.5
version: 18.2.5
eslint:
specifier: 8.46.0
version: 8.46.0
eslint-config-saleor:
specifier: workspace:*
version: link:../../packages/eslint-config-saleor
node-mocks-http:
specifier: ^1.12.2
version: 1.12.2
typescript:
specifier: 5.1.6
version: 5.1.6
apps/products-feed: apps/products-feed:
dependencies: dependencies:
'@aws-sdk/client-s3': '@aws-sdk/client-s3':
@ -5782,6 +5925,7 @@ packages:
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
dependencies: dependencies:
regenerator-runtime: 0.13.11 regenerator-runtime: 0.13.11
dev: true
/@babel/template@7.22.5: /@babel/template@7.22.5:
resolution: {integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==} resolution: {integrity: sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==}
@ -12876,6 +13020,7 @@ packages:
/chevrotain@6.5.0: /chevrotain@6.5.0:
resolution: {integrity: sha512-BwqQ/AgmKJ8jcMEjaSnfMybnKMgGTrtDKowfTP3pX4jwVy0kNjRsT/AP6h+wC3+3NC+X8X15VWBnTCQlX+wQFg==} resolution: {integrity: sha512-BwqQ/AgmKJ8jcMEjaSnfMybnKMgGTrtDKowfTP3pX4jwVy0kNjRsT/AP6h+wC3+3NC+X8X15VWBnTCQlX+wQFg==}
requiresBuild: true
dependencies: dependencies:
regexp-to-ast: 0.4.0 regexp-to-ast: 0.4.0
dev: false dev: false
@ -15738,7 +15883,7 @@ packages:
graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
dependencies: dependencies:
graphql: 16.7.1 graphql: 16.7.1
tslib: 2.6.1 tslib: 2.6.2
/graphql-ws@5.14.0(graphql@16.7.1): /graphql-ws@5.14.0(graphql@16.7.1):
resolution: {integrity: sha512-itrUTQZP/TgswR4GSSYuwWUzrE/w5GhbwM2GX3ic2U7aw33jgEsayfIlvaj7/GcIvZgNMzsPTrE5hqPuFUiE5g==} resolution: {integrity: sha512-itrUTQZP/TgswR4GSSYuwWUzrE/w5GhbwM2GX3ic2U7aw33jgEsayfIlvaj7/GcIvZgNMzsPTrE5hqPuFUiE5g==}
@ -19455,7 +19600,7 @@ packages:
peerDependencies: peerDependencies:
react: '>=16.13.1' react: '>=16.13.1'
dependencies: dependencies:
'@babel/runtime': 7.22.6 '@babel/runtime': 7.22.11
react: 18.2.0 react: 18.2.0
dev: false dev: false
@ -19948,6 +20093,7 @@ packages:
/regexp-to-ast@0.4.0: /regexp-to-ast@0.4.0:
resolution: {integrity: sha512-4qf/7IsIKfSNHQXSwial1IFmfM1Cc/whNBQqRwe0V2stPe7KmN1U0tWQiIx6JiirgSrisjE0eECdNf7Tav1Ntw==} resolution: {integrity: sha512-4qf/7IsIKfSNHQXSwial1IFmfM1Cc/whNBQqRwe0V2stPe7KmN1U0tWQiIx6JiirgSrisjE0eECdNf7Tav1Ntw==}
requiresBuild: true
dev: false dev: false
optional: true optional: true
@ -21181,6 +21327,7 @@ packages:
/tiny-emitter@2.1.0: /tiny-emitter@2.1.0:
resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==} resolution: {integrity: sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==}
requiresBuild: true
dev: false dev: false
optional: true optional: true
@ -21342,6 +21489,7 @@ packages:
/tslib@2.6.1: /tslib@2.6.1:
resolution: {integrity: sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==} resolution: {integrity: sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==}
dev: false
/tslib@2.6.2: /tslib@2.6.2:
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
@ -21697,6 +21845,7 @@ packages:
/unorm@1.6.0: /unorm@1.6.0:
resolution: {integrity: sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==} resolution: {integrity: sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==}
engines: {node: '>= 0.4.0'} engines: {node: '>= 0.4.0'}
requiresBuild: true
dev: false dev: false
optional: true optional: true
@ -22615,7 +22764,3 @@ packages:
/zod@3.21.4: /zod@3.21.4:
resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==}
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false