feat: time for testing!

This commit is contained in:
dvelo 2025-03-22 20:23:14 -05:00
parent 7385705c8d
commit e31881e890
11 changed files with 517 additions and 99 deletions

@ -35,6 +35,7 @@
"@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-switch": "1.1.0",
"@radix-ui/react-tabs": "^1.1.3",
"@types/lodash": "^4.17.16",
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"@unocss/eslint-plugin": "^0.61.5",
@ -51,7 +52,9 @@
"inngest": "^3.21.2",
"input-otp": "^1.2.4",
"json-beautify": "^1.1.1",
"lodash": "^4.17.21",
"lucide-react": "^0.479.0",
"lz-string": "^1.5.0",
"mini-svg-data-uri": "^1.4.4",
"minimessage-2-html": "1.6.0",
"minimessage-js": "^1.1.3",

@ -81,7 +81,7 @@ export default function RootLayout({
<FontBoundary className="max-w-[800px]">
<IframeProtector>
<TooltipProvider>
<Toaster richColors position="top-center" />
<Toaster richColors position="bottom-center" />
<div className="overflow-x-hidden">{children}</div>
</TooltipProvider>
</IframeProtector>

@ -53,18 +53,19 @@ export default function ServerListCategoryFrame({
return (
<main className="max-w-[800px] p-4">
<h1 className="text-xl font-bold w-full">
<h1 className="text-xl font-bold w-full flex items-center gap-2">
<Link href="/servers/embedded/sl-modification-frame">
<ArrowLeft />
<ArrowLeft size={20} />
</Link>
{categoryObj?.displayTitle}
</h1>
<Markdown className="text-wrap pt-2">{categoryObj?.description}</Markdown>
<div className="pt-10 p-4 grid grid-cols-6 gap-2">
<Material className="mt-10 p-4 grid grid-cols-6 gap-2">
{categoryObj?.entries.map((m) => (
<Material
className="p-2 hover:drop-shadow-card-hover cursor-pointer"
elevation="high"
onClick={() =>
router.push(
`/servers/embedded/sl-modification-frame/category/${category}/modification/${btoa(m.name)}?b=${encodeURIComponent(`/servers/embedded/sl-modification-frame/category/${category}`)}`
@ -85,7 +86,7 @@ export default function ServerListCategoryFrame({
</span>
</Material>
))}
</div>
</Material>
</main>
);
}

@ -1,43 +1,101 @@
"use client";
import { use } from "react";
import { use, useEffect, useRef, useState } from "react";
import { useUser } from "@clerk/nextjs";
import type { ClerkCustomModification } from "@/components/feat/server-list/modification/modification-file-creation-dialog";
import { Link } from "@/components/util/link";
import { ArrowLeft } from "lucide-react";
import { AlertOctagon, ArrowLeft, ExternalLink } from "lucide-react";
import Editor from "@monaco-editor/react";
import poimandres from "@/theme.json";
import { Button } from "@/components/ui/button";
import { toast } from "sonner";
import * as ts from "typescript";
import useClipboard from "@/lib/useClipboard";
import { useTheme } from "@/lib/hooks/use-theme";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import type { languages, Uri } from "monaco-editor";
import {
Drawer,
DrawerContent,
DrawerTitle,
DrawerTrigger,
} from "@/components/ui/drawer";
import { Alert } from "@/components/ui/alert";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { compressToEncodedURIComponent } from "lz-string";
import { Geist_Mono } from "next/font/google";
import { cn } from "@/lib/utils";
import { debounce } from "lodash";
import { tryCatch } from "@/lib/try-catch";
const typeDefs = `
export interface Server {
staticInfo: {
_id: string;
serverPlan: string;
serviceStartDate: number;
platform: string;
planMaxPlayers: number;
planRam: number;
alwaysOnline: boolean;
rawPlan: string;
connectedServers: any[];
};
maxPlayers: number;
name: string;
motd: string;
icon: string;
playerData: {
playerCount: number;
timeNoPlayers: number;
};
connectable: boolean;
visibility: boolean;
allCategories: string[];
usingCosmetics: boolean;
author?: string;
authorRank: string;
const typeDefs = `// Hi :) how'd you get here?
// Here, in return I'll provide you with a random number: ${Math.ceil(Math.random() * 100)}
// I just wanted you to know that people love you
// and people are there for you :)
// Even when things get sad, just think about the bright side (seriously!)
export namespace Minehut {
/**
* A Minehut server that is online. You could get this value by using the \`/servers\` endpoint.
*/
export interface OnlineServer {
staticInfo: {
_id: string;
serverPlan: string;
serviceStartDate: number;
platform: string;
planMaxPlayers: number;
planRam: number;
alwaysOnline: boolean;
rawPlan: string;
connectedServers: any[];
};
maxPlayers: number;
name: string;
motd: string;
icon: string;
playerData: {
playerCount: number;
timeNoPlayers: number;
};
connectable: boolean;
visibility: boolean;
allCategories: string[];
usingCosmetics: boolean;
author?: string;
authorRank: string;
}
}
`;
const transpileTypeScript = (code: string) => {
try {
const result = ts.transpileModule(typeDefs + code, {
compilerOptions: {
module: ts.ModuleKind.ESNext,
target: ts.ScriptTarget.ESNext,
jsx: ts.JsxEmit.ReactJSX,
esModuleInterop: true,
},
});
return result.outputText;
} catch (error) {
console.error("TypeScript transpilation error:", error);
toast.error(`TypeScript error: ${error}`);
return null;
}
};
const geistMono = Geist_Mono({ subsets: ["latin"] });
export default function CustomFilePage({
params,
}: {
@ -45,35 +103,242 @@ export default function CustomFilePage({
}) {
const { filename } = use(params);
const { user } = useUser();
const monacoRef =
useRef<typeof import("monaco-editor/esm/vs/editor/editor.api")>(null);
const { resolvedTheme } = useTheme();
const [successfullyLinted, setSuccessfullyLinted] = useState(false);
const [syntaxErrors, setSyntaxErrors] = useState<
languages.typescript.Diagnostic[] | null
>(null);
const file = (
(user?.unsafeMetadata.customFiles as Array<ClerkCustomModification>) ?? []
).find((c) => c.name === filename);
).findIndex((c) => c.name === filename);
if (!file) {
if (file === -1) {
return <>Bruh.</>;
}
const fileContents = file.contents;
const validateCode = (code: string) => {
if (!monacoRef.current) return;
monacoRef.current.languages.typescript
.getTypeScriptWorker()
.then((worker) => {
worker(
monacoRef.current?.Uri.parse(`file:///${filename}.ts`) as Uri
).then((client) => {
client
.getSemanticDiagnostics(
(
monacoRef.current?.Uri.parse(`file:///${filename}.ts`) as Uri
).toString()
)
.then((diags) => {
setSyntaxErrors(diags);
});
});
});
};
const fileContents = ((user?.unsafeMetadata
.customFiles as Array<ClerkCustomModification>) ?? [])[file].contents;
const [value, setValue] = useState(fileContents);
const clipboard = useClipboard();
validateCode(value);
const saveFile = async () => {
const metadata =
(user?.unsafeMetadata.customFiles as Array<ClerkCustomModification>) ??
[];
const index = (
(user?.unsafeMetadata.customFiles as Array<ClerkCustomModification>) ?? []
).findIndex((c) => c.name === filename);
metadata[index].contents = value;
await user?.update({
unsafeMetadata: {
customFiles: metadata,
},
});
};
const lintFile = async () => {
toast.info("Transpiling TypeScript...");
const { error, data: transpiledCode } = await tryCatch(
(async () => transpileTypeScript(value))()
);
if (error) {
toast.error("Failed to transpile TypeScript! Error: " + error.message);
return;
}
const startTime = Date.now();
if (transpiledCode === null) {
toast.error("Cannot continue.");
return;
}
console.log("[MHSF Filters] Transpiled TypeScript:", transpiledCode ?? "");
toast.info("Generating function...");
const functionBody = transpiledCode.match(
/function\s+filter\s*\([^)]*\)\s*\{([\s\S]*)\}/
)?.[1];
const { error: filterErr, data: filterFunc } = await tryCatch(
(async () => new Function("data", functionBody as string))()
);
if (filterErr) {
toast.error(`Failed to generate function! Error: ${filterErr.message}`);
return;
}
if (typeof filterFunc === "function") {
toast.success("Linted in " + (Date.now() - startTime) + "ms");
setSuccessfullyLinted(true);
} else {
toast.error("Code doesn't have a 'filter' function. Cannot be tested.");
toast.error(typeof filterFunc);
}
};
const debouncedSave = debounce(async () => {
const { error } = await tryCatch(saveFile());
if (error)
toast.error(
"Whoa! We encountered an error while auto-saving. Please copy your code locally to ensure you'll keep your code changes."
);
}, 300);
useEffect(() => {
setSuccessfullyLinted(false);
validateCode(value);
debouncedSave();
}, [value]);
return (
<main className="max-w-[800px] p-4">
<strong className="font-bold w-full">
<Link href="/servers/embedded/sl-modification-frame/files">
<ArrowLeft />
</Link>
{filename}.ts
</strong>
<div className="h-[400px]">
<div className="w-full justify-between flex items-center gap-2 my-2">
<strong className="flex items-center gap-1">
<Link href="/servers/embedded/sl-modification-frame/files">
<ArrowLeft size={20} />
</Link>
{filename}.ts
</strong>
<span className="flex items-center gap-2">
{syntaxErrors !== null && syntaxErrors.length !== 0 && (
<Drawer direction="right">
<DrawerTrigger>
<Button
variant="danger-subtle"
size="square-md"
className="flex items-center justify-center"
>
<AlertOctagon />
</Button>
</DrawerTrigger>
<DrawerContent className="p-4 min-w-[400px] overflow-x-hidden max-h-screen overflow-y-auto">
<DrawerTitle>Type Errors</DrawerTitle>
<div className="p-2">
{syntaxErrors.map((c, i) => (
<Alert
variant={
c.category === 1
? "error"
: c.category === 0
? "warning"
: "info"
}
key={i}
className="gap-1 my-2"
>
{c.messageText.toString()}{" "}
<DropdownMenu>
<DropdownMenuTrigger>
<small className="flex items-center gap-1 cursor-pointer">
(TS{typeof c !== "string" && c.code})
<ExternalLink size={16} />
</small>
</DropdownMenuTrigger>
<DropdownMenuContent>
<Link
noExtraIcons
target="_blank"
href={`https://typescript.tv/errors/#ts${c.code}`}
>
<DropdownMenuItem>typescript.tv</DropdownMenuItem>
</Link>
<Link
noExtraIcons
target="_blank"
href={`https://ts-error-translator.vercel.app/?error=${compressToEncodedURIComponent(c.messageText.toString())}`}
>
<DropdownMenuItem>
ts-error-translator
</DropdownMenuItem>
</Link>
</DropdownMenuContent>
</DropdownMenu>
</Alert>
))}
</div>
</DrawerContent>
</Drawer>
)}
<Tooltip>
<TooltipTrigger>
<Button
onClick={lintFile}
disabled={syntaxErrors === null || syntaxErrors.length !== 0}
variant={successfullyLinted ? "success-subtle" : "secondary"}
>
Lint
</Button>
</TooltipTrigger>
<TooltipContent>
{syntaxErrors !== null && syntaxErrors.length !== 0
? "You must have no type errors in the editor to lint, you have " +
syntaxErrors.length +
" error(s)."
: "Check for possible runtime errors."}
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger>
<Button
disabled={!successfullyLinted}
onClick={() => {
const array =
(user?.unsafeMetadata
.customFiles as Array<ClerkCustomModification>) ?? [];
array[file].testId = guidGenerator();
user?.update({
unsafeMetadata: { customFiles: array },
});
}}
>
Test
</Button>
</TooltipTrigger>
<TooltipContent>
{successfullyLinted
? "Open a full server-list instance with your filter activated in test mode."
: "You must lint before testing."}
</TooltipContent>
</Tooltip>
</span>
</div>
<div>
<Editor
height="100%"
className={cn("h-[calc(100vh-100px)]")}
defaultLanguage="typescript"
defaultValue={fileContents}
theme="vs-dark"
value={value}
theme={resolvedTheme === "dark" ? "vs-dark" : "vs"}
onChange={(newValue) => {
setValue(newValue || "");
}}
onMount={(editor, monaco) => {
monacoRef.current = monaco;
// Ensure TypeScript is properly configured
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.Latest,
allowNonTsExtensions: true, // This is important!
allowNonTsExtensions: true,
moduleResolution:
monaco.languages.typescript.ModuleResolutionKind.NodeJs,
module: monaco.languages.typescript.ModuleKind.CommonJS,
@ -91,15 +356,32 @@ export default function CustomFilePage({
// Add typedefs as a library
monaco.languages.typescript.typescriptDefaults.addExtraLib(
typeDefs,
libUri,
libUri
);
// Add actions
[
{
id: "manually-save-file",
label: "MHSF: Manually Save File",
run: () => {
saveFile();
toast.success("Manually saved file!");
},
},
{
id: "lint-file",
label: "MHSF: Lint File",
run: lintFile
},
].forEach((e) => editor.addAction(e));
// Create a model for the libUri file
if (!monaco.editor.getModel(monaco.Uri.parse(libUri))) {
monaco.editor.createModel(
typeDefs,
"typescript",
monaco.Uri.parse(libUri),
monaco.Uri.parse(libUri)
);
}
@ -111,11 +393,7 @@ export default function CustomFilePage({
const currentUri = monaco.Uri.parse(`file:///${filename}.ts`);
if (!monaco.editor.getModel(currentUri)) {
monaco.editor.createModel(
fileContents,
"typescript",
currentUri,
);
monaco.editor.createModel(fileContents, "typescript", currentUri);
editor.setModel(monaco.editor.getModel(currentUri));
}
}}
@ -134,6 +412,7 @@ export default function CustomFilePage({
acceptSuggestionOnEnter: "on",
tabCompletion: "on",
wordBasedSuggestions: "currentDocument",
cursorSmoothCaretAnimation: "on",
parameterHints: {
enabled: true,
},
@ -148,3 +427,23 @@ export default function CustomFilePage({
</main>
);
}
function guidGenerator() {
const S4 = () => {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
};
return (
S4() +
S4() +
"-" +
S4() +
"-" +
S4() +
"-" +
S4() +
"-" +
S4() +
S4() +
S4()
);
}

@ -31,9 +31,27 @@
"use client";
import { ClerkCustomModification } from "@/components/feat/server-list/modification/modification-file-creation-dialog";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Material } from "@/components/ui/material";
import { Placeholder } from "@/components/ui/placeholder";
import { Link } from "@/components/util/link";
import { useUser } from "@clerk/nextjs";
import { File, FileCode } from "lucide-react";
import {
ArrowLeft,
Braces,
EllipsisVertical,
File,
FileCode,
Pencil,
Trash,
} from "lucide-react";
import { toast } from "sonner";
export default function ServerListModificationFrame() {
const { user } = useUser();
@ -41,14 +59,72 @@ export default function ServerListModificationFrame() {
(user?.unsafeMetadata.customFiles as Array<ClerkCustomModification>) ?? [];
return (
<main className="max-w-[800px] p-4">
<h1 className="text-xl font-bold w-full">Files</h1>
<div className="grid gap-1">
{files.map((c) => (
<Link href={`/servers/embedded/sl-modification-frame/file/${c.name}`} className="w-full py-1 px-2 rounded-xl flex items-center gap-1 hover:bg-slate-100" key={c.name}>
<FileCode size={16}/>{c.name}.ts
<h1 className="text-xl font-bold w-full flex items-center gap-2">
<Link href="/servers/embedded/sl-modification-frame">
<ArrowLeft size={16} />
</Link>
Files
</h1>
<Material className="grid gap-1 mt-4">
{files.length === 0 && (
<Placeholder
icon={<Braces />}
title="We couldn't find any files"
description="Try creating a filter!"
/>
)}
{files.map((c, i) => (
<Link
href={`/servers/embedded/sl-modification-frame/file/${c.name}`}
className="w-full py-1 px-2 rounded-xl flex items-center gap-1 justify-between hover:bg-slate-100"
key={c.name}
>
<span className="flex items-center gap-1">
<FileCode size={16} />
{c.name}.ts
</span>
<span>
<DropdownMenu>
<DropdownMenuTrigger>
<Button
variant="tertiary"
className="flex items-center justify-center hover:bg-slate-200"
size="square-sm"
>
<EllipsisVertical
size={16}
className="text-muted-foreground"
/>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
className="flex items-center gap-2"
onClick={async (e) => {
e.stopPropagation();
const startTime = Date.now();
files.splice(i, 1);
await user?.update({
unsafeMetadata: {
customFiles: files,
},
});
toast.success(
"Deleted file in " + (Date.now() - startTime) + "ms"
);
}}
>
<Trash size={16} /> Delete
</DropdownMenuItem>
<DropdownMenuItem className="flex items-center gap-2">
<Pencil size={16} /> Rename
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</span>
</Link>
))}
</div>
</Material>
</main>
);
}

@ -56,8 +56,7 @@ export default function ServerListModificationFrame() {
viewing experience. We frequently add new filters in accordance to new
features, as well.
</span>
<Separator className="mt-4" />
<div className="pt-10 p-4">
<Material className="mt-10 p-4">
{serverModDB.map((c) => (
<span key={c.displayTitle}>
<h2 className="text-lg font-bold pb-3 flex justify-between">
@ -73,6 +72,7 @@ export default function ServerListModificationFrame() {
<div className="grid grid-cols-6 gap-2">
{c.entries.map((m) => (
<Material
elevation="high"
className="p-2 hover:drop-shadow-card-hover cursor-pointer"
key={m.name}
onClick={() =>
@ -95,7 +95,7 @@ export default function ServerListModificationFrame() {
</div>
</span>
))}
</div>
</Material>
</main>
);
}

@ -42,16 +42,16 @@ import { useUser } from "@clerk/nextjs";
import { useState, type ReactNode } from "react";
import { toast } from "sonner";
const sortTemplate = `import type { Server } from "mhsf";
const sortTemplate = `import type { Minehut } from "mhsf";
export function sort(serverA: Server, serverB: Server): number {
export function sort(serverA: Minehut.OnlineServer, serverB: Minehut.OnlineServer): number {
// Your code here
// Use logic like \`Array.sort\` or <V>(a: V, b: V) => number
}`;
const filterTemplate = `import type { Server } from "mhsf";
const filterTemplate = `import type { Minehut } from "mhsf";
export function filter(server: Server): boolean {
export function filter(server: Minehut.OnlineServer): boolean {
// Your code here
// Returning true indicates the server will stay, while returning false will remove the server from the queue.
}`;
@ -60,6 +60,7 @@ export type ClerkCustomModification = {
name: string; // Add .ts to the end
active: boolean;
contents: string;
testId?: string;
};
export function ModificationFileCreationDialog({

@ -0,0 +1,55 @@
/*
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://mhsf.app/docs/legal/external-content-agreement
*
* All code under MHSF is licensed under the MIT License
* by open source contributors
*
* Copyright (c) 2025 dvelo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
// Thanks theo <3
// Types for the result object with discriminated union
type Success<T> = {
data: T;
error: null;
};
type Failure<E> = {
data: null;
error: E;
};
type Result<T, E = Error> = Success<T> | Failure<E>;
// Main wrapper function
export async function tryCatch<T, E = Error>(
promise: Promise<T>,
): Promise<Result<T, E>> {
try {
const data = await promise;
return { data, error: null };
} catch (error) {
return { data: null, error: error as E };
}
}

@ -1,27 +0,0 @@
declare type Server = {
staticInfo: {
_id: string;
serverPlan: string;
serviceStartDate: number;
platform: string;
planMaxPlayers: number;
planRam: number;
alwaysOnline: boolean;
rawPlan: string;
connectedServers: any[];
};
maxPlayers: number;
name: string;
motd: string;
icon: string;
playerData: {
playerCount: number;
timeNoPlayers: number;
};
connectable: boolean;
visibility: boolean;
allCategories: string[];
usingCosmetics: boolean;
author?: string;
authorRank: string;
}

@ -34,7 +34,7 @@ import {
createRouteMatcher,
} from "@clerk/nextjs/server";
import { type NextRequest, NextResponse } from "next/server";
import { ServerResponse } from "./lib/types/mh-server";
import type { ServerResponse } from "./lib/types/mh-server";
// Thanks for the router matcher API Clerk <3
const isRootRoute = createRouteMatcher(["/"]);

@ -3399,6 +3399,11 @@
resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.16.7.tgz#03ab680ab4fa4fbc6cb46ecf987ecad5d8019868"
integrity sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==
"@types/lodash@^4.17.16":
version "4.17.16"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.16.tgz#94ae78fab4a38d73086e962d0b65c30d816bfb0a"
integrity sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==
"@types/luxon@~3.4.0":
version "3.4.2"
resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.4.2.tgz#e4fc7214a420173cea47739c33cdf10874694db7"
@ -8123,6 +8128,11 @@ luxon@~3.5.0:
resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.5.0.tgz#6b6f65c5cd1d61d1fd19dbf07ee87a50bf4b8e20"
integrity sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==
lz-string@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
magic-bytes.js@^1.10.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz#c41cf4bc2f802992b05e64962411c9dd44fdef92"