Optimize docker image size (#91)

* Move prisma to runtime dependencies

* Optimize Dockerfile and build script

* Fix: remove mention of generated next-env.d.ts in Dockerfile

* Add missing reset.d.ts file to Dockerfile

* Remove compression steps from Dockerfile and entrypoint script

* Add an env file with mocked env vars added for Docker production builds

* Use server actions to get runtime env vars

* Refactor types and names

* Rollback serverActions, use parsed Zod object for runtime env

* Reintroduce featureFlags object to avoid passing secret envs to the frontend

* Improve string to boolean coercion

Co-authored-by: Sebastien Castiel <sebastien@castiel.me>

* Run prettier autoformat

* Fix type issue, rename function to match behaviour better

---------

Co-authored-by: Lauri Vuorela <lauri.vuorela@gmail.com>
Co-authored-by: Sebastien Castiel <sebastien@castiel.me>
This commit is contained in:
Jan T
2024-02-14 17:18:30 +02:00
committed by GitHub
parent 50525ad881
commit 2af0660383
12 changed files with 146 additions and 77 deletions

View File

@@ -6,6 +6,7 @@ import {
getExpense,
updateExpense,
} from '@/lib/api'
import { getRuntimeFeatureFlags } from '@/lib/featureFlags'
import { expenseFormSchema } from '@/lib/schemas'
import { Metadata } from 'next'
import { notFound, redirect } from 'next/navigation'
@@ -47,6 +48,7 @@ export default async function EditExpensePage({
categories={categories}
onSubmit={updateExpenseAction}
onDelete={deleteExpenseAction}
runtimeFeatureFlags={await getRuntimeFeatureFlags()}
/>
</Suspense>
)

View File

@@ -1,6 +1,7 @@
import { cached } from '@/app/cached-functions'
import { ExpenseForm } from '@/components/expense-form'
import { createExpense, getCategories } from '@/lib/api'
import { getRuntimeFeatureFlags } from '@/lib/featureFlags'
import { expenseFormSchema } from '@/lib/schemas'
import { Metadata } from 'next'
import { notFound, redirect } from 'next/navigation'
@@ -32,6 +33,7 @@ export default async function ExpensePage({
group={group}
categories={categories}
onSubmit={createExpenseAction}
runtimeFeatureFlags={await getRuntimeFeatureFlags()}
/>
</Suspense>
)

View File

@@ -35,6 +35,7 @@ import {
SelectValue,
} from '@/components/ui/select'
import { getCategories, getExpense, getGroup, randomId } from '@/lib/api'
import { RuntimeFeatureFlags } from '@/lib/featureFlags'
import { ExpenseFormValues, expenseFormSchema } from '@/lib/schemas'
import { cn } from '@/lib/utils'
import { zodResolver } from '@hookform/resolvers/zod'
@@ -52,6 +53,7 @@ export type Props = {
categories: NonNullable<Awaited<ReturnType<typeof getCategories>>>
onSubmit: (values: ExpenseFormValues) => Promise<void>
onDelete?: () => Promise<void>
runtimeFeatureFlags: RuntimeFeatureFlags
}
export function ExpenseForm({
@@ -60,6 +62,7 @@ export function ExpenseForm({
categories,
onSubmit,
onDelete,
runtimeFeatureFlags,
}: Props) {
const isCreate = expense === undefined
const searchParams = useSearchParams()
@@ -161,7 +164,7 @@ export function ExpenseForm({
{...field}
onBlur={async () => {
field.onBlur() // avoid skipping other blur event listeners since we overwrite `field`
if (process.env.NEXT_PUBLIC_ENABLE_CATEGORY_EXTRACT) {
if (runtimeFeatureFlags.enableCategoryExtract) {
setCategoryLoading(true)
const { categoryId } = await extractCategoryFromTitle(
field.value,
@@ -541,7 +544,7 @@ export function ExpenseForm({
</CardContent>
</Card>
{process.env.NEXT_PUBLIC_ENABLE_EXPENSE_DOCUMENTS && (
{runtimeFeatureFlags.enableExpenseDocuments && (
<Card className="mt-4">
<CardHeader>
<CardTitle className="flex justify-between">

View File

@@ -1,5 +1,10 @@
import { ZodIssueCode, z } from 'zod'
const interpretEnvVarAsBool = (val: unknown): boolean => {
if (typeof val !== 'string') return false
return ['true', 'yes', '1', 'on'].includes(val.toLowerCase())
}
const envSchema = z
.object({
POSTGRES_URL_NON_POOLING: z.string().url(),
@@ -12,14 +17,23 @@ const envSchema = z
? `https://${process.env.VERCEL_URL}`
: 'http://localhost:3000',
),
NEXT_PUBLIC_ENABLE_EXPENSE_DOCUMENTS: z.coerce.boolean().default(false),
NEXT_PUBLIC_ENABLE_EXPENSE_DOCUMENTS: z.preprocess(
interpretEnvVarAsBool,
z.boolean().default(false),
),
S3_UPLOAD_KEY: z.string().optional(),
S3_UPLOAD_SECRET: z.string().optional(),
S3_UPLOAD_BUCKET: z.string().optional(),
S3_UPLOAD_REGION: z.string().optional(),
S3_UPLOAD_ENDPOINT: z.string().optional(),
NEXT_PUBLIC_ENABLE_RECEIPT_EXTRACT: z.coerce.boolean().default(false),
NEXT_PUBLIC_ENABLE_CATEGORY_EXTRACT: z.coerce.boolean().default(false),
NEXT_PUBLIC_ENABLE_RECEIPT_EXTRACT: z.preprocess(
interpretEnvVarAsBool,
z.boolean().default(false),
),
NEXT_PUBLIC_ENABLE_CATEGORY_EXTRACT: z.preprocess(
interpretEnvVarAsBool,
z.boolean().default(false),
),
OPENAI_API_KEY: z.string().optional(),
})
.superRefine((env, ctx) => {

15
src/lib/featureFlags.ts Normal file
View File

@@ -0,0 +1,15 @@
'use server'
import { env } from './env'
export async function getRuntimeFeatureFlags() {
return {
enableExpenseDocuments: env.NEXT_PUBLIC_ENABLE_EXPENSE_DOCUMENTS,
enableReceiptExtract: env.NEXT_PUBLIC_ENABLE_RECEIPT_EXTRACT,
enableCategoryExtract: env.NEXT_PUBLIC_ENABLE_CATEGORY_EXTRACT,
}
}
export type RuntimeFeatureFlags = Awaited<
ReturnType<typeof getRuntimeFeatureFlags>
>