Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Centralize configuration in one place. #312

Merged
merged 15 commits into from
Apr 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to default shouldMigrate to true or process.env.NODE_ENV === 'production'? I think this might trip people up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's set explicitly by the SHOULD_MIGRATE environment variable. Which, I realized I forgot to add to the .env files.

}),
});

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,
cullylarson marked this conversation as resolved.
Show resolved Hide resolved
},
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