Add initial support for new env handling (#10525)
* Add initial support for new env config file * Fix serverless processEnv call when no env is provided * Add missing await for test method * Update env config to .env.json and add dotenv loading * ncc dotenv package * Update type * Update with new discussed behavior removing .env.json * Update hot-reloader createEntrypoints * Make sure .env is loaded before next.config.js * Add tests for all separate .env files * Remove comments * Add override tests * Add test for overriding env vars based on local environment * Add support for .env.test * Apply suggestions from code review Co-Authored-By: Joe Haddad <joe.haddad@zeit.co> * Use chalk for env loaded message * Remove constant as it’s not needed * Update test * Update errsh, taskr, and CNA template ignores * Make sure to only consider undefined missing * Remove old .env ignore * Update to not populate process.env with loaded env * Add experimental flag and add loading of global env values Co-authored-by: Tim Neutkens <timneutkens@me.com> Co-authored-by: Joe Haddad <joe.haddad@zeit.co>
This commit is contained in:
parent
a391d328ae
commit
d8155b22ff
42 changed files with 1103 additions and 10 deletions
28
errors/missing-env-value.md
Normal file
28
errors/missing-env-value.md
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Missing Env Value
|
||||
|
||||
#### Why This Error Occurred
|
||||
|
||||
One of your pages' config requested an env value that wasn't populated.
|
||||
|
||||
```js
|
||||
// pages/index.js
|
||||
export const config = {
|
||||
// this value isn't provided in `.env`
|
||||
env: ['MISSING_KEY'],
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
// .env (notice no `MISSING_KEY` provided here)
|
||||
NOTION_KEY='...'
|
||||
```
|
||||
|
||||
#### Possible Ways to Fix It
|
||||
|
||||
Either remove the requested env value from the page's config, populate it in your `.env` file, or manually populate it in your environment before running `next dev` or `next build`.
|
||||
|
||||
### Useful Links
|
||||
|
||||
- [dotenv](https://npmjs.com/package/dotenv)
|
||||
- [dotenv-expand](https://npmjs.com/package/dotenv-expand)
|
||||
- [Environment Variables](https://en.wikipedia.org/wiki/Environment_variable)
|
|
@ -17,9 +17,14 @@
|
|||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env*
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
|
|
@ -71,6 +71,7 @@ import {
|
|||
} from './utils'
|
||||
import getBaseWebpackConfig from './webpack-config'
|
||||
import { writeBuildId } from './write-build-id'
|
||||
import { loadEnvConfig } from '../lib/load-env-config'
|
||||
|
||||
const fsAccess = promisify(fs.access)
|
||||
const fsUnlink = promisify(fs.unlink)
|
||||
|
@ -110,6 +111,9 @@ export default async function build(dir: string, conf = null): Promise<void> {
|
|||
)
|
||||
}
|
||||
|
||||
// attempt to load global env values so they are available in next.config.js
|
||||
loadEnvConfig(dir)
|
||||
|
||||
const config = loadConfig(PHASE_PRODUCTION_BUILD, dir, conf)
|
||||
const { target } = config
|
||||
const buildId = await generateBuildId(config.generateBuildId, nanoid)
|
||||
|
|
|
@ -707,6 +707,17 @@ export default async function getBaseWebpackConfig(
|
|||
// This plugin makes sure `output.filename` is used for entry chunks
|
||||
new ChunkNamesPlugin(),
|
||||
new webpack.DefinePlugin({
|
||||
...(config.experimental.pageEnv
|
||||
? Object.keys(process.env).reduce(
|
||||
(prev: { [key: string]: string }, key: string) => {
|
||||
if (key.startsWith('NEXT_APP_')) {
|
||||
prev[key] = process.env[key]!
|
||||
}
|
||||
return prev
|
||||
},
|
||||
{}
|
||||
)
|
||||
: {}),
|
||||
...Object.keys(config.env).reduce((acc, key) => {
|
||||
if (/^(?:NODE_.+)|^(?:__.+)$/i.test(key)) {
|
||||
throw new Error(
|
||||
|
|
|
@ -181,6 +181,7 @@ const nextServerlessLoader: loader.Loader = function() {
|
|||
Object.assign({}, parsedUrl.query, params ),
|
||||
resolver,
|
||||
${encodedPreviewProps},
|
||||
process.env,
|
||||
onError
|
||||
)
|
||||
} catch (err) {
|
||||
|
@ -257,6 +258,7 @@ const nextServerlessLoader: loader.Loader = function() {
|
|||
assetPrefix: "${assetPrefix}",
|
||||
runtimeConfig: runtimeConfig.publicRuntimeConfig || {},
|
||||
previewProps: ${encodedPreviewProps},
|
||||
env: process.env,
|
||||
..._renderOpts
|
||||
}
|
||||
let _nextData = false
|
||||
|
|
|
@ -35,6 +35,7 @@ import loadConfig, {
|
|||
import { eventCliSession } from '../telemetry/events'
|
||||
import { Telemetry } from '../telemetry/storage'
|
||||
import { normalizePagePath } from '../next-server/server/normalize-page-path'
|
||||
import { loadEnvConfig } from '../lib/load-env-config'
|
||||
|
||||
const copyFile = promisify(copyFileOrig)
|
||||
const mkdir = promisify(mkdirOrig)
|
||||
|
@ -230,6 +231,7 @@ export default async function(
|
|||
dir,
|
||||
buildId,
|
||||
nextExport: true,
|
||||
env: loadEnvConfig(dir),
|
||||
assetPrefix: nextConfig.assetPrefix.replace(/\/$/, ''),
|
||||
distDir,
|
||||
dev: false,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
const existsSync = (f: string): boolean => {
|
||||
export const existsSync = (f: string): boolean => {
|
||||
try {
|
||||
fs.accessSync(f, fs.constants.F_OK)
|
||||
return true
|
||||
|
|
84
packages/next/lib/load-env-config.ts
Normal file
84
packages/next/lib/load-env-config.ts
Normal file
|
@ -0,0 +1,84 @@
|
|||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import chalk from 'chalk'
|
||||
import dotenvExpand from 'next/dist/compiled/dotenv-expand'
|
||||
import dotenv, { DotenvConfigOutput } from 'next/dist/compiled/dotenv'
|
||||
import findUp from 'find-up'
|
||||
|
||||
export type Env = { [key: string]: string }
|
||||
|
||||
export function loadEnvConfig(dir: string, dev?: boolean): Env | false {
|
||||
const packageJson = findUp.sync('package.json', { cwd: dir })
|
||||
|
||||
// only do new env loading if dotenv isn't installed since we
|
||||
// can't check for an experimental flag in next.config.js
|
||||
// since we want to load the env before loading next.config.js
|
||||
if (packageJson) {
|
||||
const { dependencies, devDependencies } = require(packageJson)
|
||||
const allPackages = Object.keys({
|
||||
...dependencies,
|
||||
...devDependencies,
|
||||
})
|
||||
|
||||
if (allPackages.some(pkg => pkg === 'dotenv')) {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
// we should always have a package.json but disable in case we don't
|
||||
return false
|
||||
}
|
||||
|
||||
const isTest = process.env.NODE_ENV === 'test'
|
||||
const mode = isTest ? 'test' : dev ? 'development' : 'production'
|
||||
const dotenvFiles = [
|
||||
`.env.${mode}.local`,
|
||||
`.env.${mode}`,
|
||||
// Don't include `.env.local` for `test` environment
|
||||
// since normally you expect tests to produce the same
|
||||
// results for everyone
|
||||
mode !== 'test' && `.env.local`,
|
||||
'.env',
|
||||
].filter(Boolean) as string[]
|
||||
|
||||
const combinedEnv: Env = {
|
||||
...(process.env as any),
|
||||
}
|
||||
|
||||
for (const envFile of dotenvFiles) {
|
||||
// only load .env if the user provided has an env config file
|
||||
const dotEnvPath = path.join(dir, envFile)
|
||||
|
||||
try {
|
||||
const contents = fs.readFileSync(dotEnvPath, 'utf8')
|
||||
let result: DotenvConfigOutput = {}
|
||||
result.parsed = dotenv.parse(contents)
|
||||
|
||||
result = dotenvExpand(result)
|
||||
|
||||
if (result.parsed) {
|
||||
console.log(`> ${chalk.cyan.bold('Info:')} Loaded env from ${envFile}`)
|
||||
}
|
||||
|
||||
Object.assign(combinedEnv, result.parsed)
|
||||
} catch (err) {
|
||||
if (err.code !== 'ENOENT') {
|
||||
console.log(
|
||||
`> ${chalk.cyan.bold('Error: ')} Failed to load env from ${envFile}`,
|
||||
err
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// load global env values prefixed with `NEXT_APP_` to process.env
|
||||
for (const key of Object.keys(combinedEnv)) {
|
||||
if (
|
||||
key.startsWith('NEXT_APP_') &&
|
||||
typeof process.env[key] === 'undefined'
|
||||
) {
|
||||
process.env[key] = combinedEnv[key]
|
||||
}
|
||||
}
|
||||
|
||||
return combinedEnv
|
||||
}
|
|
@ -4,6 +4,7 @@ import { ComponentType } from 'react'
|
|||
import { format, URLFormatOptions, UrlObject } from 'url'
|
||||
import { ManifestItem } from '../server/load-components'
|
||||
import { NextRouter } from './router/router'
|
||||
import { Env } from '../../lib/load-env-config'
|
||||
|
||||
/**
|
||||
* Types used by both next and next-server
|
||||
|
@ -186,6 +187,8 @@ export type NextApiRequest = IncomingMessage & {
|
|||
}
|
||||
|
||||
body: any
|
||||
|
||||
env: Env
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,6 +8,8 @@ import { isResSent, NextApiRequest, NextApiResponse } from '../lib/utils'
|
|||
import { decryptWithSecret, encryptWithSecret } from './crypto-utils'
|
||||
import { interopDefault } from './load-components'
|
||||
import { Params } from './router'
|
||||
import { collectEnv } from './utils'
|
||||
import { Env } from '../../lib/load-env-config'
|
||||
|
||||
export type NextApiRequestCookies = { [key: string]: string }
|
||||
export type NextApiRequestQuery = { [key: string]: string | string[] }
|
||||
|
@ -24,26 +26,23 @@ export async function apiResolver(
|
|||
params: any,
|
||||
resolverModule: any,
|
||||
apiContext: __ApiPreviewProps,
|
||||
env: Env | false,
|
||||
onError?: ({ err }: { err: any }) => Promise<void>
|
||||
) {
|
||||
const apiReq = req as NextApiRequest
|
||||
const apiRes = res as NextApiResponse
|
||||
|
||||
try {
|
||||
let config: PageConfig = {}
|
||||
let bodyParser = true
|
||||
if (!resolverModule) {
|
||||
res.statusCode = 404
|
||||
res.end('Not Found')
|
||||
return
|
||||
}
|
||||
const config: PageConfig = resolverModule.config || {}
|
||||
const bodyParser = config.api?.bodyParser !== false
|
||||
|
||||
apiReq.env = env ? collectEnv(req.url!, env, config.env) : {}
|
||||
|
||||
if (resolverModule.config) {
|
||||
config = resolverModule.config
|
||||
if (config.api && config.api.bodyParser === false) {
|
||||
bodyParser = false
|
||||
}
|
||||
}
|
||||
// Parsing of cookies
|
||||
setLazyProp({ req: apiReq }, 'cookies', getCookieParser(req))
|
||||
// Parsing query string
|
||||
|
|
|
@ -54,6 +54,7 @@ const defaultConfig: { [key: string]: any } = {
|
|||
workerThreads: false,
|
||||
basePath: '',
|
||||
sassOptions: {},
|
||||
pageEnv: false,
|
||||
},
|
||||
future: {
|
||||
excludeDefaultMomentLocales: false,
|
||||
|
|
|
@ -61,6 +61,7 @@ import {
|
|||
setSprCache,
|
||||
} from './spr-cache'
|
||||
import { isBlockedPage } from './utils'
|
||||
import { loadEnvConfig, Env } from '../../lib/load-env-config'
|
||||
|
||||
const getCustomRouteMatcher = pathMatch(true)
|
||||
|
||||
|
@ -117,6 +118,7 @@ export default class Server {
|
|||
documentMiddlewareEnabled: boolean
|
||||
hasCssMode: boolean
|
||||
dev?: boolean
|
||||
env: Env | false
|
||||
previewProps: __ApiPreviewProps
|
||||
customServer?: boolean
|
||||
ampOptimizerConfig?: { [key: string]: any }
|
||||
|
@ -145,6 +147,8 @@ export default class Server {
|
|||
this.dir = resolve(dir)
|
||||
this.quiet = quiet
|
||||
const phase = this.currentPhase()
|
||||
const env = loadEnvConfig(this.dir, dev)
|
||||
|
||||
this.nextConfig = loadConfig(phase, this.dir, conf)
|
||||
this.distDir = join(this.dir, this.nextConfig.distDir)
|
||||
this.publicDir = join(this.dir, CLIENT_PUBLIC_FILES_PATH)
|
||||
|
@ -171,6 +175,7 @@ export default class Server {
|
|||
staticMarkup,
|
||||
buildId: this.buildId,
|
||||
generateEtags,
|
||||
env: this.nextConfig.experimental.pageEnv && env,
|
||||
previewProps: this.getPreviewProps(),
|
||||
customServer: customServer === true ? true : undefined,
|
||||
ampOptimizerConfig: this.nextConfig.experimental.amp?.optimizer,
|
||||
|
@ -684,6 +689,7 @@ export default class Server {
|
|||
query,
|
||||
pageModule,
|
||||
this.renderOpts.previewProps,
|
||||
this.renderOpts.env,
|
||||
this.onErrorMiddleware
|
||||
)
|
||||
return true
|
||||
|
|
|
@ -38,6 +38,8 @@ import { tryGetPreviewData, __ApiPreviewProps } from './api-utils'
|
|||
import { getPageFiles } from './get-page-files'
|
||||
import { LoadComponentsReturnType, ManifestItem } from './load-components'
|
||||
import optimizeAmp from './optimize-amp'
|
||||
import { collectEnv } from './utils'
|
||||
import { Env } from '../../lib/load-env-config'
|
||||
import { UnwrapPromise } from '../../lib/coalesced-function'
|
||||
import { GetStaticProps, GetServerSideProps } from '../../types'
|
||||
|
||||
|
@ -154,6 +156,7 @@ export type RenderOptsPartial = {
|
|||
isDataReq?: boolean
|
||||
params?: ParsedUrlQuery
|
||||
previewProps: __ApiPreviewProps
|
||||
env: Env | false
|
||||
}
|
||||
|
||||
export type RenderOpts = LoadComponentsReturnType & RenderOptsPartial
|
||||
|
@ -288,6 +291,7 @@ export async function renderToHTML(
|
|||
staticMarkup = false,
|
||||
ampPath = '',
|
||||
App,
|
||||
env = {},
|
||||
Document,
|
||||
pageConfig = {},
|
||||
DocumentMiddleware,
|
||||
|
@ -303,6 +307,8 @@ export async function renderToHTML(
|
|||
previewProps,
|
||||
} = renderOpts
|
||||
|
||||
const curEnv = env ? collectEnv(pathname, env, pageConfig.env) : {}
|
||||
|
||||
const callMiddleware = async (method: string, args: any[], props = false) => {
|
||||
let results: any = props ? {} : []
|
||||
|
||||
|
@ -503,6 +509,7 @@ export async function renderToHTML(
|
|||
|
||||
try {
|
||||
data = await getStaticProps!({
|
||||
env: curEnv,
|
||||
...(pageIsDynamic ? { params: query as ParsedUrlQuery } : undefined),
|
||||
...(previewData !== false
|
||||
? { preview: true, previewData: previewData }
|
||||
|
@ -585,6 +592,7 @@ export async function renderToHTML(
|
|||
req,
|
||||
res,
|
||||
query,
|
||||
env: curEnv,
|
||||
...(pageIsDynamic ? { params: params as ParsedUrlQuery } : undefined),
|
||||
...(previewData !== false
|
||||
? { preview: true, previewData: previewData }
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { BLOCKED_PAGES } from '../lib/constants'
|
||||
import { Env } from '../../lib/load-env-config'
|
||||
|
||||
export function isBlockedPage(pathname: string): boolean {
|
||||
return BLOCKED_PAGES.indexOf(pathname) !== -1
|
||||
|
@ -14,3 +15,28 @@ export function cleanAmpPath(pathname: string): string {
|
|||
pathname = pathname.replace(/\?$/, '')
|
||||
return pathname
|
||||
}
|
||||
|
||||
export function collectEnv(page: string, env: Env, pageEnv?: string[]): Env {
|
||||
const missingEnvKeys = new Set()
|
||||
const collected = pageEnv
|
||||
? pageEnv.reduce((prev: Env, key): Env => {
|
||||
if (typeof env[key] !== 'undefined') {
|
||||
prev[key] = env[key]!
|
||||
} else {
|
||||
missingEnvKeys.add(key)
|
||||
}
|
||||
return prev
|
||||
}, {})
|
||||
: {}
|
||||
|
||||
if (missingEnvKeys.size > 0) {
|
||||
console.warn(
|
||||
`Missing env value${missingEnvKeys.size === 1 ? '' : 's'}: ${[
|
||||
...missingEnvKeys,
|
||||
].join(', ')} for ${page}.\n` +
|
||||
`Make sure to supply this value in either your .env file or in your environment.\n` +
|
||||
`See here for more info: https://err.sh/next.js/missing-env-value`
|
||||
)
|
||||
}
|
||||
return collected
|
||||
}
|
||||
|
|
|
@ -166,6 +166,7 @@
|
|||
"@types/content-type": "1.1.3",
|
||||
"@types/cookie": "0.3.2",
|
||||
"@types/cross-spawn": "6.0.0",
|
||||
"@types/dotenv": "8.2.0",
|
||||
"@types/etag": "1.8.0",
|
||||
"@types/find-up": "2.1.1",
|
||||
"@types/fresh": "0.5.0",
|
||||
|
@ -191,6 +192,8 @@
|
|||
"arg": "4.1.0",
|
||||
"ast-types": "0.13.2",
|
||||
"babel-plugin-dynamic-import-node": "2.3.0",
|
||||
"dotenv": "8.2.0",
|
||||
"dotenv-expand": "5.1.0",
|
||||
"nanoid": "2.0.3",
|
||||
"resolve": "1.11.0",
|
||||
"taskr": "1.1.0",
|
||||
|
|
|
@ -40,6 +40,11 @@ function writePackageManifest(packageName) {
|
|||
|
||||
let typesFile = types || typings
|
||||
if (typesFile) {
|
||||
// if they provide a types directory resolve it
|
||||
if (extname(typesFile) === '') {
|
||||
typesFile = join(typesFile, 'index.d.ts')
|
||||
}
|
||||
|
||||
typesFile = require.resolve(join(packageName, typesFile))
|
||||
} else {
|
||||
try {
|
||||
|
|
|
@ -41,6 +41,22 @@ export async function ncc_text_table(task, opts) {
|
|||
.target('dist/compiled/text-table')
|
||||
}
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function ncc_dotenv(task, opts) {
|
||||
await task
|
||||
.source(opts.src || relative(__dirname, require.resolve('dotenv')))
|
||||
.ncc({ packageName: 'dotenv' })
|
||||
.target('dist/compiled/dotenv')
|
||||
}
|
||||
|
||||
// eslint-disable-next-line camelcase
|
||||
export async function ncc_dotenv_expand(task, opts) {
|
||||
await task
|
||||
.source(opts.src || relative(__dirname, require.resolve('dotenv-expand')))
|
||||
.ncc({ packageName: 'dotenv-expand' })
|
||||
.target('dist/compiled/dotenv-expand')
|
||||
}
|
||||
|
||||
export async function precompile(task) {
|
||||
await task.parallel([
|
||||
'ncc_unistore',
|
||||
|
@ -48,6 +64,8 @@ export async function precompile(task) {
|
|||
'ncc_arg',
|
||||
'ncc_nanoid',
|
||||
'ncc_text_table',
|
||||
'ncc_dotenv',
|
||||
'ncc_dotenv_expand',
|
||||
])
|
||||
}
|
||||
|
||||
|
|
4
packages/next/types/index.d.ts
vendored
4
packages/next/types/index.d.ts
vendored
|
@ -5,6 +5,7 @@
|
|||
import React from 'react'
|
||||
import { ParsedUrlQuery } from 'querystring'
|
||||
import { IncomingMessage, ServerResponse } from 'http'
|
||||
import { Env } from '../lib/load-env-config'
|
||||
|
||||
import {
|
||||
NextPageContext,
|
||||
|
@ -54,6 +55,7 @@ export type PageConfig = {
|
|||
*/
|
||||
bodyParser?: { sizeLimit?: number | string } | false
|
||||
}
|
||||
env?: Array<string>
|
||||
}
|
||||
|
||||
export {
|
||||
|
@ -70,6 +72,7 @@ export type GetStaticProps<
|
|||
params?: ParsedUrlQuery
|
||||
preview?: boolean
|
||||
previewData?: any
|
||||
env: Env
|
||||
}) => Promise<{
|
||||
props: P
|
||||
revalidate?: number | boolean
|
||||
|
@ -87,6 +90,7 @@ export type GetServerSideProps<
|
|||
res: ServerResponse
|
||||
params?: ParsedUrlQuery
|
||||
query: ParsedUrlQuery
|
||||
env: Env
|
||||
preview?: boolean
|
||||
previewData?: any
|
||||
}) => Promise<{ props: P }>
|
||||
|
|
6
packages/next/types/misc.d.ts
vendored
6
packages/next/types/misc.d.ts
vendored
|
@ -43,6 +43,12 @@ declare module 'next/dist/compiled/text-table' {
|
|||
export = textTable
|
||||
}
|
||||
|
||||
declare module 'next/dist/compiled/dotenv' {
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
export = dotenv
|
||||
}
|
||||
|
||||
declare module 'next/dist/compiled/arg/index.js' {
|
||||
function arg<T extends arg.Spec>(
|
||||
spec: T,
|
||||
|
|
3
test/integration/env-config-errors/app/package.json
Normal file
3
test/integration/env-config-errors/app/package.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"name": "env-config-errors"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export const config = {
|
||||
env: ['NOTION_KEY', 'SENTRY_DSN', 'DATABASE_KEY', 'DATABASE_USER'],
|
||||
}
|
||||
|
||||
export default (req, res) => res.json(req.env)
|
14
test/integration/env-config-errors/app/pages/index.js
Normal file
14
test/integration/env-config-errors/app/pages/index.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
export const config = {
|
||||
env: ['NOTION_KEY', 'SENTRY_DSN', 'DATABASE_KEY', 'DATABASE_USER'],
|
||||
}
|
||||
|
||||
export async function getStaticProps({ env }) {
|
||||
return {
|
||||
// Do not pass any sensitive values here as they will
|
||||
// be made PUBLICLY available in `pageProps`
|
||||
props: { env },
|
||||
revalidate: 1,
|
||||
}
|
||||
}
|
||||
|
||||
export default ({ env }) => <p>{JSON.stringify(env)}</p>
|
13
test/integration/env-config-errors/app/pages/ssp.js
Normal file
13
test/integration/env-config-errors/app/pages/ssp.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
export const config = {
|
||||
env: ['NOTION_KEY', 'SENTRY_DSN', 'DATABASE_KEY', 'DATABASE_USER'],
|
||||
}
|
||||
|
||||
export async function getServerSideProps({ env }) {
|
||||
return {
|
||||
// Do not pass any sensitive values here as they will
|
||||
// be made PUBLICLY available in `pageProps`
|
||||
props: { env },
|
||||
}
|
||||
}
|
||||
|
||||
export default ({ env }) => <p>{JSON.stringify(env)}</p>
|
150
test/integration/env-config-errors/test/index.test.js
Normal file
150
test/integration/env-config-errors/test/index.test.js
Normal file
|
@ -0,0 +1,150 @@
|
|||
/* eslint-env jest */
|
||||
/* global jasmine */
|
||||
import fs from 'fs-extra'
|
||||
import { join } from 'path'
|
||||
import {
|
||||
nextBuild,
|
||||
findPort,
|
||||
launchApp,
|
||||
killApp,
|
||||
nextStart,
|
||||
renderViaHTTP,
|
||||
} from 'next-test-utils'
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 2
|
||||
|
||||
const appDir = join(__dirname, '../app')
|
||||
const envFile = join(appDir, '.env')
|
||||
const nextConfig = join(appDir, 'next.config.js')
|
||||
const nextConfigContent = `
|
||||
experimental: {
|
||||
pageEnv: true
|
||||
}
|
||||
`
|
||||
|
||||
let app
|
||||
let appPort
|
||||
let output = ''
|
||||
|
||||
const envValues = ['NOTION_KEY', 'SENTRY_DSN', 'DATABASE_KEY', 'DATABASE_USER']
|
||||
|
||||
const writeEnv = () =>
|
||||
fs.writeFile(envFile, envValues.map(val => `${val}=value`).join('\n'))
|
||||
const rmEnv = () => fs.remove(envFile)
|
||||
|
||||
const runTests = (isDev = false) => {
|
||||
const startApp = async () => {
|
||||
output = ''
|
||||
appPort = await findPort()
|
||||
let method = isDev ? launchApp : nextStart
|
||||
|
||||
app = await method(appDir, appPort, {
|
||||
onStdout(msg) {
|
||||
output += msg
|
||||
},
|
||||
onStderr(msg) {
|
||||
output += msg
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (isDev) {
|
||||
it('should warn for missing values on SSG page', async () => {
|
||||
await startApp()
|
||||
await renderViaHTTP(appPort, '/')
|
||||
await killApp(app)
|
||||
expect(output).toContain(
|
||||
`Missing env values: ${envValues.join(', ')} for /`
|
||||
)
|
||||
})
|
||||
|
||||
it('should not warn for missing values on SSG page', async () => {
|
||||
await writeEnv()
|
||||
await startApp()
|
||||
await renderViaHTTP(appPort, '/')
|
||||
await killApp(app)
|
||||
await rmEnv()
|
||||
expect(output).not.toContain(
|
||||
`Missing env values: ${envValues.join(', ')} for /`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
it('should warn for missing values on server props page', async () => {
|
||||
await startApp()
|
||||
await renderViaHTTP(appPort, '/ssp')
|
||||
await killApp(app)
|
||||
expect(output).toContain(
|
||||
`Missing env values: ${envValues.join(', ')} for /ssp`
|
||||
)
|
||||
})
|
||||
|
||||
it('should not warn for missing values on server props page', async () => {
|
||||
await writeEnv()
|
||||
await startApp()
|
||||
await renderViaHTTP(appPort, '/ssp')
|
||||
await killApp(app)
|
||||
await rmEnv()
|
||||
expect(output).not.toContain(
|
||||
`Missing env values: ${envValues.join(', ')} for /ssp`
|
||||
)
|
||||
})
|
||||
|
||||
it('should warn for missing values on API route', async () => {
|
||||
await startApp()
|
||||
await renderViaHTTP(appPort, '/api/hello')
|
||||
await killApp(app)
|
||||
expect(output).toContain(
|
||||
`Missing env values: ${envValues.join(', ')} for /api/hello`
|
||||
)
|
||||
})
|
||||
|
||||
it('should not warn for missing values on API route', async () => {
|
||||
await writeEnv()
|
||||
await startApp()
|
||||
await renderViaHTTP(appPort, '/api/hello')
|
||||
await killApp(app)
|
||||
await rmEnv()
|
||||
expect(output).not.toContain(
|
||||
`Missing env values: ${envValues.join(', ')} for /api/hello`
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
describe('Env Config', () => {
|
||||
afterEach(async () => {
|
||||
await fs.remove(envFile)
|
||||
try {
|
||||
await killApp(app)
|
||||
} catch (_) {}
|
||||
})
|
||||
afterAll(() => fs.remove(nextConfig))
|
||||
|
||||
describe('dev mode', () => {
|
||||
beforeAll(() =>
|
||||
fs.writeFile(nextConfig, `module.exports = { ${nextConfigContent} }`)
|
||||
)
|
||||
runTests(true)
|
||||
})
|
||||
|
||||
describe('server mode', () => {
|
||||
beforeAll(async () => {
|
||||
beforeAll(() =>
|
||||
fs.writeFile(nextConfig, `module.exports = { ${nextConfigContent} }`)
|
||||
)
|
||||
await nextBuild(appDir)
|
||||
})
|
||||
runTests()
|
||||
})
|
||||
|
||||
describe('serverless mode', () => {
|
||||
beforeAll(async () => {
|
||||
await fs.writeFile(
|
||||
nextConfig,
|
||||
`module.exports = { target: 'experimental-serverless-trace', ${nextConfigContent} }`
|
||||
)
|
||||
await nextBuild(appDir)
|
||||
})
|
||||
runTests()
|
||||
})
|
||||
})
|
10
test/integration/env-config/app/.env
Normal file
10
test/integration/env-config/app/.env
Normal file
|
@ -0,0 +1,10 @@
|
|||
PROCESS_ENV_KEY=env
|
||||
ENV_FILE_KEY=env
|
||||
ENV_FILE_LOCAL_OVERRIDE_TEST=env
|
||||
ENV_FILE_DEVELOPMENT_OVERRIDE_TEST=env
|
||||
ENV_FILE_DEVELOPMENT_LOCAL_OVERRIDEOVERRIDE_TEST=env
|
||||
ENV_FILE_PRODUCTION_OVERRIDEOVERRIDE_TEST=env
|
||||
ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST=env
|
||||
ENV_FILE_TEST_OVERRIDE_TEST=env
|
||||
ENV_FILE_TEST_LOCAL_OVERRIDEOVERRIDE_TEST=env
|
||||
NEXT_APP_TEST_DEST=another
|
3
test/integration/env-config/app/.env.development
Normal file
3
test/integration/env-config/app/.env.development
Normal file
|
@ -0,0 +1,3 @@
|
|||
DEVELOPMENT_ENV_FILE_KEY=development
|
||||
ENV_FILE_DEVELOPMENT_OVERRIDE_TEST=development
|
||||
ENV_FILE_DEVELOPMENT_LOCAL_OVERRIDEOVERRIDE_TEST=development
|
2
test/integration/env-config/app/.env.development.local
Normal file
2
test/integration/env-config/app/.env.development.local
Normal file
|
@ -0,0 +1,2 @@
|
|||
LOCAL_DEVELOPMENT_ENV_FILE_KEY=localdevelopment
|
||||
ENV_FILE_DEVELOPMENT_LOCAL_OVERRIDEOVERRIDE_TEST=localdevelopment
|
2
test/integration/env-config/app/.env.local
Normal file
2
test/integration/env-config/app/.env.local
Normal file
|
@ -0,0 +1,2 @@
|
|||
LOCAL_ENV_FILE_KEY=localenv
|
||||
ENV_FILE_LOCAL_OVERRIDE_TEST=localenv
|
3
test/integration/env-config/app/.env.production
Normal file
3
test/integration/env-config/app/.env.production
Normal file
|
@ -0,0 +1,3 @@
|
|||
PRODUCTION_ENV_FILE_KEY=production
|
||||
ENV_FILE_PRODUCTION_OVERRIDEOVERRIDE_TEST=production
|
||||
ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST=production
|
2
test/integration/env-config/app/.env.production.local
Normal file
2
test/integration/env-config/app/.env.production.local
Normal file
|
@ -0,0 +1,2 @@
|
|||
LOCAL_PRODUCTION_ENV_FILE_KEY=localproduction
|
||||
ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST=localproduction
|
3
test/integration/env-config/app/.env.test
Normal file
3
test/integration/env-config/app/.env.test
Normal file
|
@ -0,0 +1,3 @@
|
|||
TEST_ENV_FILE_KEY=test
|
||||
ENV_FILE_TEST_OVERRIDE_TEST=test
|
||||
ENV_FILE_TEST_LOCAL_OVERRIDEOVERRIDE_TEST=test
|
2
test/integration/env-config/app/.env.test.local
Normal file
2
test/integration/env-config/app/.env.test.local
Normal file
|
@ -0,0 +1,2 @@
|
|||
LOCAL_TEST_ENV_FILE_KEY=localtest
|
||||
ENV_FILE_TEST_LOCAL_OVERRIDEOVERRIDE_TEST=localtest
|
4
test/integration/env-config/app/package.json
Normal file
4
test/integration/env-config/app/package.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "env-config",
|
||||
"dependencies": {}
|
||||
}
|
25
test/integration/env-config/app/pages/api/all.js
Normal file
25
test/integration/env-config/app/pages/api/all.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
export const config = {
|
||||
env: [
|
||||
'PROCESS_ENV_KEY',
|
||||
'ENV_FILE_KEY',
|
||||
'LOCAL_ENV_FILE_KEY',
|
||||
'ENV_FILE_LOCAL_OVERRIDE_TEST',
|
||||
'PRODUCTION_ENV_FILE_KEY',
|
||||
'LOCAL_PRODUCTION_ENV_FILE_KEY',
|
||||
'DEVELOPMENT_ENV_FILE_KEY',
|
||||
'LOCAL_DEVELOPMENT_ENV_FILE_KEY',
|
||||
'ENV_FILE_DEVELOPMENT_OVERRIDE_TEST',
|
||||
'ENV_FILE_DEVELOPMENT_LOCAL_OVERRIDEOVERRIDE_TEST',
|
||||
'ENV_FILE_PRODUCTION_OVERRIDEOVERRIDE_TEST',
|
||||
'ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST',
|
||||
'TEST_ENV_FILE_KEY',
|
||||
'LOCAL_TEST_ENV_FILE_KEY',
|
||||
'ENV_FILE_TEST_OVERRIDE_TEST',
|
||||
'ENV_FILE_TEST_LOCAL_OVERRIDEOVERRIDE_TEST',
|
||||
],
|
||||
}
|
||||
|
||||
export default async (req, res) => {
|
||||
// Only for testing, don't do this...
|
||||
res.json(req.env)
|
||||
}
|
1
test/integration/env-config/app/pages/global.js
Normal file
1
test/integration/env-config/app/pages/global.js
Normal file
|
@ -0,0 +1 @@
|
|||
export default () => <p>{process.env.NEXT_APP_TEST_DEST}</p>
|
31
test/integration/env-config/app/pages/index.js
Normal file
31
test/integration/env-config/app/pages/index.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
export const config = {
|
||||
env: [
|
||||
'PROCESS_ENV_KEY',
|
||||
'ENV_FILE_KEY',
|
||||
'LOCAL_ENV_FILE_KEY',
|
||||
'ENV_FILE_LOCAL_OVERRIDE_TEST',
|
||||
'PRODUCTION_ENV_FILE_KEY',
|
||||
'LOCAL_PRODUCTION_ENV_FILE_KEY',
|
||||
'DEVELOPMENT_ENV_FILE_KEY',
|
||||
'LOCAL_DEVELOPMENT_ENV_FILE_KEY',
|
||||
'ENV_FILE_DEVELOPMENT_OVERRIDE_TEST',
|
||||
'ENV_FILE_DEVELOPMENT_LOCAL_OVERRIDEOVERRIDE_TEST',
|
||||
'ENV_FILE_PRODUCTION_OVERRIDEOVERRIDE_TEST',
|
||||
'ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST',
|
||||
'TEST_ENV_FILE_KEY',
|
||||
'LOCAL_TEST_ENV_FILE_KEY',
|
||||
'ENV_FILE_TEST_OVERRIDE_TEST',
|
||||
'ENV_FILE_TEST_LOCAL_OVERRIDEOVERRIDE_TEST',
|
||||
],
|
||||
}
|
||||
|
||||
export async function getStaticProps({ env }) {
|
||||
return {
|
||||
// Do not pass any sensitive values here as they will
|
||||
// be made PUBLICLY available in `pageProps`
|
||||
props: { env },
|
||||
revalidate: 1,
|
||||
}
|
||||
}
|
||||
|
||||
export default ({ env }) => <p>{JSON.stringify(env)}</p>
|
|
@ -0,0 +1,16 @@
|
|||
export default () => (
|
||||
<p>
|
||||
{JSON.stringify({
|
||||
LOCAL_ENV_FILE_KEY: process.env.NC_LOCAL_ENV_FILE_KEY,
|
||||
ENV_FILE_KEY: process.env.NC_ENV_FILE_KEY,
|
||||
PRODUCTION_ENV_FILE_KEY: process.env.NC_PRODUCTION_ENV_FILE_KEY,
|
||||
LOCAL_PRODUCTION_ENV_FILE_KEY:
|
||||
process.env.NC_LOCAL_PRODUCTION_ENV_FILE_KEY,
|
||||
DEVELOPMENT_ENV_FILE_KEY: process.env.NC_DEVELOPMENT_ENV_FILE_KEY,
|
||||
TEST_ENV_FILE_KEY: process.env.NC_TEST_ENV_FILE_KEY,
|
||||
LOCAL_TEST_ENV_FILE_KEY: process.env.NC_LOCAL_TEST_ENV_FILE_KEY,
|
||||
LOCAL_DEVELOPMENT_ENV_FILE_KEY:
|
||||
process.env.NC_LOCAL_DEVELOPMENT_ENV_FILE_KEY,
|
||||
})}
|
||||
</p>
|
||||
)
|
31
test/integration/env-config/app/pages/some-ssg.js
Normal file
31
test/integration/env-config/app/pages/some-ssg.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
export const config = {
|
||||
env: [
|
||||
'PROCESS_ENV_KEY',
|
||||
'ENV_FILE_KEY',
|
||||
'LOCAL_ENV_FILE_KEY',
|
||||
'ENV_FILE_LOCAL_OVERRIDE_TEST',
|
||||
'PRODUCTION_ENV_FILE_KEY',
|
||||
'LOCAL_PRODUCTION_ENV_FILE_KEY',
|
||||
'DEVELOPMENT_ENV_FILE_KEY',
|
||||
'LOCAL_DEVELOPMENT_ENV_FILE_KEY',
|
||||
'ENV_FILE_DEVELOPMENT_OVERRIDE_TEST',
|
||||
'ENV_FILE_DEVELOPMENT_LOCAL_OVERRIDEOVERRIDE_TEST',
|
||||
'ENV_FILE_PRODUCTION_OVERRIDEOVERRIDE_TEST',
|
||||
'ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST',
|
||||
'TEST_ENV_FILE_KEY',
|
||||
'LOCAL_TEST_ENV_FILE_KEY',
|
||||
'ENV_FILE_TEST_OVERRIDE_TEST',
|
||||
'ENV_FILE_TEST_LOCAL_OVERRIDEOVERRIDE_TEST',
|
||||
],
|
||||
}
|
||||
|
||||
export async function getStaticProps({ env }) {
|
||||
return {
|
||||
// Do not pass any sensitive values here as they will
|
||||
// be made PUBLICLY available in `pageProps`
|
||||
props: { env },
|
||||
revalidate: 1,
|
||||
}
|
||||
}
|
||||
|
||||
export default ({ env }) => <p>{JSON.stringify(env)}</p>
|
30
test/integration/env-config/app/pages/some-ssp.js
Normal file
30
test/integration/env-config/app/pages/some-ssp.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
export const config = {
|
||||
env: [
|
||||
'PROCESS_ENV_KEY',
|
||||
'ENV_FILE_KEY',
|
||||
'LOCAL_ENV_FILE_KEY',
|
||||
'ENV_FILE_LOCAL_OVERRIDE_TEST',
|
||||
'PRODUCTION_ENV_FILE_KEY',
|
||||
'LOCAL_PRODUCTION_ENV_FILE_KEY',
|
||||
'DEVELOPMENT_ENV_FILE_KEY',
|
||||
'LOCAL_DEVELOPMENT_ENV_FILE_KEY',
|
||||
'ENV_FILE_DEVELOPMENT_OVERRIDE_TEST',
|
||||
'ENV_FILE_DEVELOPMENT_LOCAL_OVERRIDEOVERRIDE_TEST',
|
||||
'ENV_FILE_PRODUCTION_OVERRIDEOVERRIDE_TEST',
|
||||
'ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST',
|
||||
'TEST_ENV_FILE_KEY',
|
||||
'LOCAL_TEST_ENV_FILE_KEY',
|
||||
'ENV_FILE_TEST_OVERRIDE_TEST',
|
||||
'ENV_FILE_TEST_LOCAL_OVERRIDEOVERRIDE_TEST',
|
||||
],
|
||||
}
|
||||
|
||||
export async function getServerSideProps({ env }) {
|
||||
return {
|
||||
// Do not pass any sensitive values here as they will
|
||||
// be made PUBLICLY available in `pageProps`
|
||||
props: { env },
|
||||
}
|
||||
}
|
||||
|
||||
export default ({ env }) => <p>{JSON.stringify(env)}</p>
|
30
test/integration/env-config/app/pages/ssp.js
Normal file
30
test/integration/env-config/app/pages/ssp.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
export const config = {
|
||||
env: [
|
||||
'PROCESS_ENV_KEY',
|
||||
'ENV_FILE_KEY',
|
||||
'LOCAL_ENV_FILE_KEY',
|
||||
'ENV_FILE_LOCAL_OVERRIDE_TEST',
|
||||
'PRODUCTION_ENV_FILE_KEY',
|
||||
'LOCAL_PRODUCTION_ENV_FILE_KEY',
|
||||
'DEVELOPMENT_ENV_FILE_KEY',
|
||||
'LOCAL_DEVELOPMENT_ENV_FILE_KEY',
|
||||
'ENV_FILE_DEVELOPMENT_OVERRIDE_TEST',
|
||||
'ENV_FILE_DEVELOPMENT_LOCAL_OVERRIDEOVERRIDE_TEST',
|
||||
'ENV_FILE_PRODUCTION_OVERRIDEOVERRIDE_TEST',
|
||||
'ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST',
|
||||
'TEST_ENV_FILE_KEY',
|
||||
'LOCAL_TEST_ENV_FILE_KEY',
|
||||
'ENV_FILE_TEST_OVERRIDE_TEST',
|
||||
'ENV_FILE_TEST_LOCAL_OVERRIDEOVERRIDE_TEST',
|
||||
],
|
||||
}
|
||||
|
||||
export async function getServerSideProps({ env }) {
|
||||
return {
|
||||
// Do not pass any sensitive values here as they will
|
||||
// be made PUBLICLY available in `pageProps`
|
||||
props: { env },
|
||||
}
|
||||
}
|
||||
|
||||
export default ({ env }) => <p>{JSON.stringify(env)}</p>
|
481
test/integration/env-config/test/index.test.js
Normal file
481
test/integration/env-config/test/index.test.js
Normal file
|
@ -0,0 +1,481 @@
|
|||
/* eslint-env jest */
|
||||
/* global jasmine */
|
||||
import url from 'url'
|
||||
import fs from 'fs-extra'
|
||||
import { join } from 'path'
|
||||
import cheerio from 'cheerio'
|
||||
import {
|
||||
nextBuild,
|
||||
nextStart,
|
||||
renderViaHTTP,
|
||||
findPort,
|
||||
launchApp,
|
||||
killApp,
|
||||
fetchViaHTTP,
|
||||
} from 'next-test-utils'
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 2
|
||||
|
||||
let app
|
||||
let appPort
|
||||
const appDir = join(__dirname, '../app')
|
||||
const nextConfig = join(appDir, 'next.config.js')
|
||||
|
||||
const nextConfigContent = `
|
||||
experimental: {
|
||||
pageEnv: true,
|
||||
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
source: '/hello',
|
||||
permanent: false,
|
||||
destination: \`/\${process.env.NEXT_APP_TEST_DEST}\`,
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const getEnvFromHtml = async path => {
|
||||
const html = await renderViaHTTP(appPort, path)
|
||||
return JSON.parse(
|
||||
cheerio
|
||||
.load(html)('p')
|
||||
.text()
|
||||
)
|
||||
}
|
||||
|
||||
const runTests = (isDev, isServerless, isTestEnv) => {
|
||||
// TODO: support runtime overrides in serverless output
|
||||
if (!isServerless) {
|
||||
describe('Process environment', () => {
|
||||
it('should override .env', async () => {
|
||||
const data = await getEnvFromHtml('/')
|
||||
expect(data.PROCESS_ENV_KEY).toEqual('processenvironment')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
it('should provide global env to next.config.js', async () => {
|
||||
const res = await fetchViaHTTP(appPort, '/hello', undefined, {
|
||||
redirect: 'manual',
|
||||
})
|
||||
const { pathname } = url.parse(res.headers.get('location'))
|
||||
|
||||
expect(res.status).toBe(307)
|
||||
expect(pathname).toBe('/another')
|
||||
})
|
||||
|
||||
it('should inline global values during build', async () => {
|
||||
const html = await renderViaHTTP(appPort, '/global')
|
||||
const $ = cheerio.load(html)
|
||||
expect($('p').text()).toContain('another')
|
||||
})
|
||||
|
||||
describe('Loads .env', () => {
|
||||
it('should provide env for SSG', async () => {
|
||||
const data = await getEnvFromHtml('/some-ssg')
|
||||
expect(data.ENV_FILE_KEY).toBe('env')
|
||||
})
|
||||
|
||||
it('should provide env correctly for SSR', async () => {
|
||||
const data = await getEnvFromHtml('/some-ssp')
|
||||
expect(data.ENV_FILE_KEY).toBe('env')
|
||||
})
|
||||
|
||||
it('should provide env correctly for API routes', async () => {
|
||||
const data = await renderViaHTTP(appPort, '/api/all')
|
||||
expect(JSON.parse(data).ENV_FILE_KEY).toEqual('env')
|
||||
})
|
||||
|
||||
// TODO: uncomment once env is provided to next.config.js
|
||||
// it('should provide env correctly through next.config.js', async () => {
|
||||
// const data = await getEnvFromHtml('/next-config-loaded-env')
|
||||
// expect(data.ENV_FILE_KEY).toEqual('env')
|
||||
// })
|
||||
})
|
||||
|
||||
if (!isTestEnv) {
|
||||
describe('Loads .env.local', () => {
|
||||
it('should provide env for SSG', async () => {
|
||||
const data = await getEnvFromHtml('/some-ssg')
|
||||
expect(data.LOCAL_ENV_FILE_KEY).toBe('localenv')
|
||||
})
|
||||
|
||||
it('should provide env correctly for SSR', async () => {
|
||||
const data = await getEnvFromHtml('/some-ssp')
|
||||
expect(data.LOCAL_ENV_FILE_KEY).toBe('localenv')
|
||||
})
|
||||
|
||||
it('should provide env correctly for API routes', async () => {
|
||||
const data = await renderViaHTTP(appPort, '/api/all')
|
||||
expect(JSON.parse(data).LOCAL_ENV_FILE_KEY).toEqual('localenv')
|
||||
})
|
||||
|
||||
// TODO: uncomment once env is provided to next.config.js
|
||||
// it('should provide env correctly through next.config.js', async () => {
|
||||
// const data = await getEnvFromHtml('/next-config-loaded-env')
|
||||
// expect(data.LOCAL_ENV_FILE_KEY).toEqual('localenv')
|
||||
// })
|
||||
|
||||
it('should load env from .env', async () => {
|
||||
const data = await getEnvFromHtml('/')
|
||||
expect(data.ENV_FILE_KEY).toEqual('env')
|
||||
})
|
||||
|
||||
it('should override env from .env', async () => {
|
||||
const data = await getEnvFromHtml('/')
|
||||
expect(data.ENV_FILE_LOCAL_OVERRIDE_TEST).toEqual('localenv')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Loads .env.development', () => {
|
||||
it('should provide env for SSG', async () => {
|
||||
const data = await getEnvFromHtml('/some-ssg')
|
||||
expect(data.DEVELOPMENT_ENV_FILE_KEY).toBe(
|
||||
isDev ? 'development' : undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('should provide env correctly for SSR', async () => {
|
||||
const data = await getEnvFromHtml('/some-ssp')
|
||||
expect(data.DEVELOPMENT_ENV_FILE_KEY).toBe(
|
||||
isDev ? 'development' : undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('should provide env correctly for API routes', async () => {
|
||||
const data = await renderViaHTTP(appPort, '/api/all')
|
||||
expect(JSON.parse(data).DEVELOPMENT_ENV_FILE_KEY).toEqual(
|
||||
isDev ? 'development' : undefined
|
||||
)
|
||||
})
|
||||
|
||||
// TODO: uncomment once env is provided to next.config.js
|
||||
// it('should provide env correctly through next.config.js', async () => {
|
||||
// const data = await getEnvFromHtml('/next-config-loaded-env')
|
||||
// expect(data.DEVELOPMENT_ENV_FILE_KEY).toEqual(
|
||||
// isDev ? 'development' : undefined
|
||||
// )
|
||||
// })
|
||||
|
||||
it('should load env from .env', async () => {
|
||||
const data = await getEnvFromHtml('/')
|
||||
expect(data.ENV_FILE_KEY).toEqual('env')
|
||||
})
|
||||
|
||||
it('should override env from .env', async () => {
|
||||
const data = await getEnvFromHtml('/')
|
||||
expect(data.ENV_FILE_DEVELOPMENT_OVERRIDE_TEST).toEqual(
|
||||
isDev ? 'development' : 'env'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Loads .env.development.local', () => {
|
||||
it('should provide env for SSG', async () => {
|
||||
const data = await getEnvFromHtml('/some-ssg')
|
||||
expect(data.LOCAL_DEVELOPMENT_ENV_FILE_KEY).toBe(
|
||||
isDev ? 'localdevelopment' : undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('should provide env correctly for SSR', async () => {
|
||||
const data = await getEnvFromHtml('/some-ssp')
|
||||
expect(data.LOCAL_DEVELOPMENT_ENV_FILE_KEY).toBe(
|
||||
isDev ? 'localdevelopment' : undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('should provide env correctly for API routes', async () => {
|
||||
const data = await renderViaHTTP(appPort, '/api/all')
|
||||
expect(JSON.parse(data).LOCAL_DEVELOPMENT_ENV_FILE_KEY).toEqual(
|
||||
isDev ? 'localdevelopment' : undefined
|
||||
)
|
||||
})
|
||||
|
||||
// TODO: uncomment once env is provided to next.config.js
|
||||
// it('should provide env correctly through next.config.js', async () => {
|
||||
// const data = await getEnvFromHtml('/next-config-loaded-env')
|
||||
// expect(data.LOCAL_DEVELOPMENT_ENV_FILE_KEY).toEqual(
|
||||
// isDev ? 'localdevelopment' : undefined
|
||||
// )
|
||||
// })
|
||||
|
||||
it('should load env from .env', async () => {
|
||||
const data = await getEnvFromHtml('/')
|
||||
expect(data.ENV_FILE_KEY).toEqual('env')
|
||||
})
|
||||
|
||||
it('should override env from .env and .env.development', async () => {
|
||||
const data = await getEnvFromHtml('/')
|
||||
expect(data.ENV_FILE_DEVELOPMENT_LOCAL_OVERRIDEOVERRIDE_TEST).toEqual(
|
||||
isDev ? 'localdevelopment' : 'env'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Loads .env.production', () => {
|
||||
it('should provide env for SSG', async () => {
|
||||
const data = await getEnvFromHtml('/some-ssg')
|
||||
expect(data.PRODUCTION_ENV_FILE_KEY).toBe(
|
||||
isDev ? undefined : 'production'
|
||||
)
|
||||
})
|
||||
|
||||
it('should provide env correctly for SSR', async () => {
|
||||
const data = await getEnvFromHtml('/some-ssp')
|
||||
expect(data.PRODUCTION_ENV_FILE_KEY).toBe(
|
||||
isDev ? undefined : 'production'
|
||||
)
|
||||
})
|
||||
|
||||
it('should provide env correctly for API routes', async () => {
|
||||
const data = await renderViaHTTP(appPort, '/api/all')
|
||||
expect(JSON.parse(data).PRODUCTION_ENV_FILE_KEY).toEqual(
|
||||
isDev ? undefined : 'production'
|
||||
)
|
||||
})
|
||||
|
||||
// TODO: uncomment once env is provided to next.config.js
|
||||
// it('should provide env correctly through next.config.js', async () => {
|
||||
// const data = await getEnvFromHtml('/next-config-loaded-env')
|
||||
// expect(data.PRODUCTION_ENV_FILE_KEY).toEqual(
|
||||
// isDev ? undefined : 'production'
|
||||
// )
|
||||
// })
|
||||
|
||||
it('should load env from .env', async () => {
|
||||
const data = await getEnvFromHtml('/')
|
||||
expect(data.ENV_FILE_KEY).toEqual('env')
|
||||
})
|
||||
|
||||
it('should override env from .env', async () => {
|
||||
const data = await getEnvFromHtml('/')
|
||||
expect(data.ENV_FILE_PRODUCTION_OVERRIDEOVERRIDE_TEST).toEqual(
|
||||
isDev ? 'env' : 'production'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Loads .env.production.local', () => {
|
||||
it('should provide env for SSG', async () => {
|
||||
const data = await getEnvFromHtml('/some-ssg')
|
||||
expect(data.LOCAL_PRODUCTION_ENV_FILE_KEY).toBe(
|
||||
isDev ? undefined : 'localproduction'
|
||||
)
|
||||
})
|
||||
|
||||
it('should provide env correctly for SSR', async () => {
|
||||
const data = await getEnvFromHtml('/some-ssp')
|
||||
expect(data.LOCAL_PRODUCTION_ENV_FILE_KEY).toBe(
|
||||
isDev ? undefined : 'localproduction'
|
||||
)
|
||||
})
|
||||
|
||||
it('should provide env correctly for API routes', async () => {
|
||||
const data = await renderViaHTTP(appPort, '/api/all')
|
||||
expect(JSON.parse(data).LOCAL_PRODUCTION_ENV_FILE_KEY).toEqual(
|
||||
isDev ? undefined : 'localproduction'
|
||||
)
|
||||
})
|
||||
|
||||
// TODO: uncomment once env is provided to next.config.js
|
||||
// it('should provide env correctly through next.config.js', async () => {
|
||||
// const data = await getEnvFromHtml('/next-config-loaded-env')
|
||||
// expect(data.LOCAL_PRODUCTION_ENV_FILE_KEY).toEqual(
|
||||
// isDev ? undefined : 'localproduction'
|
||||
// )
|
||||
// })
|
||||
|
||||
it('should load env from .env', async () => {
|
||||
const data = await getEnvFromHtml('/')
|
||||
expect(data.ENV_FILE_KEY).toEqual('env')
|
||||
})
|
||||
|
||||
it('should override env from .env and .env.production', async () => {
|
||||
const data = await getEnvFromHtml('/')
|
||||
expect(data.ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST).toEqual(
|
||||
isDev ? 'env' : 'localproduction'
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (isTestEnv) {
|
||||
describe('Loads .env.test', () => {
|
||||
it('should provide env for SSG', async () => {
|
||||
const data = await getEnvFromHtml('/some-ssg')
|
||||
expect(data.TEST_ENV_FILE_KEY).toBe(isDev ? 'test' : undefined)
|
||||
})
|
||||
|
||||
it('should provide env correctly for SSR', async () => {
|
||||
const data = await getEnvFromHtml('/some-ssp')
|
||||
expect(data.TEST_ENV_FILE_KEY).toBe(isDev ? 'test' : undefined)
|
||||
})
|
||||
|
||||
it('should provide env correctly for API routes', async () => {
|
||||
const data = await renderViaHTTP(appPort, '/api/all')
|
||||
expect(JSON.parse(data).TEST_ENV_FILE_KEY).toEqual(
|
||||
isDev ? 'test' : undefined
|
||||
)
|
||||
})
|
||||
|
||||
// TODO: uncomment once env is provided to next.config.js
|
||||
// it('should provide env correctly through next.config.js', async () => {
|
||||
// const data = await getEnvFromHtml('/next-config-loaded-env')
|
||||
// expect(data.TEST_ENV_FILE_KEY).toEqual(isDev ? 'test' : undefined)
|
||||
// })
|
||||
|
||||
it('should load env from .env', async () => {
|
||||
const data = await getEnvFromHtml('/')
|
||||
expect(data.ENV_FILE_KEY).toEqual('env')
|
||||
})
|
||||
|
||||
it('should override env from .env', async () => {
|
||||
const data = await getEnvFromHtml('/')
|
||||
expect(data.ENV_FILE_TEST_OVERRIDE_TEST).toEqual(isDev ? 'test' : 'env')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Loads .env.test.local', () => {
|
||||
it('should provide env for SSG', async () => {
|
||||
const data = await getEnvFromHtml('/some-ssg')
|
||||
expect(data.LOCAL_TEST_ENV_FILE_KEY).toBe(
|
||||
isDev ? 'localtest' : undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('should provide env correctly for SSR', async () => {
|
||||
const data = await getEnvFromHtml('/some-ssp')
|
||||
expect(data.LOCAL_TEST_ENV_FILE_KEY).toBe(
|
||||
isDev ? 'localtest' : undefined
|
||||
)
|
||||
})
|
||||
|
||||
it('should provide env correctly for API routes', async () => {
|
||||
const data = await renderViaHTTP(appPort, '/api/all')
|
||||
expect(JSON.parse(data).LOCAL_TEST_ENV_FILE_KEY).toEqual(
|
||||
isDev ? 'localtest' : undefined
|
||||
)
|
||||
})
|
||||
|
||||
// TODO: uncomment once env is provided to next.config.js
|
||||
// it('should provide env correctly through next.config.js', async () => {
|
||||
// const data = await getEnvFromHtml('/next-config-loaded-env')
|
||||
// expect(data.LOCAL_TEST_ENV_FILE_KEY).toEqual(
|
||||
// isDev ? 'localtest' : undefined
|
||||
// )
|
||||
// })
|
||||
|
||||
it('should load env from .env', async () => {
|
||||
const data = await getEnvFromHtml('/')
|
||||
expect(data.ENV_FILE_KEY).toEqual('env')
|
||||
})
|
||||
|
||||
it('should override env from .env and .env.test', async () => {
|
||||
const data = await getEnvFromHtml('/')
|
||||
expect(data.ENV_FILE_TEST_LOCAL_OVERRIDEOVERRIDE_TEST).toEqual(
|
||||
isDev ? 'localtest' : 'env'
|
||||
)
|
||||
})
|
||||
|
||||
it('should not load .env.local', async () => {
|
||||
const data = await getEnvFromHtml('/')
|
||||
expect(data.LOCAL_ENV_FILE_KEY).toEqual(undefined)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
describe('Env Config', () => {
|
||||
describe('dev mode', () => {
|
||||
beforeAll(async () => {
|
||||
await fs.writeFile(
|
||||
nextConfig,
|
||||
`module.exports = { env: { NC_ENV_FILE_KEY: process.env.ENV_FILE_KEY, NC_LOCAL_ENV_FILE_KEY: process.env.LOCAL_ENV_FILE_KEY, NC_PRODUCTION_ENV_FILE_KEY: process.env.PRODUCTION_ENV_FILE_KEY, NC_LOCAL_PRODUCTION_ENV_FILE_KEY: process.env.LOCAL_PRODUCTION_ENV_FILE_KEY, NC_DEVELOPMENT_ENV_FILE_KEY: process.env.DEVELOPMENT_ENV_FILE_KEY, NC_LOCAL_DEVELOPMENT_ENV_FILE_KEY: process.env.LOCAL_DEVELOPMENT_ENV_FILE_KEY }, ${nextConfigContent} }`
|
||||
)
|
||||
appPort = await findPort()
|
||||
app = await launchApp(appDir, appPort, {
|
||||
env: {
|
||||
PROCESS_ENV_KEY: 'processenvironment',
|
||||
},
|
||||
})
|
||||
})
|
||||
afterAll(async () => {
|
||||
await fs.remove(nextConfig)
|
||||
await killApp(app)
|
||||
})
|
||||
|
||||
runTests(true, false, false)
|
||||
})
|
||||
|
||||
describe('test environment', () => {
|
||||
beforeAll(async () => {
|
||||
await fs.writeFile(
|
||||
nextConfig,
|
||||
`module.exports = { env: { NC_ENV_FILE_KEY: process.env.ENV_FILE_KEY, NC_LOCAL_ENV_FILE_KEY: process.env.LOCAL_ENV_FILE_KEY, NC_PRODUCTION_ENV_FILE_KEY: process.env.PRODUCTION_ENV_FILE_KEY, NC_LOCAL_PRODUCTION_ENV_FILE_KEY: process.env.LOCAL_PRODUCTION_ENV_FILE_KEY, NC_DEVELOPMENT_ENV_FILE_KEY: process.env.DEVELOPMENT_ENV_FILE_KEY, NC_LOCAL_DEVELOPMENT_ENV_FILE_KEY: process.env.LOCAL_DEVELOPMENT_ENV_FILE_KEY, NC_TEST_ENV_FILE_KEY: process.env.TEST_ENV_FILE_KEY, NC_LOCAL_TEST_ENV_FILE_KEY: process.env.LOCAL_TEST_ENV_FILE_KEY }, ${nextConfigContent} }`
|
||||
)
|
||||
appPort = await findPort()
|
||||
app = await launchApp(appDir, appPort, {
|
||||
env: {
|
||||
PROCESS_ENV_KEY: 'processenvironment',
|
||||
NODE_ENV: 'test',
|
||||
},
|
||||
})
|
||||
})
|
||||
afterAll(async () => {
|
||||
await fs.remove(nextConfig)
|
||||
await killApp(app)
|
||||
})
|
||||
|
||||
runTests(true, false, true)
|
||||
})
|
||||
|
||||
describe('server mode', () => {
|
||||
beforeAll(async () => {
|
||||
await fs.writeFile(
|
||||
nextConfig,
|
||||
`module.exports = { env: { NC_ENV_FILE_KEY: process.env.ENV_FILE_KEY, NC_LOCAL_ENV_FILE_KEY: process.env.LOCAL_ENV_FILE_KEY, NC_PRODUCTION_ENV_FILE_KEY: process.env.PRODUCTION_ENV_FILE_KEY, NC_LOCAL_PRODUCTION_ENV_FILE_KEY: process.env.LOCAL_PRODUCTION_ENV_FILE_KEY, NC_DEVELOPMENT_ENV_FILE_KEY: process.env.DEVELOPMENT_ENV_FILE_KEY, NC_LOCAL_DEVELOPMENT_ENV_FILE_KEY: process.env.LOCAL_DEVELOPMENT_ENV_FILE_KEY }, ${nextConfigContent} }`
|
||||
)
|
||||
const { code } = await nextBuild(appDir, [], {
|
||||
env: {
|
||||
PROCESS_ENV_KEY: 'processenvironment',
|
||||
},
|
||||
})
|
||||
if (code !== 0) throw new Error(`Build failed with exit code ${code}`)
|
||||
appPort = await findPort()
|
||||
app = await nextStart(appDir, appPort, {})
|
||||
})
|
||||
afterAll(async () => {
|
||||
await fs.remove(nextConfig)
|
||||
await killApp(app)
|
||||
})
|
||||
|
||||
runTests(false, false, false)
|
||||
})
|
||||
|
||||
describe('serverless mode', () => {
|
||||
beforeAll(async () => {
|
||||
await fs.writeFile(
|
||||
nextConfig,
|
||||
`module.exports = { target: 'experimental-serverless-trace', env: { NC_ENV_FILE_KEY: process.env.ENV_FILE_KEY, NC_LOCAL_ENV_FILE_KEY: process.env.LOCAL_ENV_FILE_KEY, NC_PRODUCTION_ENV_FILE_KEY: process.env.PRODUCTION_ENV_FILE_KEY, NC_LOCAL_PRODUCTION_ENV_FILE_KEY: process.env.LOCAL_PRODUCTION_ENV_FILE_KEY, NC_DEVELOPMENT_ENV_FILE_KEY: process.env.DEVELOPMENT_ENV_FILE_KEY, NC_LOCAL_DEVELOPMENT_ENV_FILE_KEY: process.env.LOCAL_DEVELOPMENT_ENV_FILE_KEY }, ${nextConfigContent} }`
|
||||
)
|
||||
const { code } = await nextBuild(appDir, [], {})
|
||||
if (code !== 0) throw new Error(`Build failed with exit code ${code}`)
|
||||
appPort = await findPort()
|
||||
app = await nextStart(appDir, appPort, {
|
||||
env: {
|
||||
PROCESS_ENV_KEY: 'processenvironment',
|
||||
},
|
||||
})
|
||||
})
|
||||
afterAll(async () => {
|
||||
await fs.remove(nextConfig)
|
||||
await killApp(app)
|
||||
})
|
||||
|
||||
runTests(false, true, false)
|
||||
})
|
||||
})
|
17
yarn.lock
17
yarn.lock
|
@ -2536,6 +2536,13 @@
|
|||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/dotenv@8.2.0":
|
||||
version "8.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/dotenv/-/dotenv-8.2.0.tgz#5cd64710c3c98e82d9d15844375a33bf1b45d053"
|
||||
integrity sha512-ylSC9GhfRH7m1EUXBXofhgx4lUWmFeQDINW5oLuS+gxWdfUeW4zJdeVTYVkexEW+e2VUvlZR2kGnGGipAWR7kw==
|
||||
dependencies:
|
||||
dotenv "*"
|
||||
|
||||
"@types/eslint-visitor-keys@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
|
||||
|
@ -6213,6 +6220,16 @@ dot-prop@^5.0.0:
|
|||
dependencies:
|
||||
is-obj "^2.0.0"
|
||||
|
||||
dotenv-expand@5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0"
|
||||
integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==
|
||||
|
||||
dotenv@*, dotenv@8.2.0:
|
||||
version "8.2.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
|
||||
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
|
||||
|
||||
duplexer3@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
|
||||
|
|
Loading…
Reference in a new issue