diff --git a/.github/workflows/compressed.yml b/.github/workflows/compressed.yml index ce7fc6f00..175069598 100644 --- a/.github/workflows/compressed.yml +++ b/.github/workflows/compressed.yml @@ -15,6 +15,9 @@ jobs: steps: - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: "14" - name: Count size uses: preactjs/compressed-size-action@v2 with: diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2ff8fa5ac..17483f8fe 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,9 +30,9 @@ jobs: path: | ${{ steps.yarn-cache-dir-path.outputs.dir }} **/node_modules - key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v13-${{ hashFiles('yarn.lock') }} + key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }} restore-keys: | - ${{ runner.os }}-${{ runner.node_version}}-yarn-v13- + ${{ runner.os }}-${{ runner.node_version}}-yarn-v14- - name: Install dependencies run: yarn install --frozen-lockfile --silent env: @@ -74,9 +74,9 @@ jobs: path: | ${{ steps.yarn-cache-dir-path.outputs.dir }} **/node_modules - key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v13-${{ hashFiles('yarn.lock') }} + key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }} restore-keys: | - ${{ runner.os }}-${{ runner.node_version}}-yarn-v13- + ${{ runner.os }}-${{ runner.node_version}}-yarn-v14- - run: yarn install --frozen-lockfile --check-files - name: Build Packages run: yarn build @@ -98,6 +98,10 @@ jobs: with: path: ./* key: ${{ runner.os }}-${{ github.sha }} + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: "14" - name: Setup kernel to increase watchers if: runner.os == 'Linux' run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p @@ -120,6 +124,10 @@ jobs: with: path: ./* key: ${{ runner.os }}-${{ github.sha }} + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: "14" # Needed to get cypress binary - run: yarn cypress install - name: Install sass @@ -157,9 +165,9 @@ jobs: # path: | # ${{ steps.yarn-cache-dir-path.outputs.dir }} # **/node_modules - # key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v13-${{ hashFiles('yarn.lock') }} + # key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }} # restore-keys: | - # ${{ runner.os }}-${{ runner.node_version}}-yarn-v13- + # ${{ runner.os }}-${{ runner.node_version}}-yarn-v14- - run: yarn install --frozen-lockfile --check-files - name: Build Packages run: yarn build @@ -222,6 +230,10 @@ jobs: with: path: ./* key: ${{ runner.os }}-${{ github.sha }} + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: "14" # TODO: remove after we fix watchpack watching too much - name: Setup kernel to increase watchers @@ -256,9 +268,9 @@ jobs: # path: | # ${{ steps.yarn-cache-dir-path.outputs.dir }} # **/node_modules - # key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v13-${{ hashFiles('yarn.lock') }} + # key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }} # restore-keys: | - # ${{ runner.os }}-${{ runner.node_version}}-yarn-v13- + # ${{ runner.os }}-${{ runner.node_version}}-yarn-v14- - run: yarn install --frozen-lockfile --check-files - name: Build Packages run: yarn build @@ -285,7 +297,10 @@ jobs: with: path: ./* key: ${{ runner.os }}-${{ github.sha }} - + - name: Use Node.js + uses: actions/setup-node@v2 + with: + node-version: "14" # TODO: remove after we fix watchpack watching too much - name: Setup kernel to increase watchers if: runner.os == 'Linux' @@ -318,9 +333,9 @@ jobs: # path: | # ${{ steps.yarn-cache-dir-path.outputs.dir }} # **/node_modules - # key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v13-${{ hashFiles('yarn.lock') }} + # key: ${{ runner.os }}-${{ runner.node_version}}-yarn-v14-${{ hashFiles('yarn.lock') }} # restore-keys: | - # ${{ runner.os }}-${{ runner.node_version}}-yarn-v13- + # ${{ runner.os }}-${{ runner.node_version}}-yarn-v14- - run: yarn install --frozen-lockfile --check-files - name: Build Packages run: yarn build diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 48672c9ed..3b5ed7fcf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,6 +4,12 @@ ## Notes For Core Team +### To Publish a new NPM Package under `@blitzjs/` namespace + +1. cd into the package directory +2. Run `npm publish --tag danger --access public` + - `--access public` is required because scoped packages are set to private by default + ### Syncing Next.js Fork 1. Run `yarn push-nextjs` diff --git a/examples/cypress/cypress/plugins/index.ts b/examples/cypress/cypress/plugins/index.ts index 5e198bd0b..b7967503b 100644 --- a/examples/cypress/cypress/plugins/index.ts +++ b/examples/cypress/cypress/plugins/index.ts @@ -1,5 +1,7 @@ process.env.NODE_ENV = "test" -require("dotenv-flow").config({ silent: true }) + +import { loadEnvConfig } from "@blitzjs/env" +loadEnvConfig() import "./register-ts-paths" import db from "db" diff --git a/examples/fauna/test/setup.ts b/examples/fauna/test/setup.ts index 8f777a318..ecbde24ef 100644 --- a/examples/fauna/test/setup.ts +++ b/examples/fauna/test/setup.ts @@ -3,4 +3,6 @@ // expect(element).toHaveTextContent(/react/i) // learn more: https://github.com/testing-library/jest-dom import "@testing-library/jest-dom/extend-expect" -require("dotenv-flow").config({ silent: true }) +import { loadEnvConfig } from "@blitzjs/env" + +loadEnvConfig() diff --git a/nextjs/packages/next-env/README.md b/nextjs/packages/next-env/README.md index cc08c112e..af60128ea 100644 --- a/nextjs/packages/next-env/README.md +++ b/nextjs/packages/next-env/README.md @@ -1,3 +1,3 @@ -# `@next/env` +# `@blitzjs/env` Next.js' util for loading dotenv files in with the proper priorities diff --git a/nextjs/packages/next-env/index.ts b/nextjs/packages/next-env/index.ts index a33636c70..2ab59f228 100644 --- a/nextjs/packages/next-env/index.ts +++ b/nextjs/packages/next-env/index.ts @@ -66,7 +66,7 @@ export function processEnv( } export function loadEnvConfig( - dir: string, + dir: string = process.cwd(), dev?: boolean, log: Log = console ): { @@ -79,8 +79,11 @@ export function loadEnvConfig( const isTest = process.env.NODE_ENV === 'test' const mode = isTest ? 'test' : dev ? 'development' : 'production' - const dotenvFiles = [ + const appEnv = process.env.APP_ENV + let dotenvFiles = [ `.env.${mode}.local`, + appEnv && `.env.${appEnv}.local`, + appEnv && `.env.${appEnv}`, // Don't include `.env.local` for `test` environment // since normally you expect tests to produce the same // results for everyone @@ -106,7 +109,7 @@ export function loadEnvConfig( path: envFile, contents, }) - } catch (err) { + } catch (err: any) { if (err.code !== 'ENOENT') { log.error(`Failed to load env from ${envFile}`, err) } diff --git a/nextjs/packages/next-env/package.json b/nextjs/packages/next-env/package.json index b2f6a2b63..b71c45e0e 100644 --- a/nextjs/packages/next-env/package.json +++ b/nextjs/packages/next-env/package.json @@ -1,7 +1,6 @@ { - "private": true, - "name": "@next/env", - "version": "11.1.0", + "name": "@blitzjs/env", + "version": "0.43.0", "keywords": [ "react", "next", @@ -14,7 +13,7 @@ "url": "https://github.com/vercel/next.js", "directory": "packages/next-env" }, - "author": "Next.js Team ", + "author": "Blitz.js", "license": "MIT", "main": "dist/index.js", "types": "types/index.d.ts", diff --git a/nextjs/packages/next/bin/next.ts b/nextjs/packages/next/bin/next.ts index 9e3417318..3bf38eb68 100755 --- a/nextjs/packages/next/bin/next.ts +++ b/nextjs/packages/next/bin/next.ts @@ -30,10 +30,12 @@ const args = arg( '--version': Boolean, '--help': Boolean, '--inspect': Boolean, + '--env': String, // Aliases '-v': '--version', '-h': '--help', + '-e': '--env' }, { permissive: true, @@ -61,6 +63,7 @@ if (!foundCommand && args['--help']) { ${Object.keys(commands).join(', ')} Options + --env, -e App environment name --version, -v Version number --help, -h Displays this message @@ -83,6 +86,10 @@ if (args['--help']) { forwardedArgs.push('--help') } +if (args['--env']) { + process.env.APP_ENV = args['--env'] +} + const defaultEnv = command === 'dev' ? 'development' : 'production' const standardEnv = ['production', 'development', 'test'] diff --git a/nextjs/packages/next/build/entries.ts b/nextjs/packages/next/build/entries.ts index 59a3bcc7c..2798c1436 100644 --- a/nextjs/packages/next/build/entries.ts +++ b/nextjs/packages/next/build/entries.ts @@ -8,7 +8,7 @@ import { normalizePagePath } from '../server/normalize-page-path' import { warn } from './output/log' import { ClientPagesLoaderOptions } from './webpack/loaders/next-client-pages-loader' import { ServerlessLoaderQuery } from './webpack/loaders/next-serverless-loader' -import { LoadedEnvFiles } from '@next/env' +import { LoadedEnvFiles } from '@blitzjs/env' import { convertPageFilePathToRoutePath } from './utils' import { NextConfigComplete } from '../server/config-shared' diff --git a/nextjs/packages/next/build/index.ts b/nextjs/packages/next/build/index.ts index cce32b992..cf678d947 100644 --- a/nextjs/packages/next/build/index.ts +++ b/nextjs/packages/next/build/index.ts @@ -1,4 +1,4 @@ -import { loadEnvConfig } from '@next/env' +import { loadEnvConfig } from '@blitzjs/env' import chalk from 'chalk' import crypto from 'crypto' import { promises, writeFileSync } from 'fs' diff --git a/nextjs/packages/next/build/webpack/loaders/next-serverless-loader/index.ts b/nextjs/packages/next/build/webpack/loaders/next-serverless-loader/index.ts index 7db9b8832..619623297 100644 --- a/nextjs/packages/next/build/webpack/loaders/next-serverless-loader/index.ts +++ b/nextjs/packages/next/build/webpack/loaders/next-serverless-loader/index.ts @@ -90,7 +90,7 @@ const nextServerlessLoader: webpack.loader.Loader = function () { ) const envLoading = ` - const { processEnv } = require('@next/env') + const { processEnv } = require('@blitzjs/env') processEnv(${Buffer.from(loadedEnvFiles, 'base64').toString()}) ` diff --git a/nextjs/packages/next/export/index.ts b/nextjs/packages/next/export/index.ts index 646e2eaf2..294bae266 100644 --- a/nextjs/packages/next/export/index.ts +++ b/nextjs/packages/next/export/index.ts @@ -37,7 +37,7 @@ import { normalizePagePath, denormalizePagePath, } from '../server/normalize-page-path' -import { loadEnvConfig } from '@next/env' +import { loadEnvConfig } from '@blitzjs/env' import { PrerenderManifest } from '../build' import { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin' import { getPagePath } from '../server/require' diff --git a/nextjs/packages/next/package.json b/nextjs/packages/next/package.json index 88fe41bbd..476b93ddc 100644 --- a/nextjs/packages/next/package.json +++ b/nextjs/packages/next/package.json @@ -73,8 +73,8 @@ "dependencies": { "@babel/helper-module-imports": "^7.0.0", "@babel/runtime": "7.12.5", + "@blitzjs/env": "0.43.0", "@hapi/accept": "5.0.2", - "@next/env": "11.1.0", "@next/polyfill-module": "11.1.0", "@next/react-dev-overlay": "11.1.0", "@next/react-refresh-utils": "11.1.0", diff --git a/nextjs/packages/next/server/config-utils-worker.ts b/nextjs/packages/next/server/config-utils-worker.ts index 3fa34c399..957c38584 100644 --- a/nextjs/packages/next/server/config-utils-worker.ts +++ b/nextjs/packages/next/server/config-utils-worker.ts @@ -1,4 +1,4 @@ -import { loadEnvConfig } from '@next/env' +import { loadEnvConfig } from '@blitzjs/env' import findUp from 'next/dist/compiled/find-up' import { init as initWebpack } from 'next/dist/compiled/webpack/webpack' import { CONFIG_FILE, PHASE_DEVELOPMENT_SERVER } from '../shared/lib/constants' diff --git a/nextjs/packages/next/server/config.ts b/nextjs/packages/next/server/config.ts index ecf7e3230..49ecb170f 100644 --- a/nextjs/packages/next/server/config.ts +++ b/nextjs/packages/next/server/config.ts @@ -14,7 +14,7 @@ import { } from './config-shared' import { loadWebpackHook } from './config-utils' import { ImageConfig, imageConfigDefault, VALID_LOADERS } from './image-config' -import { loadEnvConfig } from '@next/env' +import { loadEnvConfig } from '@blitzjs/env' import { hasNextSupport } from '../telemetry/ci-info' const debug = require('debug')('blitz:config') diff --git a/nextjs/packages/next/server/next-server.ts b/nextjs/packages/next/server/next-server.ts index 3580e16a8..8d991f63f 100644 --- a/nextjs/packages/next/server/next-server.ts +++ b/nextjs/packages/next/server/next-server.ts @@ -82,7 +82,7 @@ import { resultFromChunks, resultToChunks, } from './utils' -import { loadEnvConfig } from '@next/env' +import { loadEnvConfig } from '@blitzjs/env' import './node-polyfill-fetch' import { PagesManifest } from '../build/webpack/plugins/pages-manifest-plugin' import { removePathTrailingSlash } from '../client/normalize-trailing-slash' diff --git a/nextjs/packages/next/shared/lib/utils.ts b/nextjs/packages/next/shared/lib/utils.ts index ea031e843..c9f5f5d6b 100644 --- a/nextjs/packages/next/shared/lib/utils.ts +++ b/nextjs/packages/next/shared/lib/utils.ts @@ -2,7 +2,7 @@ import { formatUrl } from './router/utils/format-url' import type { BuildManifest } from '../../server/get-page-files' import type { ComponentType } from 'react' import type { DomainLocale } from '../../server/config' -import type { Env } from '@next/env' +import type { Env } from '@blitzjs/env' import type { IncomingMessage, ServerResponse } from 'http' import type { NextRouter } from './router/router' import type { ParsedUrlQuery } from 'querystring' diff --git a/nextjs/test/integration/chunking/test/index.test.js b/nextjs/test/integration/chunking/test/index.test.js index 24bcfd92d..5b0d74a80 100644 --- a/nextjs/test/integration/chunking/test/index.test.js +++ b/nextjs/test/integration/chunking/test/index.test.js @@ -12,7 +12,7 @@ import { import webdriver from 'next-webdriver' import { join } from 'path' -jest.setTimeout(1000 * 60 * 1) +jest.setTimeout(1000 * 60 * 3) const appDir = join(__dirname, '../') diff --git a/nextjs/test/integration/env-config-app-env/app/.env b/nextjs/test/integration/env-config-app-env/app/.env new file mode 100644 index 000000000..3ae85099f --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/.env @@ -0,0 +1,5 @@ +PROCESS_ENV_KEY="env" +ENV_FILE_KEY=env +NEXT_PUBLIC_TEST_DEST=another +ENV_KEY_IN_NEXT_CONFIG="hello from next.config.js" +NEXT_PUBLIC_ENV_KEY_IN_NEXT_CONFIG="hello again from next.config.js" diff --git a/nextjs/test/integration/env-config-app-env/app/.env.production b/nextjs/test/integration/env-config-app-env/app/.env.production new file mode 100644 index 000000000..04bfde25b --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/.env.production @@ -0,0 +1 @@ +PRODUCTION_ENV_FILE_KEY=production diff --git a/nextjs/test/integration/env-config-app-env/app/.env.staging b/nextjs/test/integration/env-config-app-env/app/.env.staging new file mode 100644 index 000000000..1d105ed83 --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/.env.staging @@ -0,0 +1,4 @@ +NEXT_PUBLIC_TEST_DEST=staging-route +ENV_KEY_IN_NEXT_CONFIG="hello from staging app" +NEXT_PUBLIC_ENV_KEY_IN_NEXT_CONFIG="hello again from staging app" +ENV_FILE_DEVELOPMENT_OVERRIDE_TEST=staging diff --git a/nextjs/test/integration/env-config-app-env/app/.env.staging.local b/nextjs/test/integration/env-config-app-env/app/.env.staging.local new file mode 100644 index 000000000..bf2d4e5ae --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/.env.staging.local @@ -0,0 +1 @@ +ENV_FILE_DEVELOPMENT_OVERRIDE_TEST=staginglocal diff --git a/nextjs/test/integration/env-config-app-env/app/next.config.js b/nextjs/test/integration/env-config-app-env/app/next.config.js new file mode 100644 index 000000000..df889543a --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/next.config.js @@ -0,0 +1,17 @@ +module.exports = { + cleanDistDir: false, + // update me + env: { + nextConfigEnv: process.env.ENV_KEY_IN_NEXT_CONFIG, + nextConfigPublicEnv: process.env.NEXT_PUBLIC_ENV_KEY_IN_NEXT_CONFIG, + }, + async redirects() { + return [ + { + source: '/hello', + permanent: false, + destination: `/${process.env.NEXT_PUBLIC_TEST_DEST}`, + }, + ] + }, +} diff --git a/nextjs/test/integration/env-config-app-env/app/package.json b/nextjs/test/integration/env-config-app-env/app/package.json new file mode 100644 index 000000000..2904b4924 --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/package.json @@ -0,0 +1,4 @@ +{ + "name": "env-config-app-env", + "dependencies": {} +} diff --git a/nextjs/test/integration/env-config-app-env/app/pages/api/all.js b/nextjs/test/integration/env-config-app-env/app/pages/api/all.js new file mode 100644 index 000000000..4099fa68f --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/pages/api/all.js @@ -0,0 +1,24 @@ +const variables = [ + 'PROCESS_ENV_KEY', + 'ENV_FILE_KEY', + 'ENV_FILE_EMPTY_FIRST', + 'PRODUCTION_ENV_FILE_KEY', + 'LOCAL_PRODUCTION_ENV_FILE_KEY', + 'ENV_FILE_DEVELOPMENT_OVERRIDE_TEST', + 'ENV_FILE_PRODUCTION_OVERRIDEOVERRIDE_TEST', + 'ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST', +] + +const items = { + nextConfigEnv: process.env.nextConfigEnv, + nextConfigPublicEnv: process.env.nextConfigPublicEnv, +} + +variables.forEach((variable) => { + items[variable] = process.env[variable] +}) + +export default async (req, res) => { + // Only for testing, don't do this... + res.json(items) +} diff --git a/nextjs/test/integration/env-config-app-env/app/pages/global.js b/nextjs/test/integration/env-config-app-env/app/pages/global.js new file mode 100644 index 000000000..ded4746e3 --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/pages/global.js @@ -0,0 +1 @@ +export default () =>

{process.env.NEXT_PUBLIC_TEST_DEST}

diff --git a/nextjs/test/integration/env-config-app-env/app/pages/index.js b/nextjs/test/integration/env-config-app-env/app/pages/index.js new file mode 100644 index 000000000..36d51b372 --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/pages/index.js @@ -0,0 +1,35 @@ +const variables = [ + 'PROCESS_ENV_KEY', + 'ENV_FILE_KEY', + 'ENV_FILE_EMPTY_FIRST', + 'PRODUCTION_ENV_FILE_KEY', + 'LOCAL_PRODUCTION_ENV_FILE_KEY', + 'ENV_FILE_DEVELOPMENT_OVERRIDE_TEST', + 'ENV_FILE_PRODUCTION_OVERRIDEOVERRIDE_TEST', + 'ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST', +] + +export async function getStaticProps() { + const items = {} + + variables.forEach((variable) => { + if (process.env[variable]) { + items[variable] = process.env[variable] + } + }) + + return { + // Do not pass any sensitive values here as they will + // be made PUBLICLY available in `pageProps` + props: { env: items }, + revalidate: 1, + } +} + +export default ({ env }) => ( + <> +

{JSON.stringify(env)}

+
{process.env.nextConfigEnv}
+
{process.env.nextConfigPublicEnv}
+ +) diff --git a/nextjs/test/integration/env-config-app-env/app/pages/some-ssg.js b/nextjs/test/integration/env-config-app-env/app/pages/some-ssg.js new file mode 100644 index 000000000..0fc7a3b6d --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/pages/some-ssg.js @@ -0,0 +1,35 @@ +const variables = [ + 'PROCESS_ENV_KEY', + 'ENV_FILE_KEY', + 'ENV_FILE_EMPTY_FIRST', + 'PRODUCTION_ENV_FILE_KEY', + 'LOCAL_PRODUCTION_ENV_FILE_KEY', + 'ENV_FILE_DEVELOPMENT_OVERRIDE_TEST', + 'ENV_FILE_PRODUCTION_OVERRIDEOVERRIDE_TEST', + 'ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST', +] + +export async function getStaticProps() { + const items = {} + + variables.forEach((variable) => { + if (typeof process.env[variable] !== 'undefined') { + items[variable] = process.env[variable] + } + }) + + return { + // Do not pass any sensitive values here as they will + // be made PUBLICLY available in `pageProps` + props: { env: items }, + revalidate: 1, + } +} + +export default ({ env }) => ( + <> +

{JSON.stringify(env)}

+
{process.env.nextConfigEnv}
+
{process.env.nextConfigPublicEnv}
+ +) diff --git a/nextjs/test/integration/env-config-app-env/app/pages/some-ssp.js b/nextjs/test/integration/env-config-app-env/app/pages/some-ssp.js new file mode 100644 index 000000000..e9a908e95 --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/app/pages/some-ssp.js @@ -0,0 +1,34 @@ +const variables = [ + 'PROCESS_ENV_KEY', + 'ENV_FILE_KEY', + 'ENV_FILE_EMPTY_FIRST', + 'PRODUCTION_ENV_FILE_KEY', + 'LOCAL_PRODUCTION_ENV_FILE_KEY', + 'ENV_FILE_DEVELOPMENT_OVERRIDE_TEST', + 'ENV_FILE_PRODUCTION_OVERRIDEOVERRIDE_TEST', + 'ENV_FILE_PRODUCTION_LOCAL_OVERRIDEOVERRIDE_TEST', +] + +export async function getServerSideProps() { + const items = {} + + variables.forEach((variable) => { + if (typeof process.env[variable] !== 'undefined') { + items[variable] = process.env[variable] + } + }) + + return { + // Do not pass any sensitive values here as they will + // be made PUBLICLY available in `pageProps` + props: { env: items }, + } +} + +export default ({ env }) => ( + <> +

{JSON.stringify(env)}

+
{process.env.nextConfigEnv}
+
{process.env.nextConfigPublicEnv}
+ +) diff --git a/nextjs/test/integration/env-config-app-env/test/index.test.js b/nextjs/test/integration/env-config-app-env/test/index.test.js new file mode 100644 index 000000000..653e17a5f --- /dev/null +++ b/nextjs/test/integration/env-config-app-env/test/index.test.js @@ -0,0 +1,163 @@ +/* eslint-env jest */ + +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' + +jest.setTimeout(1000 * 60 * 2) + +let app +let appPort +const appDir = join(__dirname, '../app') + +const getEnvFromHtml = async (path) => { + const html = await renderViaHTTP(appPort, path) + const $ = cheerio.load(html) + const env = JSON.parse($('p').text()) + env.nextConfigEnv = $('#nextConfigEnv').text() + env.nextConfigPublicEnv = $('#nextConfigPublicEnv').text() + return env +} + +const runTests = (mode = 'dev') => { + const isDevOnly = mode === 'dev' + + const checkEnvData = (data) => { + expect(data.ENV_FILE_KEY).toBe('env') + expect(data.PRODUCTION_ENV_FILE_KEY).toBe( + isDevOnly ? undefined : 'production' + ) + expect(data.ENV_FILE_DEVELOPMENT_OVERRIDE_TEST).toEqual('staginglocal') + + expect(data.nextConfigEnv).toBe('hello from staging app') + expect(data.nextConfigPublicEnv).toBe('hello again from staging app') + } + + it('should pass staging 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('/staging-route') + }) + + it('should provide env for SSG', async () => { + const data = await getEnvFromHtml('/some-ssg') + checkEnvData(data) + }) + + it('should provide env correctly for SSR', async () => { + const data = await getEnvFromHtml('/some-ssp') + checkEnvData(data) + }) + + it('should provide env correctly for API routes', async () => { + const data = JSON.parse(await renderViaHTTP(appPort, '/api/all')) + checkEnvData(data) + }) + + it('should load env from .env', async () => { + const data = await getEnvFromHtml('/') + expect(data.ENV_FILE_KEY).toEqual('env') + }) +} + +describe('Env Config', () => { + describe('dev mode', () => { + beforeAll(async () => { + appPort = await findPort() + app = await launchApp(appDir, appPort, { + env: { + APP_ENV: 'staging', + }, + }) + }) + afterAll(() => killApp(app)) + + runTests('dev') + }) + + describe('server mode', () => { + beforeAll(async () => { + const { code } = await nextBuild(appDir, [], { + env: { + APP_ENV: 'staging', + }, + }) + if (code !== 0) throw new Error(`Build failed with exit code ${code}`) + + appPort = await findPort() + app = await nextStart(appDir, appPort, { + env: { + APP_ENV: 'staging', + }, + }) + }) + afterAll(() => killApp(app)) + + runTests('server') + }) + + describe('serverless mode', () => { + let nextConfigContent = '' + const nextConfigPath = join(appDir, 'next.config.js') + const envFiles = [ + '.env', + '.env.staging', + '.env.staging.local', + '.env.production', + ].map((file) => join(appDir, file)) + + beforeAll(async () => { + nextConfigContent = await fs.readFile(nextConfigPath, 'utf8') + await fs.writeFile( + nextConfigPath, + nextConfigContent.replace( + '// update me', + `target: 'experimental-serverless-trace',` + ) + ) + const { code } = await nextBuild(appDir, [], { + env: { + APP_ENV: 'staging', + }, + }) + + if (code !== 0) throw new Error(`Build failed with exit code ${code}`) + appPort = await findPort() + + // rename the files so they aren't loaded by `next start` + // to test that they were bundled into the serverless files + for (const file of envFiles) { + await fs.rename(file, `${file}.bak`) + } + + app = await nextStart(appDir, appPort, { + env: { + APP_ENV: 'staging', + }, + }) + }) + afterAll(async () => { + for (const file of envFiles) { + await fs.rename(`${file}.bak`, file) + } + await fs.writeFile(nextConfigPath, nextConfigContent) + await killApp(app) + }) + + runTests('serverless') + }) +}) diff --git a/nextjs/test/package-managers/pnpm/app/package.json b/nextjs/test/package-managers/pnpm/app/package.json index 2c323a6a5..14bc0660a 100644 --- a/nextjs/test/package-managers/pnpm/app/package.json +++ b/nextjs/test/package-managers/pnpm/app/package.json @@ -10,7 +10,7 @@ "dependencies": { "react": "^16.7.0", "react-dom": "^16.7.0", - "@next/env": "^10.0.9", + "@blitzjs/env": "0.43.0", "@next/polyfill-module": "^10.0.9", "@next/react-dev-overlay": "^10.0.9", "@next/react-refresh-utils": "^10.0.9" diff --git a/nextjs/test/package-managers/pnpm/test/index.test.js b/nextjs/test/package-managers/pnpm/test/index.test.js index 8e9a4a1d4..9198e5da3 100644 --- a/nextjs/test/package-managers/pnpm/test/index.test.js +++ b/nextjs/test/package-managers/pnpm/test/index.test.js @@ -79,7 +79,7 @@ describe('pnpm support', () => { await usingTempDir(async (tempDir) => { const nextTarballPath = await pack(tempDir, 'next') const dependencyTarballPaths = { - // '@next/env': await pack(tempDir, 'next-env'), + '@blitzjs/env': await pack(tempDir, 'next-env'), // '@next/polyfill-module': await pack(tempDir, 'next-polyfill-module'), // '@next/react-dev-overlay': await pack(tempDir, 'react-dev-overlay'), // '@next/react-refresh-utils': await pack(tempDir, 'react-refresh-utils'), diff --git a/package.json b/package.json index 03bf164dc..2de6efb05 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "nextjs/packages/next-mdx", "nextjs/packages/eslint-config-next", "nextjs/packages/eslint-plugin-next", + "nextjs/packages/next-env", "examples/*", "recipes/*" ], @@ -28,14 +29,16 @@ "wait:nextjs": "wait-on -d 1000 nextjs/packages/next/dist/build/index.js", "wait:nextjs-types": "wait-on -d 1000 nextjs/packages/next/dist/build/index.d.ts", "dev:nextjs": "yarn workspace next dev", + "dev:next-env": "yarn workspace @blitzjs/env dev", "dev:nextjs-types": "yarn wait:nextjs && yarn workspace next types && echo 'Finished building nextjs types'", "dev:blitz": "cross-env BLITZ_PROD_BUILD=true preconstruct watch", "dev:tsc": "yarn dev:nextjs-types && tsc --watch --pretty --preserveWatchOutput", "dev:cli": "yarn wait:nextjs-types && yarn workspace @blitzjs/cli dev", "dev:templates": "yarn workspace @blitzjs/generator dev", - "dev": "concurrently --names \"nextjs,blitz,typecheck,cli,templates\" -c \"magenta,cyan,green,yellow,black\" -p \"{name}\" \"npm:dev:nextjs\" \"npm:dev:blitz\" \"npm:dev:tsc\" \"npm:dev:cli\" \"npm:dev:templates\"", + "dev": "concurrently --names \"nextjs,blitz,typecheck,cli,templates,next-env\" -c \"magenta,cyan,green,yellow,black,blue\" -p \"{name}\" \"npm:dev:nextjs\" \"npm:dev:blitz\" \"npm:dev:tsc\" \"npm:dev:cli\" \"npm:dev:templates\" \"npm:dev:next-env\"", "build:nextjs": "yarn workspace next prepublish", - "build": "yarn build:nextjs && cross-env BLITZ_PROD_BUILD=true preconstruct build && ultra -r --filter \"packages/*\" buildpkg && tsc", + "build:next-env": "yarn workspace @blitzjs/env prepublish", + "build": "yarn build:nextjs && yarn build:next-env && cross-env BLITZ_PROD_BUILD=true preconstruct build && ultra -r --filter \"packages/*\" buildpkg && tsc", "lint": "eslint --ext \".js,.ts,.tsx\" .", "link-cli": "yarn workspace blitz link", "unlink-cli": "yarn workspace blitz unlink", @@ -103,7 +106,6 @@ "@types/debug": "4.1.5", "@types/detect-port": "1.3.0", "@types/diff": "5.0.0", - "@types/dotenv-flow": "3.1.0", "@types/flush-write-stream": "1.0.0", "@types/from2": "2.3.0", "@types/fs-extra": "8.1.0", diff --git a/packages/blitz/jest-preset/global-setup.js b/packages/blitz/jest-preset/global-setup.js index 66196efa3..9b0b88271 100644 --- a/packages/blitz/jest-preset/global-setup.js +++ b/packages/blitz/jest-preset/global-setup.js @@ -1,4 +1,6 @@ // eslint-disable-next-line import/no-default-export +import {loadEnvConfig} from "@blitzjs/env" module.exports = function globalSetup(globalConfig) { - require("dotenv-flow").config({silent: true}) + const projectDir = process.cwd() + loadEnvConfig(projectDir) } diff --git a/packages/blitz/src/cli.ts b/packages/blitz/src/cli.ts index efef22dca..86fbf7208 100755 --- a/packages/blitz/src/cli.ts +++ b/packages/blitz/src/cli.ts @@ -33,6 +33,10 @@ async function main() { const cli = require(cliPkgPath) + if (options.e || options.env) { + process.env.APP_ENV = options.e || options.env + } + const hasVersionFlag = options._.length === 0 && (options.v || options.version) const hasVerboseFlag = options._.length === 0 && (options.V || options.verbose) diff --git a/packages/cli/package.json b/packages/cli/package.json index 9ccccfe08..e63b7ad47 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -37,7 +37,6 @@ "chalk": "^4.1.0", "cross-spawn": "7.0.3", "dotenv-expand": "^5.1.0", - "dotenv-flow": "^3.2.0", "enquirer": "2.3.6", "esm": "3.2.25", "fs-extra": "^9.1.0", diff --git a/packages/cli/src/commands/build.ts b/packages/cli/src/commands/build.ts index 2fc4e3e8a..39f701a72 100644 --- a/packages/cli/src/commands/build.ts +++ b/packages/cli/src/commands/build.ts @@ -7,6 +7,10 @@ export class Build extends Command { static flags = { help: flags.help({char: "h"}), + env: flags.string({ + char: "e", + description: "Set app environment name", + }), } async run() { diff --git a/packages/cli/src/commands/codegen.ts b/packages/cli/src/commands/codegen.ts index 6072c0ac9..c509e5354 100644 --- a/packages/cli/src/commands/codegen.ts +++ b/packages/cli/src/commands/codegen.ts @@ -9,6 +9,10 @@ export class CodeGen extends Command { static flags = { help: flags.help({char: "h"}), + env: flags.string({ + char: "e", + description: "Set app environment name", + }), } async run() { diff --git a/packages/cli/src/commands/console.ts b/packages/cli/src/commands/console.ts index 10b322de6..d9549e167 100644 --- a/packages/cli/src/commands/console.ts +++ b/packages/cli/src/commands/console.ts @@ -14,6 +14,10 @@ export class Console extends Command { static flags = { help: flags.help({char: "h"}), + env: flags.string({ + char: "e", + description: "Set app environment name", + }), } async run() { diff --git a/packages/cli/src/commands/db.ts b/packages/cli/src/commands/db.ts index e02da6ccf..2e1fcc6b0 100644 --- a/packages/cli/src/commands/db.ts +++ b/packages/cli/src/commands/db.ts @@ -69,6 +69,10 @@ ${require("chalk").bold("seed")} Generates seeded data in database via Prisma. char: "f", description: `Path to the seeds file, relative to the project root folder. Examples: db/seeds, db/seeds.ts, db/seeds/index.ts, db/my-seeds`, }), + env: flags.string({ + char: "e", + description: "Set app environment name", + }), } static strict = false diff --git a/packages/cli/src/commands/dev.ts b/packages/cli/src/commands/dev.ts index 8ce762db6..96833586d 100644 --- a/packages/cli/src/commands/dev.ts +++ b/packages/cli/src/commands/dev.ts @@ -21,6 +21,10 @@ export class Dev extends Command { "no-incremental-build": flags.boolean({ description: "Disable incremental build and start from a fresh cache", }), + env: flags.string({ + char: "e", + description: "Set app environment name", + }), } async run() { diff --git a/packages/cli/src/commands/export.ts b/packages/cli/src/commands/export.ts index da8590dc3..57c9780e1 100644 --- a/packages/cli/src/commands/export.ts +++ b/packages/cli/src/commands/export.ts @@ -11,6 +11,10 @@ export class Export extends Command { char: "o", description: "set the output dir (defaults to 'out')", }), + env: flags.string({ + char: "e", + description: "Set app environment name", + }), } async run() { diff --git a/packages/cli/src/commands/generate.ts b/packages/cli/src/commands/generate.ts index 9f04f5416..e766e7169 100644 --- a/packages/cli/src/commands/generate.ts +++ b/packages/cli/src/commands/generate.ts @@ -116,6 +116,10 @@ export class Generate extends Command { char: "d", description: "Show what files will be created without writing them to disk", }), + env: flags.string({ + char: "e", + description: "Set app environment name", + }), } static examples = [ diff --git a/packages/cli/src/commands/install.ts b/packages/cli/src/commands/install.ts index 09aa9c178..82c3af006 100644 --- a/packages/cli/src/commands/install.ts +++ b/packages/cli/src/commands/install.ts @@ -94,6 +94,10 @@ export class Install extends Command { default: false, description: "Install the recipe automatically without user confirmation", }), + env: flags.string({ + char: "e", + description: "Set app environment name", + }), } static args = [ diff --git a/packages/cli/src/commands/new.ts b/packages/cli/src/commands/new.ts index 43620d2b9..652fa4580 100644 --- a/packages/cli/src/commands/new.ts +++ b/packages/cli/src/commands/new.ts @@ -1,4 +1,5 @@ import {log} from "@blitzjs/display" +import {loadEnvConfig} from "@blitzjs/env" import type {AppGeneratorOptions} from "@blitzjs/generator" import {getLatestVersion} from "@blitzjs/generator" import {flags} from "@oclif/command" @@ -167,7 +168,8 @@ export class New extends Command { const spinner = log.spinner(log.withBrand("Initializing SQLite database")).start() try { // Required in order for DATABASE_URL to be available - require("dotenv-expand")(require("dotenv-flow").config({silent: true})) + const projectDir = process.cwd() + loadEnvConfig(projectDir) const result = await runPrisma(["migrate", "dev", "--name", "Initial migration"], true) if (!result.success) throw new Error() diff --git a/packages/cli/src/commands/prisma.ts b/packages/cli/src/commands/prisma.ts index 4a42a2e26..dd59668a5 100644 --- a/packages/cli/src/commands/prisma.ts +++ b/packages/cli/src/commands/prisma.ts @@ -1,4 +1,4 @@ -import {Command} from "@oclif/command" +import {Command, flags} from "@oclif/command" import {Readable} from "stream" const getPrismaBin = async () => { @@ -55,6 +55,13 @@ export class PrismaCommand extends Command { static description = "Loads env variables then proxies all args to Prisma CLI" static aliases = ["p"] + static flags = { + env: flags.string({ + char: "e", + description: "Set app environment name", + }), + } + static strict = false async run() { diff --git a/packages/cli/src/commands/start.ts b/packages/cli/src/commands/start.ts index 1626968b2..97dffebf1 100644 --- a/packages/cli/src/commands/start.ts +++ b/packages/cli/src/commands/start.ts @@ -18,6 +18,10 @@ export class Start extends Command { inspect: flags.boolean({ description: "Enable the Node.js inspector", }), + env: flags.string({ + char: "e", + description: "Set app environment name", + }), } async run() { diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 9b528da2d..8cdc8b070 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -2,12 +2,18 @@ require("v8-compile-cache") const cacheFile = require("path").join(__dirname, ".blitzjs-cli-cache") const lazyLoad = require("@salesforce/lazy-require").default.create(cacheFile) lazyLoad.start() +import {loadEnvConfig} from "@blitzjs/env" import {run as oclifRun} from "@oclif/command" import {compileConfig} from "next/dist/server/config-shared" import {getProjectRoot} from "next/dist/server/lib/utils" +const options = require("minimist")(process.argv.slice(2)) +if (options.e || options.env) { + process.env.APP_ENV = options.e || options.env +} + // Load the .env environment variable so it's available for all commands -require("dotenv-expand")(require("dotenv-flow").config({silent: true})) +loadEnvConfig() async function buildConfigIfNeeded() { if (["help", "-h", "autocomplete", "new", "s", "start"].includes(process.argv[2])) { diff --git a/yarn.lock b/yarn.lock index 21ad809cd..5d32ce697 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3197,11 +3197,6 @@ dependencies: webpack-bundle-analyzer "4.3.0" -"@next/env@11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@next/env/-/env-11.1.0.tgz#cae83d8e0a65aa9f2af3368f8269ffd9d911746a" - integrity sha512-zPJkMFRenSf7BLlVee8987G0qQXAhxy7k+Lb/5hLAGkPVHAHm+oFFeL+2ipbI2KTEFlazdmGY0M+AlLQn7pWaw== - "@next/polyfill-module@11.1.0": version "11.1.0" resolved "https://registry.yarnpkg.com/@next/polyfill-module/-/polyfill-module-11.1.0.tgz#ee6b9117a1f9bb137479dfa51d5a9e38e066a62f" @@ -4629,10 +4624,12 @@ resolved "https://registry.yarnpkg.com/@types/diff/-/diff-5.0.0.tgz#eb71e94feae62548282c4889308a3dfb57e36020" integrity sha512-jrm2K65CokCCX4NmowtA+MfXyuprZC13jbRuwprs6/04z/EcFg/MCwYdsHn+zgV4CQBiATiI7AEq7y1sZCtWKA== -"@types/dotenv-flow@3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@types/dotenv-flow/-/dotenv-flow-3.1.0.tgz#e9ba53f95f3d40bbc0df99b206f649b2acfca0c6" - integrity sha512-qaWT42KDePdAGZFryYoV7EZnuuYZAO4KPVDWUV9OBOyJx7xCgKKERtVB7jBCM2mtKVI+OMMDK2ef11PWcHJz3g== +"@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/duplexify@*": version "3.6.0" @@ -5866,6 +5863,11 @@ async-retry "1.2.3" lru-cache "5.1.1" +"@zeit/ncc@0.20.4": + version "0.20.4" + resolved "https://registry.yarnpkg.com/@zeit/ncc/-/ncc-0.20.4.tgz#00f0a25a88cac3712af4ba66561d9e281c6f05c9" + integrity sha512-fmq+F/QxPec+k/zvT7HiVpk7oiGFseS6brfT/AYqmCUp6QFRK7vZf2Ref46MImsg/g2W3g5X6SRvGRmOAvEfdA== + "@zeit/next-css@1.0.2-canary.2": version "1.0.2-canary.2" resolved "https://registry.yarnpkg.com/@zeit/next-css/-/next-css-1.0.2-canary.2.tgz#0eeb877e7469892b65471c1ec7c14346b8f240df" @@ -9924,14 +9926,12 @@ dotenv-expand@^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-flow@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/dotenv-flow/-/dotenv-flow-3.2.0.tgz#a5d79dd60ddb6843d457a4874aaf122cf659a8b7" - integrity sha512-GEB6RrR4AbqDJvNSFrYHqZ33IKKbzkvLYiD5eo4+9aFXr4Y4G+QaFrB/fNp0y6McWBmvaPn3ZNjIufnj8irCtg== - dependencies: - dotenv "^8.0.0" +dotenv@*: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== -dotenv@^8.0.0, dotenv@^8.2.0: +dotenv@8.2.0, 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==