diff --git a/e2e-tests/production-runtime/.env.production b/e2e-tests/production-runtime/.env.production new file mode 100644 index 0000000000000..b4b96fe5c9c50 --- /dev/null +++ b/e2e-tests/production-runtime/.env.production @@ -0,0 +1,5 @@ +# This file is commited to repo only to validate that env vars are available +# to use in frontend and don't leak if not used (secrets used in Node). +# You should NOT commit `.env` files to your repository for production sites. +EXISTING_VAR=foo bar +VERY_SECRET_VAR=it's a secret diff --git a/e2e-tests/production-runtime/__tests__/env-vars.js b/e2e-tests/production-runtime/__tests__/env-vars.js new file mode 100644 index 0000000000000..00de3acd24b4f --- /dev/null +++ b/e2e-tests/production-runtime/__tests__/env-vars.js @@ -0,0 +1,32 @@ +const { exec } = require(`child_process`) + +const grepJSFilesFor = str => + new Promise(resolve => { + const grep = exec(`grep -r "${str}" ./public/*.js`) + + grep.stdout.on(`data`, () => { + resolve(true) + return + }) + + grep.on(`close`, () => { + resolve(false) + return + }) + }) + +const checkLeakedEnvVar = async () => { + const isLeaked = + (await grepJSFilesFor(`VERY_SECRET_VAR`)) || + (await grepJSFilesFor(`it's a secret`)) + + if (isLeaked) { + console.error(`Error: VERY_SECRET_VAR found in bundle`) + process.exit(1) + } else { + console.log(`Success: VERY_SECRET_VAR not found in bundle`) + process.exit(0) + } +} + +checkLeakedEnvVar() diff --git a/e2e-tests/production-runtime/cypress/integration/production.js b/e2e-tests/production-runtime/cypress/integration/production.js index 1050897e9af41..ddff30c7859b1 100644 --- a/e2e-tests/production-runtime/cypress/integration/production.js +++ b/e2e-tests/production-runtime/cypress/integration/production.js @@ -71,4 +71,12 @@ describe(`Production build tests`, () => { .getTestElement(`404`) .should(`exist`) }) + + it(`Uses env vars`, () => { + cy.visit(`/env-vars`).waitForAPI(`onRouteUpdate`) + + cy.getTestElement(`process.env`).contains(`{}`) + cy.getTestElement(`process.env.EXISTING_VAR`).contains(`"foo bar"`) + cy.getTestElement(`process.env.NOT_EXISTING_VAR`).should(`be.empty`) + }) }) diff --git a/e2e-tests/production-runtime/package.json b/e2e-tests/production-runtime/package.json index 49594e179cd0e..937217f8e0720 100644 --- a/e2e-tests/production-runtime/package.json +++ b/e2e-tests/production-runtime/package.json @@ -21,7 +21,8 @@ "build": "gatsby build", "develop": "gatsby develop", "format": "prettier --write '**/*.js'", - "test": "npm run build && npm run start-server-and-test", + "test": "npm run build && npm run start-server-and-test && npm run test-env-vars", + "test-env-vars": " node __tests__/env-vars.js", "start-server-and-test": "start-server-and-test serve http://localhost:9000 cy:run", "serve": "gatsby serve", "cy:open": "cypress open", diff --git a/e2e-tests/production-runtime/src/pages/env-vars.js b/e2e-tests/production-runtime/src/pages/env-vars.js new file mode 100644 index 0000000000000..e4beb1b0d888a --- /dev/null +++ b/e2e-tests/production-runtime/src/pages/env-vars.js @@ -0,0 +1,29 @@ +import React from 'react' + +import Layout from '../components/layout' + +const UseEnv = ({ heading, envVar }) => ( + +

{heading}

+
+      {JSON.stringify(envVar)}
+    
+
+) + +const SecondPage = () => ( + +

Using env vars

+ + + +
+) + +export default SecondPage diff --git a/packages/gatsby/src/utils/webpack.config.js b/packages/gatsby/src/utils/webpack.config.js index 6e6d60f7de3fa..e8c6695d4c6c3 100644 --- a/packages/gatsby/src/utils/webpack.config.js +++ b/packages/gatsby/src/utils/webpack.config.js @@ -67,7 +67,17 @@ module.exports = async ( envObject.PUBLIC_DIR = JSON.stringify(`${process.cwd()}/public`) envObject.BUILD_STAGE = JSON.stringify(stage) - return Object.assign(envObject, gatsbyVarObject) + const mergedEnvVars = Object.assign(envObject, gatsbyVarObject) + + return Object.keys(mergedEnvVars).reduce( + (acc, key) => { + acc[`process.env.${key}`] = mergedEnvVars[key] + return acc + }, + { + "process.env": JSON.stringify({}), + } + ) } function getHmrPath() { @@ -172,7 +182,7 @@ module.exports = async ( // Add a few global variables. Set NODE_ENV to production (enables // optimizations for React) and what the link prefix is (__PATH_PREFIX__). plugins.define({ - "process.env": processEnv(stage, `development`), + ...processEnv(stage, `development`), __PATH_PREFIX__: JSON.stringify( program.prefixPaths ? store.getState().config.pathPrefix : `` ),