mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
feat: add api to handle license api keys
This commit is contained in:
2
apps/api/.env.example
Normal file
2
apps/api/.env.example
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
LEMON_SQUEEZY_API_KEY=""
|
||||||
|
LEMON_SQUEEZY_STORE_ID=""
|
||||||
28
apps/api/.gitignore
vendored
Normal file
28
apps/api/.gitignore
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# dev
|
||||||
|
.yarn/
|
||||||
|
!.yarn/releases
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
.idea/workspace.xml
|
||||||
|
.idea/usage.statistics.xml
|
||||||
|
.idea/shelf
|
||||||
|
|
||||||
|
# deps
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# env
|
||||||
|
.env
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
8
apps/api/README.md
Normal file
8
apps/api/README.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
```
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
open http://localhost:3000
|
||||||
|
```
|
||||||
15
apps/api/package.json
Normal file
15
apps/api/package.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "my-app",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "tsx watch src/index.ts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@hono/node-server": "^1.12.1",
|
||||||
|
"hono": "^4.5.8",
|
||||||
|
"dotenv": "^16.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.17",
|
||||||
|
"tsx": "^4.7.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
66
apps/api/src/index.ts
Normal file
66
apps/api/src/index.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { serve } from "@hono/node-server";
|
||||||
|
import { Hono } from "hono";
|
||||||
|
import { config } from "dotenv";
|
||||||
|
import { validateLemonSqueezyLicense } from "./utils";
|
||||||
|
import { cors } from "hono/cors";
|
||||||
|
|
||||||
|
config();
|
||||||
|
|
||||||
|
const app = new Hono();
|
||||||
|
|
||||||
|
app.use(
|
||||||
|
"/*",
|
||||||
|
cors({
|
||||||
|
origin: ["http://localhost:3000", "http://localhost:3001"], // Ajusta esto a los orígenes de tu aplicación Next.js
|
||||||
|
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
||||||
|
allowHeaders: ["Content-Type", "Authorization"],
|
||||||
|
exposeHeaders: ["Content-Length", "X-Kuma-Revision"],
|
||||||
|
maxAge: 600,
|
||||||
|
credentials: true,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export const LEMON_SQUEEZY_API_KEY = process.env.LEMON_SQUEEZY_API_KEY;
|
||||||
|
export const LEMON_SQUEEZY_STORE_ID = process.env.LEMON_SQUEEZY_STORE_ID;
|
||||||
|
|
||||||
|
app.get("/v1/health", (c) => {
|
||||||
|
return c.text("Hello Hono!");
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/v1/validate-license", async (c) => {
|
||||||
|
const { licenseKey } = await c.req.json();
|
||||||
|
|
||||||
|
if (!licenseKey) {
|
||||||
|
return c.json({ error: "License key is required" }, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const licenseValidation = await validateLemonSqueezyLicense(licenseKey);
|
||||||
|
|
||||||
|
if (licenseValidation.valid) {
|
||||||
|
return c.json({
|
||||||
|
valid: true,
|
||||||
|
message: "License is valid",
|
||||||
|
metadata: licenseValidation.meta,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return c.json(
|
||||||
|
{
|
||||||
|
valid: false,
|
||||||
|
message: licenseValidation.error || "Invalid license",
|
||||||
|
},
|
||||||
|
400,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error during license validation:", error);
|
||||||
|
return c.json({ error: "Internal server error" }, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const port = 4000;
|
||||||
|
console.log(`Server is running on port ${port}`);
|
||||||
|
|
||||||
|
serve({
|
||||||
|
fetch: app.fetch,
|
||||||
|
port,
|
||||||
|
});
|
||||||
16
apps/api/src/types.ts
Normal file
16
apps/api/src/types.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export interface LemonSqueezyLicenseResponse {
|
||||||
|
valid: boolean;
|
||||||
|
error?: string;
|
||||||
|
meta?: {
|
||||||
|
store_id: string;
|
||||||
|
order_id: number;
|
||||||
|
order_item_id: number;
|
||||||
|
product_id: number;
|
||||||
|
product_name: string;
|
||||||
|
variant_id: number;
|
||||||
|
variant_name: string;
|
||||||
|
customer_id: number;
|
||||||
|
customer_name: string;
|
||||||
|
customer_email: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
28
apps/api/src/utils.ts
Normal file
28
apps/api/src/utils.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { LEMON_SQUEEZY_API_KEY, LEMON_SQUEEZY_STORE_ID } from ".";
|
||||||
|
import type { LemonSqueezyLicenseResponse } from "./types";
|
||||||
|
|
||||||
|
export const validateLemonSqueezyLicense = async (
|
||||||
|
licenseKey: string,
|
||||||
|
): Promise<LemonSqueezyLicenseResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
"https://api.lemonsqueezy.com/v1/licenses/validate",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"x-api-key": LEMON_SQUEEZY_API_KEY as string,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
license_key: licenseKey,
|
||||||
|
store_id: LEMON_SQUEEZY_STORE_ID as string,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error validating license:", error);
|
||||||
|
return { valid: false, error: "Error validating license" };
|
||||||
|
}
|
||||||
|
};
|
||||||
14
apps/api/tsconfig.json
Normal file
14
apps/api/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"types": [
|
||||||
|
"node"
|
||||||
|
],
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"jsxImportSource": "hono/jsx",
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,6 +77,12 @@ export const SettingsLayout = ({ children }: Props) => {
|
|||||||
icon: Bell,
|
icon: Bell,
|
||||||
href: "/dashboard/settings/notifications",
|
href: "/dashboard/settings/notifications",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "License",
|
||||||
|
label: "",
|
||||||
|
icon: KeyIcon,
|
||||||
|
href: "/dashboard/settings/license",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
...(user?.canAccessToSSHKeys
|
...(user?.canAccessToSSHKeys
|
||||||
@@ -102,6 +108,7 @@ import {
|
|||||||
Activity,
|
Activity,
|
||||||
Bell,
|
Bell,
|
||||||
Database,
|
Database,
|
||||||
|
KeyIcon,
|
||||||
KeyRound,
|
KeyRound,
|
||||||
type LucideIcon,
|
type LucideIcon,
|
||||||
Route,
|
Route,
|
||||||
|
|||||||
1
apps/dokploy/drizzle/0034_silent_slayback.sql
Normal file
1
apps/dokploy/drizzle/0034_silent_slayback.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE "admin" ADD COLUMN "licenseKey" text;
|
||||||
3084
apps/dokploy/drizzle/meta/0034_snapshot.json
Normal file
3084
apps/dokploy/drizzle/meta/0034_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -239,6 +239,13 @@
|
|||||||
"when": 1724555040199,
|
"when": 1724555040199,
|
||||||
"tag": "0033_sweet_black_bird",
|
"tag": "0033_sweet_black_bird",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 34,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1724631497207,
|
||||||
|
"tag": "0034_silent_slayback",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
145
apps/dokploy/pages/dashboard/settings/license.tsx
Normal file
145
apps/dokploy/pages/dashboard/settings/license.tsx
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import { DashboardLayout } from "@/components/layouts/dashboard-layout";
|
||||||
|
import { SettingsLayout } from "@/components/layouts/settings-layout";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
CardDescription,
|
||||||
|
CardContent,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormControl,
|
||||||
|
FormMessage,
|
||||||
|
Form,
|
||||||
|
} from "@/components/ui/form";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { validateRequest } from "@/server/auth/auth";
|
||||||
|
import { api } from "@/utils/api";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import type { GetServerSidePropsContext } from "next";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useEffect, type ReactElement } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
licenseKey: z.string().min(1, {
|
||||||
|
message: "License key is required",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
type Schema = z.infer<typeof schema>;
|
||||||
|
|
||||||
|
export default function License() {
|
||||||
|
const { data } = api.admin.one.useQuery();
|
||||||
|
const { mutateAsync, isLoading } = api.license.setLicense.useMutation();
|
||||||
|
const form = useForm<Schema>({
|
||||||
|
defaultValues: {
|
||||||
|
licenseKey: data?.licenseKey || "",
|
||||||
|
},
|
||||||
|
resolver: zodResolver(schema),
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (data?.licenseKey) {
|
||||||
|
form.reset({
|
||||||
|
licenseKey: data.licenseKey,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [data]);
|
||||||
|
|
||||||
|
const onSubmit = async (data: Schema) => {
|
||||||
|
await mutateAsync(data.licenseKey)
|
||||||
|
.then(async () => {
|
||||||
|
toast.success("License Key Saved");
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
toast.error("Error to save the license key");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="w-full">
|
||||||
|
<Card className="bg-transparent">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-xl">License</CardTitle>
|
||||||
|
<CardDescription className="flex flex-row gap-2">
|
||||||
|
Set your license key to unlock the features
|
||||||
|
<Link
|
||||||
|
target="_blank"
|
||||||
|
href="https://dokploy.com/pricing"
|
||||||
|
className="text-primary text-md"
|
||||||
|
>
|
||||||
|
See pricing
|
||||||
|
</Link>
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="flex w-full flex-col gap-4">
|
||||||
|
<Form {...form}>
|
||||||
|
<form
|
||||||
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
|
className="grid w-full gap-4 "
|
||||||
|
>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="licenseKey"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>License Key</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
className="w-full"
|
||||||
|
placeholder={"Enter your license key"}
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex justify-end">
|
||||||
|
<Button isLoading={isLoading} type="submit">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
License.getLayout = (page: ReactElement) => {
|
||||||
|
return (
|
||||||
|
<DashboardLayout tab={"settings"}>
|
||||||
|
<SettingsLayout>{page}</SettingsLayout>
|
||||||
|
</DashboardLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export async function getServerSideProps(
|
||||||
|
ctx: GetServerSidePropsContext<{ serviceId: string }>,
|
||||||
|
) {
|
||||||
|
const { user, session } = await validateRequest(ctx.req, ctx.res);
|
||||||
|
if (!user || user.rol === "user") {
|
||||||
|
return {
|
||||||
|
redirect: {
|
||||||
|
permanent: true,
|
||||||
|
destination: "/",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
props: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -25,6 +25,7 @@ import { securityRouter } from "./routers/security";
|
|||||||
import { settingsRouter } from "./routers/settings";
|
import { settingsRouter } from "./routers/settings";
|
||||||
import { sshRouter } from "./routers/ssh-key";
|
import { sshRouter } from "./routers/ssh-key";
|
||||||
import { userRouter } from "./routers/user";
|
import { userRouter } from "./routers/user";
|
||||||
|
import { licenseRouter } from "./routers/license";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is the primary router for your server.
|
* This is the primary router for your server.
|
||||||
@@ -58,6 +59,7 @@ export const appRouter = createTRPCRouter({
|
|||||||
cluster: clusterRouter,
|
cluster: clusterRouter,
|
||||||
notification: notificationRouter,
|
notification: notificationRouter,
|
||||||
sshKey: sshRouter,
|
sshKey: sshRouter,
|
||||||
|
license: licenseRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
// export type definition of API
|
// export type definition of API
|
||||||
|
|||||||
41
apps/dokploy/server/api/routers/license.ts
Normal file
41
apps/dokploy/server/api/routers/license.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { findAdmin, updateAdmin } from "../services/admin";
|
||||||
|
import { adminProcedure, createTRPCRouter } from "../trpc";
|
||||||
|
import { TRPCError } from "@trpc/server";
|
||||||
|
|
||||||
|
export const licenseRouter = createTRPCRouter({
|
||||||
|
setLicense: adminProcedure.input(z.string()).mutation(async ({ input }) => {
|
||||||
|
const admin = await findAdmin();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await fetch("http://127.0.0.1:4000/v1/validate-license", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
licenseKey: input,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await result.json();
|
||||||
|
|
||||||
|
if (!data.valid) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message: "License is invalid",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.valid) {
|
||||||
|
return await updateAdmin(admin.authId, {
|
||||||
|
licenseKey: input,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return await updateAdmin(admin.authId, {
|
||||||
|
licenseKey: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
});
|
||||||
@@ -53,11 +53,6 @@ import { canAccessToTraefikFiles } from "../services/user";
|
|||||||
import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
|
import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
|
||||||
import { parseRawConfig, processLogs } from "@/server/utils/access-log/utils";
|
import { parseRawConfig, processLogs } from "@/server/utils/access-log/utils";
|
||||||
import { logRotationManager } from "@/server/utils/access-log/handler";
|
import { logRotationManager } from "@/server/utils/access-log/handler";
|
||||||
import { observable } from "@trpc/server/observable";
|
|
||||||
import type { LogEntry } from "@/server/utils/access-log/types";
|
|
||||||
import EventEmitter from "node:events";
|
|
||||||
|
|
||||||
const ee = new EventEmitter();
|
|
||||||
|
|
||||||
export const settingsRouter = createTRPCRouter({
|
export const settingsRouter = createTRPCRouter({
|
||||||
reloadServer: adminProcedure.mutation(async () => {
|
reloadServer: adminProcedure.mutation(async () => {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export const admins = pgTable("admin", {
|
|||||||
sshPrivateKey: text("sshPrivateKey"),
|
sshPrivateKey: text("sshPrivateKey"),
|
||||||
enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
|
enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
|
||||||
enableLogRotation: boolean("enableLogRotation").notNull().default(false),
|
enableLogRotation: boolean("enableLogRotation").notNull().default(false),
|
||||||
|
licenseKey: text("licenseKey"),
|
||||||
authId: text("authId")
|
authId: text("authId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => auth.id, { onDelete: "cascade" }),
|
.references(() => auth.id, { onDelete: "cascade" }),
|
||||||
|
|||||||
37
pnpm-lock.yaml
generated
37
pnpm-lock.yaml
generated
@@ -36,6 +36,25 @@ importers:
|
|||||||
specifier: 4.16.2
|
specifier: 4.16.2
|
||||||
version: 4.16.2
|
version: 4.16.2
|
||||||
|
|
||||||
|
apps/api:
|
||||||
|
dependencies:
|
||||||
|
'@hono/node-server':
|
||||||
|
specifier: ^1.12.1
|
||||||
|
version: 1.12.1
|
||||||
|
dotenv:
|
||||||
|
specifier: ^16.3.1
|
||||||
|
version: 16.4.5
|
||||||
|
hono:
|
||||||
|
specifier: ^4.5.8
|
||||||
|
version: 4.5.8
|
||||||
|
devDependencies:
|
||||||
|
'@types/node':
|
||||||
|
specifier: ^20.11.17
|
||||||
|
version: 20.14.10
|
||||||
|
tsx:
|
||||||
|
specifier: ^4.7.1
|
||||||
|
version: 4.16.2
|
||||||
|
|
||||||
apps/docs:
|
apps/docs:
|
||||||
dependencies:
|
dependencies:
|
||||||
fumadocs-core:
|
fumadocs-core:
|
||||||
@@ -1662,6 +1681,10 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
tailwindcss: ^3.0
|
tailwindcss: ^3.0
|
||||||
|
|
||||||
|
'@hono/node-server@1.12.1':
|
||||||
|
resolution: {integrity: sha512-C9l+08O8xtXB7Ppmy8DjBFH1hYji7JKzsU32Yt1poIIbdPp6S7aOI8IldDHD9YFJ55lv2c21ovNrmxatlHfhAg==}
|
||||||
|
engines: {node: '>=18.14.1'}
|
||||||
|
|
||||||
'@hookform/resolvers@3.9.0':
|
'@hookform/resolvers@3.9.0':
|
||||||
resolution: {integrity: sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==}
|
resolution: {integrity: sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -5664,6 +5687,10 @@ packages:
|
|||||||
highlight.js@10.7.3:
|
highlight.js@10.7.3:
|
||||||
resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
|
resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
|
||||||
|
|
||||||
|
hono@4.5.8:
|
||||||
|
resolution: {integrity: sha512-pqpSlcdqGkpTTRpLYU1PnCz52gVr0zVR9H5GzMyJWuKQLLEBQxh96q45QizJ2PPX8NATtz2mu31/PKW/Jt+90Q==}
|
||||||
|
engines: {node: '>=16.0.0'}
|
||||||
|
|
||||||
html-to-text@9.0.5:
|
html-to-text@9.0.5:
|
||||||
resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==}
|
resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@@ -9670,6 +9697,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tailwindcss: 3.4.7
|
tailwindcss: 3.4.7
|
||||||
|
|
||||||
|
'@hono/node-server@1.12.1': {}
|
||||||
|
|
||||||
'@hookform/resolvers@3.9.0(react-hook-form@7.52.1(react@18.2.0))':
|
'@hookform/resolvers@3.9.0(react-hook-form@7.52.1(react@18.2.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
react-hook-form: 7.52.1(react@18.2.0)
|
react-hook-form: 7.52.1(react@18.2.0)
|
||||||
@@ -14053,7 +14082,7 @@ snapshots:
|
|||||||
eslint: 8.45.0
|
eslint: 8.45.0
|
||||||
eslint-import-resolver-node: 0.3.9
|
eslint-import-resolver-node: 0.3.9
|
||||||
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.45.0))(eslint@8.45.0)
|
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.45.0))(eslint@8.45.0)
|
||||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.45.0))(eslint@8.45.0))(eslint@8.45.0)
|
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.1)(eslint@8.45.0)
|
||||||
eslint-plugin-jsx-a11y: 6.9.0(eslint@8.45.0)
|
eslint-plugin-jsx-a11y: 6.9.0(eslint@8.45.0)
|
||||||
eslint-plugin-react: 7.35.0(eslint@8.45.0)
|
eslint-plugin-react: 7.35.0(eslint@8.45.0)
|
||||||
eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.45.0)
|
eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.45.0)
|
||||||
@@ -14077,7 +14106,7 @@ snapshots:
|
|||||||
enhanced-resolve: 5.17.1
|
enhanced-resolve: 5.17.1
|
||||||
eslint: 8.45.0
|
eslint: 8.45.0
|
||||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.45.0))(eslint@8.45.0))(eslint@8.45.0)
|
eslint-module-utils: 2.8.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.45.0))(eslint@8.45.0))(eslint@8.45.0)
|
||||||
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.45.0))(eslint@8.45.0))(eslint@8.45.0)
|
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.1)(eslint@8.45.0)
|
||||||
fast-glob: 3.3.2
|
fast-glob: 3.3.2
|
||||||
get-tsconfig: 4.7.5
|
get-tsconfig: 4.7.5
|
||||||
is-core-module: 2.15.0
|
is-core-module: 2.15.0
|
||||||
@@ -14099,7 +14128,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.45.0))(eslint@8.45.0))(eslint@8.45.0):
|
eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.21.0(eslint@8.45.0)(typescript@5.1.6))(eslint-import-resolver-typescript@3.6.1)(eslint@8.45.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
array-includes: 3.1.8
|
array-includes: 3.1.8
|
||||||
array.prototype.findlastindex: 1.2.5
|
array.prototype.findlastindex: 1.2.5
|
||||||
@@ -14806,6 +14835,8 @@ snapshots:
|
|||||||
|
|
||||||
highlight.js@10.7.3: {}
|
highlight.js@10.7.3: {}
|
||||||
|
|
||||||
|
hono@4.5.8: {}
|
||||||
|
|
||||||
html-to-text@9.0.5:
|
html-to-text@9.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@selderee/plugin-htmlparser2': 0.11.0
|
'@selderee/plugin-htmlparser2': 0.11.0
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ packages:
|
|||||||
- "apps/dokploy"
|
- "apps/dokploy"
|
||||||
- "apps/docs"
|
- "apps/docs"
|
||||||
- "apps/website"
|
- "apps/website"
|
||||||
|
- "apps/api"
|
||||||
|
|||||||
Reference in New Issue
Block a user