Skip to content

Commit

Permalink
feat: Centralize configuration in one place. (#312)
Browse files Browse the repository at this point in the history
* Move all (most) process.env references into config.ts.

* Add zod checks for config.

* Remove ts-node-dev. Doesn't seem to be needed, creates some weird situations where processes don't exist (e.g. buildProd).

* Refine the buildProd script.

* fix: Prettier config in bisonapp is different from prettier config in packages/create-bison-app/template. So if you save a file while editing bisonapp and then create a bison app from it, it may have lint errors. Resolved this by copying the prettier config from packages/create-bison-app/template into the root folder.
  • Loading branch information
cullylarson authored Apr 12, 2023
1 parent 808a48e commit 5f810b4
Show file tree
Hide file tree
Showing 21 changed files with 181 additions and 76 deletions.
8 changes: 5 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: ["ubuntu-latest", "windows-latest"]
os: ['ubuntu-latest', 'windows-latest']
node-version: [16.x, 18.x]
outputs:
appName: ${{ steps.create-app.outputs.appName }}
Expand All @@ -25,7 +25,7 @@ jobs:
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
cache: "yarn"
cache: 'yarn'

- name: Install packages
run: yarn && yarn lerna bootstrap
Expand Down Expand Up @@ -56,7 +56,7 @@ jobs:
image: postgres:15
env:
POSTGRES_PASSWORD: postgres
ports: ["5432:5432"]
ports: ['5432:5432']
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5

strategy:
Expand Down Expand Up @@ -85,6 +85,8 @@ jobs:

- name: Build production bison app
run: yarn build
env:
DATABASE_URL: not_used_but_needs_to_be_set_to_pass_zod_config_check

- name: Lint bison app
run: yarn lint
Expand Down
1 change: 1 addition & 0 deletions packages/create-bison-app/tasks/copyFiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ async function copyFiles({ variables, targetFolder }) {
"prettier.config.js",
"tsconfig.json",
"tsconfig.cjs.json",
"config.ts",
],
targetFolder,
{
Expand Down
6 changes: 3 additions & 3 deletions packages/create-bison-app/template/_.env.development.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@

# Include NODE_ENV for package script withEnv to work as expected

APP_ENV="development"
NEXT_PUBLIC_APP_ENV="development"
NODE_ENV="development"
PORT=3000
BASE_URL="http://localhost:3000"
SHOULD_MIGRATE=0

DATABASE_URL="postgresql://<%= db.dev.user %><% if (db.dev.password) { %>:<%= db.dev.password %><% } %>@<%= db.dev.host %>:<%= db.dev.port %>/<%= db.dev.name %>?schema=public"

NEXTAUTH_SECRET="bisonDev"
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_URL="http://localhost:3000"
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@

# Include NODE_ENV for package script withEnv to work as expected

APP_ENV="development"
NEXT_PUBLIC_APP_ENV="development"
NODE_ENV="development"
PORT=3000
BASE_URL="http://localhost:3000"
SHOULD_MIGRATE=0

DATABASE_URL="postgresql://<%= db.dev.user %><% if (db.dev.password) { %>:<%= db.dev.password %><% } %>@<%= db.dev.host %>:<%= db.dev.port %>/<%= db.dev.name %>?schema=public"

NEXTAUTH_SECRET="bisonDev"
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_URL="http://localhost:3000"
6 changes: 3 additions & 3 deletions packages/create-bison-app/template/_.env.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@

# Include NODE_ENV for package script withEnv to work as expected

APP_ENV="production"
NEXT_PUBLIC_APP_ENV="production"
NODE_ENV="development" # production // change if you truly want PROD
PORT=3000
BASE_URL="http://localhost:3000"
SHOULD_MIGRATE=0

DATABASE_URL="postgresql://<%= db.dev.user %><% if (db.dev.password) { %>:<%= db.dev.password %><% } %>@<%= db.dev.host %>:<%= db.dev.port %>/<%= db.dev.name %>?schema=public"

NEXTAUTH_SECRET="bisonProd"
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_URL="http://localhost:3000"
6 changes: 3 additions & 3 deletions packages/create-bison-app/template/_.env.local.ejs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Overwrite envs here to mimic production
# Include NODE_ENV for package script withEnv to work as expected

APP_ENV="production"
NEXT_PUBLIC_APP_ENV="production"
NODE_ENV="development" # production // change if you truly want PROD
PORT=3000
BASE_URL="http://localhost:3000"
SHOULD_MIGRATE=0

DATABASE_URL="postgresql://<%= db.dev.user %><% if (db.dev.password) { %>:<%= db.dev.password %><% } %>@<%= db.dev.host %>:<%= db.dev.port %>/<%= db.dev.name %>?schema=public"

NEXTAUTH_SECRET="bisonProd"
NEXTAUTH_URL="http://localhost:3000"
NEXTAUTH_URL="http://localhost:3000"
6 changes: 3 additions & 3 deletions packages/create-bison-app/template/_.env.test.ejs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Copy to .env.test.local to override
# Include NODE_ENV for package script withEnv to work as expected

APP_ENV="test"
NEXT_PUBLIC_APP_ENV="test"
NODE_ENV="test"
PORT=3001
BASE_URL="http://localhost:3001"
SHOULD_MIGRATE=0

DATABASE_URL="postgresql://<%= db.dev.user %><% if (db.dev.password) { %>:<%= db.dev.password %><% } %>@<%= db.dev.host %>:<%= db.dev.port %>/<%= db.test.name %>?schema=public"

NEXTAUTH_SECRET="bisonTest"
NEXTAUTH_URL="http://localhost:3001"
NEXTAUTH_URL="http://localhost:3001"
6 changes: 3 additions & 3 deletions packages/create-bison-app/template/_.env.test.local.ejs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# ENV vars here override .env.test when running locally
# Include NODE_ENV for package script withEnv to work as expected

APP_ENV="test"
NEXT_PUBLIC_APP_ENV="test"
NODE_ENV="test"
PORT=3001
BASE_URL="http://localhost:3001"
SHOULD_MIGRATE=0

DATABASE_URL="postgresql://<%= db.dev.user %><% if (db.dev.password) { %>:<%= db.dev.password %><% } %>@<%= db.dev.host %>:<%= db.dev.port %>/<%= db.test.name %>?schema=public"

NEXTAUTH_SECRET="bisonTest"
NEXTAUTH_URL="http://localhost:3001"
NEXTAUTH_URL="http://localhost:3001"
93 changes: 93 additions & 0 deletions packages/create-bison-app/template/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { z } from 'zod';

import { notEmpty } from './lib/type-witchcraft';

const stages = ['production', 'development', 'test'] as const;

type Stage = (typeof stages)[number];

function getStage(stages: Stage[]) {
if (!stages.length) return 'development';

for (const stage of stages) {
// if any of the provided stages is not production, assume we aren't in production
if (stage !== 'production') {
return stage;
}
}

return stages[0];
}

function isStage(potentialStage: string): potentialStage is Stage {
return stages.includes(potentialStage as Stage);
}

function envToBoolean(value: string | undefined, defaultValue = false): boolean {
if (value === undefined || value === '') {
return defaultValue;
}

// Explicitly test for true instead of false because we don't want to turn
// something on by accident.
return ['1', 'true'].includes(value.trim().toLowerCase()) ? true : false;
}

export function isProduction() {
return stage === 'production';
}

export function isDevelopment() {
return stage === 'development';
}

export function isTesting() {
return stage === 'test';
}

export function isLocal() {
return isDevelopment() || isTesting();
}

// a bit more versatile form of boolean coercion than zod provides
const coerceBoolean = z
.string()
.optional()
.transform((value) => envToBoolean(value))
.pipe(z.boolean());

const configSchema = z.object({
stage: z.enum(stages),
ci: z.object({
isCi: coerceBoolean,
isPullRequest: coerceBoolean,
}),
database: z.object({
url: z.string(),
shouldMigrate: coerceBoolean,
}),
});

const stage = getStage(
[process.env.NODE_ENV, process.env.NEXT_PUBLIC_APP_ENV].filter(notEmpty).filter(isStage)
);

// NOTE: Remember that only env variables that start with NEXT_PUBLIC or are
// listed in next.config.js will be available on the client.
export const config = configSchema.parse({
stage,
ci: {
isCi: process.env.CI,
isPullRequest: process.env.IS_PULL_REQUEST,
},
database: {
url: process.env.DATABASE_URL,
shouldMigrate: process.env.SHOULD_MIGRATE,
},
git: {
commit: process.env.FC_GIT_COMMIT_SHA || process.env.RENDER_GIT_COMMIT,
},
auth: {
secret: process.env.NEXTAUTH_SECRET,
},
});
2 changes: 0 additions & 2 deletions packages/create-bison-app/template/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,3 @@
export const EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;

export const MIN_PASSWORD_LENGTH = 8;

export const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
4 changes: 3 additions & 1 deletion packages/create-bison-app/template/lib/prisma.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Profile, User } from '@prisma/client';
import { Prisma, PrismaClient } from '@prisma/client';

import { isProduction } from '@/config';

/**
* Instantiate prisma client for Next.js:
* https://www.prisma.io/docs/support/help-articles/nextjs-prisma-client-dev-practices#solution
Expand All @@ -27,7 +29,7 @@ export const prisma =
log: logOptions,
});

if (process.env.NODE_ENV !== 'production') {
if (!isProduction()) {
global.prisma = prisma;
}

Expand Down
3 changes: 3 additions & 0 deletions packages/create-bison-app/template/lib/type-witchcraft.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function notEmpty<TValue>(value: TValue | null | undefined): value is TValue {
return value !== null && value !== undefined;
}
22 changes: 11 additions & 11 deletions packages/create-bison-app/template/package.json.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@
"cacheDirectories": [".next/cache"],
<% } -%>
"scripts": {
"build": "yarn ts-node ./scripts/buildProd",
"build": "yarn ts-node-wrap ./scripts/buildProd",
"build:prisma": "prisma generate",
"build:next": "next build",
"db:migrate": "prisma migrate dev",
"db:migrate:prod": "prisma migrate deploy",
"db:deploy": "yarn prisma migrate deploy",
"db:reset": "yarn prisma migrate reset",
"db:deploy": "prisma migrate deploy",
"db:reset": "prisma migrate reset",
"db:reset:test": "yarn withEnv:test prisma migrate reset",
"db:seed": "yarn prisma db seed",
"db:seed:prod": "cross-env APP_ENV=production prisma db seed",
"db:seed": "prisma db seed",
"db:seed:prod": "cross-env NODE_ENV=production prisma db seed",
"db:setup": "yarn db:reset",
"dev": "next dev",
"dev:typecheck": "tsc --noEmit",
Expand All @@ -29,10 +29,10 @@
"g:test:factory": "hygen test factory --name",
"g:test:request": "hygen test request --name",
"g:test:util": "hygen test util --name",
"g:e2e": "yarn playwright codegen",
"lint": "yarn eslint . --ext .ts,.tsx --ignore-pattern tmp",
"g:e2e": "playwright codegen",
"lint": "eslint . --ext .ts,.tsx --ignore-pattern tmp",
"lint:fix": "yarn lint --fix",
"run:script": "yarn ts-node prisma/scripts/run.ts -f",
"run:script": "yarn ts-node-wrap prisma/scripts/run.ts -f",
"setup": "yarn setup:dev && yarn setup:test",
"setup:dev": "yarn build:prisma && yarn db:deploy && yarn db:seed",
"setup:test": "yarn withEnv:test -- yarn db:deploy",
Expand All @@ -43,7 +43,7 @@
"test:db:reset": "yarn withEnv:test prisma migrate reset --force",
"test:e2e": "yarn withEnv:test playwright test --workers 1",
"test:e2e:debug": "PWDEBUG=1 yarn test:e2e",
"ts-node": "ts-node-dev --project tsconfig.cjs.json -r tsconfig-paths/register",
"ts-node-wrap": "ts-node --project tsconfig.cjs.json -r tsconfig-paths/register",
"withEnv:test": "dotenv -c test --",
"watch:ts": "yarn dev:typecheck --watch"
},
Expand Down Expand Up @@ -117,7 +117,7 @@
"prisma": "^4.12.0",
"start-server-and-test": "^2.0.0",
"ts-jest": "^29.1.0",
"ts-node-dev": "^2.0.0",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.0.3"
},
Expand All @@ -133,6 +133,6 @@
"*.{ts,tsx}": "yarn lint"
},
"prisma": {
"seed": "yarn ts-node prisma/seed.ts"
"seed": "yarn ts-node-wrap prisma/seed.ts"
}
}
2 changes: 2 additions & 0 deletions packages/create-bison-app/template/pages/api/health.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { NextApiRequest, NextApiResponse } from 'next';

import { config } from '@/config';
import { prisma } from '@/lib/prisma';

export default async function handler(_req: NextApiRequest, res: NextApiResponse) {
Expand All @@ -11,6 +12,7 @@ export default async function handler(_req: NextApiRequest, res: NextApiResponse
} catch (err) {}

const data = {
stage: config.stage,
env: {
NODE_ENV: process.env.NODE_ENV,
NEXT_PUBLIC_APP_ENV: process.env.NEXT_PUBLIC_APP_ENV,
Expand Down
10 changes: 6 additions & 4 deletions packages/create-bison-app/template/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { PlaywrightTestConfig } from '@playwright/test';
import { devices } from '@playwright/test';

import { config as appConfig } from '@/config';

const TEST_SERVER_PORT = process.env.PORT ? Number(process.env.PORT) : 3001;
const IS_CI = process.env.CI === 'true';
const IS_CI = appConfig.ci.isCi;

/**
* Read environment variables from file.
Expand Down Expand Up @@ -30,11 +32,11 @@ const config: PlaywrightTestConfig = {
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
forbidOnly: IS_CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
retries: IS_CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
workers: IS_CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
Expand Down
4 changes: 2 additions & 2 deletions packages/create-bison-app/template/prisma/SeedsAndScripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
- `yarn db:seed` (DEV)
- `yarn db:seed:prod` (PROD)

We use `APP_ENV` in the scripts to determine the dataset returned for seeds.
We use `NODE_ENV` in the scripts to determine the dataset returned for seeds.
This ENV is set in your .env.local file as well, and can be manually set as an ENV in your deploy environment if needed for other scripts.

### File Breakdown (Seeds)
Expand All @@ -20,7 +20,7 @@ This ENV is set in your .env.local file as well, and can be manually set as an E
----/index.ts
```

**data.ts** contains the exported function `seedModelData: ModelCreateInput[]` this function leverages APP_ENV to return the dataset expected for Dev vs Prod. In the case of `users` this returns `initialDevUsers: UserCreateInput[]` or `initalProdUsers: UserCreateInput[]`.
**data.ts** contains the exported function `seedModelData: ModelCreateInput[]` this function leverages `NODE_ENV` to return the dataset expected for Dev vs Prod. In the case of `users` this returns `initialDevUsers: UserCreateInput[]` or `initalProdUsers: UserCreateInput[]`.

**prismaRunner.ts** this file contains the Prisma `UPSERT` call for the model. We leverage upsert here so that seeds can potentially be ran more than once as your models and data expand over time.

Expand Down
Loading

0 comments on commit 5f810b4

Please sign in to comment.