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 support for new env handling #10525

Merged
merged 41 commits into from Mar 26, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
fb4c0de
Add initial support for new env config file
ijjk Feb 13, 2020
f369d2d
Fix serverless processEnv call when no env is provided
ijjk Feb 13, 2020
f12ac4c
Add missing await for test method
ijjk Feb 13, 2020
3e3878a
Merge remote-tracking branch 'upstream/canary' into add/new-env
ijjk Feb 13, 2020
455dc35
Merge remote-tracking branch 'upstream/canary' into add/new-env
ijjk Feb 14, 2020
f0d817a
Update env config to .env.json and add dotenv loading
ijjk Feb 14, 2020
97f7e56
ncc dotenv package
ijjk Feb 14, 2020
444c401
Update type
ijjk Feb 14, 2020
8ee3eab
Merge remote-tracking branch 'upstream/canary' into add/new-env
ijjk Feb 17, 2020
e5b4ee0
Merge remote-tracking branch 'upstream/canary' into add/new-env
ijjk Feb 20, 2020
31953f7
Update with new discussed behavior removing .env.json
ijjk Feb 21, 2020
30f1a52
Merge remote-tracking branch 'upstream/canary' into add/new-env
ijjk Feb 21, 2020
266d095
Update hot-reloader createEntrypoints
ijjk Feb 21, 2020
9a6cd9e
Make sure .env is loaded before next.config.js
timneutkens Feb 24, 2020
66bf869
Add tests for all separate .env files
timneutkens Feb 24, 2020
78cb909
Remove comments
timneutkens Feb 24, 2020
cfb627e
Add override tests
timneutkens Feb 24, 2020
1c1b604
Add test for overriding env vars based on local environment
timneutkens Feb 24, 2020
25cf95c
Add support for .env.test
timneutkens Feb 24, 2020
6929e28
Apply suggestions from code review
ijjk Feb 24, 2020
e2f1877
Use chalk for env loaded message
ijjk Feb 24, 2020
d28de35
Remove constant as it’s not needed
timneutkens Feb 24, 2020
ac16529
Update test
ijjk Feb 24, 2020
129801e
Merge branch 'add/new-env' of github.com:ijjk/next.js into add/new-env
ijjk Feb 24, 2020
5a1580a
Merge remote-tracking branch 'upstream/canary' into add/new-env
ijjk Feb 24, 2020
a0c709a
Update errsh, taskr, and CNA template ignores
ijjk Feb 24, 2020
f7a0030
Make sure to only consider undefined missing
ijjk Feb 24, 2020
ab97e46
Merge remote-tracking branch 'upstream/canary' into add/new-env
ijjk Feb 24, 2020
e9401b3
Remove old .env ignore
ijjk Feb 24, 2020
cd0452d
Merge branch 'canary' into add/new-env
ijjk Feb 24, 2020
bdd50e3
Merge remote-tracking branch 'upstream/canary' into add/new-env
ijjk Feb 25, 2020
69340a3
Update to not populate process.env with loaded env
ijjk Feb 25, 2020
dea3ac5
Merge branch 'add/new-env' of github.com:ijjk/next.js into add/new-env
ijjk Feb 25, 2020
0b67f2f
Merge remote-tracking branch 'upstream/canary' into add/new-env
ijjk Feb 27, 2020
350a451
Merge remote-tracking branch 'upstream/canary' into add/new-env
ijjk Feb 28, 2020
9d086c1
Merge remote-tracking branch 'upstream/canary' into add/new-env
ijjk Mar 3, 2020
2eb53f0
Merge remote-tracking branch 'upstream/canary' into add/new-env
ijjk Mar 5, 2020
b2dee6e
Merge remote-tracking branch 'upstream/canary' into add/new-env
ijjk Mar 20, 2020
1eb2d2b
Add experimental flag and add loading of global env values
ijjk Mar 20, 2020
4bceac7
Merge remote-tracking branch 'upstream/canary' into add/new-env
ijjk Mar 20, 2020
67cf471
Merge branch 'canary' of github.com:zeit/next.js into ijjk-add/new-env
timneutkens Mar 26, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/next/build/entries.ts
Expand Up @@ -7,6 +7,7 @@ import { isTargetLikeServerless } from '../next-server/server/config'
import { normalizePagePath } from '../next-server/server/normalize-page-path'
import { warn } from './output/log'
import { ServerlessLoaderQuery } from './webpack/loaders/next-serverless-loader'
import { EnvironmentConfig } from '../lib/load-env-config'

type PagesMapping = {
[page: string]: string
Expand Down Expand Up @@ -64,7 +65,8 @@ export function createEntrypoints(
target: 'server' | 'serverless' | 'experimental-serverless-trace',
buildId: string,
previewMode: __ApiPreviewProps,
config: any
config: any,
envConfig: EnvironmentConfig
): Entrypoints {
const client: WebpackEntrypoints = {}
const server: WebpackEntrypoints = {}
Expand All @@ -73,6 +75,8 @@ export function createEntrypoints(
Object.keys(config.publicRuntimeConfig).length > 0 ||
Object.keys(config.serverRuntimeConfig).length > 0

const hasEnvConfig = Object.keys(envConfig).length > 0

const defaultServerlessOptions = {
absoluteAppPath: pages['/_app'],
absoluteDocumentPath: pages['/_document'],
Expand All @@ -90,6 +94,7 @@ export function createEntrypoints(
})
: '',
previewProps: JSON.stringify(previewMode),
envConfig: hasEnvConfig ? JSON.stringify(envConfig) : '',
}

Object.keys(pages).forEach(page => {
Expand Down
8 changes: 7 additions & 1 deletion packages/next/build/index.ts
Expand Up @@ -70,6 +70,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)
Expand Down Expand Up @@ -206,14 +207,19 @@ export default async function build(dir: string, conf = null): Promise<void> {
previewModeSigningKey: crypto.randomBytes(32).toString('hex'),
previewModeEncryptionKey: crypto.randomBytes(32).toString('hex'),
}
// TODO: should we process config here also to show any errors
// from missing env values which will be missing for auto-export
// pages/SSG pages after the build completes
const envConfig = loadEnvConfig(dir, false)

const mappedPages = createPagesMapping(pagePaths, config.pageExtensions)
const entrypoints = createEntrypoints(
mappedPages,
target,
buildId,
previewProps,
config
config,
envConfig
)
const pageKeys = Object.keys(mappedPages)
const dynamicRoutes = pageKeys.filter(page => isDynamicRoute(page))
Expand Down
19 changes: 19 additions & 0 deletions packages/next/build/webpack/loaders/next-serverless-loader.ts
Expand Up @@ -26,6 +26,7 @@ export type ServerlessLoaderQuery = {
basePath: string
runtimeConfig: string
previewProps: string
envConfig: string
}

const nextServerlessLoader: loader.Loader = function() {
Expand All @@ -43,6 +44,7 @@ const nextServerlessLoader: loader.Loader = function() {
basePath,
runtimeConfig,
previewProps,
envConfig,
}: ServerlessLoaderQuery =
typeof this.query === 'string' ? parse(this.query.substr(1)) : this.query

Expand Down Expand Up @@ -138,10 +140,21 @@ const nextServerlessLoader: loader.Loader = function() {
}
`

const envImports = envConfig
? `
const { processEnv } = require('next/dist/lib/load-env-config')
`
: ''

const processedEnv = `
const processedEnv = ${envConfig ? `processEnv(${envConfig})` : '{}'}
`

if (page.match(API_ROUTE)) {
return `
import initServer from 'next-plugin-loader?middleware=on-init-server!'
import onError from 'next-plugin-loader?middleware=on-error-server!'
${envImports}
${runtimeConfigImports}
${
/*
Expand All @@ -157,6 +170,8 @@ const nextServerlessLoader: loader.Loader = function() {
${dynamicRouteMatcher}
${handleRewrites}

${processedEnv}

export default async (req, res) => {
try {
await initServer()
Expand Down Expand Up @@ -185,6 +200,7 @@ const nextServerlessLoader: loader.Loader = function() {
Object.assign({}, parsedUrl.query, params ),
resolver,
${encodedPreviewProps},
processedEnv,
onError
)
} catch (err) {
Expand Down Expand Up @@ -215,6 +231,7 @@ const nextServerlessLoader: loader.Loader = function() {
const App = require('${absoluteAppPath}').default;
${dynamicRouteImports}
${rewriteImports}
${envImports}

const ComponentInfo = require('${absolutePagePath}')

Expand All @@ -227,6 +244,7 @@ const nextServerlessLoader: loader.Loader = function() {

${dynamicRouteMatcher}
${handleRewrites}
${processedEnv}

export const config = ComponentInfo['confi' + 'g'] || {}
export const _app = App
Expand All @@ -253,6 +271,7 @@ const nextServerlessLoader: loader.Loader = function() {
assetPrefix: "${assetPrefix}",
runtimeConfig: runtimeConfig.publicRuntimeConfig || {},
previewProps: ${encodedPreviewProps},
env: processedEnv,
..._renderOpts
}
let _nextData = false
Expand Down
2 changes: 2 additions & 0 deletions packages/next/export/index.ts
Expand Up @@ -34,6 +34,7 @@ import loadConfig, {
import { eventVersion } 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 mkdirp = promisify(mkdirpModule)
const copyFile = promisify(copyFileOrig)
Expand Down Expand Up @@ -226,6 +227,7 @@ export default async function(
dir,
buildId,
nextExport: true,
env: loadEnvConfig(dir),
assetPrefix: nextConfig.assetPrefix.replace(/\/$/, ''),
distDir,
dev: false,
Expand Down
2 changes: 1 addition & 1 deletion packages/next/lib/find-pages-dir.ts
@@ -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
Expand Down
84 changes: 84 additions & 0 deletions packages/next/lib/load-env-config.ts
@@ -0,0 +1,84 @@
import fs from 'fs'
import path from 'path'
import { existsSync } from './find-pages-dir'
import { ENV_CONFIG_FILE } from '../next-server/lib/constants'

type EnvironmentKey = {
description: string
required?: boolean // defaults to false
value?: string // the default value
env?: {
development: Omit<EnvironmentKey, 'env'>
production: Omit<EnvironmentKey, 'env'>
test: Omit<EnvironmentKey, 'env'>
}
}
export type EnvironmentConfig = {
[key: string]: EnvironmentKey
}

export type Env = { [key: string]: string }

export function processEnv(_Env: EnvironmentConfig): Env {
const missingEnvItems = new Set()
const Env: Env = {}

for (const key of Object.keys(_Env)) {
const envItem = _Env[key]
let curValue: string | undefined = envItem.value
let isRequired = envItem.required

if (process.env[key]) {
curValue = process.env[key]
} else if (envItem.env) {
const nodeEnv = process.env.NODE_ENV
const subEnv = envItem.env[nodeEnv]

if (subEnv) {
if (typeof subEnv.required === 'boolean') {
isRequired = subEnv.required
}

if (subEnv.value) {
curValue = subEnv.value
}
}
}

if (curValue) {
Env[key] = curValue
} else if (isRequired) {
missingEnvItems.add(key)
}
}

if (missingEnvItems.size > 0) {
throw new Error(
`Required environment items from \`${ENV_CONFIG_FILE}\` are missing: ` +
`${[...missingEnvItems].join(', ')}`
)
}
return Env
}

// loads andy maybe populate env config items
export function loadEnvConfig(dir: string): Env
export function loadEnvConfig(dir: string, process: false): EnvironmentConfig
export function loadEnvConfig(dir: any, process?: any): any {
const envPath = path.join(dir, ENV_CONFIG_FILE)

// don't use require so it's not cached in module cache
// in case we want to reload this file
if (!existsSync(envPath)) {
return {}
}
// don't use require so it's not cached in module cache
// in case we want to reload this file
const _Env: EnvironmentConfig = JSON.parse(fs.readFileSync(envPath, 'utf8'))

// TODO: do we want to validate extra/invalid fields in env.json?
if (process === false) {
return _Env
}
return processEnv(_Env)
}
1 change: 1 addition & 0 deletions packages/next/next-server/lib/constants.ts
Expand Up @@ -12,6 +12,7 @@ export const REACT_LOADABLE_MANIFEST = 'react-loadable-manifest.json'
export const SERVER_DIRECTORY = 'server'
export const SERVERLESS_DIRECTORY = 'serverless'
export const CONFIG_FILE = 'next.config.js'
export const ENV_CONFIG_FILE = 'env.json'
ijjk marked this conversation as resolved.
Show resolved Hide resolved
export const BUILD_ID_FILE = 'BUILD_ID'
export const BLOCKED_PAGES = ['/_document', '/_app']
export const CLIENT_PUBLIC_FILES_PATH = 'public'
Expand Down
3 changes: 3 additions & 0 deletions packages/next/next-server/lib/utils.ts
Expand Up @@ -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
Expand Down Expand Up @@ -180,6 +181,8 @@ export type NextApiRequest = IncomingMessage & {
}

body: any

env: Env
}

/**
Expand Down
19 changes: 11 additions & 8 deletions packages/next/next-server/server/api-utils.ts
Expand Up @@ -8,6 +8,7 @@ import { isResSent, NextApiRequest, NextApiResponse } from '../lib/utils'
import { decryptWithSecret, encryptWithSecret } from './crypto-utils'
import { interopDefault } from './load-components'
import { Params } from './router'
import { Env } from '../../lib/load-env-config'

export type NextApiRequestCookies = { [key: string]: string }
export type NextApiRequestQuery = { [key: string]: string | string[] }
Expand All @@ -24,26 +25,28 @@ export async function apiResolver(
params: any,
resolverModule: any,
apiContext: __ApiPreviewProps,
envConfig: Env,
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 = config.env
? config.env.reduce((prev: Env, key): Env => {
prev[key] = envConfig[key]
return prev
}, {})
: {}

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
Expand Down
3 changes: 3 additions & 0 deletions packages/next/next-server/server/load-components.ts
Expand Up @@ -11,6 +11,7 @@ import { requirePage } from './require'
import { BuildManifest } from './get-page-files'
import { AppType, DocumentType } from '../lib/utils'
import { PageConfig, NextPageContext } from 'next/types'
import { Env } from '../../lib/load-env-config'

export function interopDefault(mod: any) {
return mod.default || mod
Expand All @@ -26,6 +27,7 @@ export type ManifestItem = {
type ReactLoadableManifest = { [moduleId: string]: ManifestItem[] }

type Unstable_getStaticProps = (ctx: {
env: Env
params: ParsedUrlQuery | undefined
preview?: boolean
previewData?: any
Expand All @@ -39,6 +41,7 @@ export type Unstable_getStaticPaths = () => Promise<{
}>

type Unstable_getServerProps = (context: {
env: Env
params: ParsedUrlQuery | undefined
req: IncomingMessage
res: ServerResponse
Expand Down
5 changes: 5 additions & 0 deletions packages/next/next-server/server/next-server.ts
Expand Up @@ -59,6 +59,7 @@ import {
setSprCache,
} from './spr-cache'
import { isBlockedPage } from './utils'
import { Env, loadEnvConfig } from '../../lib/load-env-config'

const getCustomRouteMatcher = pathMatch(true)

Expand Down Expand Up @@ -115,6 +116,7 @@ export default class Server {
hasCssMode: boolean
dev?: boolean
pages404?: boolean
env: Env
}
private compression?: Middleware
private onErrorMiddleware?: ({ err }: { err: Error }) => Promise<void>
Expand Down Expand Up @@ -163,6 +165,8 @@ export default class Server {
buildId: this.buildId,
generateEtags,
pages404: this.nextConfig.experimental.pages404,
// TODO: do we want to hot-reload this in development at all?
env: loadEnvConfig(dir),
}

// Only the `publicRuntimeConfig` key is exposed to the client side
Expand Down Expand Up @@ -671,6 +675,7 @@ export default class Server {
query,
pageModule,
{ ...previewProps },
this.renderOpts.env,
this.onErrorMiddleware
)
return true
Expand Down