letsgoooo
This commit is contained in:
parent
8265f696f2
commit
7e4bd64e09
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
node_modules
|
||||||
|
|
||||||
|
/.cache
|
||||||
|
/build
|
||||||
|
/public/build
|
||||||
|
.env
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
|||||||
node_modules
|
node_modules
|
||||||
|
/.idea
|
||||||
|
|
||||||
/.cache
|
/.cache
|
||||||
/build
|
/build
|
||||||
|
45
Dockerfile
Normal file
45
Dockerfile
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# syntax = docker/dockerfile:1
|
||||||
|
|
||||||
|
# Adjust NODE_VERSION as desired
|
||||||
|
ARG NODE_VERSION=20.10.0
|
||||||
|
FROM node:${NODE_VERSION}-slim as base
|
||||||
|
|
||||||
|
LABEL fly_launch_runtime="Remix"
|
||||||
|
|
||||||
|
# Remix app lives here
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Set production environment
|
||||||
|
ENV NODE_ENV="production"
|
||||||
|
|
||||||
|
|
||||||
|
# Throw-away build stage to reduce size of final image
|
||||||
|
FROM base as build
|
||||||
|
|
||||||
|
# Install packages needed to build node modules
|
||||||
|
RUN apt-get update -qq && \
|
||||||
|
apt-get install -y build-essential pkg-config python-is-python3
|
||||||
|
|
||||||
|
# Install node modules
|
||||||
|
COPY --link package-lock.json package.json ./
|
||||||
|
RUN npm ci --include=dev
|
||||||
|
|
||||||
|
# Copy application code
|
||||||
|
COPY --link . .
|
||||||
|
|
||||||
|
# Build application
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Remove development dependencies
|
||||||
|
RUN npm prune --omit=dev
|
||||||
|
|
||||||
|
|
||||||
|
# Final stage for app image
|
||||||
|
FROM base
|
||||||
|
|
||||||
|
# Copy built application
|
||||||
|
COPY --from=build /app /app
|
||||||
|
|
||||||
|
# Start the server by default, this can be overwritten at runtime
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD [ "npm", "run", "start" ]
|
@ -1,18 +1,12 @@
|
|||||||
/**
|
|
||||||
* By default, Remix will handle hydrating your app on the client for you.
|
|
||||||
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
|
|
||||||
* For more information, see https://remix.run/file-conventions/entry.client
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { RemixBrowser } from "@remix-run/react";
|
import { RemixBrowser } from "@remix-run/react";
|
||||||
import { startTransition, StrictMode } from "react";
|
import { startTransition, StrictMode } from "react";
|
||||||
import { hydrateRoot } from "react-dom/client";
|
import { hydrateRoot } from "react-dom/client";
|
||||||
|
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
hydrateRoot(
|
hydrateRoot(
|
||||||
document,
|
document,
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<RemixBrowser />
|
<RemixBrowser />
|
||||||
</StrictMode>
|
</StrictMode>,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,9 +1,3 @@
|
|||||||
/**
|
|
||||||
* By default, Remix will handle generating the HTTP Response for you.
|
|
||||||
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
|
|
||||||
* For more information, see https://remix.run/file-conventions/entry.server
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { PassThrough } from "node:stream";
|
import { PassThrough } from "node:stream";
|
||||||
|
|
||||||
import type { AppLoadContext, EntryContext } from "@remix-run/node";
|
import type { AppLoadContext, EntryContext } from "@remix-run/node";
|
||||||
@ -15,123 +9,123 @@ import { renderToPipeableStream } from "react-dom/server";
|
|||||||
const ABORT_DELAY = 5_000;
|
const ABORT_DELAY = 5_000;
|
||||||
|
|
||||||
export default function handleRequest(
|
export default function handleRequest(
|
||||||
request: Request,
|
request: Request,
|
||||||
responseStatusCode: number,
|
responseStatusCode: number,
|
||||||
responseHeaders: Headers,
|
responseHeaders: Headers,
|
||||||
remixContext: EntryContext,
|
remixContext: EntryContext,
|
||||||
loadContext: AppLoadContext
|
loadContext: AppLoadContext,
|
||||||
) {
|
) {
|
||||||
return isbot(request.headers.get("user-agent"))
|
return isbot(request.headers.get("user-agent"))
|
||||||
? handleBotRequest(
|
? handleBotRequest(
|
||||||
request,
|
request,
|
||||||
responseStatusCode,
|
responseStatusCode,
|
||||||
responseHeaders,
|
responseHeaders,
|
||||||
remixContext
|
remixContext,
|
||||||
)
|
)
|
||||||
: handleBrowserRequest(
|
: handleBrowserRequest(
|
||||||
request,
|
request,
|
||||||
responseStatusCode,
|
responseStatusCode,
|
||||||
responseHeaders,
|
responseHeaders,
|
||||||
remixContext
|
remixContext,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleBotRequest(
|
function handleBotRequest(
|
||||||
request: Request,
|
request: Request,
|
||||||
responseStatusCode: number,
|
responseStatusCode: number,
|
||||||
responseHeaders: Headers,
|
responseHeaders: Headers,
|
||||||
remixContext: EntryContext
|
remixContext: EntryContext,
|
||||||
) {
|
) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let shellRendered = false;
|
let shellRendered = false;
|
||||||
const { pipe, abort } = renderToPipeableStream(
|
const { pipe, abort } = renderToPipeableStream(
|
||||||
<RemixServer
|
<RemixServer
|
||||||
context={remixContext}
|
context={remixContext}
|
||||||
url={request.url}
|
url={request.url}
|
||||||
abortDelay={ABORT_DELAY}
|
abortDelay={ABORT_DELAY}
|
||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
onAllReady() {
|
onAllReady() {
|
||||||
shellRendered = true;
|
shellRendered = true;
|
||||||
const body = new PassThrough();
|
const body = new PassThrough();
|
||||||
const stream = createReadableStreamFromReadable(body);
|
const stream = createReadableStreamFromReadable(body);
|
||||||
|
|
||||||
responseHeaders.set("Content-Type", "text/html");
|
responseHeaders.set("Content-Type", "text/html");
|
||||||
|
|
||||||
resolve(
|
resolve(
|
||||||
new Response(stream, {
|
new Response(stream, {
|
||||||
headers: responseHeaders,
|
headers: responseHeaders,
|
||||||
status: responseStatusCode,
|
status: responseStatusCode,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
pipe(body);
|
pipe(body);
|
||||||
},
|
},
|
||||||
onShellError(error: unknown) {
|
onShellError(error: unknown) {
|
||||||
reject(error);
|
reject(error);
|
||||||
},
|
},
|
||||||
onError(error: unknown) {
|
onError(error: unknown) {
|
||||||
responseStatusCode = 500;
|
responseStatusCode = 500;
|
||||||
// Log streaming rendering errors from inside the shell. Don't log
|
// Log streaming rendering errors from inside the shell. Don't log
|
||||||
// errors encountered during initial shell rendering since they'll
|
// errors encountered during initial shell rendering since they'll
|
||||||
// reject and get logged in handleDocumentRequest.
|
// reject and get logged in handleDocumentRequest.
|
||||||
if (shellRendered) {
|
if (shellRendered) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
setTimeout(abort, ABORT_DELAY);
|
setTimeout(abort, ABORT_DELAY);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleBrowserRequest(
|
function handleBrowserRequest(
|
||||||
request: Request,
|
request: Request,
|
||||||
responseStatusCode: number,
|
responseStatusCode: number,
|
||||||
responseHeaders: Headers,
|
responseHeaders: Headers,
|
||||||
remixContext: EntryContext
|
remixContext: EntryContext,
|
||||||
) {
|
) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let shellRendered = false;
|
let shellRendered = false;
|
||||||
const { pipe, abort } = renderToPipeableStream(
|
const { pipe, abort } = renderToPipeableStream(
|
||||||
<RemixServer
|
<RemixServer
|
||||||
context={remixContext}
|
context={remixContext}
|
||||||
url={request.url}
|
url={request.url}
|
||||||
abortDelay={ABORT_DELAY}
|
abortDelay={ABORT_DELAY}
|
||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
onShellReady() {
|
onShellReady() {
|
||||||
shellRendered = true;
|
shellRendered = true;
|
||||||
const body = new PassThrough();
|
const body = new PassThrough();
|
||||||
const stream = createReadableStreamFromReadable(body);
|
const stream = createReadableStreamFromReadable(body);
|
||||||
|
|
||||||
responseHeaders.set("Content-Type", "text/html");
|
responseHeaders.set("Content-Type", "text/html");
|
||||||
|
|
||||||
resolve(
|
resolve(
|
||||||
new Response(stream, {
|
new Response(stream, {
|
||||||
headers: responseHeaders,
|
headers: responseHeaders,
|
||||||
status: responseStatusCode,
|
status: responseStatusCode,
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
pipe(body);
|
pipe(body);
|
||||||
},
|
},
|
||||||
onShellError(error: unknown) {
|
onShellError(error: unknown) {
|
||||||
reject(error);
|
reject(error);
|
||||||
},
|
},
|
||||||
onError(error: unknown) {
|
onError(error: unknown) {
|
||||||
responseStatusCode = 500;
|
responseStatusCode = 500;
|
||||||
// Log streaming rendering errors from inside the shell. Don't log
|
// Log streaming rendering errors from inside the shell. Don't log
|
||||||
// errors encountered during initial shell rendering since they'll
|
// errors encountered during initial shell rendering since they'll
|
||||||
// reject and get logged in handleDocumentRequest.
|
// reject and get logged in handleDocumentRequest.
|
||||||
if (shellRendered) {
|
if (shellRendered) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
setTimeout(abort, ABORT_DELAY);
|
setTimeout(abort, ABORT_DELAY);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
46
app/root.tsx
46
app/root.tsx
@ -1,33 +1,33 @@
|
|||||||
import { cssBundleHref } from "@remix-run/css-bundle";
|
import { cssBundleHref } from "@remix-run/css-bundle";
|
||||||
import type { LinksFunction } from "@remix-run/node";
|
import type { LinksFunction } from "@remix-run/node";
|
||||||
import {
|
import {
|
||||||
Links,
|
Links,
|
||||||
LiveReload,
|
LiveReload,
|
||||||
Meta,
|
Meta,
|
||||||
Outlet,
|
Outlet,
|
||||||
Scripts,
|
Scripts,
|
||||||
ScrollRestoration,
|
ScrollRestoration,
|
||||||
} from "@remix-run/react";
|
} from "@remix-run/react";
|
||||||
|
|
||||||
export const links: LinksFunction = () => [
|
export const links: LinksFunction = () => [
|
||||||
...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []),
|
...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []),
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charSet="utf-8" />
|
<meta charSet="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<Meta />
|
<Meta />
|
||||||
<Links />
|
<Links />
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<ScrollRestoration />
|
<ScrollRestoration />
|
||||||
<Scripts />
|
<Scripts />
|
||||||
<LiveReload />
|
<LiveReload />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,41 +1,97 @@
|
|||||||
import type { MetaFunction } from "@remix-run/node";
|
import type { LinksFunction, MetaFunction } from "@remix-run/node";
|
||||||
|
|
||||||
|
import styles from "../styles/index.css";
|
||||||
|
|
||||||
|
export const links: LinksFunction = () => [
|
||||||
|
{ rel: "stylesheet", href: styles },
|
||||||
|
];
|
||||||
|
|
||||||
export const meta: MetaFunction = () => {
|
export const meta: MetaFunction = () => {
|
||||||
return [
|
return [
|
||||||
{ title: "New Remix App" },
|
{ title: "New Remix App" },
|
||||||
{ name: "description", content: "Welcome to Remix!" },
|
{ name: "description", content: "Welcome to Remix!" },
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
return (
|
return (
|
||||||
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
|
<>
|
||||||
<h1>Welcome to Remix</h1>
|
<header>
|
||||||
<ul>
|
<pre>
|
||||||
<li>
|
{` _ _ _ _
|
||||||
<a
|
| | | | (_) | |
|
||||||
target="_blank"
|
| | ___ ___ __ _| | _ _ __ ___| |__
|
||||||
href="https://remix.run/tutorials/blog"
|
| |/ _ \\ / __/ _\` | |_____| | '_ \\ / __| '_ \\
|
||||||
rel="noreferrer"
|
| | (_) | (_| (_| | |_____| | |_) |\\__ \\ | | |
|
||||||
>
|
|_|\\___/ \\___\\__,_|_| |_| .__(_)___/_| |_|
|
||||||
15m Quickstart Blog Tutorial
|
| |
|
||||||
</a>
|
|_|
|
||||||
</li>
|
`}
|
||||||
<li>
|
</pre>
|
||||||
<a
|
</header>
|
||||||
target="_blank"
|
|
||||||
href="https://remix.run/tutorials/jokes"
|
<main>
|
||||||
rel="noreferrer"
|
<section>
|
||||||
>
|
<header><strong>What is local-ip.sh?</strong></header>
|
||||||
Deep Dive Jokes App Tutorial
|
<main>
|
||||||
</a>
|
<article>
|
||||||
</li>
|
local-ip.sh is a magic domain name that provides wildcard DNS
|
||||||
<li>
|
for any IP address. It is heavily inspired by <a href="http://local-ip.co">local-ip.co</a>,
|
||||||
<a target="_blank" href="https://remix.run/docs" rel="noreferrer">
|
{" "}<a href="https://sslip.io">sslip.io</a>, and <a href="https://xip.io">xip.io</a>.
|
||||||
Remix Docs
|
</article>
|
||||||
</a>
|
|
||||||
</li>
|
<article>
|
||||||
</ul>
|
Quick example, say your LAN IP address is <strong>192.168.1.10</strong>.
|
||||||
</div>
|
Using local-ip.sh,
|
||||||
);
|
|
||||||
|
<br /><br />
|
||||||
|
|
||||||
|
<pre dangerouslySetInnerHTML={{
|
||||||
|
__html: ` <strong>192.168.1.10</strong>.local-ip.sh resolves to 192.168.1.10
|
||||||
|
dots.<strong>192.168.1.10</strong>.local-ip.sh resolves to 192.168.1.10
|
||||||
|
dashes.<strong>192-168-1-10</strong>.local-ip.sh resolves to 192.168.1.10`,
|
||||||
|
}} />
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
...and so on. You can use these domains to access virtual
|
||||||
|
hosts on your development web server from devices on your
|
||||||
|
local network. No configuration required!
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
The best part is, you can serve your content over HTTPS with our TLS certificate
|
||||||
|
for <code>*.local-ip.sh</code>:
|
||||||
|
<ul>
|
||||||
|
<li><a href="/server.pem">server.pem</a></li>
|
||||||
|
<li><a href="/server.key">server.key</a></li>
|
||||||
|
</ul>
|
||||||
|
Be aware that wildcard certificates are not recursive, meaning they don't match "sub-subdomains". <br />
|
||||||
|
In our case, this certificate will only match subdomains of <code>local-ip.sh</code> such as <code>192-168-1-10.local-ip.sh</code>
|
||||||
|
{" "}where dashes separate the numbers that make up the IP address.
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<header><strong>How does it work?</strong></header>
|
||||||
|
<main>
|
||||||
|
<article>
|
||||||
|
local-ip.sh runs publicly a <a href="https://github.com/m5r/local-ip.sh">custom DNS server</a>.
|
||||||
|
When your computer looks up a local-ip.sh domain, the local-ip.sh
|
||||||
|
DNS server resolves to the IP address it extracts from the domain.
|
||||||
|
</article>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
The TLS certificate is obtained from Let's Encrypt and renewed up to a month before it expires.
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer className="copyright">
|
||||||
|
© {new Date().getFullYear()} <a href="https://www.mokhtar.dev">Mokhtar Mial</a>
|
||||||
|
</footer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
7
app/routes/server[.]key.ts
Normal file
7
app/routes/server[.]key.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export async function loader() {
|
||||||
|
if (process.env.NODE_ENV === "development") {
|
||||||
|
// return fetch("http://localhost:9229/server.key");
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch("http://local-ip.internal:9229/server.key");
|
||||||
|
}
|
7
app/routes/server[.]pem.ts
Normal file
7
app/routes/server[.]pem.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export async function loader() {
|
||||||
|
if (process.env.NODE_ENV === "development") {
|
||||||
|
// return fetch("http://localhost:9229/server.pem");
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch("http://local-ip.internal:9229/server.pem");
|
||||||
|
}
|
94
app/styles/index.css
Normal file
94
app/styles/index.css
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
html {
|
||||||
|
background: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
color: #728ea7;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-inline: auto;
|
||||||
|
padding-left: 1.5em;
|
||||||
|
padding-right: 1.5em;
|
||||||
|
width: min(100%, 41.5rem);
|
||||||
|
/*margin: 50px auto;*/
|
||||||
|
margin-top: 50px;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
|
||||||
|
font-family: ui-monospace, monospace;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
/*line-height: 1.5;*/
|
||||||
|
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
header {
|
||||||
|
color: #7aa6da;
|
||||||
|
}
|
||||||
|
|
||||||
|
main a {
|
||||||
|
color: #728ea7;
|
||||||
|
}
|
||||||
|
|
||||||
|
main a:hover {
|
||||||
|
background: #7aa6da;
|
||||||
|
color: #111;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
section:nth-child(n + 2) {
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
section > main {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
row-gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 85%;
|
||||||
|
background-color: rgba(27, 31, 35, 0.95);
|
||||||
|
border-radius: 3px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
header span.dim {
|
||||||
|
color: #728ea7;
|
||||||
|
}
|
||||||
|
|
||||||
|
section strong {
|
||||||
|
color: #7aa6da;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer {
|
||||||
|
margin: 5rem auto 0;
|
||||||
|
color: #556a7d;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a:hover {
|
||||||
|
color: #728ea7;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.cursor {
|
||||||
|
display: inline-block;
|
||||||
|
background: #111;
|
||||||
|
margin-left: 1px;
|
||||||
|
margin-right: -1px;
|
||||||
|
animation: blink 2s linear 0s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes blink {
|
||||||
|
0% { background: #7aa6da }
|
||||||
|
47% { background: #728ea7 }
|
||||||
|
50% { background: #111 }
|
||||||
|
97% { background: #111 }
|
||||||
|
100% { background: #728ea7 }
|
||||||
|
}
|
22
fly.toml
Normal file
22
fly.toml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# fly.toml app configuration file generated for www-local-ip on 2023-12-12T23:37:13+01:00
|
||||||
|
#
|
||||||
|
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
|
||||||
|
#
|
||||||
|
|
||||||
|
app = "www-local-ip"
|
||||||
|
primary_region = "ams"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
|
||||||
|
[http_service]
|
||||||
|
internal_port = 3000
|
||||||
|
force_https = true
|
||||||
|
auto_stop_machines = true
|
||||||
|
auto_start_machines = true
|
||||||
|
min_machines_running = 0
|
||||||
|
processes = ["app"]
|
||||||
|
|
||||||
|
[[vm]]
|
||||||
|
cpu_kind = "shared"
|
||||||
|
cpus = 1
|
||||||
|
memory_mb = 256
|
248
package-lock.json
generated
248
package-lock.json
generated
@ -15,6 +15,7 @@
|
|||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@flydotio/dockerfile": "^0.4.11",
|
||||||
"@remix-run/dev": "^2.3.1",
|
"@remix-run/dev": "^2.3.1",
|
||||||
"@remix-run/eslint-config": "^2.3.1",
|
"@remix-run/eslint-config": "^2.3.1",
|
||||||
"@types/react": "^18.2.20",
|
"@types/react": "^18.2.20",
|
||||||
@ -1309,6 +1310,37 @@
|
|||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@flydotio/dockerfile": {
|
||||||
|
"version": "0.4.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@flydotio/dockerfile/-/dockerfile-0.4.11.tgz",
|
||||||
|
"integrity": "sha512-L52UAfrOhmAn3T4TxpeRofQOSO+Kctg+uraB4nLzo4mvvh+4Z7HYxSi7Dnq0Kirz+xx6fDIc4OMNT1EdaORecA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "^5.3.0",
|
||||||
|
"diff": "^5.1.0",
|
||||||
|
"ejs": "^3.1.9",
|
||||||
|
"shell-quote": "^1.8.1",
|
||||||
|
"yargs": "^17.7.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"dockerfile": "index.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@flydotio/dockerfile/node_modules/chalk": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@humanwhocodes/config-array": {
|
"node_modules/@humanwhocodes/config-array": {
|
||||||
"version": "0.11.13",
|
"version": "0.11.13",
|
||||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
|
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
|
||||||
@ -2635,6 +2667,12 @@
|
|||||||
"astring": "bin/astring"
|
"astring": "bin/astring"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/async": {
|
||||||
|
"version": "3.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
|
||||||
|
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/asynciterator.prototype": {
|
"node_modules/asynciterator.prototype": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz",
|
||||||
@ -3107,6 +3145,57 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cliui": {
|
||||||
|
"version": "8.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||||
|
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.1",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cliui/node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/cliui/node_modules/string-width": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^8.0.0",
|
||||||
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
|
"strip-ansi": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cliui/node_modules/wrap-ansi": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^4.0.0",
|
||||||
|
"string-width": "^4.1.0",
|
||||||
|
"strip-ansi": "^6.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/clone": {
|
"node_modules/clone": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
|
||||||
@ -3585,6 +3674,21 @@
|
|||||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||||
},
|
},
|
||||||
|
"node_modules/ejs": {
|
||||||
|
"version": "3.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
|
||||||
|
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"jake": "^10.8.5"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"ejs": "bin/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.4.609",
|
"version": "1.4.609",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.609.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.609.tgz",
|
||||||
@ -4897,6 +5001,27 @@
|
|||||||
"node": "^10.12.0 || >=12.0.0"
|
"node": "^10.12.0 || >=12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/filelist": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"minimatch": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/filelist/node_modules/minimatch": {
|
||||||
|
"version": "5.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
|
||||||
|
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||||
@ -5139,6 +5264,15 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-caller-file": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-intrinsic": {
|
"node_modules/get-intrinsic": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz",
|
||||||
@ -6138,6 +6272,46 @@
|
|||||||
"@pkgjs/parseargs": "^0.11.0"
|
"@pkgjs/parseargs": "^0.11.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jake": {
|
||||||
|
"version": "10.8.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz",
|
||||||
|
"integrity": "sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"async": "^3.2.3",
|
||||||
|
"chalk": "^4.0.2",
|
||||||
|
"filelist": "^1.0.4",
|
||||||
|
"minimatch": "^3.1.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"jake": "bin/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jake/node_modules/brace-expansion": {
|
||||||
|
"version": "1.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||||
|
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"balanced-match": "^1.0.0",
|
||||||
|
"concat-map": "0.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jake/node_modules/minimatch": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"brace-expansion": "^1.1.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/javascript-stringify": {
|
"node_modules/javascript-stringify": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz",
|
||||||
@ -8840,6 +9014,15 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/require-directory": {
|
||||||
|
"version": "2.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
|
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/require-like": {
|
"node_modules/require-like": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz",
|
||||||
@ -9239,6 +9422,15 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/shell-quote": {
|
||||||
|
"version": "1.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz",
|
||||||
|
"integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/side-channel": {
|
"node_modules/side-channel": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
|
||||||
@ -11118,6 +11310,15 @@
|
|||||||
"node": ">=0.4"
|
"node": ">=0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/y18n": {
|
||||||
|
"version": "5.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||||
@ -11133,6 +11334,53 @@
|
|||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/yargs": {
|
||||||
|
"version": "17.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||||
|
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": "^8.0.1",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.3",
|
||||||
|
"y18n": "^5.0.5",
|
||||||
|
"yargs-parser": "^21.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs-parser": {
|
||||||
|
"version": "21.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||||
|
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yargs/node_modules/emoji-regex": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/yargs/node_modules/string-width": {
|
||||||
|
"version": "4.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"emoji-regex": "^8.0.0",
|
||||||
|
"is-fullwidth-code-point": "^3.0.0",
|
||||||
|
"strip-ansi": "^6.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/yocto-queue": {
|
"node_modules/yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@flydotio/dockerfile": "^0.4.11",
|
||||||
"@remix-run/dev": "^2.3.1",
|
"@remix-run/dev": "^2.3.1",
|
||||||
"@remix-run/eslint-config": "^2.3.1",
|
"@remix-run/eslint-config": "^2.3.1",
|
||||||
"@types/react": "^18.2.20",
|
"@types/react": "^18.2.20",
|
||||||
|
Reference in New Issue
Block a user