diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9cf8728..9f2ad9b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,92 +1,129 @@ -# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/https://github.com/imperial/cpp-connect/tree/mainbuilding-and-testing-nodejs +name: CI pipeline -name: Node.js CI - -# Defines two custom environment variables for the workflow. These are used for the Container registry domain, and a name for the Docker image that this workflow builds. env: REGISTRY: ghcr.io IMAGE_NAME: ${{ github.repository }} IMPAAS_APP_NAME: cpp-connect - IMPAAS_DEPLOY_TOKEN: ${{ secrets.IMPAAS_DEPLOY_TOKEN }} on: push: - branches: ["main"] + branches: + - main + - 'feat-*' pull_request: - branches: ["main"] + branches: + - main jobs: - lint: + changes: runs-on: ubuntu-latest + outputs: + migrations: ${{ steps.filter.outputs.migrations }} + steps: + - uses: actions/checkout@v4 + - uses: dorny/paths-filter@v3 + id: filter + with: + filters: | + migrations: + - 'prisma/migrations/**' + + install-dependencies: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + - name: Cache node_modules + uses: actions/cache@v4 + id: cache-primes + with: + path: node_modules + key: ${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }} + - name: Install dependencies + run: npm ci + type-check: + runs-on: ubuntu-latest + needs: install-dependencies steps: - uses: actions/checkout@v4 - - name: Use Node.js 20.x - uses: actions/setup-node@v4 + - uses: actions/setup-node@v4 with: - node-version: 20.x - cache: "npm" - - uses: actions/cache@v4 # From https://nextjs.org/docs/pages/building-your-application/deploying/ci-build-caching#github-actions + node-version-file: '.nvmrc' + cache: 'npm' + - name: Load cached node_modules + uses: actions/cache@v4 + id: cache-primes with: - # See here for caching with `yarn` https://github.com/actions/cache/blob/main/examples.md#node---yarn or you can leverage caching with actions/setup-node https://github.com/actions/setup-node - path: | - ${{ github.workspace }}/.next/cache - # Generate a new cache whenever packages or source files change. - key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }} - # If source files changed but packages didn't, rebuild from a prior cache. - restore-keys: | - ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}- - - run: npm ci - - run: npm run lint + path: node_modules + key: ${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }} + - name: Prisma generate + run: npx prisma generate + - name: Check types + run: npm run type-check - format-check: + style-check: runs-on: ubuntu-latest + needs: install-dependencies + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + - name: Load cached node_modules + uses: actions/cache@v4 + id: cache-primes + with: + path: node_modules + key: ${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }} + - name: Run Prettier + run: npm run style-check + lint: + runs-on: ubuntu-latest + needs: install-dependencies steps: - uses: actions/checkout@v4 - - name: Use Node.js 20.x - uses: actions/setup-node@v4 + - uses: actions/setup-node@v4 with: - node-version: 20.x - cache: "npm" - - uses: actions/cache@v4 # From https://nextjs.org/docs/pages/building-your-application/deploying/ci-build-caching#github-actions + node-version-file: '.nvmrc' + cache: 'npm' + - name: Load cached node_modules + uses: actions/cache@v4 + id: cache-primes with: - # See here for caching with `yarn` https://github.com/actions/cache/blob/main/examples.md#node---yarn or you can leverage caching with actions/setup-node https://github.com/actions/setup-node - path: | - ${{ github.workspace }}/.next/cache - # Generate a new cache whenever packages or source files change. - key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }} - # If source files changed but packages didn't, rebuild from a prior cache. - restore-keys: | - ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}- - - run: npm ci - - run: npm run format -- --check + path: node_modules + key: ${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }} + - name: Run linter + run: npm run lint - # There is a single job in this workflow. It's configured to run on the latest available version of Ubuntu. build-and-push-image: needs: + - type-check + - style-check - lint - - format-check + if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest - # Sets the permissions granted to the `GITHUB_TOKEN` for the actions in this job. permissions: contents: read packages: write attestations: write id-token: write steps: - - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - # Uses the `docker/login-action` action to log in to the Container registry using the account and password that will publish the packages. Once published, the packages are scoped to the account defined here. - name: Log in to the Container registry uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - # This step uses [docker/metadata-action](https://github.com/docker/metadata-action#about) to extract tags and labels that will be applied to the specified image. The `id` "meta" allows the output of this step to be referenced in a subsequent step. The `images` value provides the base name for the tags and labels. - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@v5 @@ -98,25 +135,18 @@ jobs: type=ref,event=pr type=sha type=raw,value=latest,enable={{is_default_branch}} - # This step uses the `docker/build-push-action` action to build the image, based on your repository's `Dockerfile`. If the build succeeds, it pushes the image to GitHub Packages. - # It uses the `context` parameter to define the build's context as the set of files located in the specified path. For more information, see "[Usage](https://github.com/docker/build-push-action#usage)" in the README of the `docker/build-push-action` repository. - # It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step. - name: Build and push Docker image id: push uses: docker/build-push-action@v6 with: context: . - # pushes regardless; only main tagged with latest push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha, mode=max + no-cache: true - # Deploy! deploy-impaas: - needs: - - build-and-push-image + needs: build-and-push-image if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest env: @@ -124,37 +154,35 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Install ImPaaS CLI - run: | - curl -fsSL "https://tsuru.io/get" | bash - - name: Setup ImPaaS CLI - run: | - tsuru target add impaas https://impaas.uk -s - - name: Deploy to ImPaaS - run: | - tsuru app deploy -i ghcr.io/imperial/cpp-connect:latest -a cpp-connect + - name: Install tsuru CLI + run: curl -fsSL "https://tsuru.io/get" | bash + - name: Add impaas target + run: tsuru target add impaas https://impaas.uk -s + - name: Deploy app + run: tsuru app deploy -i ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest -a ${{ env.IMPAAS_APP_NAME }} - name: Create upload directories run: | tsuru app run "mkdir -p \$UPLOAD_DIR && cd \$UPLOAD_DIR && mkdir -p banners cvs avatars logos attachments" -a cpp-connect - - name: Set database URL - run: | - tsuru app run "DATABASE_URL=postgres://\$PGUSER:\$PGPASSWORD@\$PGHOST:\$PGPORT/\$PGDATABASE && echo \$DATABASE_URL" -a cpp-connect \ - | grep "^postgres://" \ - | xargs -I {} tsuru env set -a cpp-connect DATABASE_URL={} - - name: Run migrations - run: | - tsuru app run "npm exec prisma migrate deploy" -a cpp-connect - - name: Set environment variables - run: | - tsuru env set -a cpp-connect \ - NEXTAUTH_URL=${{ vars.NEXTAUTH_URL }} \ - EMAIL_SERVER_USER=${{ secrets.EMAIL_SERVER_USER }} \ - EMAIL_SERVER_PASSWORD=${{ secrets.EMAIL_SERVER_PASSWORD }} \ - EMAIL_SERVER_HOST=${{ secrets.EMAIL_SERVER_HOST }} \ - EMAIL_SERVER_PORT=${{ secrets.EMAIL_SERVER_PORT }} \ - EMAIL_FROM=${{ secrets.EMAIL_FROM }} \ - AUTH_SECRET=${{ secrets.AUTH_SECRET }} \ - AUTH_TRUST_HOST=true \ - MS_ENTRA_CLIENT_ID=${{ secrets.MS_ENTRA_CLIENT_ID }} \ - MS_ENTRA_CLIENT_SECRET=${{ secrets.MS_ENTRA_CLIENT_SECRET }} \ - MS_ENTRA_TENANT_ID=${{ secrets.MS_ENTRA_TENANT_ID }} + + apply-db-migrations: + runs-on: ubuntu-latest + needs: + - changes + - deploy-impaas + if: github.ref == 'refs/heads/main' && needs.changes.outputs.migrations == 'true' + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: '.nvmrc' + cache: 'npm' + - name: Load cached node_modules + uses: actions/cache@v4 + id: cache-primes + with: + path: node_modules + key: ${{ runner.os }}-nextjs-${{ hashFiles('package-lock.json') }} + - name: Apply all pending migrations to the database + run: npx prisma migrate deploy + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..d79a9a2 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v20.13.0 \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 4178802..e8a1bbb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,68 +1,47 @@ -FROM node:18-alpine AS base +# Stage 1: Build +FROM node:18-alpine AS builder -FROM base AS app_base - -# Make UPLOAD_DIRs -ENV UPLOAD_DIR=/uploads -RUN mkdir $UPLOAD_DIR -RUN mkdir $UPLOAD_DIR/banners $UPLOAD_DIR/cvs $UPLOAD_DIR/avatars $UPLOAD_DIR/logos $UPLOAD_DIR/attachments +ENV NEXT_TELEMETRY_DISABLED=1 +WORKDIR /app -# Prisma base image only for installing prisma -FROM base AS prisma_base # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. RUN apk add --no-cache libc6-compat -WORKDIR /app COPY package.json package-lock.json* ./ -RUN npm install prisma --omit=dev - -FROM prisma_base AS deps -WORKDIR /app - -# Won't need to re-install prisma, since it's already installed in the prisma_base image RUN npm ci - -FROM base AS builder -WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules COPY . . +RUN npx prisma generate +RUN npm run build -# Disable telemetry during the build. -ENV NEXT_TELEMETRY_DISABLED 1 -RUN npm run build +# Stage 2: Production +FROM node:18-alpine AS runner -# Production image, copy all the files and run next -FROM app_base AS runner +ENV NEXT_TELEMETRY_DISABLED=1 WORKDIR /app -# Disable telemetry during runtime. -ENV NEXT_TELEMETRY_DISABLED 1 - -COPY --from=builder /app/public ./public +# Make UPLOAD_DIRs +ENV UPLOAD_DIR=/uploads +RUN mkdir $UPLOAD_DIR +RUN mkdir $UPLOAD_DIR/banners $UPLOAD_DIR/cvs $UPLOAD_DIR/avatars $UPLOAD_DIR/logos $UPLOAD_DIR/attachments +RUN chown node:node /app +RUN chown -R node:node $UPLOAD_DIR # Set the correct permission for prerender cache RUN mkdir .next RUN chown node:node .next -# Copy prisma schema -COPY --from=builder --chown=node:node /app/prisma ./prisma - -# Copy over the prisma client -COPY --from=prisma_base /app/node_modules ./node_modules - -# Automatically leverage output traces to reduce image size -# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder /app/node_modules ./node_modules +COPY --from=builder /app/public ./public COPY --from=builder --chown=node:node /app/.next/standalone ./ COPY --from=builder --chown=node:node /app/.next/static ./.next/static - -# Take ownership of the app folder and uploads -RUN chown node:node /app -RUN chown -R node:node $UPLOAD_DIR +COPY --from=builder --chown=node:node /app/prisma ./prisma USER node EXPOSE 3000 ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" +ENV NODE_ENV=production -CMD HOSTNAME="0.0.0.0" node server.js +CMD ["npm", "run", "start"] diff --git a/package.json b/package.json index 1962611..99bde86 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,9 @@ "build": "next build", "start": "next start", "lint": "next lint", - "format": "prettier --write '{app,components,lib}/**/*.{ts,tsx,scss}'", + "type-check": "tsc --noEmit", + "format": "prettier --write '{app,components,lib}/**/*.{js,jsx,ts,tsx,json,css,scss,md}'", + "style-check": "prettier --check '{app,components,lib}/**/*.{ts,tsx,js,jsx,json,css,scss,md}'", "prepare": "husky", "db:pull": "dotenv -e .env.local prisma db pull", "db:push": "dotenv -e .env.local prisma db push", @@ -110,4 +112,4 @@ "prisma": { "seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts" } -} \ No newline at end of file +}