Enable users to define their env vars

This commit is contained in:
Mihovil Ilakovac 2024-10-30 14:53:27 +01:00
parent 1da4542578
commit 1d823aea51
14 changed files with 73 additions and 27 deletions

View File

@ -3,7 +3,15 @@ import * as z from 'zod'
import { ensureEnvSchema } from '../env/index.js'
const clientEnvSchema = z.object({
{=# envValidationFn.isDefined =}
{=& envValidationFn.importStatement =}
const userClientEnvSchema = {= envValidationFn.importIdentifier =}()
{=/ envValidationFn.isDefined =}
{=^ envValidationFn.isDefined =}
const userClientEnvSchema = z.object({})
{=/ envValidationFn.isDefined =}
const waspClientEnvSchema = z.object({
REACT_APP_API_URL: z
.string({
required_error: 'REACT_APP_API_URL is required',
@ -11,4 +19,6 @@ const clientEnvSchema = z.object({
.default('{= defaultServerUrl =}')
})
const clientEnvSchema = waspClientEnvSchema.merge(userClientEnvSchema)
export const env = ensureEnvSchema(import.meta.env, clientEnvSchema)

View File

@ -3,7 +3,15 @@ import * as z from 'zod'
import { ensureEnvSchema } from '../env/index.js'
const serverCommonSchema = z.object({
{=# envValidationFn.isDefined =}
{=& envValidationFn.importStatement =}
const userServerEnvSchema = {= envValidationFn.importIdentifier =}()
{=/ envValidationFn.isDefined =}
{=^ envValidationFn.isDefined =}
const userServerEnvSchema = z.object({})
{=/ envValidationFn.isDefined =}
const waspServerCommonSchema = z.object({
PORT: z.coerce.number().default({= defaultServerPort =}),
{= databaseUrlEnvVarName =}: z.string({
required_error: '{= databaseUrlEnvVarName =} is required',
@ -133,6 +141,7 @@ const serverProdSchema = z.object({
{=/ isAuthEnabled =}
})
const serverCommonSchema = waspServerCommonSchema.merge(userServerEnvSchema)
const serverEnvSchema = z.discriminatedUnion('NODE_ENV', [
serverDevSchema.merge(serverCommonSchema),
serverProdSchema.merge(serverCommonSchema)

View File

@ -1,6 +1,5 @@
import PgBoss from 'pg-boss'
import { env } from '../../../env.js'
import { config } from '../../../index.js'
import { config, env } from '../../../index.js'
const boss = createPgBoss()

View File

@ -22,3 +22,5 @@ GITHUB_CLIENT_SECRET='dummy-gh-client-secret'
# Dummy values here will allow app to run, but you will need real values to get Discord Auth to work.
DISCORD_CLIENT_SECRET='dummy-discord-client-secret'
DISCORD_CLIENT_ID='dummy-discord-client-id'
MY_ENV_VAR=123

View File

@ -52,12 +52,14 @@ app todoApp {
onAfterLogin: import { onAfterLogin } from "@src/auth/hooks.js",
},
server: {
setupFn: import setup from "@src/serverSetup",
setupFn: import { setup } from "@src/serverSetup",
middlewareConfigFn: import { serverMiddlewareFn } from "@src/serverSetup",
envValidationFn: import { serverEnvValidationFn } from "@src/env",
},
client: {
rootComponent: import { App } from "@src/App",
setupFn: import setup from "@src/clientSetup"
setupFn: import { setup } from "@src/clientSetup",
envValidationFn: import { clientEnvValidationFn } from "@src/env",
},
db: {
seeds: [

View File

@ -0,0 +1,6 @@
module.exports = {
trailingComma: 'es5',
tabWidth: 2,
semi: false,
singleQuote: true,
}

View File

@ -1,6 +0,0 @@
module.exports = {
trailingComma: 'es5',
tabWidth: 2,
semi: false,
singleQuote: true,
}

View File

@ -17,10 +17,7 @@ export function App() {
const connectionIcon = isConnected ? '🟢' : '🔴'
// TODO: enable users to define their own client env vars
const appName = import.meta.env.REACT_APP_NAME
? import.meta.env.REACT_APP_NAME
: 'TODO App'
const appName = env.REACT_APP_NAME
return (
<div className="app border-spacing-2 p-4">

View File

@ -1,7 +1,7 @@
import { testingAction } from 'wasp/client/operations'
import { sayHi } from './util'
export default function setup() {
export function setup() {
console.log('This was called from the client setup function')
testingAction()
sayHi()

View File

@ -0,0 +1,13 @@
import * as z from 'zod'
export const serverEnvValidationFn = () =>
z.object({
MY_ENV_VAR: z.string({
required_error: 'MY_ENV_VAR is required.',
}),
})
export const clientEnvValidationFn = () =>
z.object({
REACT_APP_NAME: z.string().default('TODO App'),
})

View File

@ -14,7 +14,7 @@ let someResource: string | undefined = undefined
export const getSomeResource = () => someResource
const setup: ServerSetupFn = async ({ app }) => {
export const setup: ServerSetupFn = async ({ app }) => {
addCustomRoute(app)
sayHi()
@ -34,6 +34,8 @@ const setup: ServerSetupFn = async ({ app }) => {
'submittedJob.pgBoss.details()',
await submittedJob.pgBoss.details()
)
console.log('Env var MY_ENV_VAR:', env.MY_ENV_VAR)
}
function addCustomRoute(app: Application) {
@ -52,5 +54,3 @@ export const serverMiddlewareFn: MiddlewareConfigFn = (middlewareConfig) => {
)
return middlewareConfig
}
export default setup

View File

@ -16,6 +16,7 @@ data Client = Client
{ setupFn :: Maybe ExtImport,
rootComponent :: Maybe ExtImport,
-- We expect the base dir to start with a slash e.g. /client
baseDir :: Maybe String
baseDir :: Maybe String,
envValidationFn :: Maybe ExtImport
}
deriving (Show, Eq, Data, Generic, FromJSON)

View File

@ -14,6 +14,7 @@ import Wasp.AppSpec.ExtImport (ExtImport)
data Server = Server
{ setupFn :: Maybe ExtImport,
middlewareConfigFn :: Maybe ExtImport
middlewareConfigFn :: Maybe ExtImport,
envValidationFn :: Maybe ExtImport
}
deriving (Show, Eq, Data, Generic, FromJSON)

View File

@ -9,13 +9,17 @@ import Data.Maybe (isJust)
import StrongPath (relfile)
import Wasp.AppSpec (AppSpec)
import qualified Wasp.AppSpec.App as AS.App
import qualified Wasp.AppSpec.App.Client as AS.App.Client
import qualified Wasp.AppSpec.App.Dependency as AS.Dependency
import qualified Wasp.AppSpec.App.Server as AS.App.Server
import Wasp.AppSpec.Valid (getApp)
import qualified Wasp.Generator.AuthProviders as AuthProviders
import qualified Wasp.Generator.EmailSenders as EmailSenders
import Wasp.Generator.FileDraft (FileDraft)
import qualified Wasp.Generator.JsImport as GJI
import Wasp.Generator.Monad (Generator)
import qualified Wasp.Generator.SdkGenerator.Common as C
import Wasp.Generator.SdkGenerator.Server.OperationsGenerator (extImportToJsImport)
import qualified Wasp.Generator.ServerGenerator.Common as Server
import qualified Wasp.Generator.WebAppGenerator.Common as WebApp
import qualified Wasp.Project.Db as Db
@ -24,7 +28,7 @@ genEnvValidation :: AppSpec -> Generator [FileDraft]
genEnvValidation spec =
sequence
[ genServerEnv spec,
genClientEnv,
genClientEnv spec,
genFileCopy [relfile|env/index.ts|]
]
where
@ -43,17 +47,25 @@ genServerEnv spec = return $ C.mkTmplFdWithData tmplPath tmplData
"defaultServerPort" .= Server.defaultServerPort,
"enabledAuthProviders" .= (AuthProviders.getEnabledAuthProvidersJson <$> maybeAuth),
"isEmailSenderUsed" .= isJust maybeEmailSender,
"enabledEmailSenders" .= (EmailSenders.getEnabledEmailProvidersJson <$> maybeEmailSender)
"enabledEmailSenders" .= (EmailSenders.getEnabledEmailProvidersJson <$> maybeEmailSender),
"envValidationFn" .= GJI.jsImportToImportJson (extImportToJsImport <$> maybeEnvValidationFn)
]
maybeAuth = AS.App.auth app
maybeEmailSender = AS.App.emailSender app
maybeEnvValidationFn = AS.App.server app >>= AS.App.Server.envValidationFn
app = snd $ getApp spec
genClientEnv :: Generator FileDraft
genClientEnv = return $ C.mkTmplFdWithData tmplPath tmplData
genClientEnv :: AppSpec -> Generator FileDraft
genClientEnv spec = return $ C.mkTmplFdWithData tmplPath tmplData
where
tmplPath = [relfile|client/env.ts|]
tmplData = object ["defaultServerUrl" .= Server.defaultDevServerUrl]
tmplData =
object
[ "defaultServerUrl" .= Server.defaultDevServerUrl,
"envValidationFn" .= GJI.jsImportToImportJson (extImportToJsImport <$> maybeEnvValidationFn)
]
maybeEnvValidationFn = AS.App.client app >>= AS.App.Client.envValidationFn
app = snd $ getApp spec
depsRequiredByEnvValidation :: [AS.Dependency.Dependency]
depsRequiredByEnvValidation =