feat(phase1a): admin-sdk base, audit helper, project seed, panel pages
- src/lib/audit.ts + src/lib/admin-sdk/base.ts (typed HTTP + auto audit) - prisma/seed.ts seeds 5 projects (all inactive) - pages: /projects, /projects/[key], /audit, /operations, /events, /settings - components/panel-shell.tsx shared layout - entrypoint runs seed after db push
This commit is contained in:
@@ -3,5 +3,7 @@ set -e
|
||||
cd /app/apps/web
|
||||
echo "[entrypoint] running prisma db push…"
|
||||
npx --no-install prisma db push --accept-data-loss --skip-generate || echo "[entrypoint] prisma db push failed (continuing — login will fail until schema applied)"
|
||||
echo "[entrypoint] seeding projects…"
|
||||
npx --no-install tsx prisma/seed.ts || echo "[entrypoint] seed failed (continuing)"
|
||||
cd /app
|
||||
exec "$@"
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
"lint": "next lint",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"prisma:generate": "prisma generate",
|
||||
"prisma:migrate:deploy": "prisma migrate deploy"
|
||||
"prisma:migrate:deploy": "prisma migrate deploy",
|
||||
"prisma:seed": "tsx prisma/seed.ts"
|
||||
},
|
||||
"prisma": { "seed": "tsx prisma/seed.ts" },
|
||||
"dependencies": {
|
||||
"@base-ui/react": "^1.4.1",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
@@ -43,6 +45,7 @@
|
||||
"postcss": "^8.4.49",
|
||||
"prisma": "^5.22.0",
|
||||
"tailwindcss": "^4.0.0",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.6.0"
|
||||
}
|
||||
}
|
||||
|
||||
29
apps/web/prisma/seed.ts
Normal file
29
apps/web/prisma/seed.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const projects = [
|
||||
{ key: "sase", name: "Sase.tr", description: "VIN/chassis lookup + auto parts catalog", active: false },
|
||||
{ key: "catvicer", name: "Catvicer", description: "Multi-tenant catering SaaS", active: false },
|
||||
{ key: "kokpit", name: "Kokpit", description: "Internal ops dashboard", active: false },
|
||||
{ key: "otoyedekparca", name: "otoyedekparca.co", description: "Auto parts marketplace", active: false },
|
||||
{ key: "eryaman", name: "Eryaman Evleri TYY", description: "Residence portal", active: false },
|
||||
];
|
||||
|
||||
async function main() {
|
||||
for (const p of projects) {
|
||||
await prisma.project.upsert({
|
||||
where: { key: p.key },
|
||||
update: { name: p.name, description: p.description },
|
||||
create: p,
|
||||
});
|
||||
}
|
||||
console.log(`seeded ${projects.length} projects`);
|
||||
}
|
||||
|
||||
main()
|
||||
.then(() => prisma.$disconnect())
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
return prisma.$disconnect().finally(() => process.exit(1));
|
||||
});
|
||||
80
apps/web/src/app/audit/page.tsx
Normal file
80
apps/web/src/app/audit/page.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import { prisma } from "@/lib/db";
|
||||
import { PanelShell } from "@/components/panel-shell";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function AuditPage() {
|
||||
const rows = await prisma.auditLog.findMany({
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 100,
|
||||
});
|
||||
|
||||
return (
|
||||
<PanelShell title="Audit">
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="w-[180px]">When</TableHead>
|
||||
<TableHead>Project</TableHead>
|
||||
<TableHead>Endpoint</TableHead>
|
||||
<TableHead className="w-[70px]">Method</TableHead>
|
||||
<TableHead className="w-[90px]">Status</TableHead>
|
||||
<TableHead className="w-[80px] text-right">Duration</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{rows.map((r) => (
|
||||
<TableRow key={r.id}>
|
||||
<TableCell className="font-mono text-xs">
|
||||
{r.createdAt.toISOString().replace("T", " ").slice(0, 19)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{r.projectKey ? <Badge variant="outline">{r.projectKey}</Badge> : "—"}
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-xs">{r.endpoint}</TableCell>
|
||||
<TableCell className="font-mono text-xs">{r.method}</TableCell>
|
||||
<TableCell>
|
||||
{r.responseStatus ? (
|
||||
<Badge
|
||||
variant={
|
||||
r.responseStatus < 400
|
||||
? "default"
|
||||
: r.responseStatus < 500
|
||||
? "secondary"
|
||||
: "destructive"
|
||||
}
|
||||
>
|
||||
{r.responseStatus}
|
||||
</Badge>
|
||||
) : (
|
||||
"—"
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="text-right font-mono text-xs">
|
||||
{r.durationMs ? `${r.durationMs}ms` : "—"}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
{rows.length === 0 && (
|
||||
<TableRow>
|
||||
<TableCell colSpan={6} className="text-center text-sm text-muted-foreground">
|
||||
No audit entries yet.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</PanelShell>
|
||||
);
|
||||
}
|
||||
15
apps/web/src/app/events/page.tsx
Normal file
15
apps/web/src/app/events/page.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { PanelShell } from "@/components/panel-shell";
|
||||
|
||||
export default function EventsPage() {
|
||||
return (
|
||||
<PanelShell title="Events">
|
||||
<div className="rounded-lg border p-8">
|
||||
<h2 className="text-lg font-semibold">Cross-project event timeline</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
Phase 3 — Redis Streams consumer + SSE push. Spokes publish to their own stream
|
||||
(e.g. <code>sase:events</code>); panel worker persists and broadcasts here.
|
||||
</p>
|
||||
</div>
|
||||
</PanelShell>
|
||||
);
|
||||
}
|
||||
27
apps/web/src/app/operations/page.tsx
Normal file
27
apps/web/src/app/operations/page.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { PanelShell } from "@/components/panel-shell";
|
||||
|
||||
export default function OperationsPage() {
|
||||
return (
|
||||
<PanelShell title="Operations">
|
||||
<ComingSoon
|
||||
title="Operations"
|
||||
items={["Coolify deploy triggers", "Backup status", "Migration runner"]}
|
||||
phase="Phase 5"
|
||||
/>
|
||||
</PanelShell>
|
||||
);
|
||||
}
|
||||
|
||||
function ComingSoon({ title, items, phase }: { title: string; items: string[]; phase: string }) {
|
||||
return (
|
||||
<div className="rounded-lg border p-8">
|
||||
<h2 className="text-lg font-semibold">{title}</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">Planned for {phase}. Will include:</p>
|
||||
<ul className="mt-3 list-inside list-disc text-sm text-muted-foreground">
|
||||
{items.map((i) => (
|
||||
<li key={i}>{i}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
51
apps/web/src/app/projects/[key]/page.tsx
Normal file
51
apps/web/src/app/projects/[key]/page.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import { prisma } from "@/lib/db";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
import { PanelShell } from "@/components/panel-shell";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
export default async function ProjectDetail({ params }: { params: Promise<{ key: string }> }) {
|
||||
const { key } = await params;
|
||||
const project = await prisma.project.findUnique({ where: { key } });
|
||||
if (!project) notFound();
|
||||
|
||||
return (
|
||||
<PanelShell title={project.name}>
|
||||
<div className="flex items-center gap-3">
|
||||
<h2 className="text-xl font-semibold">{project.name}</h2>
|
||||
<Badge variant={project.active ? "default" : "outline"}>
|
||||
{project.active ? "wired" : "not wired"}
|
||||
</Badge>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{project.description}</p>
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardDescription>Connection</CardDescription>
|
||||
<CardTitle className="text-base">
|
||||
{project.active ? "Read-only DB attached" : "No DB connection configured"}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-sm text-muted-foreground">
|
||||
{project.active
|
||||
? "DATABASE_URL is wired. See live metrics below."
|
||||
: "Configure DATABASE_URL_<KEY>_RO in Coolify env."}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardDescription>Admin SDK</CardDescription>
|
||||
<CardTitle className="text-base">0 endpoints wired</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-sm text-muted-foreground">
|
||||
Spoke needs <code>/internal/admin/*</code> endpoints exposed.
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</PanelShell>
|
||||
);
|
||||
}
|
||||
36
apps/web/src/app/projects/page.tsx
Normal file
36
apps/web/src/app/projects/page.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import Link from "next/link";
|
||||
import { prisma } from "@/lib/db";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
import { PanelShell } from "@/components/panel-shell";
|
||||
import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
export default async function ProjectsPage() {
|
||||
const projects = await prisma.project.findMany({ orderBy: { name: "asc" } });
|
||||
|
||||
return (
|
||||
<PanelShell title="Projects">
|
||||
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{projects.map((p) => (
|
||||
<Link key={p.id} href={`/projects/${p.key}`} className="block">
|
||||
<Card className="transition-colors hover:border-foreground/40">
|
||||
<CardHeader>
|
||||
<div className="flex items-center justify-between">
|
||||
<CardTitle className="text-base">{p.name}</CardTitle>
|
||||
<Badge variant={p.active ? "default" : "outline"}>
|
||||
{p.active ? "wired" : "not wired"}
|
||||
</Badge>
|
||||
</div>
|
||||
<CardDescription>{p.description ?? "—"}</CardDescription>
|
||||
</CardHeader>
|
||||
</Card>
|
||||
</Link>
|
||||
))}
|
||||
{projects.length === 0 && (
|
||||
<p className="text-sm text-muted-foreground">No projects yet — run prisma seed.</p>
|
||||
)}
|
||||
</div>
|
||||
</PanelShell>
|
||||
);
|
||||
}
|
||||
35
apps/web/src/app/settings/page.tsx
Normal file
35
apps/web/src/app/settings/page.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { headers } from "next/headers";
|
||||
import { PanelShell } from "@/components/panel-shell";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default async function SettingsPage() {
|
||||
const session = await auth.api.getSession({ headers: await headers() });
|
||||
|
||||
return (
|
||||
<PanelShell title="Settings">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardDescription>Account</CardDescription>
|
||||
<CardTitle className="text-base">{session?.user.email}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-1 text-sm text-muted-foreground">
|
||||
<div>Name: {session?.user.name}</div>
|
||||
<div>Session expires: {session?.session.expiresAt.toISOString()}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardDescription>Panel</CardDescription>
|
||||
<CardTitle className="text-base">Tailscale-only · Phase 0/1</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-sm text-muted-foreground">
|
||||
See <code>super-panel-prd.md</code> for roadmap.
|
||||
</CardContent>
|
||||
</Card>
|
||||
</PanelShell>
|
||||
);
|
||||
}
|
||||
33
apps/web/src/components/panel-shell.tsx
Normal file
33
apps/web/src/components/panel-shell.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { headers } from "next/headers";
|
||||
import { redirect } from "next/navigation";
|
||||
import { auth } from "@/lib/auth";
|
||||
import { AppSidebar } from "@/components/app-sidebar";
|
||||
import { SiteHeader } from "@/components/site-header";
|
||||
import { SidebarInset, SidebarProvider } from "@/components/ui/sidebar";
|
||||
|
||||
export async function PanelShell({
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const session = await auth.api.getSession({ headers: await headers() });
|
||||
if (!session) redirect("/login");
|
||||
return (
|
||||
<SidebarProvider
|
||||
style={
|
||||
{
|
||||
"--sidebar-width": "calc(var(--spacing) * 72)",
|
||||
"--header-height": "calc(var(--spacing) * 12)",
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<AppSidebar variant="inset" user={{ name: session.user.name, email: session.user.email }} />
|
||||
<SidebarInset>
|
||||
<SiteHeader title={title} />
|
||||
<div className="flex flex-1 flex-col gap-4 p-4 md:p-6">{children}</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { SidebarTrigger } from "@/components/ui/sidebar"
|
||||
|
||||
export function SiteHeader() {
|
||||
export function SiteHeader({ title = "Overview" }: { title?: string }) {
|
||||
return (
|
||||
<header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
|
||||
<div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
|
||||
@@ -10,7 +10,7 @@ export function SiteHeader() {
|
||||
orientation="vertical"
|
||||
className="mx-2 h-4 data-vertical:self-auto"
|
||||
/>
|
||||
<h1 className="text-base font-medium">Overview</h1>
|
||||
<h1 className="text-base font-medium">{title}</h1>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
|
||||
65
apps/web/src/lib/admin-sdk/base.ts
Normal file
65
apps/web/src/lib/admin-sdk/base.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { writeAudit } from "../audit";
|
||||
|
||||
export class AdminApiError extends Error {
|
||||
constructor(public readonly status: number, public readonly endpoint: string, message: string) {
|
||||
super(message);
|
||||
this.name = "AdminApiError";
|
||||
}
|
||||
}
|
||||
|
||||
type ClientOptions = {
|
||||
projectKey: string;
|
||||
baseUrl: string;
|
||||
token: string;
|
||||
timeoutMs?: number;
|
||||
};
|
||||
|
||||
export class AdminClient {
|
||||
constructor(private readonly opts: ClientOptions) {}
|
||||
|
||||
async call<T>(
|
||||
method: "GET" | "POST" | "PATCH" | "PUT" | "DELETE",
|
||||
path: string,
|
||||
body?: unknown,
|
||||
): Promise<T> {
|
||||
const url = `${this.opts.baseUrl.replace(/\/$/, "")}${path}`;
|
||||
const started = Date.now();
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), this.opts.timeoutMs ?? 15_000);
|
||||
|
||||
let status = 0;
|
||||
let payload: unknown = null;
|
||||
try {
|
||||
const res = await fetch(url, {
|
||||
method,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"X-Internal-Token": this.opts.token,
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
signal: controller.signal,
|
||||
cache: "no-store",
|
||||
});
|
||||
status = res.status;
|
||||
payload = await res.json().catch(() => null);
|
||||
if (!res.ok) {
|
||||
throw new AdminApiError(
|
||||
status,
|
||||
path,
|
||||
(payload as { message?: string })?.message ?? `Upstream ${status}`,
|
||||
);
|
||||
}
|
||||
return payload as T;
|
||||
} finally {
|
||||
clearTimeout(timer);
|
||||
void writeAudit({
|
||||
projectKey: this.opts.projectKey,
|
||||
endpoint: path,
|
||||
method,
|
||||
requestPayload: body,
|
||||
responseStatus: status,
|
||||
durationMs: Date.now() - started,
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
38
apps/web/src/lib/audit.ts
Normal file
38
apps/web/src/lib/audit.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { headers } from "next/headers";
|
||||
import { createHash } from "node:crypto";
|
||||
import { prisma } from "./db";
|
||||
import { auth } from "./auth";
|
||||
|
||||
type AuditInput = {
|
||||
projectKey?: string;
|
||||
endpoint: string;
|
||||
method: string;
|
||||
requestPayload?: unknown;
|
||||
responseStatus?: number;
|
||||
durationMs?: number;
|
||||
};
|
||||
|
||||
export async function writeAudit(input: AuditInput) {
|
||||
const h = await headers();
|
||||
const session = await auth.api.getSession({ headers: h });
|
||||
const sourceIp =
|
||||
h.get("x-forwarded-for")?.split(",")[0]?.trim() ?? h.get("x-real-ip") ?? null;
|
||||
const userAgent = h.get("user-agent") ?? null;
|
||||
const requestHash = input.requestPayload
|
||||
? createHash("sha256").update(JSON.stringify(input.requestPayload)).digest("hex").slice(0, 16)
|
||||
: null;
|
||||
|
||||
await prisma.auditLog.create({
|
||||
data: {
|
||||
actorUserId: session?.user.id ?? null,
|
||||
projectKey: input.projectKey ?? null,
|
||||
endpoint: input.endpoint,
|
||||
method: input.method,
|
||||
requestHash,
|
||||
responseStatus: input.responseStatus ?? null,
|
||||
durationMs: input.durationMs ?? null,
|
||||
sourceIp,
|
||||
userAgent,
|
||||
},
|
||||
});
|
||||
}
|
||||
295
pnpm-lock.yaml
generated
295
pnpm-lock.yaml
generated
@@ -105,6 +105,9 @@ importers:
|
||||
tailwindcss:
|
||||
specifier: ^4.0.0
|
||||
version: 4.3.0
|
||||
tsx:
|
||||
specifier: ^4.19.2
|
||||
version: 4.21.0
|
||||
typescript:
|
||||
specifier: ^5.6.0
|
||||
version: 5.9.3
|
||||
@@ -395,6 +398,162 @@ packages:
|
||||
'@emnapi/runtime@1.10.0':
|
||||
resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==}
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.7':
|
||||
resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/android-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.27.7':
|
||||
resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.27.7':
|
||||
resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/darwin-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.27.7':
|
||||
resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.27.7':
|
||||
resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/linux-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.27.7':
|
||||
resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.27.7':
|
||||
resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.27.7':
|
||||
resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.27.7':
|
||||
resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.27.7':
|
||||
resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.27.7':
|
||||
resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.27.7':
|
||||
resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.27.7':
|
||||
resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/netbsd-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-x64@0.27.7':
|
||||
resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/openbsd-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.27.7':
|
||||
resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openharmony-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@esbuild/sunos-x64@0.27.7':
|
||||
resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/win32-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.27.7':
|
||||
resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.27.7':
|
||||
resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@floating-ui/core@1.7.5':
|
||||
resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==}
|
||||
|
||||
@@ -1534,6 +1693,11 @@ packages:
|
||||
es-toolkit@1.46.1:
|
||||
resolution: {integrity: sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==}
|
||||
|
||||
esbuild@0.27.7:
|
||||
resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
escalade@3.2.0:
|
||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -1689,6 +1853,9 @@ packages:
|
||||
resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
get-tsconfig@4.14.0:
|
||||
resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==}
|
||||
|
||||
glob-parent@5.1.2:
|
||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
||||
engines: {node: '>= 6'}
|
||||
@@ -2332,6 +2499,9 @@ packages:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
resolve-pkg-maps@1.0.0:
|
||||
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
|
||||
|
||||
restore-cursor@5.1.0:
|
||||
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -2544,6 +2714,11 @@ packages:
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
tsx@4.21.0:
|
||||
resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
hasBin: true
|
||||
|
||||
turbo@2.9.12:
|
||||
resolution: {integrity: sha512-lCPgus1NuTiBdaITWqzSH/Ff6HVL8HHGBtOXHg1dHRfcshN79XkygSdh0M6g8b0td91ILLG5MTkLOkp5UvyPJw==}
|
||||
hasBin: true
|
||||
@@ -3015,6 +3190,84 @@ snapshots:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openharmony-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@floating-ui/core@1.7.5':
|
||||
dependencies:
|
||||
'@floating-ui/utils': 0.2.11
|
||||
@@ -3927,6 +4180,35 @@ snapshots:
|
||||
|
||||
es-toolkit@1.46.1: {}
|
||||
|
||||
esbuild@0.27.7:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.27.7
|
||||
'@esbuild/android-arm': 0.27.7
|
||||
'@esbuild/android-arm64': 0.27.7
|
||||
'@esbuild/android-x64': 0.27.7
|
||||
'@esbuild/darwin-arm64': 0.27.7
|
||||
'@esbuild/darwin-x64': 0.27.7
|
||||
'@esbuild/freebsd-arm64': 0.27.7
|
||||
'@esbuild/freebsd-x64': 0.27.7
|
||||
'@esbuild/linux-arm': 0.27.7
|
||||
'@esbuild/linux-arm64': 0.27.7
|
||||
'@esbuild/linux-ia32': 0.27.7
|
||||
'@esbuild/linux-loong64': 0.27.7
|
||||
'@esbuild/linux-mips64el': 0.27.7
|
||||
'@esbuild/linux-ppc64': 0.27.7
|
||||
'@esbuild/linux-riscv64': 0.27.7
|
||||
'@esbuild/linux-s390x': 0.27.7
|
||||
'@esbuild/linux-x64': 0.27.7
|
||||
'@esbuild/netbsd-arm64': 0.27.7
|
||||
'@esbuild/netbsd-x64': 0.27.7
|
||||
'@esbuild/openbsd-arm64': 0.27.7
|
||||
'@esbuild/openbsd-x64': 0.27.7
|
||||
'@esbuild/openharmony-arm64': 0.27.7
|
||||
'@esbuild/sunos-x64': 0.27.7
|
||||
'@esbuild/win32-arm64': 0.27.7
|
||||
'@esbuild/win32-ia32': 0.27.7
|
||||
'@esbuild/win32-x64': 0.27.7
|
||||
|
||||
escalade@3.2.0: {}
|
||||
|
||||
escape-html@1.0.3: {}
|
||||
@@ -4118,6 +4400,10 @@ snapshots:
|
||||
'@sec-ant/readable-stream': 0.4.1
|
||||
is-stream: 4.0.1
|
||||
|
||||
get-tsconfig@4.14.0:
|
||||
dependencies:
|
||||
resolve-pkg-maps: 1.0.0
|
||||
|
||||
glob-parent@5.1.2:
|
||||
dependencies:
|
||||
is-glob: 4.0.3
|
||||
@@ -4664,6 +4950,8 @@ snapshots:
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve-pkg-maps@1.0.0: {}
|
||||
|
||||
restore-cursor@5.1.0:
|
||||
dependencies:
|
||||
onetime: 7.0.0
|
||||
@@ -4937,6 +5225,13 @@ snapshots:
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
tsx@4.21.0:
|
||||
dependencies:
|
||||
esbuild: 0.27.7
|
||||
get-tsconfig: 4.14.0
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
|
||||
turbo@2.9.12:
|
||||
optionalDependencies:
|
||||
'@turbo/darwin-64': 2.9.12
|
||||
|
||||
Reference in New Issue
Block a user