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

Add initial process.env stubbing for new env support #11893

Merged
merged 4 commits into from
Apr 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 4 additions & 13 deletions errors/missing-env-value.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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)
})
})
})