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: verify environment variables #4115

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions web/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ NEXT_PUBLIC_PUBLIC_API_PREFIX=http://localhost:5001/api

# SENTRY
NEXT_PUBLIC_SENTRY_DSN=

NEXT_PUBLIC_SITE_ABOUT=
NEXT_PUBLIC_MAINTENANCE_NOTICE=
5 changes: 5 additions & 0 deletions web/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@@ -1,71 +1,73 @@
# Dify Frontend
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

Expand Down Expand Up @@ -30,6 +31,10 @@ NEXT_PUBLIC_PUBLIC_API_PREFIX=http://localhost:5001/api

# SENTRY
NEXT_PUBLIC_SENTRY_DSN=




```

Finally, run the development server:
Expand Down
5 changes: 3 additions & 2 deletions web/app/components/header/github-star/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import React, { useEffect, useState } from 'react'
import { Github } from '@/app/components/base/icons/src/public/common'
import type { GithubRepo } from '@/models/common'
import { env } from '@/env'

const getStar = async () => {
const res = await fetch('https://api.github.com/repos/langgenius/dify')
Expand All @@ -18,10 +19,10 @@ const GithubStar = () => {
useEffect(() => {
(async () => {
try {
if (process.env.NODE_ENV === 'development')
if (env.NODE_ENV === 'development')
return

await setGithubRepo(await getStar())
setGithubRepo(await getStar())
setIsFetched(true)
}
catch (e) {
Expand Down
5 changes: 3 additions & 2 deletions web/app/components/sentry-initor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

import { useEffect } from 'react'
import * as Sentry from '@sentry/react'
import { env } from '@/env'

const isDevelopment = process.env.NODE_ENV === 'development'
const isDevelopment = env.NEXT_PUBLIC_NODE_ENV === 'DEVELOPMENT'

const SentryInit = ({
children,
}: { children: React.ReactElement }) => {
useEffect(() => {
const SENTRY_DSN = document?.body?.getAttribute('data-public-sentry-dsn')
const SENTRY_DSN = env.NEXT_PUBLIC_SENTRY_DSN
if (!isDevelopment && SENTRY_DSN) {
Sentry.init({
dsn: SENTRY_DSN,
Expand Down
9 changes: 3 additions & 6 deletions web/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Topbar from './components/base/topbar'
import { getLocaleOnServer } from '@/i18n/server'
import './styles/globals.css'
import './styles/markdown.scss'
import { env } from '@/env'

export const metadata = {
title: 'Dify',
Expand Down Expand Up @@ -36,12 +37,8 @@ const LocaleLayout = ({
</head>
<body
className="h-full select-auto"
data-api-prefix={process.env.NEXT_PUBLIC_API_PREFIX}
data-pubic-api-prefix={process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX}
data-public-edition={process.env.NEXT_PUBLIC_EDITION}
data-public-sentry-dsn={process.env.NEXT_PUBLIC_SENTRY_DSN}
data-public-maintenance-notice={process.env.NEXT_PUBLIC_MAINTENANCE_NOTICE}
data-public-site-about={process.env.NEXT_PUBLIC_SITE_ABOUT}
data-public-maintenance-notice={env.NEXT_PUBLIC_MAINTENANCE_NOTICE}
data-public-site-about={env.NEXT_PUBLIC_SITE_ABOUT}
>
<Topbar/>
<BrowerInitor>
Expand Down
54 changes: 16 additions & 38 deletions web/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,15 @@
/* eslint-disable import/no-mutable-exports */
import { InputVarType } from '@/app/components/workflow/types'
import { AgentStrategy } from '@/types/app'
import { PromptRole } from '@/models/debug'
import { env } from '@/env'

export let apiPrefix = ''
export let publicApiPrefix = ''
// NEXT_PUBLIC_API_PREFIX=/console/api NEXT_PUBLIC_PUBLIC_API_PREFIX=/api yarn run start

// NEXT_PUBLIC_API_PREFIX=/console/api NEXT_PUBLIC_PUBLIC_API_PREFIX=/api npm run start
if (process.env.NEXT_PUBLIC_API_PREFIX && process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX) {
apiPrefix = process.env.NEXT_PUBLIC_API_PREFIX
publicApiPrefix = process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX
}
else if (
globalThis.document?.body?.getAttribute('data-api-prefix')
&& globalThis.document?.body?.getAttribute('data-pubic-api-prefix')
) {
// Not bulild can not get env from process.env.NEXT_PUBLIC_ in browser https://nextjs.org/docs/basic-features/environment-variables#exposing-environment-variables-to-the-browser
apiPrefix = globalThis.document.body.getAttribute('data-api-prefix') as string
publicApiPrefix = globalThis.document.body.getAttribute('data-pubic-api-prefix') as string
}
else {
// const domainParts = globalThis.location?.host?.split('.');
// in production env, the host is dify.app . In other env, the host is [dev].dify.app
// const env = domainParts.length === 2 ? 'ai' : domainParts?.[0];
apiPrefix = 'http://localhost:5001/console/api'
publicApiPrefix = 'http://localhost:5001/api' // avoid browser private mode api cross origin
}
export const API_PREFIX: string = env.NEXT_PUBLIC_API_PREFIX
export const apiPrefix = env.NEXT_PUBLIC_API_PREFIX
export const PUBLIC_API_PREFIX: string = env.NEXT_PUBLIC_PUBLIC_API_PREFIX

export const API_PREFIX: string = apiPrefix
export const PUBLIC_API_PREFIX: string = publicApiPrefix

const EDITION = process.env.NEXT_PUBLIC_EDITION || globalThis.document?.body?.getAttribute('data-public-edition') || 'SELF_HOSTED'
export const IS_CE_EDITION = EDITION === 'SELF_HOSTED'
export const IS_CE_EDITION = env.NEXT_PUBLIC_EDITION === 'SELF_HOSTED'

export const TONE_LIST = [
{
Expand Down Expand Up @@ -156,28 +134,28 @@ export const DEFAULT_AGENT_SETTING = {
}

export const DEFAULT_AGENT_PROMPT = {
chat: `Respond to the human as helpfully and accurately as possible.
chat: `Respond to the human as helpfully and accurately as possible.

{{instruction}}

You have access to the following tools:

{{tools}}

Use a json blob to specify a tool by providing an {{TOOL_NAME_KEY}} key (tool name) and an {{ACTION_INPUT_KEY}} key (tool input).
Valid "{{TOOL_NAME_KEY}}" values: "Final Answer" or {{tool_names}}

Provide only ONE action per $JSON_BLOB, as shown:

\`\`\`
{
"{{TOOL_NAME_KEY}}": $TOOL_NAME,
"{{ACTION_INPUT_KEY}}": $ACTION_INPUT
}
\`\`\`

Follow this format:

Question: input question to answer
Thought: consider previous and subsequent steps
Action:
Expand All @@ -194,10 +172,10 @@ export const DEFAULT_AGENT_PROMPT = {
"{{ACTION_INPUT_KEY}}": "Final response to human"
}
\`\`\`

Begin! Reminder to ALWAYS respond with a valid json blob of a single action. Use tools if necessary. Respond directly if appropriate. Format is Action:\`\`\`$JSON_BLOB\`\`\`then Observation:.`,
completion: `
Respond to the human as helpfully and accurately as possible.
Respond to the human as helpfully and accurately as possible.

{{instruction}}

Expand Down
3 changes: 2 additions & 1 deletion web/context/app-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { fetchCurrentWorkspace, fetchLanggeniusVersion, fetchUserProfile } from
import type { App } from '@/types/app'
import type { ICurrentWorkspace, LangGeniusVersionResponse, UserProfileResponse } from '@/models/common'
import MaintenanceNotice from '@/app/components/header/maintenance-notice'
import { env } from '@/env'

export type AppContextValue = {
apps: App[]
Expand Down Expand Up @@ -91,7 +92,7 @@ export const AppContextProvider: FC<AppContextProviderProps> = ({ children }) =>
const result = await userProfileResponse.json()
setUserProfile(result)
const current_version = userProfileResponse.headers.get('x-version')
const current_env = process.env.NODE_ENV === 'development' ? 'DEVELOPMENT' : userProfileResponse.headers.get('x-env')
const current_env = env.NODE_ENV === 'development' ? 'DEVELOPMENT' : userProfileResponse.headers.get('x-env')
const versionData = await fetchLanggeniusVersion({ url: '/version', params: { current_version } })
setLangeniusVersionInfo({ ...versionData, current_version, latest_version: versionData.version, current_env })
}
Expand Down
61 changes: 61 additions & 0 deletions web/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { createEnv } from '@t3-oss/env-nextjs'
import { z } from 'zod'
import { upperCase } from 'lodash-es'

export const env = createEnv({
/**
* Specify your server-side environment variables schema here. This way you can ensure the app
* isn't built with invalid env vars.
*/
server: {
NODE_ENV: z
.enum(['DEVELOPMENT', 'PRODUCTION'])
.default('DEVELOPMENT'),
},

/**
* Specify your client-side environment variables schema here. This way you can ensure the app
* isn't built with invalid env vars. To expose them to the client, prefix them with
* `NEXT_PUBLIC_`.
*/
client: {
NEXT_PUBLIC_DEPLOY_ENV: z
.enum(['DEVELOPMENT', 'PRODUCTION']),
NEXT_PUBLIC_EDITION: z
.enum(['SELF_HOSTED']),
NEXT_PUBLIC_API_PREFIX: z.string().url(),
NEXT_PUBLIC_PUBLIC_API_PREFIX: z.string().url(),
NEXT_PUBLIC_SENTRY_DSN: z.string().url().url().optional(),
NEXT_PUBLIC_NODE_ENV: z
.enum(['DEVELOPMENT', 'PRODUCTION'])
.default('DEVELOPMENT'),
NEXT_PUBLIC_MAINTENANCE_NOTICE: z.boolean().optional(),
NEXT_PUBLIC_SITE_ABOUT: z.boolean().optional(),
},

/**
* You can't destruct `process.env` as a regular object in the Next.js edge runtimes (e.g.
* middlewares) or client-side so we need to destruct manually.
*/
runtimeEnv: {
NODE_ENV: upperCase(process.env.NODE_ENV),
NEXT_PUBLIC_NODE_ENV: upperCase(process.env.NODE_ENV),
NEXT_PUBLIC_DEPLOY_ENV: process.env.NEXT_PUBLIC_DEPLOY_ENV,
NEXT_PUBLIC_EDITION: process.env.NEXT_PUBLIC_EDITION,
NEXT_PUBLIC_API_PREFIX: process.env.NEXT_PUBLIC_API_PREFIX,
NEXT_PUBLIC_PUBLIC_API_PREFIX: process.env.NEXT_PUBLIC_PUBLIC_API_PREFIX,
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN,
NEXT_PUBLIC_MAINTENANCE_NOTICE: process.env.NEXT_PUBLIC_MAINTENANCE_NOTICE,
NEXT_PUBLIC_SITE_ABOUT: process.env.NEXT_PUBLIC_MAINTENANCE_NOTICE,
},
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
* useful for Docker builds.
*/
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
/**
* Makes it so that empty strings are treated as undefined. `SOME_VAR: z.string()` and
* `SOME_VAR=''` will throw an error.
*/
emptyStringAsUndefined: true,
})
16 changes: 13 additions & 3 deletions web/next.config.js → web/next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
const { codeInspectorPlugin } = require('code-inspector-plugin')
const withMDX = require('@next/mdx')({
/**
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially useful
* for Docker builds.
*/
import { fileURLToPath } from 'node:url'
import { codeInspectorPlugin } from 'code-inspector-plugin'
import mdx from '@next/mdx'
import createJiti from 'jiti'
const jiti = createJiti(fileURLToPath(import.meta.url))
jiti('./env.ts')

const withMDX = mdx({
extension: /\.mdx?$/,
options: {
// If you use remark-gfm, you'll need to use next.config.mjs
Expand Down Expand Up @@ -46,4 +56,4 @@ const nextConfig = {
output: 'standalone',
}

module.exports = withMDX(nextConfig)
export default withMDX(nextConfig)
9 changes: 6 additions & 3 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"lint": "next lint",
"fix": "next lint --fix",
"eslint-fix": "eslint --fix",
"prepare": "cd ../ && node -e \"if (process.env.NODE_ENV !== 'production'){process.exit(1)} \" || husky install ./web/.husky",
"prepare": "node ./scripts/update-env.mjs && cd ../ && node -e \"if (process.env.NODE_ENV !== 'production'){process.exit(1)} \" || husky install ./web/.husky",
"gen-icons": "node ./app/components/base/icons/script.js",
"uglify-embed": "node ./bin/uglify-embed",
"check-i18n": "node ./i18n/script.js"
Expand All @@ -28,6 +28,7 @@
"@next/mdx": "^14.0.4",
"@sentry/react": "^7.54.0",
"@sentry/utils": "^7.54.0",
"@t3-oss/env-nextjs": "^0.10.1",
"@tailwindcss/line-clamp": "^0.4.4",
"@tailwindcss/typography": "^0.5.9",
"ahooks": "^3.7.5",
Expand Down Expand Up @@ -84,6 +85,7 @@
"swr": "^2.1.0",
"use-context-selector": "^1.4.1",
"uuid": "^9.0.1",
"zod": "^3.23.6",
"zustand": "^4.5.1"
},
"devDependencies": {
Expand Down Expand Up @@ -112,11 +114,12 @@
"eslint": "^8.36.0",
"eslint-config-next": "^14.0.4",
"husky": "^8.0.3",
"jiti": "^1.21.0",
"lint-staged": "^13.2.2",
"postcss": "^8.4.31",
"sass": "^1.61.0",
"tailwindcss": "^3.3.3",
"typescript": "4.9.5",
"typescript": "^5.4.5",
"uglify-js": "^3.17.4"
},
"lint-staged": {
Expand All @@ -130,4 +133,4 @@
"engines": {
"node": ">=18.17.0"
}
}
}
File renamed without changes.
37 changes: 37 additions & 0 deletions web/scripts/update-env.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as fs from 'node:fs'

function parseEnvFile(contents) {
return contents.split('\n').reduce((acc, line) => {
const [key, value] = line.split('=')
if (key)
acc.set(key.trim(), value === undefined ? '' : value.trim())

return acc
}, new Map())
}

function updateEnvironmentFile() {
const examplePath = '.env.example'
const envPath = '.env'

const exampleContents = fs.readFileSync(examplePath, 'utf-8')
const envContents = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf-8') : ''

const exampleConfig = parseEnvFile(exampleContents)
const envConfig = parseEnvFile(envContents)

exampleConfig.forEach((value, key) => {
if (!envConfig.has(key))
envConfig.set(key, value)
})

// 生成新的 .env 内容
const newEnvContents = Array.from(envConfig.entries())
.map(([key, value]) => `${key}=${value}`)
.join('\n')

fs.writeFileSync(envPath, newEnvContents)
console.log('.env file has been updated')
}

updateEnvironmentFile()
File renamed without changes.
4 changes: 2 additions & 2 deletions web/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
Expand All @@ -34,7 +34,7 @@
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts",
"app/components/develop/Prose.jsx"
"app/components/develop/Prose.jsx",
],
"exclude": [
"node_modules"
Expand Down