Skip to content

Commit

Permalink
Add initial process.env stubbing for new env support (#11893)
Browse files Browse the repository at this point in the history
* Add initial process.env stubbing for new env support

* Fix server process.env being stubbed in production

* bump

Co-authored-by: Joe Haddad <joe.haddad@zeit.co>
  • Loading branch information
ijjk and Timer committed Apr 15, 2020
1 parent 2b116ce commit 20c7b5c
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 13 deletions.
17 changes: 4 additions & 13 deletions errors/missing-env-value.md
Expand Up @@ -2,24 +2,15 @@

#### Why This Error Occurred

One of your pages' config requested an env value that wasn't populated.
In one of your pages you attempted to access an environment value that is not provided in the environment.

```js
// pages/index.js
export const config = {
// this value isn't provided in `.env`
env: ['MISSING_KEY'],
}
```
When accessing environment variables on the client they must be prefixed with `NEXT_PUBLIC_` to signify they are safe to be inlined for the client.

```
// .env (notice no `MISSING_KEY` provided here)
NOTION_KEY='...'
```
When accessing environment variables on the server in `getStaticProps`, `getServerSideProps`, or an API route the value must be provided in the environment at runtime.

#### 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`.
Either remove the code accessing the env value, populate it in your `.env` file, or manually populate it in your environment before running `next dev` or `next build`.

### Useful Links

Expand Down
21 changes: 21 additions & 0 deletions packages/next/build/webpack-config.ts
Expand Up @@ -838,6 +838,27 @@ export default async function getBaseWebpackConfig(
'global.GENTLY': JSON.stringify(false),
}
: undefined),
// stub process.env with proxy to warn a missing value is
// being accessed
...(config.experimental.pageEnv
? {
'process.env':
process.env.NODE_ENV === 'production'
? isServer
? 'process.env'
: '{}'
: `
new Proxy(${isServer ? 'process.env' : '{}'}, {
get(target, prop) {
if (typeof target[prop] === 'undefined') {
console.warn(\`An environment variable (\${prop}) that was not provided in the environment was accessed.\nSee more info here: https://err.sh/next.js/missing-env-value\`)
}
return target[prop]
}
})
`,
}
: {}),
}),
!isServer &&
new ReactLoadablePlugin({
Expand Down
5 changes: 5 additions & 0 deletions test/integration/process-env-stub/next.config.js
@@ -0,0 +1,5 @@
module.exports = {
experimental: {
pageEnv: true,
},
}
4 changes: 4 additions & 0 deletions test/integration/process-env-stub/pages/also-not-missing.js
@@ -0,0 +1,4 @@
export default () => {
console.log(process.env.I_SHOULD_BE_HERE)
return <p>hi there 👋</p>
}
4 changes: 4 additions & 0 deletions test/integration/process-env-stub/pages/api/hi.js
@@ -0,0 +1,4 @@
export default (req, res) => {
console.log(process.env.where_is_it)
res.end('done')
}
12 changes: 12 additions & 0 deletions test/integration/process-env-stub/pages/missing-gsp.js
@@ -0,0 +1,12 @@
export default () => {
return <p>hi there 👋</p>
}

export const getStaticProps = () => {
console.log(process.env.SECRET)
return {
props: {
hi: 'there',
},
}
}
12 changes: 12 additions & 0 deletions test/integration/process-env-stub/pages/missing-gssp.js
@@ -0,0 +1,12 @@
export default () => {
return <p>hi there 👋</p>
}

export const getServerSideProps = () => {
console.log(process.env.SECRET)
return {
props: {
hi: 'there',
},
}
}
4 changes: 4 additions & 0 deletions test/integration/process-env-stub/pages/missing.js
@@ -0,0 +1,4 @@
export default () => {
console.log(process.env.hi)
return <p>hi there 👋</p>
}
4 changes: 4 additions & 0 deletions test/integration/process-env-stub/pages/not-missing.js
@@ -0,0 +1,4 @@
export default () => {
console.log(process.env.NEXT_PUBLIC_HI)
return <p>hi there 👋</p>
}
110 changes: 110 additions & 0 deletions test/integration/process-env-stub/test/index.test.js
@@ -0,0 +1,110 @@
/* eslint-env jest */
/* global jasmine */
import {
findPort,
killApp,
launchApp,
renderViaHTTP,
waitFor,
} from 'next-test-utils'
import { join } from 'path'
import webdriver from 'next-webdriver'

jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 2
const appDir = join(__dirname, '..')
let app
let stderr
let appPort

const buildWarning = prop =>
`An environment variable (${prop}) that was not provided in the environment was accessed`

const checkMissing = async (pathname, prop, shouldWarn = false) => {
stderr = ''
await renderViaHTTP(appPort, pathname)
await waitFor(1000)

if (shouldWarn) {
expect(stderr).toContain(buildWarning(prop))
} else {
expect(stderr).not.toContain(buildWarning(prop))
}
}

const checkMissingClient = async (pathname, prop, shouldWarn = false) => {
const browser = await webdriver(appPort, '/404')
await browser.eval(`(function() {
window.warnLogs = []
var origWarn = console.warn
console.warn = function() {
window.warnLogs.push(arguments[0])
origWarn.apply(this, arguments)
}
window.next.router.push("${pathname}")
})()`)
await waitFor(2000)

const logs = await browser.eval(`window.warnLogs`)
const warning = buildWarning(prop)
const hasWarn = logs.some(log => log.includes(warning))

expect(hasWarn).toBe(shouldWarn)
await browser.close()
}

describe('process.env stubbing', () => {
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort, {
env: {
NEXT_PUBLIC_HI: 'hi',
I_SHOULD_BE_HERE: 'hello',
},
onStderr(msg) {
stderr += msg || ''
},
})
})
afterAll(() => killApp(app))

describe('server side', () => {
it('should not show missing env value when its not missing public', async () => {
await checkMissing('/not-missing', 'NEXT_PUBLIC_HI')
})

it('should not show missing env value when its not missing runtime', async () => {
await checkMissing('/also-not-missing', 'I_SHOULD_BE_HERE')
})

it('should show missing env value when its missing normal', async () => {
await checkMissing('/missing', 'hi', true)
})

it('should show missing env value when its missing GSP', async () => {
await checkMissing('/missing-gsp', 'SECRET', true)
})

it('should show missing env value when its missing GSSP', async () => {
await checkMissing('/missing-gssp', 'SECRET', true)
})

it('should show missing env value when its missing API', async () => {
await checkMissing('/api/hi', 'where_is_it', true)
})
})

describe('client side', () => {
it('should not show missing env value when its not missing public', async () => {
await checkMissingClient('/not-missing', 'NEXT_PUBLIC_HI')
})

it('should show missing env value when its missing runtime', async () => {
await checkMissingClient('/also-not-missing', 'I_SHOULD_BE_HERE', true)
})

it('should show missing env value when its missing normal', async () => {
await checkMissingClient('/missing', 'hi', true)
})
})
})

0 comments on commit 20c7b5c

Please sign in to comment.