Skip to content
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
36 changes: 36 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,42 @@ updates:
- "dependencies"
- "auto-update"

- package-ecosystem: "npm"
directory: "/configs/eslint"
schedule:
interval: "weekly"
commit-message:
prefix: "chore"
include: "scope"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "auto-update"

- package-ecosystem: "npm"
directory: "/configs/prettier"
schedule:
interval: "weekly"
commit-message:
prefix: "chore"
include: "scope"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "auto-update"

- package-ecosystem: "npm"
directory: "/configs/semantic-release"
schedule:
interval: "weekly"
commit-message:
prefix: "chore"
include: "scope"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "auto-update"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
Expand Down
39 changes: 39 additions & 0 deletions .github/workflows/close-superseded-dependabot-prs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Close superseded Dependabot PRs

on:
pull_request:
types: [closed]

permissions:
pull-requests: write
contents: read

jobs:
close-superseded:
if: github.event.pull_request.merged == true && github.actor == 'dependabot[bot]'
runs-on: ubuntu-latest
steps:
- name: Fetch Dependabot metadata
uses: dependabot/fetch-metadata@v2
id: metadata

- name: Close superseded PRs
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
DEPENDENCY="${{ steps.metadata.outputs.dependency-names }}"
PR_NUMBER="${{ github.event.pull_request.number }}"

gh pr list \
--repo "${{ github.repository }}" \
--state open \
--label "dependencies" \
--json number,title,author \
--jq ".[] | select(.author.login == \"dependabot[bot]\") | select(.title | test(\"$DEPENDENCY\")) | .number" \
| while read -r num; do
if [ "$num" != "$PR_NUMBER" ]; then
gh pr close "$num" \
--repo "${{ github.repository }}" \
--comment "Superseded by #$PR_NUMBER which updated $DEPENDENCY to a newer version."
fi
done
6 changes: 3 additions & 3 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ jobs:
- name: Install dependencies
run: pnpm install

- name: Build
run: pnpm --filter "./packages/*" --filter "./configs/*" --filter "./examples/*" build

- name: Run linter
run: pnpm --filter "./packages/*" --filter "./examples/*" lint

- name: Run unit tests
run: pnpm --filter "./packages/*" --filter "./examples/*" test:unit:ci

- name: Build
run: pnpm --filter "./packages/*" --filter "./examples/*" build

- name: Run e2e tests
run: pnpm --filter "./packages/*" --filter "./examples/*" test:e2e:ci
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
run: pnpm --filter "./packages/*" --filter "./examples/*" test:unit:ci

- name: Build
run: pnpm --filter "./packages/*" --filter "./examples/*" build
run: pnpm --filter "./packages/*" --filter "./configs/*" --filter "./examples/*" build

- name: Run e2e tests
run: pnpm --filter "./packages/*" --filter "./examples/*" test:e2e:ci
Expand Down
104 changes: 22 additions & 82 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,106 +1,46 @@
# envex
# Monorepo

> Runtime environment variables for Next.js

[![npm version](https://img.shields.io/npm/v/@daniel-rose/envex.svg)](https://www.npmjs.com/package/@daniel-rose/envex)
[![License](https://img.shields.io/npm/l/@daniel-rose/envex.svg)](https://github.com/daniel-rose/envex/blob/main/LICENSE)

Envex dynamically injects environment variables into your Next.js (>= 15) application at runtime.
This approach follows the **"build once, deploy many"** principle, allowing the same build to be used across different environments without requiring rebuilds.
Fully supports both client- and server-side usage with TypeScript.
Monorepo for envex and shared configurations.

> **Note:** The main package code is located in `packages/envex`.
## Packages

---
| Package | Description |
|---------|-------------|
| [@daniel-rose/envex](./packages/envex) | Runtime environment variables for Next.js |
| [@daniel-rose/eslint-config](./configs/eslint) | Shared ESLint configuration |
| [@daniel-rose/prettier-config](./configs/prettier) | Shared Prettier configuration |
| [@daniel-rose/semantic-release-config](./configs/semantic-release) | Shared semantic-release configuration |

## Monorepo Structure

```text
envex/
├─ packages/
│ └─ envex/ # main package code
└─ examples/
└─ nextjs/ # example Next.js integration
├─ packages/envex/ # main library
├─ configs/eslint/ # ESLint config
├─ configs/prettier/ # Prettier config
├─ configs/semantic-release/ # semantic-release config
└─ examples/nextjs/ # example Next.js integration
```

## Installation
## Development

```bash
# npm
npm install @daniel-rose/envex

# yarn
yarn add @daniel-rose/envex

# pnpm
pnpm add @daniel-rose/envex
```

## Usage

### Client

Wrap your app in layout.tsx with EnvScript and EnvProvider to enable the useEnv hook:

```tsx
// app/layout.tsx
import { EnvScript } from "@daniel-rose/envex/script";
import { EnvexProvider } from "@daniel-rose/envex";
import { getPublicEnv } from "@daniel-rose/envex/server";

export default async function RootLayout({ children }: { children: React.ReactNode }) {
const initialEnv = await getPublicEnv()

return (
<html lang="en">
<body>
<EnvScript />
<EnvexProvider initialEnv={initialEnv}>{children}</EnvexProvider>
</body>
</html>
)
}
```

Then you can use useEnv anywhere in your client components:
# install dependencies
pnpm install

```tsx
import { useEnv } from '@daniel-rose/envex'
# build all packages
pnpm build

export function Example() {
const env = useEnv()
return <div>{env['NEXT_PUBLIC_API_URL']}</div>
}
# run tests
pnpm test
```

### Server

Access environment variables via callbacks:

```ts
import { getEnv, getEnvByName, getPublicEnv, getPublicEnvByName } from '@daniel-rose/envex/server'

const allEnv = getEnv()
const secret = getEnvByName('DATABASE_PASSWORD')

const onlyPublicEnv = getPublicEnv()
const apiUrl = getPublicEnvByName('NEXT_PUBLIC_API_URL')
```

## Example

See examples/nextjs for a full integration.

## Testing

- Unit tests: Vitest
- End-to-end: Playwright

## License

MIT &copy; Daniel Rose

Links

- GitHub: https://github.com/daniel-rose/envex
- npm: https://www.npmjs.com/package/@daniel-rose/envex
- GitHub: https://github.com/daniel-rose/monorepo
24 changes: 24 additions & 0 deletions configs/eslint/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
4 changes: 4 additions & 0 deletions configs/eslint/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist
coverage
docker
certs
3 changes: 3 additions & 0 deletions configs/eslint/.prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import baseConfig from '@daniel-rose/prettier-config'

export default { ...baseConfig }
3 changes: 3 additions & 0 deletions configs/eslint/.releaserc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "@daniel-rose/semantic-release-config"
}
59 changes: 59 additions & 0 deletions configs/eslint/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# @daniel-rose/eslint-config

> Shared ESLint configuration for TypeScript and React projects

[![npm version](https://img.shields.io/npm/v/@daniel-rose/eslint-config.svg)](https://www.npmjs.com/package/@daniel-rose/eslint-config)
[![License](https://img.shields.io/npm/l/@daniel-rose/eslint-config.svg)](https://github.com/daniel-rose/envex/blob/main/LICENSE)

Opinionated ESLint 9 flat config with TypeScript, Prettier integration, and an optional React preset.

## Installation

```bash
# npm
npm install @daniel-rose/eslint-config

# yarn
yarn add @daniel-rose/eslint-config

# pnpm
pnpm add @daniel-rose/eslint-config
```

Peer dependencies: `eslint ^9`
Optional peers (for React): `eslint-plugin-react-hooks`, `eslint-plugin-react-refresh`

## Usage

### Base (TypeScript)

```js
// eslint.config.js
import { baseConfig } from '@daniel-rose/eslint-config'

export default [...baseConfig]
```

### React

```js
// eslint.config.js
import { reactConfig } from '@daniel-rose/eslint-config/react'

export default [...reactConfig]
```

### Included Rules

- `no-console` — only `warn` and `error` are allowed
- `@typescript-eslint/consistent-type-imports` — enforces `type` imports
- Prettier integration via `eslint-plugin-prettier`

## License

MIT &copy; Daniel Rose

Links

- GitHub: https://github.com/daniel-rose/envex
- npm: https://www.npmjs.com/package/@daniel-rose/eslint-config
36 changes: 36 additions & 0 deletions configs/eslint/base.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import js from '@eslint/js'
import eslintConfigPrettier from 'eslint-config-prettier/flat'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
import tseslint from 'typescript-eslint'

export const sharedRules = {
'no-console': ['error', { allow: ['warn', 'error'] }],
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
fixStyle: 'separate-type-imports',
},
],
}

export const baseConfig = tseslint.config(
{ ignores: ['dist'] },
{
files: ['**/*.ts'],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
projectService: true,
},
sourceType: 'module',
},
extends: [
js.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
],
rules: sharedRules,
},
eslintConfigPrettier,
eslintPluginPrettierRecommended
)
10 changes: 10 additions & 0 deletions configs/eslint/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import js from '@eslint/js'
import eslintConfigPrettier from 'eslint-config-prettier/flat'
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'

export default [
{ ignores: ['dist'] },
js.configs.recommended,
eslintConfigPrettier,
eslintPluginPrettierRecommended,
]
1 change: 1 addition & 0 deletions configs/eslint/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { baseConfig, sharedRules } from './base.js'
Loading