From 8a190e28c6aa6efe16198f7cfb07a79368a478bb Mon Sep 17 00:00:00 2001 From: Max Peintner Date: Tue, 16 May 2023 17:34:52 +0200 Subject: [PATCH] session service, username, password form --- .../app/(login)/register/success/page.tsx | 2 - apps/login/app/(login)/username/page.tsx | 30 +------ apps/login/app/layout.tsx | 22 ++--- apps/login/app/session/route.ts | 26 ++++++ apps/login/lib/zitadel.ts | 40 +++++++-- apps/login/ui/PasswordForm.tsx | 84 +++++++++++++++++++ apps/login/ui/RegisterForm.tsx | 17 ++-- apps/login/ui/ThemeWrapper.tsx | 5 +- apps/login/ui/UsernameForm.tsx | 82 ++++++++++++++++++ apps/login/utils/colors.ts | 71 ++++++++-------- packages/zitadel-server/package.json | 5 +- packages/zitadel-server/src/index.ts | 45 +++++++--- .../zitadel-server/src/management/index.ts | 2 +- packages/zitadel-server/src/server.ts | 23 +++++ .../zitadel-server/src/v2/session/index.ts | 2 + .../zitadel-server/src/v2/session/session.ts | 29 +++++++ .../zitadel-server/src/v2/settings/index.ts | 4 - .../src/v2/settings/settings.ts | 19 +---- packages/zitadel-server/tsconfig.json | 7 +- packages/zitadel-server/tsup.config.ts | 13 +++ 20 files changed, 395 insertions(+), 133 deletions(-) create mode 100644 apps/login/app/session/route.ts create mode 100644 apps/login/ui/PasswordForm.tsx create mode 100644 apps/login/ui/UsernameForm.tsx create mode 100644 packages/zitadel-server/src/v2/session/index.ts create mode 100644 packages/zitadel-server/src/v2/session/session.ts create mode 100644 packages/zitadel-server/tsup.config.ts diff --git a/apps/login/app/(login)/register/success/page.tsx b/apps/login/app/(login)/register/success/page.tsx index bf133037e92..baa484a004b 100644 --- a/apps/login/app/(login)/register/success/page.tsx +++ b/apps/login/app/(login)/register/success/page.tsx @@ -1,7 +1,5 @@ import { Button, ButtonVariants } from "#/ui/Button"; -import { NextPage, NextPageContext } from "next"; import Link from "next/link"; -import { useSearchParams } from "next/navigation"; type Props = { searchParams: { [key: string]: string | string[] | undefined }; diff --git a/apps/login/app/(login)/username/page.tsx b/apps/login/app/(login)/username/page.tsx index 86280e0351b..a68482ef906 100644 --- a/apps/login/app/(login)/username/page.tsx +++ b/apps/login/app/(login)/username/page.tsx @@ -2,41 +2,15 @@ import { Button, ButtonVariants } from "#/ui/Button"; import IdentityProviders from "#/ui/IdentityProviders"; -import { TextInput } from "#/ui/Input"; -import { useRouter } from "next/navigation"; +import UsernameForm from "#/ui/UsernameForm"; export default function Page() { - const router = useRouter(); - - function submit() { - router.push("/password"); - } return (

Welcome back!

Enter your login data.

-
submit()}> -
- -
- -
- -
-
- - -
-
+
); } diff --git a/apps/login/app/layout.tsx b/apps/login/app/layout.tsx index 93b649a6a97..9ca933f31e6 100644 --- a/apps/login/app/layout.tsx +++ b/apps/login/app/layout.tsx @@ -8,7 +8,7 @@ import { Analytics } from "@vercel/analytics/react"; import ThemeWrapper from "#/ui/ThemeWrapper"; import { getBrandingSettings } from "#/lib/zitadel"; import { server } from "../lib/zitadel"; -import { LabelPolicyColors } from "#/utils/colors"; +import { BrandingSettings } from "@zitadel/server"; const lato = Lato({ weight: ["400", "700", "900"], @@ -25,26 +25,20 @@ export default async function RootLayout({ // later only shown with dev mode enabled const showNav = true; - const branding = await getBrandingSettings(server); - let partialPolicy: LabelPolicyColors | undefined; - console.log(branding); + // const general = await getGeneralSettings(server); + const branding: BrandingSettings = await getBrandingSettings(server); + let partial: Partial | undefined; if (branding) { - partialPolicy = { - backgroundColor: branding?.backgroundColor, - backgroundColorDark: branding?.backgroundColorDark, - primaryColor: branding?.primaryColor, - primaryColorDark: branding?.primaryColorDark, - warnColor: branding?.warnColor, - warnColorDark: branding?.warnColorDark, - fontColor: branding?.fontColor, - fontColorDark: branding?.fontColorDark, + partial = { + lightTheme: branding?.lightTheme, + darkTheme: branding?.darkTheme, }; } return ( - +
{showNav && } diff --git a/apps/login/app/session/route.ts b/apps/login/app/session/route.ts new file mode 100644 index 00000000000..0ee1f7a8368 --- /dev/null +++ b/apps/login/app/session/route.ts @@ -0,0 +1,26 @@ +import { createSession, server, setSession } from "#/lib/zitadel"; +import { NextRequest, NextResponse } from "next/server"; + +export async function POST(request: NextRequest) { + const body = await request.json(); + if (body) { + const { loginName } = body; + + const session = await createSession(server, loginName); + return NextResponse.json(session); + } else { + return NextResponse.error(); + } +} + +export async function PUT(request: NextRequest) { + const body = await request.json(); + if (body) { + const { loginName } = body; + + const session = await setSession(server, loginName); + return NextResponse.json(session); + } else { + return NextResponse.error(); + } +} diff --git a/apps/login/lib/zitadel.ts b/apps/login/lib/zitadel.ts index ccf3f9adc07..96476e8e407 100644 --- a/apps/login/lib/zitadel.ts +++ b/apps/login/lib/zitadel.ts @@ -1,14 +1,12 @@ import { - management, ZitadelServer, ZitadelServerOptions, - orgMetadata, - getServer, + management, + settings, getServers, initializeServer, - settings, + session, } from "@zitadel/server"; -// import { getAuth } from "@zitadel/server/auth"; export const zitadelConfig: ZitadelServerOptions = { name: "zitadel login", @@ -38,6 +36,21 @@ export function getBrandingSettings( .then((resp) => resp.settings); } +export function getGeneralSettings( + server: ZitadelServer +): Promise { + // settings.branding_settings.BrandingSettings + const settingsService = settings.getSettings(server); + return settingsService + .getGeneralSettings( + {}, + { + // metadata: orgMetadata(process.env.ZITADEL_ORG_ID ?? "") + } + ) + .then((resp) => resp.supportedLanguages); +} + export function getLegalAndSupportSettings( server: ZitadelServer ): Promise { @@ -56,6 +69,7 @@ export function getPasswordComplexitySettings( server: ZitadelServer ): Promise { const settingsService = settings.getSettings(server); + return settingsService .getPasswordComplexitySettings( {}, @@ -66,6 +80,22 @@ export function getPasswordComplexitySettings( .then((resp) => resp.settings); } +export function createSession( + server: ZitadelServer, + loginName: string +): Promise { + const sessionService = session.getSession(server); + return sessionService.createSession({ checks: { user: { loginName } } }, {}); +} + +export function setSession( + server: ZitadelServer, + loginName: string +): Promise { + const sessionService = session.getSession(server); + return sessionService.setSession({ checks: { user: { loginName } } }, {}); +} + export type AddHumanUserData = { firstName: string; lastName: string; diff --git a/apps/login/ui/PasswordForm.tsx b/apps/login/ui/PasswordForm.tsx new file mode 100644 index 00000000000..ed994e02015 --- /dev/null +++ b/apps/login/ui/PasswordForm.tsx @@ -0,0 +1,84 @@ +"use client"; + +import { useState } from "react"; +import { Button, ButtonVariants } from "./Button"; +import { TextInput } from "./Input"; +import { useForm } from "react-hook-form"; +import { useRouter } from "next/navigation"; +import { Spinner } from "./Spinner"; + +type Inputs = { + password: string; +}; + +export default function UsernameForm() { + const { register, handleSubmit, formState } = useForm({ + mode: "onBlur", + }); + + const [error, setError] = useState(""); + + const [loading, setLoading] = useState(false); + + const router = useRouter(); + + async function submitUsername(values: Inputs) { + setLoading(true); + const res = await fetch("/session", { + method: "PUT", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + password: values.password, + }), + }); + + if (!res.ok) { + setLoading(false); + throw new Error("Failed to register user"); + } + + setLoading(false); + return res.json(); + } + + function submitAndLink(value: Inputs): Promise { + return submitUsername(value).then((resp: any) => { + return router.push(`/password`); + }); + } + + const { errors } = formState; + + return ( +
+
+ +
+ +
+ {/* */} + + +
+
+ ); +} diff --git a/apps/login/ui/RegisterForm.tsx b/apps/login/ui/RegisterForm.tsx index a92724a6e0f..85a114010ee 100644 --- a/apps/login/ui/RegisterForm.tsx +++ b/apps/login/ui/RegisterForm.tsx @@ -1,6 +1,9 @@ "use client"; -import { PasswordComplexityPolicy, PrivacyPolicy } from "@zitadel/server"; +import { + LegalAndSupportSettings, + PasswordComplexitySettings, +} from "@zitadel/server"; import PasswordComplexity from "./PasswordComplexity"; import { useState } from "react"; import { Button, ButtonVariants } from "./Button"; @@ -27,8 +30,8 @@ type Inputs = | FieldValues; type Props = { - privacyPolicy: PrivacyPolicy; - passwordComplexityPolicy: PasswordComplexityPolicy; + privacyPolicy: LegalAndSupportSettings; + passwordComplexityPolicy: PasswordComplexitySettings; }; export default function RegisterForm({ @@ -90,10 +93,10 @@ export default function RegisterForm({ const policyIsValid = passwordComplexityPolicy && - (passwordComplexityPolicy.hasLowercase ? hasLowercase : true) && - (passwordComplexityPolicy.hasNumber ? hasNumber : true) && - (passwordComplexityPolicy.hasUppercase ? hasUppercase : true) && - (passwordComplexityPolicy.hasSymbol ? hasSymbol : true) && + (passwordComplexityPolicy.requiresLowercase ? hasLowercase : true) && + (passwordComplexityPolicy.requiresNumber ? hasNumber : true) && + (passwordComplexityPolicy.requiresUppercase ? hasUppercase : true) && + (passwordComplexityPolicy.requiresSymbol ? hasSymbol : true) && hasMinLength; return ( diff --git a/apps/login/ui/ThemeWrapper.tsx b/apps/login/ui/ThemeWrapper.tsx index b71fd6ef2ec..b8caf797def 100644 --- a/apps/login/ui/ThemeWrapper.tsx +++ b/apps/login/ui/ThemeWrapper.tsx @@ -1,10 +1,11 @@ "use client"; -import { setTheme, LabelPolicyColors } from "#/utils/colors"; +import { BrandingSettings } from "@zitadel/server"; +import { setTheme } from "#/utils/colors"; import { useEffect } from "react"; type Props = { - branding: LabelPolicyColors | undefined; + branding: Partial | undefined; children: React.ReactNode; }; diff --git a/apps/login/ui/UsernameForm.tsx b/apps/login/ui/UsernameForm.tsx new file mode 100644 index 00000000000..fe05c8b8c4e --- /dev/null +++ b/apps/login/ui/UsernameForm.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { useState } from "react"; +import { Button, ButtonVariants } from "./Button"; +import { TextInput } from "./Input"; +import { useForm } from "react-hook-form"; +import { useRouter } from "next/navigation"; +import { Spinner } from "./Spinner"; + +type Inputs = { + loginName: string; +}; + +export default function UsernameForm() { + const { register, handleSubmit, formState } = useForm({ + mode: "onBlur", + }); + + const [loading, setLoading] = useState(false); + + const router = useRouter(); + + async function submitUsername(values: Inputs) { + setLoading(true); + const res = await fetch("/session", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + loginName: values.loginName, + }), + }); + + if (!res.ok) { + setLoading(false); + throw new Error("Failed to register user"); + } + + setLoading(false); + return res.json(); + } + + function submitAndLink(value: Inputs): Promise { + return submitUsername(value).then((resp: any) => { + return router.push(`/password`); + }); + } + + const { errors } = formState; + + return ( +
+
+ +
+ +
+ {/* */} + + +
+
+ ); +} diff --git a/apps/login/utils/colors.ts b/apps/login/utils/colors.ts index 161d2f203c7..75b1c8c3568 100644 --- a/apps/login/utils/colors.ts +++ b/apps/login/utils/colors.ts @@ -1,5 +1,7 @@ import tinycolor from "tinycolor2"; +import { BrandingSettings } from "@zitadel/server"; + export interface Color { name: string; hex: string; @@ -52,32 +54,36 @@ export type LabelPolicyColors = { primaryColorDark: string; }; -export function setTheme(document: any, policy?: LabelPolicyColors) { - const lP = { - backgroundColor: BACKGROUND, - backgroundColorDark: DARK_BACKGROUND, - primaryColor: PRIMARY, - primaryColorDark: DARK_PRIMARY, - warnColor: WARN, - warnColorDark: DARK_WARN, - fontColor: TEXT, - fontColorDark: DARK_TEXT, - linkColor: TEXT, - linkColorDark: DARK_TEXT, +type BrandingColors = { + lightTheme: { + backgroundColor: string; + fontColor: string; + primaryColor: string; + warnColor: string; }; + darkTheme: { + backgroundColor: string; + fontColor: string; + primaryColor: string; + warnColor: string; + }; +}; - if (policy) { - lP.backgroundColor = policy.backgroundColor; - lP.backgroundColorDark = policy.backgroundColorDark; - lP.primaryColor = policy.primaryColor; - lP.primaryColorDark = policy.primaryColorDark; - lP.warnColor = policy.warnColor; - lP.warnColorDark = policy.warnColorDark; - lP.fontColor = policy.fontColor; - lP.fontColorDark = policy.fontColorDark; - lP.linkColor = policy.fontColor; - lP.linkColorDark = policy.fontColorDark; - } +export function setTheme(document: any, policy?: Partial) { + const lP: BrandingColors = { + lightTheme: { + backgroundColor: policy?.lightTheme?.backgroundColor ?? BACKGROUND, + fontColor: policy?.lightTheme?.fontColor ?? TEXT, + primaryColor: policy?.lightTheme?.primaryColor ?? PRIMARY, + warnColor: policy?.lightTheme?.warnColor ?? WARN, + }, + darkTheme: { + backgroundColor: policy?.darkTheme?.backgroundColor ?? DARK_BACKGROUND, + fontColor: policy?.darkTheme?.fontColor ?? DARK_TEXT, + primaryColor: policy?.darkTheme?.primaryColor ?? DARK_PRIMARY, + warnColor: policy?.darkTheme?.warnColor ?? DARK_WARN, + }, + }; const dark = computeMap(lP, true); const light = computeMap(lP, false); @@ -177,25 +183,24 @@ function getContrast(color: string): string { } } -export function computeMap( - labelpolicy: LabelPolicyColors, - dark: boolean -): ColorMap { +export function computeMap(branding: BrandingColors, dark: boolean): ColorMap { return { background: computeColors( - dark ? labelpolicy.backgroundColorDark : labelpolicy.backgroundColor + dark + ? branding.darkTheme.backgroundColor + : branding.lightTheme.backgroundColor ), primary: computeColors( - dark ? labelpolicy.primaryColorDark : labelpolicy.primaryColor + dark ? branding.darkTheme.primaryColor : branding.lightTheme.primaryColor ), warn: computeColors( - dark ? labelpolicy.warnColorDark : labelpolicy.warnColor + dark ? branding.darkTheme.warnColor : branding.lightTheme.warnColor ), text: computeColors( - dark ? labelpolicy.fontColorDark : labelpolicy.fontColor + dark ? branding.darkTheme.fontColor : branding.lightTheme.fontColor ), link: computeColors( - dark ? labelpolicy.fontColorDark : labelpolicy.fontColor + dark ? branding.darkTheme.fontColor : branding.lightTheme.fontColor ), }; } diff --git a/packages/zitadel-server/package.json b/packages/zitadel-server/package.json index 32598e1fff5..e211e7c9893 100644 --- a/packages/zitadel-server/package.json +++ b/packages/zitadel-server/package.json @@ -4,14 +4,15 @@ "main": "./dist/index.js", "module": "./dist/index.mjs", "types": "./dist/index.d.ts", + "type": "commonjs", "sideEffects": false, "license": "MIT", "files": [ "dist/**" ], "scripts": { - "build": "tsup src/index.ts src/*/index.ts --format esm,cjs --dts", - "dev": "tsup src/index.ts src/*/index.ts --format esm,cjs --watch --dts", + "build": "tsup --dts", + "dev": "tsup --dts --watch", "lint": "eslint \"src/**/*.ts*\"", "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist", "prebuild": "pnpm run generate", diff --git a/packages/zitadel-server/src/index.ts b/packages/zitadel-server/src/index.ts index 174ae537ff5..84016632487 100644 --- a/packages/zitadel-server/src/index.ts +++ b/packages/zitadel-server/src/index.ts @@ -1,13 +1,36 @@ -export * from "./server"; +import * as management from "./management"; +import * as settings from "./v2/settings"; +import * as session from "./v2/session"; + +import * as login from "./proto/server/zitadel/settings/v2alpha/login_settings"; +import * as password from "./proto/server/zitadel/settings/v2alpha/password_settings"; +import * as legal from "./proto/server/zitadel/settings/v2alpha/legal_settings"; + +export { + BrandingSettings, + Theme, +} from "./proto/server/zitadel/settings/v2alpha/branding_settings"; + +export { type LegalAndSupportSettings } from "./proto/server/zitadel/settings/v2alpha/legal_settings"; +export { type PasswordComplexitySettings } from "./proto/server/zitadel/settings/v2alpha/password_settings"; + +import { + getServers, + initializeServer, + ZitadelServer, + ZitadelServerOptions, +} from "./server"; export * from "./middleware"; -export * as management from "./management"; -export * as settings from "./v2/settings"; -// export * as auth from "./auth"; -// export * as management from "./management"; -// export * as admin from "./admin"; -// export * as system from "./system"; - -// export * from "./proto/server/zitadel/management"; -// export * from "./proto/server/zitadel/system"; -// export * from "./proto/server/zitadel/admin"; +export { + getServers, + ZitadelServer, + type ZitadelServerOptions, + initializeServer, + management, + session, + settings, + login, + password, + legal, +}; diff --git a/packages/zitadel-server/src/management/index.ts b/packages/zitadel-server/src/management/index.ts index a5567f3802f..5df1ddd3192 100644 --- a/packages/zitadel-server/src/management/index.ts +++ b/packages/zitadel-server/src/management/index.ts @@ -1,3 +1,3 @@ export * from "./management"; export * as management from "../proto/server/zitadel/management"; -export * from "../proto/server/zitadel/policy"; +export * as policy from "../proto/server/zitadel/policy"; diff --git a/packages/zitadel-server/src/server.ts b/packages/zitadel-server/src/server.ts index 2a6d2da456c..a74e55189ff 100644 --- a/packages/zitadel-server/src/server.ts +++ b/packages/zitadel-server/src/server.ts @@ -1,3 +1,11 @@ +import { createChannel, createClientFactory } from "nice-grpc"; +import { + SettingsServiceClient, + SettingsServiceDefinition, +} from "./proto/server/zitadel/settings/v2alpha/settings_service"; +import { authMiddleware } from "./middleware"; +import { CompatServiceDefinition } from "nice-grpc/lib/service-definitions"; + let apps: ZitadelServer[] = []; export interface ZitadelServerProps { @@ -49,3 +57,18 @@ export function getServer(name?: string): ZitadelServer { } } } + +export const createClient = ( + definition: CompatServiceDefinition, + apiUrl: string, + token: string +) => { + if (!apiUrl) { + throw Error("ZITADEL_API_URL not set"); + } + + const channel = createChannel(process.env.ZITADEL_API_URL ?? ""); + return createClientFactory() + .use(authMiddleware(token)) + .create(definition, channel) as Client; +}; diff --git a/packages/zitadel-server/src/v2/session/index.ts b/packages/zitadel-server/src/v2/session/index.ts new file mode 100644 index 00000000000..cc86a9f3a0d --- /dev/null +++ b/packages/zitadel-server/src/v2/session/index.ts @@ -0,0 +1,2 @@ +export * from "./session"; +export * from "../../proto/server/zitadel/session/v2alpha/session"; diff --git a/packages/zitadel-server/src/v2/session/session.ts b/packages/zitadel-server/src/v2/session/session.ts new file mode 100644 index 00000000000..493cf4091a6 --- /dev/null +++ b/packages/zitadel-server/src/v2/session/session.ts @@ -0,0 +1,29 @@ +import { CompatServiceDefinition } from "nice-grpc/lib/service-definitions"; + +import { + SessionServiceClient, + SessionServiceDefinition, +} from "../../proto/server/zitadel/session/v2alpha/session_service"; + +import { ZitadelServer, createClient, getServers } from "../../server"; + +export const getSession = (server?: string | ZitadelServer) => { + console.log("init session"); + let config; + if (server && typeof server === "string") { + const apps = getServers(); + config = apps.find((a) => a.name === server)?.config; + } else if (server && typeof server === "object") { + config = server.config; + } + + if (!config) { + throw Error("No ZITADEL server found"); + } + + return createClient( + SessionServiceDefinition as CompatServiceDefinition, + config.apiUrl, + config.token + ); +}; diff --git a/packages/zitadel-server/src/v2/settings/index.ts b/packages/zitadel-server/src/v2/settings/index.ts index e96aa88159a..3f58216cf15 100644 --- a/packages/zitadel-server/src/v2/settings/index.ts +++ b/packages/zitadel-server/src/v2/settings/index.ts @@ -1,6 +1,2 @@ export * from "./settings"; export * from "../../proto/server/zitadel/settings/v2alpha/settings"; -export * as branding from "../../proto/server/zitadel/settings/v2alpha/branding_settings"; -export * as login from "../../proto/server/zitadel/settings/v2alpha/login_settings"; -export * as password from "../../proto/server/zitadel/settings/v2alpha/password_settings"; -export * as legal from "../../proto/server/zitadel/settings/v2alpha/legal_settings"; diff --git a/packages/zitadel-server/src/v2/settings/settings.ts b/packages/zitadel-server/src/v2/settings/settings.ts index 4c495d907e4..e3cc156e7e9 100644 --- a/packages/zitadel-server/src/v2/settings/settings.ts +++ b/packages/zitadel-server/src/v2/settings/settings.ts @@ -1,28 +1,11 @@ import { CompatServiceDefinition } from "nice-grpc/lib/service-definitions"; -import { createChannel, createClientFactory } from "nice-grpc"; import { SettingsServiceClient, SettingsServiceDefinition, } from "../../proto/server/zitadel/settings/v2alpha/settings_service"; -import { authMiddleware } from "../../middleware"; -import { ZitadelServer, getServers } from "../../server"; - -const createClient = ( - definition: CompatServiceDefinition, - apiUrl: string, - token: string -) => { - if (!apiUrl) { - throw Error("ZITADEL_API_URL not set"); - } - - const channel = createChannel(process.env.ZITADEL_API_URL ?? ""); - return createClientFactory() - .use(authMiddleware(token)) - .create(definition, channel) as Client; -}; +import { ZitadelServer, createClient, getServers } from "../../server"; export const getSettings = (server?: string | ZitadelServer) => { console.log("init settings"); diff --git a/packages/zitadel-server/tsconfig.json b/packages/zitadel-server/tsconfig.json index d64c667c7f6..3d12dfd6278 100644 --- a/packages/zitadel-server/tsconfig.json +++ b/packages/zitadel-server/tsconfig.json @@ -3,12 +3,7 @@ "include": ["src/**/*.ts"], "compilerOptions": { "baseUrl": ".", - "rootDir": "src", - "paths": { - "#": ["."], - "*": ["./*"], - "#/*": ["./*"] - } + "rootDir": "src" }, "exclude": ["dist", "build", "node_modules"] } diff --git a/packages/zitadel-server/tsup.config.ts b/packages/zitadel-server/tsup.config.ts new file mode 100644 index 00000000000..2341f37cd49 --- /dev/null +++ b/packages/zitadel-server/tsup.config.ts @@ -0,0 +1,13 @@ +import { defineConfig, Options } from "tsup"; + +export default defineConfig((options: Options) => ({ + treeshake: true, + splitting: true, + publicDir: true, + entry: ["src/index.ts", "src/**/index.ts"], + format: ["esm", "cjs"], + dts: true, + minify: true, + clean: true, + ...options, +}));