diff --git a/.dockerignore b/.dockerignore index 26feb14a1..53f29c1c7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,6 +3,7 @@ .github # editor .idea +.vscode # dependencies node_modules .pnp @@ -34,4 +35,11 @@ cypress/screenshots cypress/videos apps/**/styles/tailwind.css -packages/**/styles/tailwind.css \ No newline at end of file +packages/**/styles/tailwind.css + +.changeset +examples +CHANGESETS.md +CONTRIBUTING.md +README.md +LICENSE diff --git a/apps/webapp/package.json b/apps/webapp/package.json index 990b81707..809f5f588 100644 --- a/apps/webapp/package.json +++ b/apps/webapp/package.json @@ -36,6 +36,7 @@ "@codemirror/view": "^6.5.0", "@conform-to/react": "^0.6.1", "@conform-to/zod": "^0.6.1", + "@godaddy/terminus": "^4.12.1", "@headlessui/react": "^1.7.8", "@heroicons/react": "^2.0.12", "@highlight-run/node": "^3.1.0", @@ -173,4 +174,4 @@ "engines": { "node": ">=16.0.0" } -} \ No newline at end of file +} diff --git a/apps/webapp/server.ts b/apps/webapp/server.ts index 14d0c1b79..ade30ed42 100644 --- a/apps/webapp/server.ts +++ b/apps/webapp/server.ts @@ -3,6 +3,7 @@ import express from "express"; import compression from "compression"; import morgan from "morgan"; import { createRequestHandler } from "@remix-run/express"; +import { createTerminus } from "@godaddy/terminus"; const app = express(); @@ -56,12 +57,15 @@ app.all( const port = process.env.REMIX_APP_PORT || 3000; -app.listen(port, () => { +const server = app.listen(port, () => { // require the built app so we're ready when the first request comes in require(BUILD_DIR); console.log(`✅ app ready: http://localhost:${port}`); }); +// Handle shutdowns gracefully +createTerminus(server, { signals: ["SIGINT", "SIGTERM"], timeout: 5000 }); + function purgeRequireCache() { // purge require cache on requests for "server side HMR" this won't let // you have in-memory objects between requests in development, diff --git a/docker/Dockerfile b/docker/Dockerfile index 0e12cd8e2..e1d20273f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,19 +1,19 @@ -FROM node:18 AS pruner +FROM node:18.16.1-bullseye-slim AS pruner WORKDIR /triggerdotdev -COPY . . +COPY --chown=node:node . . RUN npx -q turbo prune --scope=webapp --docker RUN find . -name "node_modules" -type d -prune -exec rm -rf '{}' + # Base strategy to have layer caching -FROM node:18 AS base -RUN apt-get update && apt-get install -y openssl +FROM node:18.16.1-bullseye-slim AS base +RUN apt-get update && apt-get install -y openssl dumb-init WORKDIR /triggerdotdev -COPY .gitignore .gitignore -COPY --from=pruner /triggerdotdev/out/json/ . -COPY --from=pruner /triggerdotdev/out/pnpm-lock.yaml ./pnpm-lock.yaml -COPY --from=pruner /triggerdotdev/out/pnpm-workspace.yaml ./pnpm-workspace.yaml +COPY --chown=node:node .gitignore .gitignore +COPY --from=pruner --chown=node:node /triggerdotdev/out/json/ . +COPY --from=pruner --chown=node:node /triggerdotdev/out/pnpm-lock.yaml ./pnpm-lock.yaml +COPY --from=pruner --chown=node:node /triggerdotdev/out/pnpm-workspace.yaml ./pnpm-workspace.yaml ## Dev deps FROM base AS dev-deps @@ -30,7 +30,7 @@ WORKDIR /triggerdotdev RUN corepack enable ENV NODE_ENV production RUN pnpm install --prod --no-frozen-lockfile -COPY --from=pruner /triggerdotdev/packages/database/prisma/schema.prisma /triggerdotdev/packages/database/prisma/schema.prisma +COPY --from=pruner --chown=node:node /triggerdotdev/packages/database/prisma/schema.prisma /triggerdotdev/packages/database/prisma/schema.prisma RUN pnpx prisma@^4.16.0 generate --schema /triggerdotdev/packages/database/prisma/schema.prisma ## Builder (builds the webapp) @@ -38,28 +38,29 @@ FROM base AS builder WORKDIR /triggerdotdev # Corepack is used to install pnpm RUN corepack enable -COPY --from=pruner /triggerdotdev/out/full/ . -COPY --from=dev-deps /triggerdotdev/ . -COPY turbo.json turbo.json -COPY docker/scripts ./scripts +COPY --from=pruner --chown=node:node /triggerdotdev/out/full/ . +COPY --from=dev-deps --chown=node:node /triggerdotdev/ . +COPY --chown=node:node turbo.json turbo.json +COPY --chown=node:node docker/scripts ./scripts RUN chmod +x ./scripts/wait-for-it.sh RUN chmod +x ./scripts/entrypoint.sh RUN pnpm run generate RUN pnpm run build --filter=webapp... # Runner -FROM node:18 AS runner +FROM node:18.16.1-bullseye-slim AS runner RUN apt-get update && apt-get install -y openssl WORKDIR /triggerdotdev RUN corepack enable ENV NODE_ENV production -COPY --from=pruner /triggerdotdev/out/full/ . -COPY --from=production-deps /triggerdotdev . -COPY --from=builder /triggerdotdev/apps/webapp/build/server.js ./apps/webapp/build/server.js -COPY --from=builder /triggerdotdev/apps/webapp/build ./apps/webapp/build -COPY --from=builder /triggerdotdev/apps/webapp/public ./apps/webapp/public -COPY --from=builder /triggerdotdev/scripts ./scripts +COPY --from=base /usr/bin/dumb-init /usr/bin/dumb-init +COPY --from=pruner --chown=node:node /triggerdotdev/out/full/ . +COPY --from=production-deps --chown=node:node /triggerdotdev . +COPY --from=builder --chown=node:node /triggerdotdev/apps/webapp/build/server.js ./apps/webapp/build/server.js +COPY --from=builder --chown=node:node /triggerdotdev/apps/webapp/build ./apps/webapp/build +COPY --from=builder --chown=node:node /triggerdotdev/apps/webapp/public ./apps/webapp/public +COPY --from=builder --chown=node:node /triggerdotdev/scripts ./scripts EXPOSE 3000 @@ -67,4 +68,5 @@ EXPOSE 3000 RUN npm install -g prisma@4.16.0 RUN npm install -g ts-node@10.9.1 -CMD ["./scripts/entrypoint.sh"] \ No newline at end of file +USER node +CMD ["./scripts/entrypoint.sh"] diff --git a/docker/scripts/entrypoint.sh b/docker/scripts/entrypoint.sh index 122bd82b1..23821fcfe 100755 --- a/docker/scripts/entrypoint.sh +++ b/docker/scripts/entrypoint.sh @@ -9,4 +9,4 @@ npx --no-install prisma migrate deploy --schema /triggerdotdev/packages/database npx --no-install ts-node --transpile-only /triggerdotdev/apps/webapp/prisma/seed.ts cd /triggerdotdev/apps/webapp -exec pnpm run start +exec dumb-init pnpm run start diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e7238ca78..a9bc1d021 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,6 +51,7 @@ importers: '@codemirror/view': ^6.5.0 '@conform-to/react': ^0.6.1 '@conform-to/zod': ^0.6.1 + '@godaddy/terminus': ^4.12.1 '@headlessui/react': ^1.7.8 '@heroicons/react': ^2.0.12 '@highlight-run/node': ^3.1.0 @@ -193,6 +194,7 @@ importers: '@codemirror/view': 6.7.2 '@conform-to/react': 0.6.1_react@18.2.0 '@conform-to/zod': 0.6.1_zod@3.21.4 + '@godaddy/terminus': 4.12.1 '@headlessui/react': 1.7.8_biqbaboplfbrettd7655fr4n2y '@heroicons/react': 2.0.13_react@18.2.0 '@highlight-run/node': 3.1.0 @@ -4839,6 +4841,12 @@ packages: resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} dev: true + /@godaddy/terminus/4.12.1: + resolution: {integrity: sha512-Tm+wVu1/V37uZXcT7xOhzdpFoovQReErff8x3y82k6YyWa1gzxWBjTyrx4G2enjEqoXPnUUmJ3MOmwH+TiP6Sw==} + dependencies: + stoppable: 1.1.0 + dev: false + /@graphile/logger/0.2.0: resolution: {integrity: sha512-jjcWBokl9eb1gVJ85QmoaQ73CQ52xAaOCF29ukRbYNl6lY+ts0ErTaDYOBlejcbUs2OpaiqYLO5uDhyLFzWw4w==} dev: false @@ -9774,7 +9782,7 @@ packages: /@types/accepts/1.3.5: resolution: {integrity: sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==} dependencies: - '@types/node': 20.3.3 + '@types/node': 18.15.13 dev: false /@types/acorn/4.0.6: @@ -9886,7 +9894,7 @@ packages: '@types/connect': 3.4.35 '@types/express': 4.17.15 '@types/keygrip': 1.0.2 - '@types/node': 20.3.3 + '@types/node': 18.15.13 dev: false /@types/debug/4.1.7: @@ -9993,7 +10001,7 @@ packages: resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.3.3 + '@types/node': 18.15.13 dev: true /@types/graceful-fs/4.1.6: @@ -10034,7 +10042,7 @@ packages: /@types/hapi__shot/4.1.2: resolution: {integrity: sha512-8wWgLVP1TeGqgzZtCdt+F+k15DWQvLG1Yv6ZzPfb3D5WIo5/S+GGKtJBVo2uNEcqabP5Ifc71QnJTDnTmw1axA==} dependencies: - '@types/node': 20.3.3 + '@types/node': 18.15.13 dev: false /@types/hast/2.3.4: @@ -10072,7 +10080,7 @@ packages: /@types/ioredis/4.28.10: resolution: {integrity: sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==} dependencies: - '@types/node': 20.3.3 + '@types/node': 18.15.13 dev: false /@types/is-ci/3.0.0: @@ -19999,7 +20007,7 @@ packages: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 20.3.3 + '@types/node': 18.15.13 long: 5.2.3 dev: false @@ -21548,6 +21556,11 @@ packages: resolution: {integrity: sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA==} dev: true + /stoppable/1.1.0: + resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} + engines: {node: '>=4', npm: '>=6'} + dev: false + /store2/2.14.2: resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==} dev: true