mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
feat(web): add shareable stack page (#556)
This commit is contained in:
@@ -123,25 +123,6 @@ export async function gatherConfig(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result.backend === "convex") {
|
|
||||||
result.runtime = "none";
|
|
||||||
result.database = "none";
|
|
||||||
result.orm = "none";
|
|
||||||
result.api = "none";
|
|
||||||
result.dbSetup = "none";
|
|
||||||
result.examples = ["todo"];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.backend === "none") {
|
|
||||||
result.runtime = "none";
|
|
||||||
result.database = "none";
|
|
||||||
result.orm = "none";
|
|
||||||
result.api = "none";
|
|
||||||
result.auth = "none";
|
|
||||||
result.dbSetup = "none";
|
|
||||||
result.examples = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
projectName: projectName,
|
projectName: projectName,
|
||||||
projectDir: projectDir,
|
projectDir: projectDir,
|
||||||
|
|||||||
@@ -175,6 +175,116 @@ export function validateDatabaseSetup(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function validateConvexConstraints(
|
||||||
|
config: Partial<ProjectConfig>,
|
||||||
|
providedFlags: Set<string>,
|
||||||
|
): void {
|
||||||
|
const { backend } = config;
|
||||||
|
|
||||||
|
if (backend !== "convex") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const has = (k: string) => providedFlags.has(k);
|
||||||
|
|
||||||
|
if (has("runtime") && config.runtime !== "none") {
|
||||||
|
exitWithError(
|
||||||
|
"Convex backend requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has("database") && config.database !== "none") {
|
||||||
|
exitWithError(
|
||||||
|
"Convex backend requires '--database none'. Please remove the --database flag or set it to 'none'.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has("orm") && config.orm !== "none") {
|
||||||
|
exitWithError(
|
||||||
|
"Convex backend requires '--orm none'. Please remove the --orm flag or set it to 'none'.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has("api") && config.api !== "none") {
|
||||||
|
exitWithError(
|
||||||
|
"Convex backend requires '--api none'. Please remove the --api flag or set it to 'none'.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has("dbSetup") && config.dbSetup !== "none") {
|
||||||
|
exitWithError(
|
||||||
|
"Convex backend requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has("serverDeploy") && config.serverDeploy !== "none") {
|
||||||
|
exitWithError(
|
||||||
|
"Convex backend requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has("auth") && config.auth === "better-auth") {
|
||||||
|
exitWithError(
|
||||||
|
"Better-Auth is not compatible with Convex backend. Please use '--auth clerk' or '--auth none'.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateBackendNoneConstraints(
|
||||||
|
config: Partial<ProjectConfig>,
|
||||||
|
providedFlags: Set<string>,
|
||||||
|
): void {
|
||||||
|
const { backend } = config;
|
||||||
|
|
||||||
|
if (backend !== "none") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const has = (k: string) => providedFlags.has(k);
|
||||||
|
|
||||||
|
if (has("runtime") && config.runtime !== "none") {
|
||||||
|
exitWithError(
|
||||||
|
"Backend 'none' requires '--runtime none'. Please remove the --runtime flag or set it to 'none'.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has("database") && config.database !== "none") {
|
||||||
|
exitWithError(
|
||||||
|
"Backend 'none' requires '--database none'. Please remove the --database flag or set it to 'none'.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has("orm") && config.orm !== "none") {
|
||||||
|
exitWithError(
|
||||||
|
"Backend 'none' requires '--orm none'. Please remove the --orm flag or set it to 'none'.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has("api") && config.api !== "none") {
|
||||||
|
exitWithError(
|
||||||
|
"Backend 'none' requires '--api none'. Please remove the --api flag or set it to 'none'.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has("auth") && config.auth !== "none") {
|
||||||
|
exitWithError(
|
||||||
|
"Backend 'none' requires '--auth none'. Please remove the --auth flag or set it to 'none'.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has("dbSetup") && config.dbSetup !== "none") {
|
||||||
|
exitWithError(
|
||||||
|
"Backend 'none' requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has("serverDeploy") && config.serverDeploy !== "none") {
|
||||||
|
exitWithError(
|
||||||
|
"Backend 'none' requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function validateBackendConstraints(
|
export function validateBackendConstraints(
|
||||||
config: Partial<ProjectConfig>,
|
config: Partial<ProjectConfig>,
|
||||||
providedFlags: Set<string>,
|
providedFlags: Set<string>,
|
||||||
@@ -201,16 +311,6 @@ export function validateBackendConstraints(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
backend === "convex" &&
|
|
||||||
config.auth === "better-auth" &&
|
|
||||||
providedFlags.has("auth")
|
|
||||||
) {
|
|
||||||
exitWithError(
|
|
||||||
"Better-Auth is not compatible with the Convex backend. Please use '--auth clerk' or '--auth none'.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
providedFlags.has("backend") &&
|
providedFlags.has("backend") &&
|
||||||
backend &&
|
backend &&
|
||||||
@@ -287,6 +387,8 @@ export function validateFullConfig(
|
|||||||
validateDatabaseOrmAuth(config, providedFlags);
|
validateDatabaseOrmAuth(config, providedFlags);
|
||||||
validateDatabaseSetup(config, providedFlags);
|
validateDatabaseSetup(config, providedFlags);
|
||||||
|
|
||||||
|
validateConvexConstraints(config, providedFlags);
|
||||||
|
validateBackendNoneConstraints(config, providedFlags);
|
||||||
validateBackendConstraints(config, providedFlags, options);
|
validateBackendConstraints(config, providedFlags, options);
|
||||||
|
|
||||||
validateFrontendConstraints(config, providedFlags);
|
validateFrontendConstraints(config, providedFlags);
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export { twMerge as cn } from "tailwind-merge";
|
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"convex": "^1.25.4",
|
"convex": "^1.25.4",
|
||||||
"convex-helpers": "^0.1.104",
|
"convex-helpers": "^0.1.104",
|
||||||
|
"culori": "^4.0.2",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"fumadocs-core": "15.6.7",
|
"fumadocs-core": "15.6.7",
|
||||||
"fumadocs-mdx": "11.7.3",
|
"fumadocs-mdx": "11.7.3",
|
||||||
@@ -38,6 +39,7 @@
|
|||||||
"nuqs": "^2.4.3",
|
"nuqs": "^2.4.3",
|
||||||
"papaparse": "^5.5.3",
|
"papaparse": "^5.5.3",
|
||||||
"posthog-js": "^1.258.5",
|
"posthog-js": "^1.258.5",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
"radix-ui": "^1.4.2",
|
"radix-ui": "^1.4.2",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
@@ -52,9 +54,11 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4.1.11",
|
"@tailwindcss/postcss": "^4.1.11",
|
||||||
|
"@types/culori": "^4.0.0",
|
||||||
"@types/mdx": "^2.0.13",
|
"@types/mdx": "^2.0.13",
|
||||||
"@types/node": "24.1.0",
|
"@types/node": "24.1.0",
|
||||||
"@types/papaparse": "^5.3.16",
|
"@types/papaparse": "^5.3.16",
|
||||||
|
"@types/qrcode": "^1.5.5",
|
||||||
"@types/react": "^19.1.9",
|
"@types/react": "^19.1.9",
|
||||||
"@types/react-dom": "^19.1.7",
|
"@types/react-dom": "^19.1.7",
|
||||||
"eslint": "^9.32.0",
|
"eslint": "^9.32.0",
|
||||||
|
|||||||
@@ -242,23 +242,19 @@ async function generateAnalyticsData() {
|
|||||||
cliVersionCounts[cliVersion] =
|
cliVersionCounts[cliVersion] =
|
||||||
(cliVersionCounts[cliVersion] || 0) + 1;
|
(cliVersionCounts[cliVersion] || 0) + 1;
|
||||||
|
|
||||||
// Auth
|
|
||||||
const auth =
|
const auth =
|
||||||
row["*.properties.auth"] === "True" ? "enabled" : "disabled";
|
row["*.properties.auth"] === "True" ? "enabled" : "disabled";
|
||||||
authCounts[auth] = (authCounts[auth] || 0) + 1;
|
authCounts[auth] = (authCounts[auth] || 0) + 1;
|
||||||
if (auth === "enabled") authEnabledCount++;
|
if (auth === "enabled") authEnabledCount++;
|
||||||
|
|
||||||
// Git
|
|
||||||
const git =
|
const git =
|
||||||
row["*.properties.git"] === "True" ? "enabled" : "disabled";
|
row["*.properties.git"] === "True" ? "enabled" : "disabled";
|
||||||
gitCounts[git] = (gitCounts[git] || 0) + 1;
|
gitCounts[git] = (gitCounts[git] || 0) + 1;
|
||||||
|
|
||||||
// Install
|
|
||||||
const install =
|
const install =
|
||||||
row["*.properties.install"] === "True" ? "enabled" : "disabled";
|
row["*.properties.install"] === "True" ? "enabled" : "disabled";
|
||||||
installCounts[install] = (installCounts[install] || 0) + 1;
|
installCounts[install] = (installCounts[install] || 0) + 1;
|
||||||
|
|
||||||
// Examples
|
|
||||||
const examples = [
|
const examples = [
|
||||||
row["*.properties.examples.0"],
|
row["*.properties.examples.0"],
|
||||||
row["*.properties.examples.1"],
|
row["*.properties.examples.1"],
|
||||||
@@ -271,7 +267,6 @@ async function generateAnalyticsData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Addons
|
|
||||||
const addons = [
|
const addons = [
|
||||||
row["*.properties.addons.0"],
|
row["*.properties.addons.0"],
|
||||||
row["*.properties.addons.1"],
|
row["*.properties.addons.1"],
|
||||||
@@ -288,23 +283,19 @@ async function generateAnalyticsData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Runtime
|
|
||||||
const runtime = row["*.properties.runtime"] || "unknown";
|
const runtime = row["*.properties.runtime"] || "unknown";
|
||||||
runtimeCounts[runtime] = (runtimeCounts[runtime] || 0) + 1;
|
runtimeCounts[runtime] = (runtimeCounts[runtime] || 0) + 1;
|
||||||
|
|
||||||
// Web Deploy (migrate "workers" to "wrangler")
|
|
||||||
const webDeploy = row["*.properties.webDeploy"] || "none";
|
const webDeploy = row["*.properties.webDeploy"] || "none";
|
||||||
const normalizedWebDeploy =
|
const normalizedWebDeploy =
|
||||||
webDeploy === "workers" ? "wrangler" : webDeploy;
|
webDeploy === "workers" ? "wrangler" : webDeploy;
|
||||||
webDeployCounts[normalizedWebDeploy] =
|
webDeployCounts[normalizedWebDeploy] =
|
||||||
(webDeployCounts[normalizedWebDeploy] || 0) + 1;
|
(webDeployCounts[normalizedWebDeploy] || 0) + 1;
|
||||||
|
|
||||||
// Server Deploy
|
|
||||||
const serverDeploy = row["*.properties.serverDeploy"] || "none";
|
const serverDeploy = row["*.properties.serverDeploy"] || "none";
|
||||||
serverDeployCounts[serverDeploy] =
|
serverDeployCounts[serverDeploy] =
|
||||||
(serverDeployCounts[serverDeploy] || 0) + 1;
|
(serverDeployCounts[serverDeploy] || 0) + 1;
|
||||||
|
|
||||||
// Project type
|
|
||||||
const hasFrontend =
|
const hasFrontend =
|
||||||
(frontend0 && frontend0 !== "none") ||
|
(frontend0 && frontend0 !== "none") ||
|
||||||
(frontend1 && frontend1 !== "none");
|
(frontend1 && frontend1 !== "none");
|
||||||
@@ -321,7 +312,6 @@ async function generateAnalyticsData() {
|
|||||||
}
|
}
|
||||||
projectTypeCounts[type] = (projectTypeCounts[type] || 0) + 1;
|
projectTypeCounts[type] = (projectTypeCounts[type] || 0) + 1;
|
||||||
|
|
||||||
// Stack combinations
|
|
||||||
const frontends = [frontend0, frontend1].filter(
|
const frontends = [frontend0, frontend1].filter(
|
||||||
(f) => f && f !== "none" && f !== "",
|
(f) => f && f !== "none" && f !== "",
|
||||||
);
|
);
|
||||||
@@ -332,7 +322,6 @@ async function generateAnalyticsData() {
|
|||||||
const combo = parts.length > 0 ? parts.join(" + ") : "none";
|
const combo = parts.length > 0 ? parts.join(" + ") : "none";
|
||||||
stackComboCounts[combo] = (stackComboCounts[combo] || 0) + 1;
|
stackComboCounts[combo] = (stackComboCounts[combo] || 0) + 1;
|
||||||
|
|
||||||
// Database + ORM combinations
|
|
||||||
if (database !== "none" && orm !== "none") {
|
if (database !== "none" && orm !== "none") {
|
||||||
const combo = `${database} + ${orm}`;
|
const combo = `${database} + ${orm}`;
|
||||||
dbORMComboCounts[combo] = (dbORMComboCounts[combo] || 0) + 1;
|
dbORMComboCounts[combo] = (dbORMComboCounts[combo] || 0) + 1;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
220
apps/web/src/app/(home)/stack/_components/stack-display.tsx
Normal file
220
apps/web/src/app/(home)/stack/_components/stack-display.tsx
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Check, ChevronDown, Copy, Edit, Share2, Terminal } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { usePathname, useSearchParams } from "next/navigation";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu";
|
||||||
|
import { ShareDialog } from "@/components/ui/share-dialog";
|
||||||
|
import { TechBadge } from "@/components/ui/tech-badge";
|
||||||
|
import { type StackState, TECH_OPTIONS } from "@/lib/constant";
|
||||||
|
import type { LoadedStackState } from "@/lib/stack-server";
|
||||||
|
import {
|
||||||
|
CATEGORY_ORDER,
|
||||||
|
generateStackSummary,
|
||||||
|
generateStackUrl,
|
||||||
|
} from "@/lib/stack-utils";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import PackageIcon from "../../_components/icons";
|
||||||
|
|
||||||
|
interface StackDisplayProps {
|
||||||
|
stackState: LoadedStackState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function StackDisplay({ stackState }: StackDisplayProps) {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const searchParamsHook = useSearchParams();
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
const [selectedPM, setSelectedPM] = useState<"npm" | "pnpm" | "bun">("bun");
|
||||||
|
|
||||||
|
const stackUrl = generateStackUrl(pathname, searchParamsHook);
|
||||||
|
const stack = stackState;
|
||||||
|
const stackSummary = generateStackSummary(stack);
|
||||||
|
|
||||||
|
const commands = {
|
||||||
|
npm: "npx create-better-t-stack@latest",
|
||||||
|
pnpm: "pnpm create better-t-stack@latest",
|
||||||
|
bun: "bun create better-t-stack@latest",
|
||||||
|
};
|
||||||
|
|
||||||
|
const command = commands[selectedPM];
|
||||||
|
|
||||||
|
const techBadges = (() => {
|
||||||
|
const badges: React.ReactNode[] = [];
|
||||||
|
for (const category of CATEGORY_ORDER) {
|
||||||
|
const categoryKey = category as keyof StackState;
|
||||||
|
const options = TECH_OPTIONS[category as keyof typeof TECH_OPTIONS];
|
||||||
|
const selectedValue = stack[categoryKey];
|
||||||
|
|
||||||
|
if (!options) continue;
|
||||||
|
|
||||||
|
if (Array.isArray(selectedValue)) {
|
||||||
|
if (
|
||||||
|
selectedValue.length === 0 ||
|
||||||
|
(selectedValue.length === 1 && selectedValue[0] === "none")
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const id of selectedValue) {
|
||||||
|
if (id === "none") continue;
|
||||||
|
const tech = options.find((opt) => opt.id === id);
|
||||||
|
if (tech) {
|
||||||
|
badges.push(
|
||||||
|
<TechBadge
|
||||||
|
key={`${category}-${tech.id}`}
|
||||||
|
icon={tech.icon}
|
||||||
|
name={tech.name}
|
||||||
|
category={category}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const tech = options.find((opt) => opt.id === selectedValue);
|
||||||
|
if (
|
||||||
|
!tech ||
|
||||||
|
tech.id === "none" ||
|
||||||
|
tech.id === "false" ||
|
||||||
|
((category === "git" ||
|
||||||
|
category === "install" ||
|
||||||
|
category === "auth") &&
|
||||||
|
tech.id === "true")
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
badges.push(
|
||||||
|
<TechBadge
|
||||||
|
key={`${category}-${tech.id}`}
|
||||||
|
icon={tech.icon}
|
||||||
|
name={tech.name}
|
||||||
|
category={category}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return badges;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const copyCommand = async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(command);
|
||||||
|
setCopied(true);
|
||||||
|
toast.success("Command copied to clipboard!");
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
} catch {
|
||||||
|
toast.error("Failed to copy command");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="mx-auto min-h-svh max-w-[1280px]">
|
||||||
|
<div className="mx-auto flex flex-col gap-8 px-4 pt-12">
|
||||||
|
<div className="mb-8">
|
||||||
|
<div className="flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h1 className="font-bold text-4xl text-foreground">Tech Stack</h1>
|
||||||
|
<p className="text-lg text-muted-foreground">{stackSummary}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<Link href={`/new?${searchParamsHook.toString()}`}>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
<Edit className="h-4 w-4" />
|
||||||
|
Edit Stack
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<ShareDialog stackUrl={stackUrl} stackState={stackState}>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
<Share2 className="h-4 w-4" />
|
||||||
|
Share
|
||||||
|
</Button>
|
||||||
|
</ShareDialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-8">
|
||||||
|
<div className="rounded border border-border p-4">
|
||||||
|
<div className="mb-4 flex items-center justify-between">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Terminal className="h-4 w-4 text-primary" />
|
||||||
|
<span className="font-semibold text-sm">GENERATE_COMMAND</span>
|
||||||
|
</div>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="flex items-center gap-2 rounded border border-border px-3 py-1.5 text-xs transition-colors hover:bg-muted/10"
|
||||||
|
>
|
||||||
|
<PackageIcon pm={selectedPM} className="h-3 w-3" />
|
||||||
|
<span>{selectedPM.toUpperCase()}</span>
|
||||||
|
<ChevronDown className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
{(["bun", "pnpm", "npm"] as const).map((pm) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
key={pm}
|
||||||
|
onClick={() => setSelectedPM(pm)}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-2",
|
||||||
|
selectedPM === pm && "bg-accent text-background",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<PackageIcon pm={pm} className="h-3 w-3" />
|
||||||
|
<span>{pm.toUpperCase()}</span>
|
||||||
|
{selectedPM === pm && (
|
||||||
|
<Check className="ml-auto h-3 w-3 text-background" />
|
||||||
|
)}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between rounded border border-border p-3">
|
||||||
|
<div className="flex items-center gap-2 font-mono text-sm">
|
||||||
|
<span className="text-primary">$</span>
|
||||||
|
<span className="text-foreground">{command}</span>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={copyCommand}
|
||||||
|
className="flex items-center gap-1 rounded border border-border px-2 py-1 text-xs hover:bg-muted/10"
|
||||||
|
>
|
||||||
|
{copied ? (
|
||||||
|
<Check className="h-3 w-3 text-primary" />
|
||||||
|
) : (
|
||||||
|
<Copy className="h-3 w-3" />
|
||||||
|
)}
|
||||||
|
{copied ? "COPIED!" : "COPY"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<h2 className="font-semibold text-2xl text-foreground">
|
||||||
|
Technologies
|
||||||
|
</h2>
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
{techBadges.length > 0 ? (
|
||||||
|
techBadges
|
||||||
|
) : (
|
||||||
|
<p className="text-muted-foreground">No technologies selected</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
}
|
||||||
42
apps/web/src/app/(home)/stack/page.tsx
Normal file
42
apps/web/src/app/(home)/stack/page.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { Suspense } from "react";
|
||||||
|
import { loadStackParams } from "@/lib/stack-server";
|
||||||
|
import { StackDisplay } from "./_components/stack-display";
|
||||||
|
|
||||||
|
interface StackPageProps {
|
||||||
|
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "Tech Stack - Better-T-Stack",
|
||||||
|
description: "View and share your custom tech stack configuration",
|
||||||
|
openGraph: {
|
||||||
|
title: "Tech Stack - Better-T-Stack",
|
||||||
|
description: "View and share your custom tech stack configuration",
|
||||||
|
url: "https://better-t-stack.dev/stack",
|
||||||
|
images: [
|
||||||
|
{
|
||||||
|
url: "https://r2.better-t-stack.dev/og.png",
|
||||||
|
width: 1200,
|
||||||
|
height: 630,
|
||||||
|
alt: "Better-T-Stack Tech Stack",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: "summary_large_image",
|
||||||
|
title: "Tech Stack - Better-T-Stack",
|
||||||
|
description: "View and share your custom tech stack configuration",
|
||||||
|
images: ["https://r2.better-t-stack.dev/og.png"],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function StackPage({ searchParams }: StackPageProps) {
|
||||||
|
const stackState = await loadStackParams(searchParams);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Suspense>
|
||||||
|
<StackDisplay stackState={stackState} />
|
||||||
|
</Suspense>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
MessageCircleIcon,
|
MessageCircleIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useMemo, useState } from "react";
|
import { useMemo, useState } from "react";
|
||||||
import { cn } from "../../../lib/cn";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const cache = new Map<string, string>();
|
const cache = new Map<string, string>();
|
||||||
|
|
||||||
|
|||||||
88
apps/web/src/components/ui/kibo-ui/qr-code/index.tsx
Normal file
88
apps/web/src/components/ui/kibo-ui/qr-code/index.tsx
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { formatHex, oklch } from "culori";
|
||||||
|
import QR from "qrcode";
|
||||||
|
import { type HTMLAttributes, useEffect, useState } from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
export type QRCodeProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
|
data: string;
|
||||||
|
foreground?: string;
|
||||||
|
background?: string;
|
||||||
|
robustness?: "L" | "M" | "Q" | "H";
|
||||||
|
};
|
||||||
|
|
||||||
|
const oklchRegex = /oklch\(([0-9.]+)\s+([0-9.]+)\s+([0-9.]+)\)/;
|
||||||
|
|
||||||
|
const getOklch = (color: string, fallback: [number, number, number]) => {
|
||||||
|
const oklchMatch = color.match(oklchRegex);
|
||||||
|
|
||||||
|
if (!oklchMatch) {
|
||||||
|
return { l: fallback[0], c: fallback[1], h: fallback[2] };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
l: Number.parseFloat(oklchMatch[1]),
|
||||||
|
c: Number.parseFloat(oklchMatch[2]),
|
||||||
|
h: Number.parseFloat(oklchMatch[3]),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const QRCode = ({
|
||||||
|
data,
|
||||||
|
foreground,
|
||||||
|
background,
|
||||||
|
robustness = "M",
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: QRCodeProps) => {
|
||||||
|
const [svg, setSVG] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const generateQR = async () => {
|
||||||
|
try {
|
||||||
|
const styles = getComputedStyle(document.documentElement);
|
||||||
|
const foregroundColor =
|
||||||
|
foreground ?? styles.getPropertyValue("--foreground");
|
||||||
|
const backgroundColor =
|
||||||
|
background ?? styles.getPropertyValue("--background");
|
||||||
|
|
||||||
|
const foregroundOklch = getOklch(
|
||||||
|
foregroundColor,
|
||||||
|
[0.21, 0.006, 285.885],
|
||||||
|
);
|
||||||
|
const backgroundOklch = getOklch(backgroundColor, [0.985, 0, 0]);
|
||||||
|
|
||||||
|
const newSvg = await QR.toString(data, {
|
||||||
|
type: "svg",
|
||||||
|
color: {
|
||||||
|
dark: formatHex(oklch({ mode: "oklch", ...foregroundOklch })),
|
||||||
|
light: formatHex(oklch({ mode: "oklch", ...backgroundOklch })),
|
||||||
|
},
|
||||||
|
width: 200,
|
||||||
|
errorCorrectionLevel: robustness,
|
||||||
|
margin: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
setSVG(newSvg);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
generateQR();
|
||||||
|
}, [data, foreground, background, robustness]);
|
||||||
|
|
||||||
|
if (!svg) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn("size-full", "[&_svg]:size-full", className)}
|
||||||
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: "Required for SVG"
|
||||||
|
dangerouslySetInnerHTML={{ __html: svg }}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
42
apps/web/src/components/ui/kibo-ui/qr-code/server.tsx
Normal file
42
apps/web/src/components/ui/kibo-ui/qr-code/server.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import QR from "qrcode";
|
||||||
|
import type { HTMLAttributes } from "react";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
export type QRCodeProps = HTMLAttributes<HTMLDivElement> & {
|
||||||
|
data: string;
|
||||||
|
foreground: string;
|
||||||
|
background: string;
|
||||||
|
robustness?: "L" | "M" | "Q" | "H";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const QRCode = async ({
|
||||||
|
data,
|
||||||
|
foreground,
|
||||||
|
background,
|
||||||
|
robustness = "M",
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: QRCodeProps) => {
|
||||||
|
const svg = await QR.toString(data, {
|
||||||
|
type: "svg",
|
||||||
|
color: {
|
||||||
|
dark: foreground,
|
||||||
|
light: background,
|
||||||
|
},
|
||||||
|
width: 200,
|
||||||
|
errorCorrectionLevel: robustness,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!svg) {
|
||||||
|
throw new Error("Failed to generate QR code");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn("size-full", "[&_svg]:size-full", className)}
|
||||||
|
// biome-ignore lint/security/noDangerouslySetInnerHtml: "Required for SVG"
|
||||||
|
dangerouslySetInnerHTML={{ __html: svg }}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
188
apps/web/src/components/ui/share-dialog.tsx
Normal file
188
apps/web/src/components/ui/share-dialog.tsx
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Check, Copy, Share2, Twitter } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { QRCode } from "@/components/ui/kibo-ui/qr-code";
|
||||||
|
import { TechBadge } from "@/components/ui/tech-badge";
|
||||||
|
import type { StackState } from "@/lib/constant";
|
||||||
|
import { TECH_OPTIONS } from "@/lib/constant";
|
||||||
|
import { CATEGORY_ORDER } from "@/lib/stack-utils";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
interface ShareDialogProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
stackUrl: string;
|
||||||
|
stackState: StackState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ShareDialog({
|
||||||
|
children,
|
||||||
|
stackUrl,
|
||||||
|
stackState,
|
||||||
|
}: ShareDialogProps) {
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
const techBadges = (() => {
|
||||||
|
const badges: React.ReactNode[] = [];
|
||||||
|
for (const category of CATEGORY_ORDER) {
|
||||||
|
const categoryKey = category as keyof StackState;
|
||||||
|
const options = TECH_OPTIONS[category as keyof typeof TECH_OPTIONS];
|
||||||
|
const selectedValue = stackState[categoryKey];
|
||||||
|
|
||||||
|
if (!options) continue;
|
||||||
|
|
||||||
|
if (Array.isArray(selectedValue)) {
|
||||||
|
if (
|
||||||
|
selectedValue.length === 0 ||
|
||||||
|
(selectedValue.length === 1 && selectedValue[0] === "none")
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const id of selectedValue) {
|
||||||
|
if (id === "none") continue;
|
||||||
|
const tech = options.find((opt) => opt.id === id);
|
||||||
|
if (tech) {
|
||||||
|
badges.push(
|
||||||
|
<TechBadge
|
||||||
|
key={`${category}-${tech.id}`}
|
||||||
|
icon={tech.icon}
|
||||||
|
name={tech.name}
|
||||||
|
category={category}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const tech = options.find((opt) => opt.id === selectedValue);
|
||||||
|
if (
|
||||||
|
!tech ||
|
||||||
|
tech.id === "none" ||
|
||||||
|
tech.id === "false" ||
|
||||||
|
((category === "git" ||
|
||||||
|
category === "install" ||
|
||||||
|
category === "auth") &&
|
||||||
|
tech.id === "true")
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
badges.push(
|
||||||
|
<TechBadge
|
||||||
|
key={`${category}-${tech.id}`}
|
||||||
|
icon={tech.icon}
|
||||||
|
name={tech.name}
|
||||||
|
category={category}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return badges;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const copyToClipboard = async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(stackUrl);
|
||||||
|
setCopied(true);
|
||||||
|
toast.success("Link copied to clipboard!");
|
||||||
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
} catch {
|
||||||
|
toast.error("Failed to copy link");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const shareToTwitter = () => {
|
||||||
|
const text = encodeURIComponent(
|
||||||
|
`Check out this cool tech stack I configured with Create Better T Stack!\n\n🚀 ${techBadges.length} technologies selected\n\n`,
|
||||||
|
);
|
||||||
|
const url = encodeURIComponent(stackUrl);
|
||||||
|
window.open(
|
||||||
|
`https://twitter.com/intent/tweet?text=${text}&url=${url}`,
|
||||||
|
"_blank",
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog>
|
||||||
|
<DialogTrigger asChild>{children}</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center gap-2">
|
||||||
|
<Share2 className="h-5 w-5" />
|
||||||
|
Share Your Stack
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Share your custom tech stack configuration with others
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="font-medium text-foreground text-sm">
|
||||||
|
Technologies
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap gap-1.5 rounded border border-border bg-muted/20 p-3">
|
||||||
|
{techBadges.length > 0 ? (
|
||||||
|
techBadges
|
||||||
|
) : (
|
||||||
|
<span className="text-muted-foreground text-sm">
|
||||||
|
No technologies selected
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="font-medium text-foreground text-sm">QR Code</div>
|
||||||
|
<div className="flex items-center justify-center rounded border border-border bg-muted/20 p-4">
|
||||||
|
<div className="h-32 w-32">
|
||||||
|
<QRCode data={stackUrl} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="text-center text-muted-foreground text-xs">
|
||||||
|
Scan to view this tech stack
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="font-medium text-foreground text-sm">Share</div>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={shareToTwitter}
|
||||||
|
className="flex-1"
|
||||||
|
>
|
||||||
|
<Twitter className="h-4 w-4" />X (Twitter)
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
onClick={copyToClipboard}
|
||||||
|
className={cn(
|
||||||
|
"flex-1",
|
||||||
|
copied &&
|
||||||
|
"border-green-500/20 bg-green-500/10 text-green-600 dark:text-green-400",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{copied ? (
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<Copy className="h-4 w-4" />
|
||||||
|
)}
|
||||||
|
{copied ? "Copied!" : "Copy URL"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
109
apps/web/src/components/ui/tech-badge.tsx
Normal file
109
apps/web/src/components/ui/tech-badge.tsx
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Image from "next/image";
|
||||||
|
import { useTheme } from "next-themes";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
interface TechBadgeProps {
|
||||||
|
icon: string;
|
||||||
|
name: string;
|
||||||
|
category: string;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getBadgeColors = (category: string): string => {
|
||||||
|
switch (category) {
|
||||||
|
case "webFrontend":
|
||||||
|
case "nativeFrontend":
|
||||||
|
return "border-blue-300 bg-blue-100 text-blue-800 dark:border-blue-700/30 dark:bg-blue-900/30 dark:text-blue-300";
|
||||||
|
case "runtime":
|
||||||
|
return "border-amber-300 bg-amber-100 text-amber-800 dark:border-amber-700/30 dark:bg-amber-900/30 dark:text-amber-300";
|
||||||
|
case "backend":
|
||||||
|
return "border-sky-300 bg-sky-100 text-sky-800 dark:border-sky-700/30 dark:bg-sky-900/30 dark:text-sky-300";
|
||||||
|
case "api":
|
||||||
|
return "border-indigo-300 bg-indigo-100 text-indigo-800 dark:border-indigo-700/30 dark:bg-indigo-900/30 dark:text-indigo-300";
|
||||||
|
case "database":
|
||||||
|
return "border-emerald-300 bg-emerald-100 text-emerald-800 dark:border-emerald-700/30 dark:bg-emerald-900/30 dark:text-emerald-300";
|
||||||
|
case "orm":
|
||||||
|
return "border-cyan-300 bg-cyan-100 text-cyan-800 dark:border-cyan-700/30 dark:bg-cyan-900/30 dark:text-cyan-300";
|
||||||
|
case "auth":
|
||||||
|
return "border-green-300 bg-green-100 text-green-800 dark:border-green-700/30 dark:bg-green-900/30 dark:text-green-300";
|
||||||
|
case "dbSetup":
|
||||||
|
return "border-pink-300 bg-pink-100 text-pink-800 dark:border-pink-700/30 dark:bg-pink-900/30 dark:text-pink-300";
|
||||||
|
case "addons":
|
||||||
|
return "border-violet-300 bg-violet-100 text-violet-800 dark:border-violet-700/30 dark:bg-violet-900/30 dark:text-violet-300";
|
||||||
|
case "examples":
|
||||||
|
return "border-teal-300 bg-teal-100 text-teal-800 dark:border-teal-700/30 dark:bg-teal-900/30 dark:text-teal-300";
|
||||||
|
case "packageManager":
|
||||||
|
return "border-orange-300 bg-orange-100 text-orange-800 dark:border-orange-700/30 dark:bg-orange-900/30 dark:text-orange-300";
|
||||||
|
case "git":
|
||||||
|
case "webDeploy":
|
||||||
|
case "serverDeploy":
|
||||||
|
case "install":
|
||||||
|
return "border-gray-300 bg-gray-100 text-gray-700 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400";
|
||||||
|
default:
|
||||||
|
return "border-gray-300 bg-gray-100 text-gray-800 dark:border-gray-700/30 dark:bg-gray-900/30 dark:text-gray-300";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function TechIcon({
|
||||||
|
icon,
|
||||||
|
name,
|
||||||
|
className,
|
||||||
|
}: {
|
||||||
|
icon: string;
|
||||||
|
name: string;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
if (!icon) return null;
|
||||||
|
|
||||||
|
if (!icon.startsWith("https://")) {
|
||||||
|
return (
|
||||||
|
<span className={cn("inline-flex items-center text-lg", className)}>
|
||||||
|
{icon}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let iconSrc = icon;
|
||||||
|
if (
|
||||||
|
theme === "light" &&
|
||||||
|
(icon.includes("drizzle") ||
|
||||||
|
icon.includes("prisma") ||
|
||||||
|
icon.includes("express") ||
|
||||||
|
icon.includes("clerk"))
|
||||||
|
) {
|
||||||
|
iconSrc = icon.replace(".svg", "-light.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Image
|
||||||
|
suppressHydrationWarning
|
||||||
|
src={iconSrc}
|
||||||
|
alt={`${name} icon`}
|
||||||
|
width={20}
|
||||||
|
height={20}
|
||||||
|
className={cn("inline-block", className)}
|
||||||
|
unoptimized
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TechBadge({ icon, name, category, className }: TechBadgeProps) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"inline-flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-xs",
|
||||||
|
getBadgeColors(category),
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{icon !== "" && (
|
||||||
|
<TechIcon icon={icon} name={name} className={cn("h-3 w-3")} />
|
||||||
|
)}
|
||||||
|
{name}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
65
apps/web/src/lib/stack-server.ts
Normal file
65
apps/web/src/lib/stack-server.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import {
|
||||||
|
createLoader,
|
||||||
|
parseAsArrayOf,
|
||||||
|
parseAsString,
|
||||||
|
parseAsStringEnum,
|
||||||
|
} from "nuqs/server";
|
||||||
|
import { DEFAULT_STACK, type StackState, TECH_OPTIONS } from "@/lib/constant";
|
||||||
|
|
||||||
|
const getValidIds = (category: keyof typeof TECH_OPTIONS): string[] => {
|
||||||
|
return TECH_OPTIONS[category]?.map((opt) => opt.id) ?? [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Server-side parsers (same as client-side but imported from nuqs/server)
|
||||||
|
const serverStackParsers = {
|
||||||
|
projectName: parseAsString.withDefault(DEFAULT_STACK.projectName),
|
||||||
|
webFrontend: parseAsArrayOf(parseAsString).withDefault(
|
||||||
|
DEFAULT_STACK.webFrontend,
|
||||||
|
),
|
||||||
|
nativeFrontend: parseAsArrayOf(parseAsString).withDefault(
|
||||||
|
DEFAULT_STACK.nativeFrontend,
|
||||||
|
),
|
||||||
|
runtime: parseAsStringEnum<StackState["runtime"]>(
|
||||||
|
getValidIds("runtime"),
|
||||||
|
).withDefault(DEFAULT_STACK.runtime),
|
||||||
|
backend: parseAsStringEnum<StackState["backend"]>(
|
||||||
|
getValidIds("backend"),
|
||||||
|
).withDefault(DEFAULT_STACK.backend),
|
||||||
|
api: parseAsStringEnum<StackState["api"]>(getValidIds("api")).withDefault(
|
||||||
|
DEFAULT_STACK.api,
|
||||||
|
),
|
||||||
|
database: parseAsStringEnum<StackState["database"]>(
|
||||||
|
getValidIds("database"),
|
||||||
|
).withDefault(DEFAULT_STACK.database),
|
||||||
|
orm: parseAsStringEnum<StackState["orm"]>(getValidIds("orm")).withDefault(
|
||||||
|
DEFAULT_STACK.orm,
|
||||||
|
),
|
||||||
|
dbSetup: parseAsStringEnum<StackState["dbSetup"]>(
|
||||||
|
getValidIds("dbSetup"),
|
||||||
|
).withDefault(DEFAULT_STACK.dbSetup),
|
||||||
|
auth: parseAsStringEnum<StackState["auth"]>(getValidIds("auth")).withDefault(
|
||||||
|
DEFAULT_STACK.auth,
|
||||||
|
),
|
||||||
|
packageManager: parseAsStringEnum<StackState["packageManager"]>(
|
||||||
|
getValidIds("packageManager"),
|
||||||
|
).withDefault(DEFAULT_STACK.packageManager),
|
||||||
|
addons: parseAsArrayOf(parseAsString).withDefault(DEFAULT_STACK.addons),
|
||||||
|
examples: parseAsArrayOf(parseAsString).withDefault(DEFAULT_STACK.examples),
|
||||||
|
git: parseAsStringEnum<StackState["git"]>(["true", "false"]).withDefault(
|
||||||
|
DEFAULT_STACK.git,
|
||||||
|
),
|
||||||
|
install: parseAsStringEnum<StackState["install"]>([
|
||||||
|
"true",
|
||||||
|
"false",
|
||||||
|
]).withDefault(DEFAULT_STACK.install),
|
||||||
|
webDeploy: parseAsStringEnum<StackState["webDeploy"]>(
|
||||||
|
getValidIds("webDeploy"),
|
||||||
|
).withDefault(DEFAULT_STACK.webDeploy),
|
||||||
|
serverDeploy: parseAsStringEnum<StackState["serverDeploy"]>(
|
||||||
|
getValidIds("serverDeploy"),
|
||||||
|
).withDefault(DEFAULT_STACK.serverDeploy),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadStackParams = createLoader(serverStackParsers);
|
||||||
|
|
||||||
|
export type LoadedStackState = Awaited<ReturnType<typeof loadStackParams>>;
|
||||||
@@ -83,4 +83,5 @@ export const stackQueryStatesOptions = {
|
|||||||
history: "replace" as const,
|
history: "replace" as const,
|
||||||
shallow: false,
|
shallow: false,
|
||||||
urlKeys: stackUrlKeys,
|
urlKeys: stackUrlKeys,
|
||||||
|
clearOnDefault: true,
|
||||||
};
|
};
|
||||||
|
|||||||
467
apps/web/src/lib/stack-utils.ts
Normal file
467
apps/web/src/lib/stack-utils.ts
Normal file
@@ -0,0 +1,467 @@
|
|||||||
|
import {
|
||||||
|
parseAsArrayOf,
|
||||||
|
parseAsString,
|
||||||
|
parseAsStringEnum,
|
||||||
|
useQueryState,
|
||||||
|
useQueryStates,
|
||||||
|
} from "nuqs";
|
||||||
|
import {
|
||||||
|
DEFAULT_STACK,
|
||||||
|
isStackDefault,
|
||||||
|
type StackState,
|
||||||
|
TECH_OPTIONS,
|
||||||
|
} from "@/lib/constant";
|
||||||
|
import {
|
||||||
|
stackParsers,
|
||||||
|
stackQueryStatesOptions,
|
||||||
|
stackUrlKeys,
|
||||||
|
} from "@/lib/stack-url-state";
|
||||||
|
|
||||||
|
const getValidIds = (category: keyof typeof TECH_OPTIONS): string[] => {
|
||||||
|
return TECH_OPTIONS[category]?.map((opt) => opt.id) ?? [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const CATEGORY_ORDER: Array<keyof typeof TECH_OPTIONS> = [
|
||||||
|
"webFrontend",
|
||||||
|
"nativeFrontend",
|
||||||
|
"backend",
|
||||||
|
"runtime",
|
||||||
|
"api",
|
||||||
|
"database",
|
||||||
|
"orm",
|
||||||
|
"dbSetup",
|
||||||
|
"webDeploy",
|
||||||
|
"serverDeploy",
|
||||||
|
"auth",
|
||||||
|
"packageManager",
|
||||||
|
"addons",
|
||||||
|
"examples",
|
||||||
|
"git",
|
||||||
|
"install",
|
||||||
|
];
|
||||||
|
|
||||||
|
function getStackKeyFromUrlKey(urlKey: string): keyof StackState | null {
|
||||||
|
for (const [stackKey, urlKeyValue] of Object.entries(stackUrlKeys)) {
|
||||||
|
if (urlKeyValue === urlKey) {
|
||||||
|
return stackKey as keyof StackState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseSearchParamsToStack(searchParams: {
|
||||||
|
[key: string]: string | string[] | undefined;
|
||||||
|
}): StackState {
|
||||||
|
const parsedStack: StackState = { ...DEFAULT_STACK };
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(searchParams)) {
|
||||||
|
if (
|
||||||
|
key === "utm_source" ||
|
||||||
|
key === "utm_medium" ||
|
||||||
|
key === "utm_campaign"
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stackKey = getStackKeyFromUrlKey(key);
|
||||||
|
if (stackKey && value !== undefined) {
|
||||||
|
try {
|
||||||
|
const parser = stackParsers[stackKey];
|
||||||
|
if (parser) {
|
||||||
|
const parsedValue = parser.parseServerSide(
|
||||||
|
Array.isArray(value) ? value[0] : value,
|
||||||
|
);
|
||||||
|
(parsedStack as Record<string, unknown>)[stackKey] = parsedValue;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(`Failed to parse ${key}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, defaultValue] of Object.entries(DEFAULT_STACK)) {
|
||||||
|
if (parsedStack[key as keyof StackState] === undefined) {
|
||||||
|
(parsedStack as Record<string, unknown>)[key] = defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a human-readable summary of the stack
|
||||||
|
*/
|
||||||
|
export function generateStackSummary(stack: StackState): string {
|
||||||
|
const selectedTechs: string[] = [];
|
||||||
|
|
||||||
|
for (const category of CATEGORY_ORDER) {
|
||||||
|
const categoryKey = category as keyof StackState;
|
||||||
|
const options = TECH_OPTIONS[category as keyof typeof TECH_OPTIONS];
|
||||||
|
const selectedValue = stack[categoryKey];
|
||||||
|
|
||||||
|
if (!options) continue;
|
||||||
|
|
||||||
|
if (Array.isArray(selectedValue)) {
|
||||||
|
if (
|
||||||
|
selectedValue.length === 0 ||
|
||||||
|
(selectedValue.length === 1 && selectedValue[0] === "none")
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const id of selectedValue) {
|
||||||
|
if (id === "none") continue;
|
||||||
|
const tech = options.find((opt) => opt.id === id);
|
||||||
|
if (tech) {
|
||||||
|
selectedTechs.push(tech.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const tech = options.find((opt) => opt.id === selectedValue);
|
||||||
|
if (
|
||||||
|
!tech ||
|
||||||
|
tech.id === "none" ||
|
||||||
|
tech.id === "false" ||
|
||||||
|
((category === "git" ||
|
||||||
|
category === "install" ||
|
||||||
|
category === "auth") &&
|
||||||
|
tech.id === "true")
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
selectedTechs.push(tech.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedTechs.length > 0 ? selectedTechs.join(" • ") : "Custom stack";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateStackCommand(stack: StackState): string {
|
||||||
|
let base: string;
|
||||||
|
switch (stack.packageManager) {
|
||||||
|
case "npm":
|
||||||
|
base = "npx create-better-t-stack@latest";
|
||||||
|
break;
|
||||||
|
case "pnpm":
|
||||||
|
base = "pnpm create better-t-stack@latest";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
base = "bun create better-t-stack@latest";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectName = stack.projectName || "my-better-t-app";
|
||||||
|
const flags: string[] = [];
|
||||||
|
|
||||||
|
const isDefaultStack = Object.keys(DEFAULT_STACK).every((key) => {
|
||||||
|
if (key === "projectName") return true;
|
||||||
|
const defaultKey = key as keyof StackState;
|
||||||
|
return isStackDefault(stack, defaultKey, stack[defaultKey]);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isDefaultStack) {
|
||||||
|
flags.push("--yes");
|
||||||
|
} else {
|
||||||
|
const combinedFrontends = [
|
||||||
|
...stack.webFrontend,
|
||||||
|
...stack.nativeFrontend,
|
||||||
|
].filter((v, _, arr) => v !== "none" || arr.length === 1);
|
||||||
|
|
||||||
|
if (combinedFrontends.length === 0 || combinedFrontends[0] === "none") {
|
||||||
|
flags.push("--frontend none");
|
||||||
|
} else {
|
||||||
|
flags.push(`--frontend ${combinedFrontends.join(" ")}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
flags.push(`--backend ${stack.backend}`);
|
||||||
|
flags.push(`--runtime ${stack.runtime}`);
|
||||||
|
flags.push(`--api ${stack.api}`);
|
||||||
|
flags.push(`--auth ${stack.auth}`);
|
||||||
|
flags.push(`--database ${stack.database}`);
|
||||||
|
flags.push(`--orm ${stack.orm}`);
|
||||||
|
flags.push(`--db-setup ${stack.dbSetup}`);
|
||||||
|
flags.push(`--package-manager ${stack.packageManager}`);
|
||||||
|
|
||||||
|
if (stack.git === "false") {
|
||||||
|
flags.push("--no-git");
|
||||||
|
} else {
|
||||||
|
flags.push("--git");
|
||||||
|
}
|
||||||
|
|
||||||
|
flags.push(`--web-deploy ${stack.webDeploy}`);
|
||||||
|
flags.push(`--server-deploy ${stack.serverDeploy}`);
|
||||||
|
|
||||||
|
if (stack.install === "false") {
|
||||||
|
flags.push("--no-install");
|
||||||
|
} else {
|
||||||
|
flags.push("--install");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stack.addons.length > 0) {
|
||||||
|
const validAddons = stack.addons.filter((addon) =>
|
||||||
|
[
|
||||||
|
"pwa",
|
||||||
|
"tauri",
|
||||||
|
"starlight",
|
||||||
|
"biome",
|
||||||
|
"husky",
|
||||||
|
"turborepo",
|
||||||
|
"ultracite",
|
||||||
|
"fumadocs",
|
||||||
|
"oxlint",
|
||||||
|
"ruler",
|
||||||
|
].includes(addon),
|
||||||
|
);
|
||||||
|
if (validAddons.length > 0) {
|
||||||
|
flags.push(`--addons ${validAddons.join(" ")}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
flags.push("--addons none");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stack.examples.length > 0) {
|
||||||
|
flags.push(`--examples ${stack.examples.join(" ")}`);
|
||||||
|
} else {
|
||||||
|
flags.push("--examples none");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${base} ${projectName}${
|
||||||
|
flags.length > 0 ? ` ${flags.join(" ")}` : ""
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate stack URL from pathname and search params
|
||||||
|
*/
|
||||||
|
export function generateStackUrl(
|
||||||
|
pathname: string,
|
||||||
|
searchParams: URLSearchParams,
|
||||||
|
): string {
|
||||||
|
const searchString = searchParams.toString();
|
||||||
|
const relativeUrl = `${pathname}${searchString ? `?${searchString}` : ""}`;
|
||||||
|
return `https://better-t-stack.dev${relativeUrl}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateStackUrlFromState(
|
||||||
|
stack: StackState,
|
||||||
|
baseUrl?: string,
|
||||||
|
): string {
|
||||||
|
const origin =
|
||||||
|
baseUrl ||
|
||||||
|
(typeof window !== "undefined"
|
||||||
|
? window.location.origin
|
||||||
|
: "https://better-t-stack.dev");
|
||||||
|
|
||||||
|
const isDefaultStack = Object.keys(DEFAULT_STACK).every((key) => {
|
||||||
|
if (key === "projectName") return true;
|
||||||
|
const defaultKey = key as keyof StackState;
|
||||||
|
return isStackDefault(stack, defaultKey, stack[defaultKey]);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isDefaultStack) {
|
||||||
|
return `${origin}/stack`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stackParams = new URLSearchParams();
|
||||||
|
|
||||||
|
for (const [stackKey, urlKey] of Object.entries(stackUrlKeys)) {
|
||||||
|
const value = stack[stackKey as keyof StackState];
|
||||||
|
if (value !== undefined) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
stackParams.set(urlKey, value.join(","));
|
||||||
|
} else {
|
||||||
|
stackParams.set(urlKey, String(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${origin}/stack?${stackParams.toString()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useStackStateWithAllParams() {
|
||||||
|
const [stack, setStack] = useQueryStates(
|
||||||
|
stackParsers,
|
||||||
|
stackQueryStatesOptions,
|
||||||
|
);
|
||||||
|
|
||||||
|
const setStackWithAllParams = async (
|
||||||
|
newStack: Partial<StackState> | ((prev: StackState) => Partial<StackState>),
|
||||||
|
) => {
|
||||||
|
const updatedStack =
|
||||||
|
typeof newStack === "function" ? newStack(stack) : newStack;
|
||||||
|
const finalStack = { ...stack, ...updatedStack };
|
||||||
|
|
||||||
|
const isFinalStackDefault = Object.keys(DEFAULT_STACK).every((key) => {
|
||||||
|
if (key === "projectName") return true;
|
||||||
|
const defaultKey = key as keyof StackState;
|
||||||
|
return isStackDefault(finalStack, defaultKey, finalStack[defaultKey]);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isFinalStackDefault) {
|
||||||
|
await setStack(null);
|
||||||
|
} else {
|
||||||
|
await setStack(finalStack);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return [stack, setStackWithAllParams] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useIndividualStackStates() {
|
||||||
|
const [projectName, setProjectName] = useQueryState(
|
||||||
|
"name",
|
||||||
|
parseAsString.withDefault(DEFAULT_STACK.projectName),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [webFrontend, setWebFrontend] = useQueryState(
|
||||||
|
"fe-w",
|
||||||
|
parseAsArrayOf(parseAsString).withDefault(DEFAULT_STACK.webFrontend),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [nativeFrontend, setNativeFrontend] = useQueryState(
|
||||||
|
"fe-n",
|
||||||
|
parseAsArrayOf(parseAsString).withDefault(DEFAULT_STACK.nativeFrontend),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [runtime, setRuntime] = useQueryState(
|
||||||
|
"rt",
|
||||||
|
parseAsStringEnum(getValidIds("runtime")).withDefault(
|
||||||
|
DEFAULT_STACK.runtime,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [backend, setBackend] = useQueryState(
|
||||||
|
"be",
|
||||||
|
parseAsStringEnum(getValidIds("backend")).withDefault(
|
||||||
|
DEFAULT_STACK.backend,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [api, setApi] = useQueryState(
|
||||||
|
"api",
|
||||||
|
parseAsStringEnum(getValidIds("api")).withDefault(DEFAULT_STACK.api),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [database, setDatabase] = useQueryState(
|
||||||
|
"db",
|
||||||
|
parseAsStringEnum(getValidIds("database")).withDefault(
|
||||||
|
DEFAULT_STACK.database,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [orm, setOrm] = useQueryState(
|
||||||
|
"orm",
|
||||||
|
parseAsStringEnum(getValidIds("orm")).withDefault(DEFAULT_STACK.orm),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [dbSetup, setDbSetup] = useQueryState(
|
||||||
|
"dbs",
|
||||||
|
parseAsStringEnum(getValidIds("dbSetup")).withDefault(
|
||||||
|
DEFAULT_STACK.dbSetup,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [auth, setAuth] = useQueryState(
|
||||||
|
"au",
|
||||||
|
parseAsStringEnum(getValidIds("auth")).withDefault(DEFAULT_STACK.auth),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [packageManager, setPackageManager] = useQueryState(
|
||||||
|
"pm",
|
||||||
|
parseAsStringEnum(getValidIds("packageManager")).withDefault(
|
||||||
|
DEFAULT_STACK.packageManager,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [addons, setAddons] = useQueryState(
|
||||||
|
"add",
|
||||||
|
parseAsArrayOf(parseAsString).withDefault(DEFAULT_STACK.addons),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [examples, setExamples] = useQueryState(
|
||||||
|
"ex",
|
||||||
|
parseAsArrayOf(parseAsString).withDefault(DEFAULT_STACK.examples),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [git, setGit] = useQueryState(
|
||||||
|
"git",
|
||||||
|
parseAsStringEnum(["true", "false"] as const).withDefault(
|
||||||
|
DEFAULT_STACK.git as "true" | "false",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [install, setInstall] = useQueryState(
|
||||||
|
"i",
|
||||||
|
parseAsStringEnum(["true", "false"] as const).withDefault(
|
||||||
|
DEFAULT_STACK.install as "true" | "false",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [webDeploy, setWebDeploy] = useQueryState(
|
||||||
|
"wd",
|
||||||
|
parseAsStringEnum(getValidIds("webDeploy")).withDefault(
|
||||||
|
DEFAULT_STACK.webDeploy,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [serverDeploy, setServerDeploy] = useQueryState(
|
||||||
|
"sd",
|
||||||
|
parseAsStringEnum(getValidIds("serverDeploy")).withDefault(
|
||||||
|
DEFAULT_STACK.serverDeploy,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const stack: StackState = {
|
||||||
|
projectName,
|
||||||
|
webFrontend,
|
||||||
|
nativeFrontend,
|
||||||
|
runtime,
|
||||||
|
backend,
|
||||||
|
api,
|
||||||
|
database,
|
||||||
|
orm,
|
||||||
|
dbSetup,
|
||||||
|
auth,
|
||||||
|
packageManager,
|
||||||
|
addons,
|
||||||
|
examples,
|
||||||
|
git,
|
||||||
|
install,
|
||||||
|
webDeploy,
|
||||||
|
serverDeploy,
|
||||||
|
};
|
||||||
|
|
||||||
|
const setStack = async (updates: Partial<StackState>) => {
|
||||||
|
const setters = {
|
||||||
|
projectName: setProjectName,
|
||||||
|
webFrontend: setWebFrontend,
|
||||||
|
nativeFrontend: setNativeFrontend,
|
||||||
|
runtime: setRuntime,
|
||||||
|
backend: setBackend,
|
||||||
|
api: setApi,
|
||||||
|
database: setDatabase,
|
||||||
|
orm: setOrm,
|
||||||
|
dbSetup: setDbSetup,
|
||||||
|
auth: setAuth,
|
||||||
|
packageManager: setPackageManager,
|
||||||
|
addons: setAddons,
|
||||||
|
examples: setExamples,
|
||||||
|
git: setGit,
|
||||||
|
install: setInstall,
|
||||||
|
webDeploy: setWebDeploy,
|
||||||
|
serverDeploy: setServerDeploy,
|
||||||
|
};
|
||||||
|
|
||||||
|
const promises = Object.entries(updates).map(([key, value]) => {
|
||||||
|
const setter = setters[key as keyof typeof setters];
|
||||||
|
return setter(value as never);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
};
|
||||||
|
|
||||||
|
return [stack, setStack] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { CATEGORY_ORDER };
|
||||||
60
bun.lock
60
bun.lock
@@ -60,6 +60,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"convex": "^1.25.4",
|
"convex": "^1.25.4",
|
||||||
"convex-helpers": "^0.1.104",
|
"convex-helpers": "^0.1.104",
|
||||||
|
"culori": "^4.0.2",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"fumadocs-core": "15.6.7",
|
"fumadocs-core": "15.6.7",
|
||||||
"fumadocs-mdx": "11.7.3",
|
"fumadocs-mdx": "11.7.3",
|
||||||
@@ -71,6 +72,7 @@
|
|||||||
"nuqs": "^2.4.3",
|
"nuqs": "^2.4.3",
|
||||||
"papaparse": "^5.5.3",
|
"papaparse": "^5.5.3",
|
||||||
"posthog-js": "^1.258.5",
|
"posthog-js": "^1.258.5",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
"radix-ui": "^1.4.2",
|
"radix-ui": "^1.4.2",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
@@ -85,9 +87,11 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/postcss": "^4.1.11",
|
"@tailwindcss/postcss": "^4.1.11",
|
||||||
|
"@types/culori": "^4.0.0",
|
||||||
"@types/mdx": "^2.0.13",
|
"@types/mdx": "^2.0.13",
|
||||||
"@types/node": "24.1.0",
|
"@types/node": "24.1.0",
|
||||||
"@types/papaparse": "^5.3.16",
|
"@types/papaparse": "^5.3.16",
|
||||||
|
"@types/qrcode": "^1.5.5",
|
||||||
"@types/react": "^19.1.9",
|
"@types/react": "^19.1.9",
|
||||||
"@types/react-dom": "^19.1.7",
|
"@types/react-dom": "^19.1.7",
|
||||||
"eslint": "^9.32.0",
|
"eslint": "^9.32.0",
|
||||||
@@ -947,6 +951,8 @@
|
|||||||
|
|
||||||
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
|
"@types/chai": ["@types/chai@5.2.2", "", { "dependencies": { "@types/deep-eql": "*" } }, "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg=="],
|
||||||
|
|
||||||
|
"@types/culori": ["@types/culori@4.0.0", "", {}, "sha512-aFljQwjb++sl6TAyEXeHTiK/fk9epZOQ+nMmadjnAvzZFIvNoQ0x8XQYfcOaRTBwmDUPUlghhZCJ66MTcqQAsg=="],
|
||||||
|
|
||||||
"@types/d3-array": ["@types/d3-array@3.2.1", "", {}, "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="],
|
"@types/d3-array": ["@types/d3-array@3.2.1", "", {}, "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg=="],
|
||||||
|
|
||||||
"@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="],
|
"@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="],
|
||||||
@@ -997,6 +1003,8 @@
|
|||||||
|
|
||||||
"@types/papaparse": ["@types/papaparse@5.3.16", "", { "dependencies": { "@types/node": "*" } }, "sha512-T3VuKMC2H0lgsjI9buTB3uuKj3EMD2eap1MOuEQuBQ44EnDx/IkGhU6EwiTf9zG3za4SKlmwKAImdDKdNnCsXg=="],
|
"@types/papaparse": ["@types/papaparse@5.3.16", "", { "dependencies": { "@types/node": "*" } }, "sha512-T3VuKMC2H0lgsjI9buTB3uuKj3EMD2eap1MOuEQuBQ44EnDx/IkGhU6EwiTf9zG3za4SKlmwKAImdDKdNnCsXg=="],
|
||||||
|
|
||||||
|
"@types/qrcode": ["@types/qrcode@1.5.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg=="],
|
||||||
|
|
||||||
"@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="],
|
"@types/react": ["@types/react@19.1.12", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w=="],
|
||||||
|
|
||||||
"@types/react-dom": ["@types/react-dom@19.1.9", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ=="],
|
"@types/react-dom": ["@types/react-dom@19.1.9", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ=="],
|
||||||
@@ -1203,6 +1211,8 @@
|
|||||||
|
|
||||||
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
|
||||||
|
|
||||||
|
"camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
|
||||||
|
|
||||||
"caniuse-lite": ["caniuse-lite@1.0.30001737", "", {}, "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw=="],
|
"caniuse-lite": ["caniuse-lite@1.0.30001737", "", {}, "sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw=="],
|
||||||
|
|
||||||
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
||||||
@@ -1309,6 +1319,8 @@
|
|||||||
|
|
||||||
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
|
||||||
|
|
||||||
|
"culori": ["culori@4.0.2", "", {}, "sha512-1+BhOB8ahCn4O0cep0Sh2l9KCOfOdY+BXJnKMHFFzDEouSr/el18QwXEMRlOj9UY5nCeA8UN3a/82rUWRBeyBw=="],
|
||||||
|
|
||||||
"d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
|
"d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
|
||||||
|
|
||||||
"d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
|
"d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
|
||||||
@@ -1343,6 +1355,8 @@
|
|||||||
|
|
||||||
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
|
||||||
|
|
||||||
|
"decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
|
||||||
|
|
||||||
"decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="],
|
"decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="],
|
||||||
|
|
||||||
"decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="],
|
"decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="],
|
||||||
@@ -1379,6 +1393,8 @@
|
|||||||
|
|
||||||
"diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="],
|
"diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="],
|
||||||
|
|
||||||
|
"dijkstrajs": ["dijkstrajs@1.0.3", "", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="],
|
||||||
|
|
||||||
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
|
"doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
|
||||||
|
|
||||||
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
|
"dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="],
|
||||||
@@ -1735,7 +1751,7 @@
|
|||||||
|
|
||||||
"is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="],
|
"is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="],
|
||||||
|
|
||||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="],
|
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||||
|
|
||||||
"is-generator-function": ["is-generator-function@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "get-proto": "^1.0.0", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ=="],
|
"is-generator-function": ["is-generator-function@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "get-proto": "^1.0.0", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ=="],
|
||||||
|
|
||||||
@@ -2131,6 +2147,8 @@
|
|||||||
|
|
||||||
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
|
||||||
|
|
||||||
|
"p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
|
||||||
|
|
||||||
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
|
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
|
||||||
|
|
||||||
"paged-request": ["paged-request@2.0.2", "", { "dependencies": { "axios": "^0.21.1" } }, "sha512-NWrGqneZImDdcMU/7vMcAOo1bIi5h/pmpJqe7/jdsy85BA/s5MSaU/KlpxwW/IVPmIwBcq2uKPrBWWhEWhtxag=="],
|
"paged-request": ["paged-request@2.0.2", "", { "dependencies": { "axios": "^0.21.1" } }, "sha512-NWrGqneZImDdcMU/7vMcAOo1bIi5h/pmpJqe7/jdsy85BA/s5MSaU/KlpxwW/IVPmIwBcq2uKPrBWWhEWhtxag=="],
|
||||||
@@ -2177,6 +2195,8 @@
|
|||||||
|
|
||||||
"pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
|
"pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="],
|
||||||
|
|
||||||
|
"pngjs": ["pngjs@5.0.0", "", {}, "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw=="],
|
||||||
|
|
||||||
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
"possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
|
||||||
|
|
||||||
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
|
||||||
@@ -2203,6 +2223,8 @@
|
|||||||
|
|
||||||
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
|
||||||
|
|
||||||
|
"qrcode": ["qrcode@1.5.4", "", { "dependencies": { "dijkstrajs": "^1.0.1", "pngjs": "^5.0.0", "yargs": "^15.3.1" }, "bin": { "qrcode": "bin/qrcode" } }, "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg=="],
|
||||||
|
|
||||||
"qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
|
"qs": ["qs@6.13.0", "", { "dependencies": { "side-channel": "^1.0.6" } }, "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg=="],
|
||||||
|
|
||||||
"quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="],
|
"quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="],
|
||||||
@@ -2277,6 +2299,10 @@
|
|||||||
|
|
||||||
"remeda": ["remeda@2.30.0", "", { "dependencies": { "type-fest": "^4.41.0" } }, "sha512-TcRpI1ecqnMer3jHhFtMerGvHFCDlCHljUp0/9A4HxHOh5bSY3kP1l8nQDFMnWYJKl3MSarDNY1tb0Bs/bCmvw=="],
|
"remeda": ["remeda@2.30.0", "", { "dependencies": { "type-fest": "^4.41.0" } }, "sha512-TcRpI1ecqnMer3jHhFtMerGvHFCDlCHljUp0/9A4HxHOh5bSY3kP1l8nQDFMnWYJKl3MSarDNY1tb0Bs/bCmvw=="],
|
||||||
|
|
||||||
|
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||||
|
|
||||||
|
"require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="],
|
||||||
|
|
||||||
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
|
"resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
|
||||||
|
|
||||||
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||||
@@ -2323,6 +2349,8 @@
|
|||||||
|
|
||||||
"serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
|
"serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
|
||||||
|
|
||||||
|
"set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="],
|
||||||
|
|
||||||
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
"set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="],
|
||||||
|
|
||||||
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
|
"set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="],
|
||||||
@@ -2619,6 +2647,8 @@
|
|||||||
|
|
||||||
"which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="],
|
"which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="],
|
||||||
|
|
||||||
|
"which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="],
|
||||||
|
|
||||||
"which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="],
|
"which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="],
|
||||||
|
|
||||||
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
|
"why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "^2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="],
|
||||||
@@ -3361,20 +3391,22 @@
|
|||||||
|
|
||||||
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
"prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
|
||||||
|
|
||||||
|
"qrcode/yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="],
|
||||||
|
|
||||||
"restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
|
"restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="],
|
||||||
|
|
||||||
"router/path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="],
|
"router/path-to-regexp": ["path-to-regexp@8.2.0", "", {}, "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ=="],
|
||||||
|
|
||||||
"slice-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
|
"slice-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
|
||||||
|
|
||||||
|
"slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="],
|
||||||
|
|
||||||
"string-width/emoji-regex": ["emoji-regex@10.5.0", "", {}, "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg=="],
|
"string-width/emoji-regex": ["emoji-regex@10.5.0", "", {}, "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg=="],
|
||||||
|
|
||||||
"string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
"string-width/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
|
||||||
|
|
||||||
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
"string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
|
|
||||||
"string-width-cjs/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
|
||||||
|
|
||||||
"terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
|
"terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
|
||||||
|
|
||||||
"to-object-path/kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="],
|
"to-object-path/kind-of": ["kind-of@3.2.2", "", { "dependencies": { "is-buffer": "^1.1.5" } }, "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ=="],
|
||||||
@@ -3973,6 +4005,16 @@
|
|||||||
|
|
||||||
"p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
"p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
|
||||||
|
|
||||||
|
"qrcode/yargs/cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="],
|
||||||
|
|
||||||
|
"qrcode/yargs/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
|
||||||
|
|
||||||
|
"qrcode/yargs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||||
|
|
||||||
|
"qrcode/yargs/y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="],
|
||||||
|
|
||||||
|
"qrcode/yargs/yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="],
|
||||||
|
|
||||||
"string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.0", "", {}, "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg=="],
|
"string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.0", "", {}, "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg=="],
|
||||||
|
|
||||||
"vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="],
|
"vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.9", "", { "os": "aix", "cpu": "ppc64" }, "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA=="],
|
||||||
@@ -4029,8 +4071,6 @@
|
|||||||
|
|
||||||
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
"wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
|
|
||||||
"wrap-ansi-cjs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
|
||||||
|
|
||||||
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.2.0", "", {}, "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg=="],
|
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.2.0", "", {}, "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg=="],
|
||||||
|
|
||||||
"@aws-sdk/client-dynamodb/@aws-crypto/sha256-js/@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
"@aws-sdk/client-dynamodb/@aws-crypto/sha256-js/@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="],
|
||||||
@@ -4179,6 +4219,12 @@
|
|||||||
|
|
||||||
"changelogen/c12/giget/tar": ["tar@6.2.1", "", { "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" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
|
"changelogen/c12/giget/tar": ["tar@6.2.1", "", { "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" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="],
|
||||||
|
|
||||||
|
"qrcode/yargs/cliui/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
|
||||||
|
|
||||||
|
"qrcode/yargs/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
|
||||||
|
|
||||||
|
"qrcode/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||||
|
|
||||||
"@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.0.5", "", { "dependencies": { "@smithy/types": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-6SV7md2CzNG/WUeTjVe6Dj8noH32r4MnUeFKZrnVYsQxpGSIcphAanQMayi8jJLZAWm6pdM9ZXvKCpWOsIGg0w=="],
|
"@aws-sdk/core/@smithy/smithy-client/@smithy/middleware-endpoint/@smithy/url-parser/@smithy/querystring-parser": ["@smithy/querystring-parser@4.0.5", "", { "dependencies": { "@smithy/types": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-6SV7md2CzNG/WUeTjVe6Dj8noH32r4MnUeFKZrnVYsQxpGSIcphAanQMayi8jJLZAWm6pdM9ZXvKCpWOsIGg0w=="],
|
||||||
|
|
||||||
"@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.5", "", { "dependencies": { "@smithy/types": "^4.3.2", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-NJeSCU57piZ56c+/wY+AbAw6rxCCAOZLCIniRE7wqvndqxcKKDOXzwWjrY7wGKEISfhL9gBbAaWWgHsUGedk+A=="],
|
"@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder": ["@smithy/querystring-builder@4.0.5", "", { "dependencies": { "@smithy/types": "^4.3.2", "@smithy/util-uri-escape": "^4.0.0", "tslib": "^2.6.2" } }, "sha512-NJeSCU57piZ56c+/wY+AbAw6rxCCAOZLCIniRE7wqvndqxcKKDOXzwWjrY7wGKEISfhL9gBbAaWWgHsUGedk+A=="],
|
||||||
@@ -4227,6 +4273,8 @@
|
|||||||
|
|
||||||
"changelogen/c12/giget/tar/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
|
"changelogen/c12/giget/tar/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
|
||||||
|
|
||||||
|
"qrcode/yargs/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
|
||||||
|
|
||||||
"@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg=="],
|
"@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/fetch-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg=="],
|
||||||
|
|
||||||
"@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg=="],
|
"@aws-sdk/core/@smithy/smithy-client/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg=="],
|
||||||
@@ -4236,5 +4284,7 @@
|
|||||||
"@aws-sdk/middleware-sdk-sqs/@smithy/smithy-client/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg=="],
|
"@aws-sdk/middleware-sdk-sqs/@smithy/smithy-client/@smithy/util-stream/@smithy/node-http-handler/@smithy/querystring-builder/@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg=="],
|
||||||
|
|
||||||
"changelogen/c12/giget/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
"changelogen/c12/giget/tar/minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
|
||||||
|
|
||||||
|
"qrcode/yargs/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user