mirror of
https://github.com/jordienr/zenblog.git
synced 2026-10-05 21:36:42 -04:00
wip
This commit is contained in:
@@ -1,11 +1,47 @@
|
||||
import { Hono } from "hono";
|
||||
import { Context, Hono } from "hono";
|
||||
import { zValidator } from "@hono/zod-validator";
|
||||
import { z } from "zod";
|
||||
import { logger } from "hono/logger";
|
||||
import { prettyJSON } from "hono/pretty-json";
|
||||
import { handle } from "hono/vercel";
|
||||
import { createClient } from "@/lib/server/supabase";
|
||||
import { createId } from "@/lib/create-id";
|
||||
export const dynamic = "force-dynamic";
|
||||
import bcrypt from "bcrypt";
|
||||
import { axiom } from "lib/axiom";
|
||||
|
||||
const UnauthorizedError = (c: Context) => {
|
||||
return c.json({ message: "Unauthorized" }, { status: 401 });
|
||||
};
|
||||
|
||||
const BlogNotFoundError = (c: Context) => {
|
||||
return c.json({ message: "Blog not found" }, { status: 404 });
|
||||
};
|
||||
|
||||
const ErrorRotatingAPIKey = (c: Context) => {
|
||||
return c.json({ message: "Error rotating API key" }, { status: 500 });
|
||||
};
|
||||
|
||||
const errors = {
|
||||
UnauthorizedError,
|
||||
BlogNotFoundError,
|
||||
ErrorRotatingAPIKey,
|
||||
};
|
||||
|
||||
const handleError = (c: Context, error: keyof typeof errors, rawLog: any) => {
|
||||
console.log("🔴", error);
|
||||
axiom.ingest("api", {
|
||||
message: error,
|
||||
error: true,
|
||||
blogId: c.req.param("blogId"),
|
||||
userId: c.req.param("userId"),
|
||||
method: c.req.method,
|
||||
path: c.req.url,
|
||||
rawLog,
|
||||
});
|
||||
|
||||
return errors[error](c);
|
||||
};
|
||||
|
||||
const getUser = async () => {
|
||||
const supabase = createClient();
|
||||
@@ -17,26 +53,65 @@ const getUser = async () => {
|
||||
};
|
||||
};
|
||||
|
||||
const api = new Hono().post(
|
||||
"/test",
|
||||
zValidator(
|
||||
"json",
|
||||
z.object({
|
||||
name: z.string().min(1),
|
||||
})
|
||||
),
|
||||
async (c) => {
|
||||
const api = new Hono()
|
||||
// Rotates the API key for a blog
|
||||
.post("/blogs/:blogId/api-keys/rotate", async (c) => {
|
||||
const { user } = await getUser();
|
||||
console.log("USER -->>", user?.email);
|
||||
return c.json({ message: "test" });
|
||||
}
|
||||
);
|
||||
const supabase = createClient();
|
||||
|
||||
if (!user) {
|
||||
return UnauthorizedError(c);
|
||||
}
|
||||
|
||||
// Check that the user is the owner of the blog
|
||||
const { data: blog, error: blogError } = await supabase
|
||||
.from("blogs")
|
||||
.select("id")
|
||||
.eq("id", c.req.param("blogId"))
|
||||
.eq("user_id", user?.id)
|
||||
.single();
|
||||
|
||||
if (blogError || !blog) {
|
||||
return BlogNotFoundError(c);
|
||||
}
|
||||
|
||||
// Create API key
|
||||
const newAPIKey = createId({ type: "blog", secret: true });
|
||||
const hashedAPIKey = await bcrypt.hash(newAPIKey, 10);
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from("blogs")
|
||||
.update({
|
||||
access_token: hashedAPIKey,
|
||||
})
|
||||
.eq("id", blog?.id);
|
||||
|
||||
if (error) {
|
||||
console.log(error);
|
||||
return handleError(c, "ErrorRotatingAPIKey", error);
|
||||
}
|
||||
|
||||
// Return the new API key to the client for use / storage
|
||||
return c.json({ message: "success", apiKey: newAPIKey }, 200);
|
||||
});
|
||||
|
||||
const app = new Hono()
|
||||
.basePath("/api")
|
||||
// MIDDLEWARE
|
||||
.use("*", logger())
|
||||
.use("*", prettyJSON())
|
||||
.use("*", async (c, next) => {
|
||||
const start = Date.now();
|
||||
await next();
|
||||
const duration = Date.now() - start;
|
||||
axiom.ingest("api", {
|
||||
message: "Request completed",
|
||||
duration,
|
||||
method: c.req.method,
|
||||
path: c.req.url,
|
||||
status: c.res.status,
|
||||
});
|
||||
})
|
||||
// ROUTES
|
||||
.route("/v2", api);
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Axiom } from "@axiomhq/js";
|
||||
|
||||
const axiom = new Axiom({
|
||||
token: process.env.AXIOM_TOKEN,
|
||||
});
|
||||
|
||||
export { axiom };
|
||||
@@ -11,6 +11,7 @@
|
||||
"supabase:typegen": "npx supabase gen types typescript --local --schema public > src/types/supabase.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@axiomhq/js": "^1.2.0",
|
||||
"@builder.io/react-hydration-overlay": "^0.0.8",
|
||||
"@chronark/zod-bird": "^0.3.9",
|
||||
"@emoji-mart/data": "^1.1.2",
|
||||
@@ -65,6 +66,7 @@
|
||||
"@uidotdev/usehooks": "^2.0.1",
|
||||
"@vercel/og": "^0.6.2",
|
||||
"ai": "^3.1.30",
|
||||
"bcrypt": "^5.1.1",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
@@ -112,6 +114,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/eslint": "^8.21.3",
|
||||
"@types/formidable": "^3.4.3",
|
||||
"@types/lowlight": "^0.0.7",
|
||||
|
||||
@@ -5,17 +5,19 @@ import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-semibold ring-offset-white transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-orange-200 focus-visible focus-visible:border-orange-700 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-zinc-950 ring-1 ring-transparent antialiased dark:focus-visible:ring-zinc-300 gap-1.5 [&>svg]:w-4 leading-none [&>svg]:opacity-60 active:scale-95",
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-lg text-sm font-semibold ring-offset-white transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-orange-200 focus-visible focus-visible:border-orange-700 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-zinc-950 ring-1 ring-transparent antialiased dark:focus-visible:ring-zinc-300 gap-1.5 [&>svg]:w-4 leading-none [&>svg]:opacity-60 active:scale-95 border border-transparent",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border border-slate-500/20 border-b-slate-800/70 border-t-slate-500/70 bg-gradient-to-b from-slate-700 to-slate-800 text-white shadow-md ring-slate-800 flex items-center hover:brightness-125",
|
||||
destructive: "bg-red-500 text-zinc-50 hover:bg-red-500/90",
|
||||
"border-slate-500/20 border-b-slate-800/70 border-t-slate-500/70 bg-gradient-to-b from-slate-700 to-slate-800 text-white shadow-md ring-slate-800 flex items-center hover:brightness-125",
|
||||
destructive:
|
||||
"border-red-400/20 border-b-red-700/70 border-t-red-400/70 bg-gradient-to-b from-red-500 to-red-500 text-white shadow-md ring-red-600 flex items-center hover:brightness-110",
|
||||
|
||||
outline:
|
||||
"border border-transparent ring-zinc-200 bg-white hover:bg-zinc-100 hover:text-zinc-900 text-zinc-600",
|
||||
"border-transparent ring-zinc-200 bg-white hover:bg-zinc-100 hover:text-zinc-900 text-zinc-600",
|
||||
secondary:
|
||||
"bg-zinc-100 hover:bg-orange-100/70 hover:text-orange-600 text-zinc-600",
|
||||
"bg-zinc-100 hover:bg-zinc-100/70 text-zinc-700 hover:text-zinc-800",
|
||||
ghost:
|
||||
"hover:bg-zinc-100 hover:text-zinc-900 dark:hover:bg-zinc-800 dark:hover:text-zinc-50 text-zinc-600",
|
||||
link: "text-zinc-800 underline-offset-4 hover:underline dark:text-zinc-50",
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
import { Blog, DeleteBlogRes, PatchBlog } from "@/lib/models/blogs/Blogs";
|
||||
import { z } from "zod";
|
||||
import { getPostBySlugRes, getPostsRes } from "../models/posts/Posts";
|
||||
import { GetBlogRes } from "../models/blogs/Blogs";
|
||||
import { JSONContent } from "@tiptap/react";
|
||||
|
||||
export type UpdatePostBody = {
|
||||
title?: string;
|
||||
slug?: string;
|
||||
published?: boolean;
|
||||
content?: JSONContent;
|
||||
cover_image?: string;
|
||||
};
|
||||
|
||||
export function createAPIClient() {
|
||||
async function _fetch<T extends z.ZodTypeAny>(
|
||||
input: RequestInfo,
|
||||
init: RequestInit,
|
||||
type: T
|
||||
): Promise<z.infer<T>> {
|
||||
const headers = new Headers(init?.headers);
|
||||
const URL_START = "/api/v1";
|
||||
const res = await fetch(URL_START + input, { ...init, headers });
|
||||
|
||||
if (!res.ok) {
|
||||
console.error(`[🚨 ${init.method} ${input}] `, res.statusText);
|
||||
throw new Error(res.statusText);
|
||||
}
|
||||
const data = await res.json();
|
||||
|
||||
const result = await type.safeParseAsync(data);
|
||||
|
||||
if (!result.success) {
|
||||
console.error(`[ZOD] `, result.error.message, data);
|
||||
throw new Error(result.error.message);
|
||||
}
|
||||
|
||||
return result.data;
|
||||
}
|
||||
|
||||
async function deleteBlog(slug: string) {
|
||||
const res = await _fetch(
|
||||
`/blogs/${slug}`,
|
||||
{ method: "DELETE" },
|
||||
DeleteBlogRes
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async function patchBlog(slug: string, body: PatchBlog) {
|
||||
const res = await _fetch(
|
||||
`/blogs/${slug}`,
|
||||
{ method: "PATCH", body: JSON.stringify(body) },
|
||||
GetBlogRes
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async function createBlog({
|
||||
title,
|
||||
description,
|
||||
emoji,
|
||||
}: {
|
||||
title: string;
|
||||
description: string;
|
||||
emoji: string;
|
||||
}) {
|
||||
const res = await _fetch(
|
||||
"/blogs",
|
||||
{
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
title,
|
||||
description,
|
||||
emoji,
|
||||
}),
|
||||
},
|
||||
GetBlogRes
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async function getPostsForBlog(blogId: string) {
|
||||
const res = await _fetch(
|
||||
`/blogs/${blogId}/posts`,
|
||||
{ method: "GET" },
|
||||
getPostsRes
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async function getPostBySlug(blogId: string, postSlug: string) {
|
||||
const res = await _fetch(
|
||||
`/blogs/${blogId}/posts/${postSlug}`,
|
||||
{ method: "GET" },
|
||||
getPostBySlugRes
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async function deletePostBySlug(blogId: string, postSlug: string) {
|
||||
const res = await _fetch(
|
||||
`/blogs/${blogId}/posts/${postSlug}`,
|
||||
{ method: "DELETE" },
|
||||
z.object({ success: z.boolean() })
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async function updatePostBySlug(
|
||||
blogId: string,
|
||||
postSlug: string,
|
||||
body: UpdatePostBody
|
||||
) {
|
||||
const res = await _fetch(
|
||||
`/blogs/${blogId}/posts/${postSlug}`,
|
||||
{ method: "PATCH", body: JSON.stringify(body) },
|
||||
z.object({ success: z.boolean() })
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// TODO!: move these methods to separate files this is getting too big
|
||||
return {
|
||||
user: {
|
||||
setup: () =>
|
||||
_fetch(
|
||||
"/user/setup",
|
||||
{ method: "GET" },
|
||||
z.object({ success: z.boolean() })
|
||||
),
|
||||
},
|
||||
blogs: {
|
||||
update: patchBlog,
|
||||
delete: deleteBlog,
|
||||
create: createBlog,
|
||||
},
|
||||
posts: {
|
||||
getAll: getPostsForBlog,
|
||||
get: getPostBySlug,
|
||||
delete: deletePostBySlug,
|
||||
update: updatePostBySlug,
|
||||
},
|
||||
images: {
|
||||
getAll: (blogId: string) =>
|
||||
_fetch(
|
||||
`/blogs/${blogId}/images`,
|
||||
{ method: "GET" },
|
||||
z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
url: z.string(),
|
||||
createdAt: z.string(),
|
||||
updatedAt: z.string(),
|
||||
})
|
||||
)
|
||||
),
|
||||
|
||||
upload(blogId: string, file: File) {
|
||||
const body = new FormData();
|
||||
body.append("file", file);
|
||||
|
||||
return _fetch(
|
||||
`/upload`,
|
||||
{ method: "POST", body: JSON.stringify(body) },
|
||||
z.any()
|
||||
);
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -17,6 +17,16 @@ import Link from "next/link";
|
||||
import { getHostedBlogUrl } from "@/utils/get-hosted-blog-url";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { createId } from "@/lib/create-id";
|
||||
import { API } from "app/utils/api-client";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
} from "@/components/ui/dialog";
|
||||
import { DialogTitle } from "@radix-ui/react-dialog";
|
||||
|
||||
export default function BlogSettings() {
|
||||
type FormData = {
|
||||
@@ -45,12 +55,6 @@ export default function BlogSettings() {
|
||||
|
||||
const updateBlog = useUpdateBlogMutation();
|
||||
|
||||
const accessTokenToShow = blog?.access_token
|
||||
? blog.access_token.slice(0, 4) +
|
||||
"......................" +
|
||||
blog.access_token.slice(-4)
|
||||
: "";
|
||||
|
||||
const onSubmit = handleSubmit(async (formData) => {
|
||||
try {
|
||||
console.log(formData);
|
||||
@@ -90,11 +94,26 @@ export default function BlogSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
const generateAccessToken = async () => {
|
||||
const newId = createId({ type: "blog", secret: true });
|
||||
await updateBlog.mutateAsync({ id: blogId, access_token: newId });
|
||||
const [newAPIKey, setNewAPIKey] = useState("");
|
||||
const [showNewAPIKeyDialog, setShowNewAPIKeyDialog] = useState(false);
|
||||
const generateAPIKey = async () => {
|
||||
const res = await API().v2.blogs[":blogId"]["api-keys"].rotate.$post({
|
||||
param: {
|
||||
blogId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
toast.error("Error generating api key");
|
||||
return;
|
||||
}
|
||||
|
||||
const data = (await res.json()) as { message: string; apiKey: string };
|
||||
setNewAPIKey(data.apiKey);
|
||||
setShowNewAPIKeyDialog(true);
|
||||
|
||||
refetchBlog();
|
||||
toast.success("Access token created");
|
||||
toast.success("API key created");
|
||||
};
|
||||
|
||||
if (blogLoading) {
|
||||
@@ -167,68 +186,89 @@ export default function BlogSettings() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="actions">
|
||||
{isDirty && <Button type="submit">Save</Button>}
|
||||
<div className="actions mt-1">
|
||||
<Button type="submit">Save</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="section p-3">
|
||||
<h2 className="text-lg font-medium">API Access Tokens</h2>
|
||||
<p>Use this token to fetch your blogs content through the API.</p>
|
||||
<h2 className="text-lg font-medium">Blog API key</h2>
|
||||
<p className="text-zinc-500">
|
||||
Use this key to fetch your blogs content through the API.
|
||||
</p>
|
||||
<div className="mt-4 flex items-center gap-2">
|
||||
{blog.access_token ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<code className="truncate rounded-lg border px-2 py-1 font-mono text-sm">
|
||||
{accessTokenToShow}
|
||||
</code>
|
||||
|
||||
<Button
|
||||
variant={"outline"}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(blog.access_token || "");
|
||||
toast.success("API key copied to clipboard");
|
||||
}}
|
||||
>
|
||||
Copy
|
||||
</Button>
|
||||
<div className="flex w-full items-center justify-end gap-3">
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (
|
||||
confirm(
|
||||
"Are you sure you want to regenerate the access token?\n The current one will stop working."
|
||||
"Are you sure you want to rotate the API key?\n The current one will stop working."
|
||||
)
|
||||
) {
|
||||
generateAccessToken();
|
||||
generateAPIKey();
|
||||
}
|
||||
}}
|
||||
variant={"destructive"}
|
||||
>
|
||||
Regenerate
|
||||
Rotate API key
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Button
|
||||
onClick={() => {
|
||||
generateAccessToken();
|
||||
generateAPIKey();
|
||||
}}
|
||||
variant={"outline"}
|
||||
>
|
||||
Generate Access Token
|
||||
Generate API key
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Dialog open={showNewAPIKeyDialog}>
|
||||
<DialogContent className="flex max-w-sm flex-col gap-4">
|
||||
<DialogHeader>
|
||||
<DialogTitle>New API key</DialogTitle>
|
||||
<DialogDescription>
|
||||
Make sure to save this key in a secure location. <br /> It
|
||||
will not be shown again.{" "}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
value={newAPIKey}
|
||||
readOnly
|
||||
className="flex-grow font-mono"
|
||||
/>
|
||||
<Button
|
||||
variant={"outline"}
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(newAPIKey || "");
|
||||
toast.success("API key copied to clipboard");
|
||||
}}
|
||||
>
|
||||
Copy
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button onClick={() => setShowNewAPIKeyDialog(false)}>
|
||||
I have saved the key securely
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</section>
|
||||
|
||||
<section className="section border bg-white p-3">
|
||||
<h2 className="mb-4 flex items-center gap-2 text-lg font-medium text-red-600">
|
||||
<AlertCircle size={18} className="text-red-600" />
|
||||
Danger zone
|
||||
</h2>
|
||||
<p className="">
|
||||
<p className="text-zinc-500">
|
||||
This action cannot be undone. This will delete all posts in the
|
||||
blog.
|
||||
</p>
|
||||
|
||||
@@ -4,7 +4,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PiPencilLine } from "react-icons/pi";
|
||||
import { Paintbrush, Pencil, Plus, Settings } from "lucide-react";
|
||||
import { Paintbrush, Plus, Settings } from "lucide-react";
|
||||
import { useUser } from "@/utils/supabase/browser";
|
||||
import { useState } from "react";
|
||||
|
||||
@@ -97,7 +97,9 @@ export default function Dashboard() {
|
||||
<Settings size="24" />
|
||||
</Button>
|
||||
<Button
|
||||
variant={hovering === blog.id ? "default" : "outline"}
|
||||
variant={
|
||||
hovering === blog.id ? "secondary" : "secondary"
|
||||
}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
Generated
+283
-5
@@ -567,6 +567,7 @@
|
||||
"apps/web": {
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@axiomhq/js": "^1.2.0",
|
||||
"@builder.io/react-hydration-overlay": "^0.0.8",
|
||||
"@chronark/zod-bird": "^0.3.9",
|
||||
"@emoji-mart/data": "^1.1.2",
|
||||
@@ -621,6 +622,7 @@
|
||||
"@uidotdev/usehooks": "^2.0.1",
|
||||
"@vercel/og": "^0.6.2",
|
||||
"ai": "^3.1.30",
|
||||
"bcrypt": "^5.1.1",
|
||||
"browser-image-compression": "^2.0.2",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.0.0",
|
||||
@@ -668,6 +670,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/eslint": "^8.21.3",
|
||||
"@types/formidable": "^3.4.3",
|
||||
"@types/lowlight": "^0.0.7",
|
||||
@@ -1917,6 +1920,26 @@
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/@axiomhq/js": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@axiomhq/js/-/js-1.2.0.tgz",
|
||||
"integrity": "sha512-ALvNo8YeDwE7EHWr9TGijdfo/gbmgTB5XHHBcIcu6Ra+A9+2ltBO5UvTzvSSGPaxn4neEyh+8ESzEBJVEjNAnA==",
|
||||
"dependencies": {
|
||||
"fetch-retry": "^6.0.0",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@axiomhq/js/node_modules/uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
|
||||
"bin": {
|
||||
"uuid": "dist/bin/uuid"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.12.11",
|
||||
"license": "MIT",
|
||||
@@ -5842,6 +5865,124 @@
|
||||
"@lezer/lr": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
|
||||
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"make-dir": "^3.1.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"nopt": "^5.0.0",
|
||||
"npmlog": "^5.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.5",
|
||||
"tar": "^6.1.11"
|
||||
},
|
||||
"bin": {
|
||||
"node-pre-gyp": "bin/node-pre-gyp"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/abbrev": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
||||
"dependencies": {
|
||||
"semver": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/minizlib": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
|
||||
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/minizlib/node_modules/minipass": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/nopt": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
|
||||
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
|
||||
"dependencies": {
|
||||
"abbrev": "1"
|
||||
},
|
||||
"bin": {
|
||||
"nopt": "bin/nopt.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/tar": {
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
|
||||
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
|
||||
"dependencies": {
|
||||
"chownr": "^2.0.0",
|
||||
"fs-minipass": "^2.0.0",
|
||||
"minipass": "^5.0.0",
|
||||
"minizlib": "^2.1.1",
|
||||
"mkdirp": "^1.0.3",
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@mdx-js/mdx": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.0.1.tgz",
|
||||
@@ -12293,6 +12434,15 @@
|
||||
"@babel/types": "^7.20.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/bcrypt": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz",
|
||||
"integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
|
||||
@@ -13730,6 +13880,17 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agentkeepalive": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
|
||||
@@ -13968,6 +14129,24 @@
|
||||
"resolved": "apps/api",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/aproba": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
|
||||
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
|
||||
},
|
||||
"node_modules/are-we-there-yet": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
|
||||
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"dependencies": {
|
||||
"delegates": "^1.0.0",
|
||||
"readable-stream": "^3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/arg": {
|
||||
"version": "5.0.2",
|
||||
"license": "MIT"
|
||||
@@ -14412,6 +14591,19 @@
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bcrypt": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz",
|
||||
"integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.11",
|
||||
"node-addon-api": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/beautify": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/beautify/-/beautify-0.0.8.tgz",
|
||||
@@ -15365,6 +15557,14 @@
|
||||
"simple-swizzle": "^0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/color-support": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
|
||||
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
|
||||
"bin": {
|
||||
"color-support": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"license": "MIT",
|
||||
@@ -15517,6 +15717,11 @@
|
||||
"node": "^14.18.0 || >=16.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
|
||||
},
|
||||
"node_modules/constant-case": {
|
||||
"version": "2.0.0",
|
||||
"dev": true,
|
||||
@@ -16168,6 +16373,11 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/delegates": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
|
||||
@@ -16222,7 +16432,6 @@
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -18005,6 +18214,11 @@
|
||||
"node": "^12.20 || >= 14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/fetch-retry": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-6.0.0.tgz",
|
||||
"integrity": "sha512-BUFj1aMubgib37I3v4q78fYo63Po7t4HUPTpQ6/QE6yK6cIQrP+W43FYToeTEyg5m2Y7eFUtijUuAv/PDlWuag=="
|
||||
},
|
||||
"node_modules/fflate": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.4.tgz",
|
||||
@@ -18462,7 +18676,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
|
||||
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"minipass": "^3.0.0"
|
||||
},
|
||||
@@ -18474,7 +18687,6 @@
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
|
||||
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
@@ -18545,6 +18757,26 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/gauge": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
|
||||
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"dependencies": {
|
||||
"aproba": "^1.0.3 || ^2.0.0",
|
||||
"color-support": "^1.1.2",
|
||||
"console-control-strings": "^1.0.0",
|
||||
"has-unicode": "^2.0.1",
|
||||
"object-assign": "^4.1.1",
|
||||
"signal-exit": "^3.0.0",
|
||||
"string-width": "^4.2.3",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wide-align": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/gensync": {
|
||||
"version": "1.0.0-beta.2",
|
||||
"license": "MIT",
|
||||
@@ -18977,6 +19209,11 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/has-unicode": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
|
||||
},
|
||||
"node_modules/hash": {
|
||||
"resolved": "packages/hash",
|
||||
"link": true
|
||||
@@ -19728,6 +19965,18 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/httpsnippet-lite": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/httpsnippet-lite/-/httpsnippet-lite-3.0.5.tgz",
|
||||
@@ -22953,6 +23202,11 @@
|
||||
"lower-case": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz",
|
||||
"integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA=="
|
||||
},
|
||||
"node_modules/node-dir": {
|
||||
"version": "0.1.17",
|
||||
"resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz",
|
||||
@@ -23160,6 +23414,18 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/npmlog": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
|
||||
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"dependencies": {
|
||||
"are-we-there-yet": "^2.0.0",
|
||||
"console-control-strings": "^1.1.0",
|
||||
"gauge": "^3.0.0",
|
||||
"set-blocking": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nypm": {
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.9.tgz",
|
||||
@@ -26609,6 +26875,11 @@
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/set-blocking": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
|
||||
},
|
||||
"node_modules/set-function-length": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
|
||||
@@ -30257,6 +30528,14 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/wide-align": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
|
||||
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
|
||||
"dependencies": {
|
||||
"string-width": "^1.0.2 || 2 || 3 || 4"
|
||||
}
|
||||
},
|
||||
"node_modules/widest-line": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz",
|
||||
@@ -30439,8 +30718,7 @@
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"peer": true
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "1.10.2",
|
||||
|
||||
Reference in New Issue
Block a user