Skip to content

Commit

Permalink
feat!: read .env.test and allow overriding with nuxt.dotenv options
Browse files Browse the repository at this point in the history
  • Loading branch information
danielroe committed Dec 16, 2023
1 parent bbaa15b commit de116eb
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 14 deletions.
1 change: 1 addition & 0 deletions examples/app-vitest-full/.env.test
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NUXT_PUBLIC_TEST_VALUE=123
1 change: 1 addition & 0 deletions examples/app-vitest-full/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export default defineNuxtConfig({
runtimeConfig: {
public: {
hello: 'world',
testValue: 'default'
},
},
})
1 change: 1 addition & 0 deletions examples/app-vitest-full/tests/nuxt/config.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ it('should return the runtimeConfig from nuxt.config', () => {
expect(config).toBeTypeOf('object')
expect(config?.public).toEqual({
hello: 'world',
testValue: 123,
})
})
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"pathe": "^1.1.1",
"perfect-debounce": "^1.0.0",
"radix3": "^1.1.0",
"scule": "^1.1.1",
"std-env": "^3.6.0",
"ufo": "^1.3.2",
"unenv": "^1.8.0",
Expand Down
16 changes: 10 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 33 additions & 8 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,43 @@
import type { Nuxt, NuxtConfig } from '@nuxt/schema'
import type { InlineConfig as VitestConfig } from 'vitest'
import { defineConfig } from 'vite'
import type { DotenvOptions } from 'c12'
import type { InlineConfig } from 'vite'
import { defu } from 'defu'
import { createResolver } from '@nuxt/kit'

import { applyEnv } from './config/utils'

interface GetVitestConfigOptions {
nuxt: Nuxt
viteConfig: InlineConfig
}

interface LoadNuxtOptions {
dotenv?: Partial<DotenvOptions>
overrides?: Partial<NuxtConfig>
}

// https://github.com/nuxt/framework/issues/6496
async function startNuxtAndGetViteConfig(
rootDir = process.cwd(),
overrides?: Partial<NuxtConfig>
options: LoadNuxtOptions = {}
) {
const { loadNuxt, buildNuxt } = await import('@nuxt/kit')
const nuxt = await loadNuxt({
cwd: rootDir,
dev: false,
dotenv: defu(options.dotenv, {
cwd: rootDir,
fileName: '.env.test'
}),
overrides: defu(
{
ssr: false,
test: true,
modules: ['@nuxt/test-utils/module'],
},
overrides
options.overrides
),
})

Expand Down Expand Up @@ -61,14 +73,17 @@ const excludedPlugins = [

export async function getVitestConfigFromNuxt(
options?: GetVitestConfigOptions,
overrides?: NuxtConfig
loadNuxtOptions: LoadNuxtOptions = {}
): Promise<InlineConfig & { test: VitestConfig }> {
const { rootDir = process.cwd(), ..._overrides } = overrides || {}
const { rootDir = process.cwd(), ..._overrides } = loadNuxtOptions.overrides || {}

if (!options) {
options = await startNuxtAndGetViteConfig(rootDir, {
test: true,
..._overrides
dotenv: loadNuxtOptions.dotenv,
overrides: {
test: true,
..._overrides
}
})
}

Expand All @@ -83,7 +98,7 @@ export async function getVitestConfigFromNuxt(
test: {
dir: process.cwd(),
environmentOptions: {
nuxtRuntimeConfig: options.nuxt.options.runtimeConfig,
nuxtRuntimeConfig: applyEnv(options.nuxt.options.runtimeConfig, { prefix: 'NUXT_' }),
nuxtRouteRules: defu(
{},
options.nuxt.options.routeRules,
Expand Down Expand Up @@ -177,7 +192,10 @@ export function defineVitestConfig(config: InlineConfig & { test?: VitestConfig

return defu(
config,
await getVitestConfigFromNuxt(undefined, structuredClone(overrides)),
await getVitestConfigFromNuxt(undefined, {
dotenv: config.test?.environmentOptions?.nuxt?.dotenv,
overrides: structuredClone(overrides)
}),
)
})
}
Expand All @@ -191,6 +209,13 @@ declare module 'vitest' {
* @default {http://localhost:3000}
*/
url?: string
/**
* You can define how environment options are read when loading the Nuxt configuration.
*/
dotenv?: Partial<DotenvOptions>
/**
* Configuration that will override the values in your `nuxt.config` file.
*/
overrides?: NuxtConfig
/**
* The id of the root div to which the app should be mounted. You should also set `app.rootId` to the same value.
Expand Down
48 changes: 48 additions & 0 deletions src/config/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// TODO: export these
// https://github.com/unjs/nitro/tree/main/src/runtime/utils.env.ts

import destr from 'destr'
import { snakeCase } from 'scule'

export type EnvOptions = {
prefix?: string
altPrefix?: string
}

export function getEnv (key: string, opts: EnvOptions) {
const envKey = snakeCase(key).toUpperCase()
return destr(
process.env[opts.prefix + envKey] ?? process.env[opts.altPrefix + envKey]
)
}

function _isObject (input: unknown) {
return typeof input === 'object' && !Array.isArray(input)
}

export function applyEnv (obj: Record<string, any>, opts: EnvOptions, parentKey = '') {
for (const key in obj) {
const subKey = parentKey ? `${parentKey}_${key}` : key
const envValue = getEnv(subKey, opts)
if (_isObject(obj[key])) {
// Same as before
if (_isObject(envValue)) {
obj[key] = { ...obj[key], ...(envValue as object) }
applyEnv(obj[key], opts, subKey)
}
// If envValue is undefined
// Then proceed to nested properties
else if (envValue === undefined) {
applyEnv(obj[key], opts, subKey)
}
// If envValue is a primitive other than undefined
// Then set objValue and ignore the nested properties
else {
obj[key] = envValue ?? obj[key]
}
} else {
obj[key] = envValue ?? obj[key]
}
}
return obj
}

0 comments on commit de116eb

Please sign in to comment.