parent
40039ced43
commit
bed389213a
|
|
@ -0,0 +1,5 @@
|
|||
/.cache
|
||||
/.env
|
||||
/build
|
||||
/node_modules
|
||||
/public/build
|
||||
|
|
@ -9,7 +9,3 @@ node_modules
|
|||
/public/build
|
||||
/api/index.js
|
||||
/api/**/*.js.map
|
||||
dist
|
||||
.vercel
|
||||
.DS_Store
|
||||
/app/generated.css
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
### base
|
||||
FROM node:16-bullseye-slim as base
|
||||
|
||||
# Install openssl for Prisma
|
||||
RUN apt-get update && apt-get install -y openssl
|
||||
|
||||
### build
|
||||
FROM base as build
|
||||
|
||||
RUN mkdir /app
|
||||
WORKDIR /app
|
||||
|
||||
RUN npm i -g pnpm
|
||||
|
||||
ADD . .
|
||||
RUN pnpm install
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN pnpm prisma generate
|
||||
RUN pnpm build
|
||||
# keep production dependencies only
|
||||
RUN pnpm prune --production
|
||||
|
||||
# Finally, build the production image with minimal footprint
|
||||
FROM base
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN mkdir /app
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=build /app/node_modules /app/node_modules
|
||||
COPY --from=build /app/build /app/build
|
||||
COPY --from=build /app/public /app/public
|
||||
ADD . .
|
||||
|
||||
CMD ["npm", "start"]
|
||||
1756
app/main.css
1756
app/main.css
File diff suppressed because one or more lines are too long
|
|
@ -1,51 +0,0 @@
|
|||
const REDIRECTS = new Set([301, 302, 303, 307, 308])
|
||||
|
||||
export function validateURL(url: string | URL): string {
|
||||
try {
|
||||
return String(new URL(String(url)))
|
||||
} catch (error: any) {
|
||||
throw new Error(`URLs is malformed. Please use only absolute URLs`)
|
||||
}
|
||||
}
|
||||
|
||||
// Should kept in sync with Next.js https://cs.github.com/vercel/next.js/blob/4a8a3d2400a54448b615fbc75cd91ca8cfea256c/packages/next/server/utils.ts#L18
|
||||
export function isBot(userAgent: string): boolean {
|
||||
return /Googlebot|Mediapartners-Google|AdsBot-Google|googleweblight|Storebot-Google|Google-PageRenderer|Bingbot|BingPreview|Slurp|DuckDuckBot|baiduspider|yandex|sogou|LinkedInBot|bitlybot|tumblr|vkShare|quora link preview|facebookexternalhit|facebookcatalog|Twitterbot|applebot|redditbot|Slackbot|Discordbot|WhatsApp|SkypeUriPreview|ia_archiver/i.test(
|
||||
userAgent
|
||||
)
|
||||
}
|
||||
|
||||
export const REWRITE_HEADER = "x-middleware-rewrite"
|
||||
export const NEXT_HEADER = "x-middleware-next"
|
||||
|
||||
export class MiddlewareResponse extends Response {
|
||||
static rewrite(destination: string | URL) {
|
||||
return new MiddlewareResponse(null, {
|
||||
headers: {
|
||||
[REWRITE_HEADER]: validateURL(destination),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
static next() {
|
||||
return new MiddlewareResponse(null, {
|
||||
headers: {
|
||||
[NEXT_HEADER]: "1",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
static redirect(url: string | URL, status = 307) {
|
||||
if (!REDIRECTS.has(status)) {
|
||||
throw new RangeError(
|
||||
'Failed to execute "redirect" on "response": Invalid status code'
|
||||
)
|
||||
}
|
||||
|
||||
const destination = validateURL(url)
|
||||
return new MiddlewareResponse(destination, {
|
||||
headers: { Location: destination },
|
||||
status,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
import { MiddlewareResponse } from "./middleware-utils"
|
||||
|
||||
export default (req: Request) => {
|
||||
const url = new URL(req.url)
|
||||
|
||||
const cookieHeader = req.headers.get("cookie") || ""
|
||||
if (
|
||||
url.pathname.startsWith("/dashboard") &&
|
||||
!cookieHeader.includes(process.env.AUTH_COOKIE_NAME)
|
||||
) {
|
||||
const redirectUrl = new URL(req.url)
|
||||
redirectUrl.pathname = "/"
|
||||
return MiddlewareResponse.redirect(redirectUrl)
|
||||
}
|
||||
|
||||
if (url.pathname === "/__test_middleware") {
|
||||
return new Response("home")
|
||||
}
|
||||
|
||||
return MiddlewareResponse.next()
|
||||
}
|
||||
|
|
@ -16,7 +16,7 @@ import {
|
|||
import LoginModal from "~/components/common/LoginModal"
|
||||
import { createStore, StoreProvider } from "./lib/store"
|
||||
import { Toaster } from "react-hot-toast"
|
||||
import css from "./generated.css"
|
||||
import css from "./main.css"
|
||||
import { APP_NAME } from "./lib/config.shared"
|
||||
|
||||
export const meta: MetaFunction = () => {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
# fly.toml file generated for proselog on 2022-05-02T21:19:40+08:00
|
||||
|
||||
app = "proselog"
|
||||
|
||||
kill_signal = "SIGINT"
|
||||
kill_timeout = 5
|
||||
processes = []
|
||||
|
||||
[env]
|
||||
PORT = "8080"
|
||||
|
||||
[experimental]
|
||||
allowed_public_ports = []
|
||||
auto_rollback = true
|
||||
|
||||
[[services]]
|
||||
http_checks = []
|
||||
internal_port = 8080
|
||||
processes = ["app"]
|
||||
protocol = "tcp"
|
||||
script_checks = []
|
||||
|
||||
[services.concurrency]
|
||||
hard_limit = 25
|
||||
soft_limit = 20
|
||||
type = "connections"
|
||||
|
||||
[[services.ports]]
|
||||
force_https = true
|
||||
handlers = ["http"]
|
||||
port = 80
|
||||
|
||||
[[services.ports]]
|
||||
handlers = ["tls", "http"]
|
||||
port = 443
|
||||
|
||||
[[services.tcp_checks]]
|
||||
grace_period = "1s"
|
||||
interval = "15s"
|
||||
restart_limit = 0
|
||||
timeout = "2s"
|
||||
24
package.json
24
package.json
|
|
@ -6,14 +6,14 @@
|
|||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"build-remix": "remix build",
|
||||
"build": "pnpm build-css && pnpm build-remix && pnpm build-vercel",
|
||||
"build-vercel": "node -r esbuild-register scripts/vercel-build.ts",
|
||||
"dev-node": "nodemon build/index.js --watch build",
|
||||
"dev-remix": "remix watch",
|
||||
"build": "pnpm build-css && pnpm build-remix",
|
||||
"dev-remix": "remix dev",
|
||||
"dev": "pnpm generate-css && run-p dev-*",
|
||||
"generate-css": "tailwindcss --input ./css/main.css --output ./app/generated.css --postcss",
|
||||
"generate-css": "tailwindcss --input ./css/main.css --output ./app/main.css --postcss",
|
||||
"dev-css": "pnpm generate-css --watch",
|
||||
"build-css": "pnpm generate-css --minify"
|
||||
"build-css": "pnpm generate-css --minify",
|
||||
"start": "remix-serve build",
|
||||
"deploy": "fly deploy --remote-only"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.81.0",
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
"@prisma/client": "^3.13.0",
|
||||
"@remix-run/node": "*",
|
||||
"@remix-run/react": "*",
|
||||
"@remix-run/vercel": "*",
|
||||
"@remix-run/serve": "*",
|
||||
"@uiw/react-codemirror": "^4.7.0",
|
||||
"@vercel/node": "^1.14.0",
|
||||
"clsx": "^1.1.1",
|
||||
|
|
@ -48,23 +48,13 @@
|
|||
"@remix-run/dev": "*",
|
||||
"@remix-run/eslint-config": "*",
|
||||
"@remix-run/serve": "*",
|
||||
"@types/connect": "^3.4.35",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/fs-extra": "^9.0.13",
|
||||
"@types/markdown-it": "^12.2.3",
|
||||
"@types/react": "^17.0.24",
|
||||
"@types/react-avatar-editor": "^12.0.0",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"@vercel/nft": "^0.18.2",
|
||||
"dotenv": "^16.0.0",
|
||||
"esbuild": "^0.14.38",
|
||||
"esbuild-register": "^3.3.2",
|
||||
"eslint": "^8.11.0",
|
||||
"express": "^4.18.1",
|
||||
"fs-extra": "^10.1.0",
|
||||
"nodemon": "^2.0.16",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss-import": "^14.1.0",
|
||||
"prisma": "^3.13.0",
|
||||
|
|
|
|||
1146
pnpm-lock.yaml
1146
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -3,8 +3,8 @@
|
|||
*/
|
||||
module.exports = {
|
||||
ignoredRouteFiles: ["**/.*"],
|
||||
server:
|
||||
process.env.NODE_ENV === "development"
|
||||
? "./server-dev.ts"
|
||||
: "./server-prod.ts",
|
||||
// appDirectory: "app",
|
||||
// assetsBuildDirectory: "public/build",
|
||||
// serverBuildPath: "build/index.js",
|
||||
// publicPath: "/build/",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
import * as esbuild from "esbuild"
|
||||
import dotenv from "dotenv"
|
||||
import fs from "fs-extra"
|
||||
import { nodeFileTrace } from "@vercel/nft"
|
||||
|
||||
async function main() {
|
||||
const { parsed = {} } = dotenv.config()
|
||||
const envKeys = Object.keys(parsed)
|
||||
|
||||
// Copy static file
|
||||
await fs.copy("public", ".vercel/output/static")
|
||||
|
||||
// Edge function
|
||||
await esbuild.build({
|
||||
entryPoints: ["./app/middleware.ts"],
|
||||
format: "esm",
|
||||
platform: "node",
|
||||
bundle: true,
|
||||
outfile: ".vercel/output/functions/edge.func/index.js",
|
||||
define: {
|
||||
"process.env.NODE_ENV": JSON.stringify("production"),
|
||||
},
|
||||
})
|
||||
|
||||
fs.outputJSONSync(".vercel/output/functions/edge.func/.vc-config.json", {
|
||||
runtime: "edge",
|
||||
entrypoint: "index.js",
|
||||
envVarsInUse: envKeys,
|
||||
})
|
||||
|
||||
// Serverless function
|
||||
await fs.copy("build", ".vercel/output/functions/render.func")
|
||||
const { fileList } = await nodeFileTrace([
|
||||
".vercel/output/functions/render.func/index.js",
|
||||
])
|
||||
for (const filepath of fileList) {
|
||||
if (filepath.includes("node_modules")) {
|
||||
await fs.copy(
|
||||
filepath,
|
||||
`.vercel/output/functions/render.func/${filepath}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fs.outputJSONSync(".vercel/output/functions/render.func/.vc-config.json", {
|
||||
runtime: "nodejs14.x",
|
||||
handler: "index.js",
|
||||
launcherType: "Nodejs",
|
||||
})
|
||||
|
||||
// Routes
|
||||
fs.outputJSONSync(".vercel/output/config.json", {
|
||||
version: 3,
|
||||
routes: [
|
||||
{
|
||||
src: `/build/.+`,
|
||||
headers: {
|
||||
"cache-control": "public, immutable, max-age=31536000",
|
||||
},
|
||||
},
|
||||
{
|
||||
handle: "filesystem",
|
||||
},
|
||||
{
|
||||
src: "/.*",
|
||||
middlewarePath: "edge",
|
||||
continue: true,
|
||||
},
|
||||
{ src: "/.*", dest: "/render" },
|
||||
].filter(Boolean),
|
||||
})
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error)
|
||||
process.exit(1)
|
||||
})
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
/**
|
||||
* The dev server serve the Remix app and use ./app/middleware.ts as a middleware (Vercel)
|
||||
* In production the middleware will be bundled to .vercel folder and Vercel will handle that
|
||||
*/
|
||||
import "dotenv/config"
|
||||
import path from "path"
|
||||
import express from "express"
|
||||
import { createRequestHandler } from "@remix-run/vercel"
|
||||
import { createRemixRequest, sendRemixResponse } from "@remix-run/vercel/server"
|
||||
import { NEXT_HEADER, REWRITE_HEADER } from "~/middleware-utils"
|
||||
|
||||
const app = express()
|
||||
|
||||
const BUILD_DIR = path.resolve("build")
|
||||
|
||||
// Remix fingerprints its assets so we can cache forever.
|
||||
app.use("/build", express.static("public/build"))
|
||||
|
||||
// Everything else (like favicon.ico) is cached for an hour. You may want to be
|
||||
// more aggressive with this caching.
|
||||
app.use(express.static("public"))
|
||||
|
||||
app.use(async (req, res, next) => {
|
||||
const middleware = require("~/middleware").default
|
||||
|
||||
const request = createRemixRequest(req as any)
|
||||
let response: Response = await middleware(request)
|
||||
|
||||
if (response.headers.has(NEXT_HEADER)) {
|
||||
return next()
|
||||
}
|
||||
|
||||
const rewriteTo = response.headers.get(REWRITE_HEADER)
|
||||
if (rewriteTo) {
|
||||
response = await fetch(rewriteTo)
|
||||
response.headers.delete("content-encoding")
|
||||
}
|
||||
|
||||
sendRemixResponse(res as any, response as any)
|
||||
})
|
||||
|
||||
app.all("*", (req, res) => {
|
||||
purgeRequireCache()
|
||||
const handler = createRequestHandler({
|
||||
build: require("@remix-run/dev/server-build"),
|
||||
mode: process.env.NODE_ENV,
|
||||
})
|
||||
// @ts-expect-error
|
||||
return handler(req, res)
|
||||
})
|
||||
|
||||
const PORT = process.env.PORT || "3000"
|
||||
app.listen(PORT)
|
||||
|
||||
console.log(`Open http://localhost:${PORT}`)
|
||||
|
||||
function purgeRequireCache() {
|
||||
// purge require cache on requests for "server side HMR" this won't let
|
||||
// you have in-memory objects between requests in development,
|
||||
// alternatively you can set up nodemon/pm2-dev to restart the server on
|
||||
// file changes, but then you'll have to reconnect to databases/etc on each
|
||||
// change. We prefer the DX of this, so we've included it for you by default
|
||||
for (let key in require.cache) {
|
||||
if (key.startsWith(BUILD_DIR)) {
|
||||
delete require.cache[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
/**
|
||||
* The production server is just a vercel serverless function
|
||||
*/
|
||||
|
||||
import { createRequestHandler } from "@remix-run/vercel"
|
||||
import * as build from "@remix-run/dev/server-build"
|
||||
|
||||
export default createRequestHandler({ build, mode: process.env.NODE_ENV })
|
||||
Loading…
Reference in New Issue