From 7e4bd64e095908c51e7420b4d5e9fde9f1e00a9c Mon Sep 17 00:00:00 2001 From: m5r Date: Wed, 13 Dec 2023 00:15:37 +0100 Subject: [PATCH] letsgoooo --- .dockerignore | 6 + .gitignore | 1 + Dockerfile | 45 +++++++ app/entry.client.tsx | 18 +-- app/entry.server.tsx | 210 +++++++++++++++---------------- app/root.tsx | 46 +++---- app/routes/_index.tsx | 126 +++++++++++++------ app/routes/server[.]key.ts | 7 ++ app/routes/server[.]pem.ts | 7 ++ app/styles/index.css | 94 ++++++++++++++ fly.toml | 22 ++++ package-lock.json | 248 +++++++++++++++++++++++++++++++++++++ package.json | 3 +- 13 files changed, 654 insertions(+), 179 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 app/routes/server[.]key.ts create mode 100644 app/routes/server[.]pem.ts create mode 100644 app/styles/index.css create mode 100644 fly.toml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..3f7bf98 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +node_modules + +/.cache +/build +/public/build +.env diff --git a/.gitignore b/.gitignore index 3f7bf98..3d3ba9a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules +/.idea /.cache /build diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..9173eb0 --- /dev/null +++ b/Dockerfile @@ -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" ] diff --git a/app/entry.client.tsx b/app/entry.client.tsx index 94d5dc0..beda79c 100644 --- a/app/entry.client.tsx +++ b/app/entry.client.tsx @@ -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 { startTransition, StrictMode } from "react"; import { hydrateRoot } from "react-dom/client"; startTransition(() => { - hydrateRoot( - document, - - - - ); + hydrateRoot( + document, + + + , + ); }); diff --git a/app/entry.server.tsx b/app/entry.server.tsx index 0c7712b..8a5252f 100644 --- a/app/entry.server.tsx +++ b/app/entry.server.tsx @@ -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 type { AppLoadContext, EntryContext } from "@remix-run/node"; @@ -15,123 +9,123 @@ import { renderToPipeableStream } from "react-dom/server"; const ABORT_DELAY = 5_000; export default function handleRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext, - loadContext: AppLoadContext + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, + loadContext: AppLoadContext, ) { - return isbot(request.headers.get("user-agent")) - ? handleBotRequest( - request, - responseStatusCode, - responseHeaders, - remixContext - ) - : handleBrowserRequest( - request, - responseStatusCode, - responseHeaders, - remixContext - ); + return isbot(request.headers.get("user-agent")) + ? handleBotRequest( + request, + responseStatusCode, + responseHeaders, + remixContext, + ) + : handleBrowserRequest( + request, + responseStatusCode, + responseHeaders, + remixContext, + ); } function handleBotRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, ) { - return new Promise((resolve, reject) => { - let shellRendered = false; - const { pipe, abort } = renderToPipeableStream( - , - { - onAllReady() { - shellRendered = true; - const body = new PassThrough(); - const stream = createReadableStreamFromReadable(body); + return new Promise((resolve, reject) => { + let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( + , + { + onAllReady() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); - responseHeaders.set("Content-Type", "text/html"); + responseHeaders.set("Content-Type", "text/html"); - resolve( - new Response(stream, { - headers: responseHeaders, - status: responseStatusCode, - }) - ); + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }), + ); - pipe(body); - }, - onShellError(error: unknown) { - reject(error); - }, - onError(error: unknown) { - responseStatusCode = 500; - // Log streaming rendering errors from inside the shell. Don't log - // errors encountered during initial shell rendering since they'll - // reject and get logged in handleDocumentRequest. - if (shellRendered) { - console.error(error); - } - }, - } - ); + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + }, + ); - setTimeout(abort, ABORT_DELAY); - }); + setTimeout(abort, ABORT_DELAY); + }); } function handleBrowserRequest( - request: Request, - responseStatusCode: number, - responseHeaders: Headers, - remixContext: EntryContext + request: Request, + responseStatusCode: number, + responseHeaders: Headers, + remixContext: EntryContext, ) { - return new Promise((resolve, reject) => { - let shellRendered = false; - const { pipe, abort } = renderToPipeableStream( - , - { - onShellReady() { - shellRendered = true; - const body = new PassThrough(); - const stream = createReadableStreamFromReadable(body); + return new Promise((resolve, reject) => { + let shellRendered = false; + const { pipe, abort } = renderToPipeableStream( + , + { + onShellReady() { + shellRendered = true; + const body = new PassThrough(); + const stream = createReadableStreamFromReadable(body); - responseHeaders.set("Content-Type", "text/html"); + responseHeaders.set("Content-Type", "text/html"); - resolve( - new Response(stream, { - headers: responseHeaders, - status: responseStatusCode, - }) - ); + resolve( + new Response(stream, { + headers: responseHeaders, + status: responseStatusCode, + }), + ); - pipe(body); - }, - onShellError(error: unknown) { - reject(error); - }, - onError(error: unknown) { - responseStatusCode = 500; - // Log streaming rendering errors from inside the shell. Don't log - // errors encountered during initial shell rendering since they'll - // reject and get logged in handleDocumentRequest. - if (shellRendered) { - console.error(error); - } - }, - } - ); + pipe(body); + }, + onShellError(error: unknown) { + reject(error); + }, + onError(error: unknown) { + responseStatusCode = 500; + // Log streaming rendering errors from inside the shell. Don't log + // errors encountered during initial shell rendering since they'll + // reject and get logged in handleDocumentRequest. + if (shellRendered) { + console.error(error); + } + }, + }, + ); - setTimeout(abort, ABORT_DELAY); - }); + setTimeout(abort, ABORT_DELAY); + }); } diff --git a/app/root.tsx b/app/root.tsx index b46b8fb..a5259f9 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -1,33 +1,33 @@ import { cssBundleHref } from "@remix-run/css-bundle"; import type { LinksFunction } from "@remix-run/node"; import { - Links, - LiveReload, - Meta, - Outlet, - Scripts, - ScrollRestoration, + Links, + LiveReload, + Meta, + Outlet, + Scripts, + ScrollRestoration, } from "@remix-run/react"; export const links: LinksFunction = () => [ - ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []), + ...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []), ]; export default function App() { - return ( - - - - - - - - - - - - - - - ); + return ( + + + + + + + + + + + + + + + ); } diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index 5347369..d251b0f 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -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 = () => { - return [ - { title: "New Remix App" }, - { name: "description", content: "Welcome to Remix!" }, - ]; + return [ + { title: "New Remix App" }, + { name: "description", content: "Welcome to Remix!" }, + ]; }; export default function Index() { - return ( - - ); + return ( + <> +
+
+{`        _                 _       _            _     
+       | |               | |     (_)          | |    
+       | | ___   ___ __ _| |      _ _ __   ___| |__  
+       | |/ _ \\ / __/ _\` | |_____| | '_ \\ / __| '_ \\ 
+       | | (_) | (_| (_| | |_____| | |_) |\\__ \\ | | |
+       |_|\\___/ \\___\\__,_|_|     |_| .__(_)___/_| |_|
+                                   | |               
+                                   |_|
+`}
+				
+
+ +
+
+
What is local-ip.sh?
+
+
+ local-ip.sh is a magic domain name that provides wildcard DNS + for any IP address. It is heavily inspired by local-ip.co, + {" "}sslip.io, and xip.io. +
+ +
+ Quick example, say your LAN IP address is 192.168.1.10. + Using local-ip.sh, + +

+ +
192.168.1.10.local-ip.sh resolves to 192.168.1.10
+  dots.192.168.1.10.local-ip.sh resolves to 192.168.1.10
+dashes.192-168-1-10.local-ip.sh resolves to 192.168.1.10`,
+							}} />
+						
+ +
+ ...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! +
+ +
+ The best part is, you can serve your content over HTTPS with our TLS certificate + for *.local-ip.sh: + + Be aware that wildcard certificates are not recursive, meaning they don't match "sub-subdomains".
+ In our case, this certificate will only match subdomains of local-ip.sh such as 192-168-1-10.local-ip.sh + {" "}where dashes separate the numbers that make up the IP address. +
+
+
+ +
+
How does it work?
+
+
+ local-ip.sh runs publicly a custom DNS server. + 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. +
+ +
+ The TLS certificate is obtained from Let's Encrypt and renewed up to a month before it expires. +
+
+
+
+ + + + ); } diff --git a/app/routes/server[.]key.ts b/app/routes/server[.]key.ts new file mode 100644 index 0000000..40a1889 --- /dev/null +++ b/app/routes/server[.]key.ts @@ -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"); +} diff --git a/app/routes/server[.]pem.ts b/app/routes/server[.]pem.ts new file mode 100644 index 0000000..d0a42ab --- /dev/null +++ b/app/routes/server[.]pem.ts @@ -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"); +} diff --git a/app/styles/index.css b/app/styles/index.css new file mode 100644 index 0000000..a46ce44 --- /dev/null +++ b/app/styles/index.css @@ -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 } +} diff --git a/fly.toml b/fly.toml new file mode 100644 index 0000000..4ed1a8f --- /dev/null +++ b/fly.toml @@ -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 diff --git a/package-lock.json b/package-lock.json index b906a71..6579b4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { + "@flydotio/dockerfile": "^0.4.11", "@remix-run/dev": "^2.3.1", "@remix-run/eslint-config": "^2.3.1", "@types/react": "^18.2.20", @@ -1309,6 +1310,37 @@ "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": { "version": "0.11.13", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", @@ -2635,6 +2667,12 @@ "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": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/asynciterator.prototype/-/asynciterator.prototype-1.0.0.tgz", @@ -3107,6 +3145,57 @@ "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": { "version": "1.0.4", "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", "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": { "version": "1.4.609", "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_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": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -5139,6 +5264,15 @@ "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": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", @@ -6138,6 +6272,46 @@ "@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": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz", @@ -8840,6 +9014,15 @@ "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": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", @@ -9239,6 +9422,15 @@ "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": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", @@ -11118,6 +11310,15 @@ "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": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -11133,6 +11334,53 @@ "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": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index db53c92..4d65431 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { + "@flydotio/dockerfile": "^0.4.11", "@remix-run/dev": "^2.3.1", "@remix-run/eslint-config": "^2.3.1", "@types/react": "^18.2.20", @@ -30,4 +31,4 @@ "engines": { "node": ">=18.0.0" } -} \ No newline at end of file +}