Skip to content

Commit af44aaf

Browse files
authored
Merge pull request #1 from dmno-dev/use-varlock
Use varlock
2 parents 5fe1c0a + 06e6a41 commit af44aaf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+444
-294
lines changed

.env.example

Lines changed: 0 additions & 29 deletions
This file was deleted.

.env.schema

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# This env file uses @env-spec - see https://varlock.dev/env-spec for more info
2+
#
3+
# @defaultRequired=true @defaultSensitive=false
4+
# @currentEnv=$NODE_ENV
5+
# @generateTypes(lang=ts, path=types/env-vars.d.ts)
6+
# ----------
7+
8+
# @type=enum(development, production, test)
9+
NODE_ENV=development
10+
# @type=enum(development, production, test)
11+
MODE=$NODE_ENV
12+
13+
# @type=port
14+
PORT=3000
15+
16+
LITEFS_DIR="/litefs/data"
17+
DATABASE_PATH="./prisma/data.db"
18+
DATABASE_URL="file:./data.db?connection_limit=1"
19+
CACHE_DATABASE_PATH="./other/cache.db"
20+
21+
# used to secure sessions
22+
# @sensitive
23+
# @docs(https://stack-staging.epicweb.dev/topic/deployment)
24+
SESSION_SECRET="super-duper-s3cret"
25+
26+
# encryption seed for honeypot server
27+
# @sensitive
28+
# @docs(https://stack-staging.epicweb.dev/topic/deployment)
29+
HONEYPOT_SECRET="super-duper-s3cret"
30+
31+
# this is set to a random value in the Dockerfile
32+
# @sensitive
33+
INTERNAL_COMMAND_TOKEN="some-made-up-token"
34+
35+
# set to false to prevent search engines from indexing the website (defaults to allow)
36+
ALLOW_INDEXING=true
37+
38+
# enables mocks for external services
39+
MOCKS=forEnv(development, test)
40+
41+
# will be set to curent commit sha in deployments
42+
# @optional
43+
COMMIT_SHA=
44+
45+
# API key for Resend (email service)
46+
# @type=string(startsWith=re_)
47+
# @sensitive
48+
# @optional # remove this if using resend
49+
# @docs(https://resend.com/docs/dashboard/api-keys/introduction#what-is-an-api-key)
50+
RESEND_API_KEY=
51+
52+
# will be set to true when running in CI
53+
CI=false
54+
55+
# Sentry settings (error tracking)
56+
# note that SENTRY_AUTH_TOKEN, SENTRY_ORG, SENTRY_PROJECT are optional
57+
# but enable @sentry/react-router integration and release tagging
58+
# ---
59+
# @type=url
60+
# @optional # remove this if using sentry
61+
# @example=https://examplePublicKey@o0.ingest.sentry.io/0
62+
# @docs(https://docs.sentry.io/concepts/key-terms/dsn-explainer/)
63+
SENTRY_DSN=
64+
# @optional @sensitive
65+
SENTRY_AUTH_TOKEN=
66+
# @required=if($SENTRY_AUTH_TOKEN)
67+
SENTRY_ORG=
68+
# @required=if($SENTRY_AUTH_TOKEN)
69+
SENTRY_PROJECT=
70+
71+
# GitHub settings
72+
#
73+
# the mocks and some code rely on these being prefixed with "MOCK_"
74+
# if they aren't then the real github api will be attempted
75+
# ---
76+
GITHUB_CLIENT_ID="MOCK_GITHUB_CLIENT_ID"
77+
# @sensitive
78+
GITHUB_CLIENT_SECRET="MOCK_GITHUB_CLIENT_SECRET"
79+
# @sensitive
80+
GITHUB_TOKEN="MOCK_GITHUB_TOKEN"
81+
# @type=url
82+
GITHUB_REDIRECT_URI="https://example.com/auth/github/callback"
83+
84+
85+
# Tigris Object Storage (S3-compatible) Configuration
86+
# ---
87+
AWS_ACCESS_KEY_ID="mock-access-key"
88+
# @sensitive
89+
AWS_SECRET_ACCESS_KEY="mock-secret-key"
90+
AWS_REGION="auto"
91+
# @type=url
92+
AWS_ENDPOINT_URL_S3="https://fly.storage.tigris.dev"
93+
BUCKET_NAME="mock-bucket"
94+
95+
# Populated by fly.io
96+
# ---
97+
# current fly.io region
98+
# @optional
99+
FLY_REGION=
100+
# app name as set in fly.io
101+
# @optional
102+
FLY_APP_NAME=

.env.test

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
DATABASE_PATH="./tests/prisma/base.db"
2+
DATABASE_URL=file:../tests/prisma/base.db

.github/workflows/deploy.yml

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@ jobs:
3030
- name: 📥 Download deps
3131
uses: bahmutov/npm-install@v1
3232

33-
- name: 🏄 Copy test env vars
34-
run: cp .env.example .env
35-
3633
- name: 🛠 Setup Database
3734
run: npx prisma migrate deploy && npx prisma generate --sql
3835

@@ -57,9 +54,6 @@ jobs:
5754
- name: 🏗 Build
5855
run: npm run build
5956

60-
- name: 🏄 Copy test env vars
61-
run: cp .env.example .env
62-
6357
- name: 🛠 Setup Database
6458
run: npx prisma migrate deploy && npx prisma generate --sql
6559

@@ -81,9 +75,6 @@ jobs:
8175
- name: 📥 Download deps
8276
uses: bahmutov/npm-install@v1
8377

84-
- name: 🏄 Copy test env vars
85-
run: cp .env.example .env
86-
8778
- name: 🛠 Setup Database
8879
run: npx prisma migrate deploy && npx prisma generate --sql
8980

@@ -98,9 +89,6 @@ jobs:
9889
- name: ⬇️ Checkout repo
9990
uses: actions/checkout@v4
10091

101-
- name: 🏄 Copy test env vars
102-
run: cp .env.example .env
103-
10492
- name: ⎔ Setup node
10593
uses: actions/setup-node@v4
10694
with:

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ node_modules
44
/build
55
/server-build
66
.env
7+
.env.local
8+
.env.*.local
79
.cache
810

911
/prisma/data.db
@@ -26,3 +28,4 @@ node_modules
2628
# generated files
2729
/app/components/ui/icons
2830
.react-router/
31+
/types/env-vars.d.ts

.prettierignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ node_modules
44
/public/build
55
/server-build
66
.env
7+
.env.*
78

89
/test-results/
910
/playwright-report/
1011
/playwright/.cache/
1112
/tests/fixtures/email/*.json
1213
/coverage
1314
/prisma/migrations
15+
/types/env-vars.d.ts
1416

1517
package-lock.json

.vscode/extensions.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"prisma.prisma",
77
"qwtel.sqlite-viewer",
88
"yoavbls.pretty-ts-errors",
9-
"github.vscode-github-actions"
9+
"github.vscode-github-actions",
10+
"varlock.env-spec-language"
1011
]
1112
}

README.md

Lines changed: 25 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,35 @@
1-
<div align="center">
2-
<h1 align="center"><a href="https://www.epicweb.dev/epic-stack">The Epic Stack 🚀</a></h1>
3-
<strong align="center">
4-
Ditch analysis paralysis and start shipping Epic Web apps.
5-
</strong>
6-
<p>
7-
This is an opinionated project starter and reference that allows teams to
8-
ship their ideas to production faster and on a more stable foundation based
9-
on the experience of <a href="https://kentcdodds.com">Kent C. Dodds</a> and
10-
<a href="https://github.com/epicweb-dev/epic-stack/graphs/contributors">contributors</a>.
11-
</p>
12-
</div>
1+
# Epic Stack + Varlock
132

14-
```sh
15-
npx epicli
16-
```
3+
An example repo using [Varlock](https://varlock.dev/) within the Epic Stack to help manage configuration and secrets.
174

18-
[![The Epic Stack](https://github-production-user-asset-6210df.s3.amazonaws.com/1500684/246885449-1b00286c-aa3d-44b2-9ef2-04f694eb3592.png)](https://www.epicweb.dev/epic-stack)
5+
With Varlock, we convert the `.env.example` file into a `.env.schema` which contains additional schema information about all configuration in the system. This will improve developer onboarding into the epic stack, as well as ongoing DX as devs add more config into their apps. It adds additional guardrails around configuration in general, and notably adds additional protection for sensitive secrets.
196

20-
[The Epic Stack](https://www.epicweb.dev/epic-stack)
7+
## Why do this?
8+
- validations, default values, and documentation are all now in one source of truth (`.env.schema`)
9+
- no more duplication between `.env.example` and `.env`, which means it will never get out of sync
10+
- only overrides must be added by user
11+
- clear env validation, decoupled from the application booting
12+
- improved TS types / IntelliSense
13+
- allows more flexible validation and composition of values based on other items
14+
- easy to now pull secrets from secure backends like 1pass, etc
15+
- leak prevention! log redaction!
16+
- clear error messages when accessing bad env vars, or using them in wrong place
2117

22-
<hr />
18+
## Screenshots
2319

24-
## Watch Kent's Introduction to The Epic Stack
20+
Some screenshots of varlock in action:
2521

26-
[![Epic Stack Talk slide showing Flynn Rider with knives, the text "I've been around and I've got opinions" and Kent speaking in the corner](https://github-production-user-asset-6210df.s3.amazonaws.com/1500684/277818553-47158e68-4efc-43ae-a477-9d1670d4217d.png)](https://www.epicweb.dev/talks/the-epic-stack)
22+
_`varlock load` showing loaded and validated env_
23+
<img width="488" height="393" alt="image" src="https://github.com/user-attachments/assets/9e80775e-ddf4-47b8-8ca1-0e4471c37299" />
2724

28-
["The Epic Stack" by Kent C. Dodds](https://www.epicweb.dev/talks/the-epic-stack)
25+
_Improved IntelliSense_
26+
<img width="435" height="131" alt="image" src="https://github.com/user-attachments/assets/3732dc0f-79f5-4ee5-a846-d314b31db1da" />
2927

30-
## Docs
28+
_Leak detection example_
29+
<img width="657" height="201" alt="image" src="https://github.com/user-attachments/assets/7598448a-d18c-47b6-b7c5-df3c68bbd875" />
3130

32-
[Read the docs](https://github.com/epicweb-dev/epic-stack/blob/main/docs)
33-
(please 🙏).
31+
_Log redaction example_
32+
<img width="202" height="52" alt="image" src="https://github.com/user-attachments/assets/3643b5d0-eec6-4f68-a488-0dfda7f18684" />
3433

35-
## Support
36-
37-
- 🆘 Join the
38-
[discussion on GitHub](https://github.com/epicweb-dev/epic-stack/discussions)
39-
and the [KCD Community on Discord](https://kcd.im/discord).
40-
- 💡 Create an
41-
[idea discussion](https://github.com/epicweb-dev/epic-stack/discussions/new?category=ideas)
42-
for suggestions.
43-
- 🐛 Open a [GitHub issue](https://github.com/epicweb-dev/epic-stack/issues) to
44-
report a bug.
45-
46-
## Branding
47-
48-
Want to talk about the Epic Stack in a blog post or talk? Great! Here are some
49-
assets you can use in your material:
50-
[EpicWeb.dev/brand](https://epicweb.dev/brand)
51-
52-
## Thanks
53-
54-
You rock 🪨
34+
_Example of failing env validation_
35+
<img width="430" height="176" alt="image" src="https://github.com/user-attachments/assets/d6258c48-43b8-4b6a-95d9-596a99f24e2b" />

app/entry.client.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { startTransition } from 'react'
22
import { hydrateRoot } from 'react-dom/client'
33
import { HydratedRouter } from 'react-router/dom'
4+
import { ENV } from 'varlock/env'
45

56
if (ENV.MODE === 'production' && ENV.SENTRY_DSN) {
67
void import('./utils/monitoring.client.tsx').then(({ init }) => init())

app/entry.server.tsx

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,30 +12,26 @@ import {
1212
type ActionFunctionArgs,
1313
type HandleDocumentRequestFunction,
1414
} from 'react-router'
15-
import { getEnv, init } from './utils/env.server.ts'
15+
import { ENV } from 'varlock/env'
1616
import { getInstanceInfo } from './utils/litefs.server.ts'
1717
import { NonceProvider } from './utils/nonce-provider.ts'
1818
import { makeTimings } from './utils/timing.server.ts'
1919

2020
export const streamTimeout = 5000
2121

22-
init()
23-
global.ENV = getEnv()
24-
25-
const MODE = process.env.NODE_ENV ?? 'development'
2622

2723
type DocRequestArgs = Parameters<HandleDocumentRequestFunction>
2824

2925
export default async function handleRequest(...args: DocRequestArgs) {
3026
const [request, responseStatusCode, responseHeaders, reactRouterContext] =
3127
args
3228
const { currentInstance, primaryInstance } = await getInstanceInfo()
33-
responseHeaders.set('fly-region', process.env.FLY_REGION ?? 'unknown')
34-
responseHeaders.set('fly-app', process.env.FLY_APP_NAME ?? 'unknown')
29+
responseHeaders.set('fly-region', ENV.FLY_REGION ?? 'unknown')
30+
responseHeaders.set('fly-app', ENV.FLY_APP_NAME ?? 'unknown')
3531
responseHeaders.set('fly-primary-instance', primaryInstance)
3632
responseHeaders.set('fly-instance', currentInstance)
3733

38-
if (process.env.NODE_ENV === 'production' && process.env.SENTRY_DSN) {
34+
if (ENV.NODE_ENV === 'production' && ENV.SENTRY_DSN) {
3935
responseHeaders.append('Document-Policy', 'js-profiling')
4036
}
4137

@@ -72,8 +68,8 @@ export default async function handleRequest(...args: DocRequestArgs) {
7268
directives: {
7369
fetch: {
7470
'connect-src': [
75-
MODE === 'development' ? 'ws:' : undefined,
76-
process.env.SENTRY_DSN ? '*.sentry.io' : undefined,
71+
ENV.MODE === 'development' ? 'ws:' : undefined,
72+
ENV.SENTRY_DSN ? '*.sentry.io' : undefined,
7773
"'self'",
7874
],
7975
'font-src': ["'self'"],
@@ -114,8 +110,8 @@ export default async function handleRequest(...args: DocRequestArgs) {
114110

115111
export async function handleDataRequest(response: Response) {
116112
const { currentInstance, primaryInstance } = await getInstanceInfo()
117-
response.headers.set('fly-region', process.env.FLY_REGION ?? 'unknown')
118-
response.headers.set('fly-app', process.env.FLY_APP_NAME ?? 'unknown')
113+
response.headers.set('fly-region', ENV.FLY_REGION ?? 'unknown')
114+
response.headers.set('fly-app', ENV.FLY_APP_NAME ?? 'unknown')
119115
response.headers.set('fly-primary-instance', primaryInstance)
120116
response.headers.set('fly-instance', currentInstance)
121117

0 commit comments

Comments
 (0)