letsgoooo

This commit is contained in:
m5r 2023-12-13 00:15:37 +01:00
parent 8265f696f2
commit 7e4bd64e09
No known key found for this signature in database
GPG Key ID: 5BC847276DD5DDEA
13 changed files with 654 additions and 179 deletions

6
.dockerignore Normal file
View File

@ -0,0 +1,6 @@
node_modules
/.cache
/build
/public/build
.env

1
.gitignore vendored
View File

@ -1,4 +1,5 @@
node_modules node_modules
/.idea
/.cache /.cache
/build /build

45
Dockerfile Normal file
View 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" ]

View File

@ -1,9 +1,3 @@
/**
* 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";
@ -13,6 +7,6 @@ startTransition(() => {
document, document,
<StrictMode> <StrictMode>
<RemixBrowser /> <RemixBrowser />
</StrictMode> </StrictMode>,
); );
}); });

View File

@ -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";
@ -19,20 +13,20 @@ export default function handleRequest(
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,
); );
} }
@ -40,7 +34,7 @@ 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;
@ -62,7 +56,7 @@ function handleBotRequest(
new Response(stream, { new Response(stream, {
headers: responseHeaders, headers: responseHeaders,
status: responseStatusCode, status: responseStatusCode,
}) }),
); );
pipe(body); pipe(body);
@ -79,7 +73,7 @@ function handleBotRequest(
console.error(error); console.error(error);
} }
}, },
} },
); );
setTimeout(abort, ABORT_DELAY); setTimeout(abort, ABORT_DELAY);
@ -90,7 +84,7 @@ 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;
@ -112,7 +106,7 @@ function handleBrowserRequest(
new Response(stream, { new Response(stream, {
headers: responseHeaders, headers: responseHeaders,
status: responseStatusCode, status: responseStatusCode,
}) }),
); );
pipe(body); pipe(body);
@ -129,7 +123,7 @@ function handleBrowserRequest(
console.error(error); console.error(error);
} }
}, },
} },
); );
setTimeout(abort, ABORT_DELAY); setTimeout(abort, ABORT_DELAY);

View File

@ -1,4 +1,10 @@
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 [
@ -9,33 +15,83 @@ export const meta: MetaFunction = () => {
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>
<pre>
{` _ _ _ _
| | | | (_) | |
| | ___ ___ __ _| | _ _ __ ___| |__
| |/ _ \\ / __/ _\` | |_____| | '_ \\ / __| '_ \\
| | (_) | (_| (_| | |_____| | |_) |\\__ \\ | | |
|_|\\___/ \\___\\__,_|_| |_| .__(_)___/_| |_|
| |
|_|
`}
</pre>
</header>
<main>
<section>
<header><strong>What is local-ip.sh?</strong></header>
<main>
<article>
local-ip.sh is a magic domain name that provides wildcard DNS
for any IP address. It is heavily inspired by <a href="http://local-ip.co">local-ip.co</a>,
{" "}<a href="https://sslip.io">sslip.io</a>, and <a href="https://xip.io">xip.io</a>.
</article>
<article>
Quick example, say your LAN IP address is <strong>192.168.1.10</strong>.
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> <ul>
<li> <li><a href="/server.pem">server.pem</a></li>
<a <li><a href="/server.key">server.key</a></li>
target="_blank"
href="https://remix.run/tutorials/blog"
rel="noreferrer"
>
15m Quickstart Blog Tutorial
</a>
</li>
<li>
<a
target="_blank"
href="https://remix.run/tutorials/jokes"
rel="noreferrer"
>
Deep Dive Jokes App Tutorial
</a>
</li>
<li>
<a target="_blank" href="https://remix.run/docs" rel="noreferrer">
Remix Docs
</a>
</li>
</ul> </ul>
</div> 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>
</>
); );
} }

View 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");
}

View 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
View 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
View 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
View File

@ -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",

View File

@ -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",