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
2 changes: 1 addition & 1 deletion functions/send-email-link/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"dependencies": {
"@constructive-io/knative-job-fn": "workspace:^",
"@launchql/mjml": "0.1.1",
"@launchql/postmaster": "0.1.4",
"@constructive-io/postmaster": "workspace:^",
"@launchql/styled-email": "0.1.0",
"@pgpmjs/env": "workspace:^",
"@pgpmjs/logger": "workspace:^",
Expand Down
2 changes: 1 addition & 1 deletion functions/send-email-link/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createJobApp } from '@constructive-io/knative-job-fn';
import { GraphQLClient } from 'graphql-request';
import gql from 'graphql-tag';
import { generate } from '@launchql/mjml';
import { send as sendPostmaster } from '@launchql/postmaster';
import { send as sendPostmaster } from '@constructive-io/postmaster';
import { send as sendSmtp } from 'simple-smtp-server';
import { parseEnvBoolean } from '@pgpmjs/env';
import { createLogger } from '@pgpmjs/logger';
Expand Down
2 changes: 1 addition & 1 deletion functions/simple-email/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
},
"dependencies": {
"@constructive-io/knative-job-fn": "workspace:^",
"@launchql/postmaster": "0.1.4",
"@constructive-io/postmaster": "workspace:^",
"@pgpmjs/env": "workspace:^",
"@pgpmjs/logger": "workspace:^",
"simple-smtp-server": "workspace:^"
Expand Down
2 changes: 1 addition & 1 deletion functions/simple-email/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createJobApp } from '@constructive-io/knative-job-fn';
import { send as sendSmtp } from 'simple-smtp-server';
import { send as sendPostmaster } from '@launchql/postmaster';
import { send as sendPostmaster } from '@constructive-io/postmaster';
import { parseEnvBoolean } from '@pgpmjs/env';
import { createLogger } from '@pgpmjs/logger';

Expand Down
2 changes: 1 addition & 1 deletion jobs/knative-job-service/__tests__/jobs.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ type MailgunFailurePayload = {
};

const createMailgunFailureApp = () => {
const { send: sendPostmaster } = require('@launchql/postmaster');
const { send: sendPostmaster } = require('@constructive-io/postmaster');
const app = createJobApp();

app.post('/', async (req: any, res: any, next: any) => {
Expand Down
5 changes: 3 additions & 2 deletions jobs/knative-job-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@
"bugs": {
"url": "https://github.com/constructive-io/jobs/issues"
},
"devDependencies": {
"@constructive-io/graphql-server": "workspace:^",
"devDependencies": {
"@constructive-io/postmaster": "workspace:^",
"@constructive-io/graphql-server": "workspace:^",
"@constructive-io/graphql-types": "workspace:^",
"@pgpm/database-jobs": "^0.16.0",
"@pgpm/inflection": "^0.16.0",
Expand Down
175 changes: 175 additions & 0 deletions packages/12factor-env/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# 12factor-env

<p align="center" width="100%">
<img height="250" src="https://raw.githubusercontent.com/constructive-io/constructive/refs/heads/main/assets/outline-logo.svg" />
</p>

<p align="center" width="100%">
<a href="https://github.com/constructive-io/constructive/actions/workflows/run-tests.yaml">
<img height="20" src="https://github.com/constructive-io/constructive/actions/workflows/run-tests.yaml/badge.svg" />
</a>
<a href="https://github.com/constructive-io/constructive/blob/main/LICENSE">
<img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/>
</a>
<a href="https://www.npmjs.com/package/12factor-env">
<img height="20" src="https://img.shields.io/github/package-json/v/constructive-io/constructive?filename=packages%2F12factor-env%2Fpackage.json"/>
</a>
</p>

> Environment variable validation with secret file support for 12-factor apps

A TypeScript library for validating environment variables with built-in support for Docker/Kubernetes secret files. Built on top of [envalid](https://github.com/af/envalid) with additional features for reading secrets from files.

## Installation

```bash
npm install 12factor-env
```

## Usage

```ts
import { env, str, num, bool, port, email, host } from '12factor-env';

const config = env(
process.env,
{
// Required environment variables
DATABASE_URL: str(),
API_KEY: str()
},
{
// Optional environment variables with defaults
PORT: port({ default: 3000 }),
DEBUG: bool({ default: false }),
LOG_LEVEL: str({ default: 'info' })
}
);

console.log(config.DATABASE_URL); // Validated string
console.log(config.PORT); // Validated number
```

## Secret File Support

This library supports reading secrets from files, which is useful for Docker secrets and Kubernetes secrets that are mounted as files.

### Direct Secret Files

Secrets can be read from `/run/secrets/` (or a custom path via `ENV_SECRETS_PATH`):

```ts
import { env, str } from '12factor-env';

// If /run/secrets/DATABASE_PASSWORD exists, it will be read automatically
const config = env(process.env, {
DATABASE_PASSWORD: str()
});
```

### _FILE Suffix Pattern

You can also use the `_FILE` suffix pattern commonly used with Docker:

```bash
# Set the path to the secret file
export DATABASE_PASSWORD_FILE=/run/secrets/db-password
```

```ts
import { env, str } from '12factor-env';

// Will read from the file specified in DATABASE_PASSWORD_FILE
const config = env(process.env, {
DATABASE_PASSWORD: str()
});
```

### Custom Secrets Path

Set `ENV_SECRETS_PATH` to change the default secrets directory:

```bash
export ENV_SECRETS_PATH=/custom/secrets/path
```

## Validators

All validators from [envalid](https://github.com/af/envalid) are re-exported:

| Validator | Description |
|-----------|-------------|
| `str()` | String value |
| `bool()` | Boolean (`true`, `false`, `1`, `0`) |
| `num()` | Number |
| `port()` | Port number (1-65535) |
| `host()` | Hostname or IP address |
| `url()` | Valid URL |
| `email()` | Valid email address |
| `json()` | JSON string (parsed) |

### Validator Options

All validators accept options:

```ts
import { str, num } from '12factor-env';

const config = env(process.env, {
API_KEY: str({ desc: 'API key for external service' }),
TIMEOUT: num({ default: 5000, desc: 'Request timeout in ms' })
});
```

## API

### `env(inputEnv, secrets, vars)`

Main function to validate environment variables.

- `inputEnv` - The environment object (usually `process.env`)
- `secrets` - Required environment variables
- `vars` - Optional environment variables

### `secret(envFile)`

Create a validator for a secret file:

```ts
import { env, secret } from '12factor-env';

const config = env(process.env, {
DB_PASSWORD: secret('DATABASE_PASSWORD')
});
```

### `getSecret(name)`

Read a secret from a file:

```ts
import { getSecret } from '12factor-env';

const password = getSecret('DATABASE_PASSWORD');
```

### `secretPath(name)`

Resolve the full path to a secret file:

```ts
import { secretPath } from '12factor-env';

const path = secretPath('DATABASE_PASSWORD');
// Returns: /run/secrets/DATABASE_PASSWORD
```

## Re-exports from envalid

The following are re-exported from envalid for convenience:

- `cleanEnv` - Low-level env cleaning function
- `makeValidator` - Create custom validators
- `EnvError` - Error class for validation errors
- `EnvMissingError` - Error class for missing required vars
- `testOnly` - Helper for test-only defaults
49 changes: 49 additions & 0 deletions packages/12factor-env/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "12factor-env",
"version": "0.1.0",
"author": "Constructive <developers@constructive.io>",
"description": "Environment variable validation with secret file support for 12-factor apps",
"main": "index.js",
"module": "esm/index.js",
"types": "index.d.ts",
"homepage": "https://github.com/constructive-io/constructive",
"license": "MIT",
"publishConfig": {
"access": "public",
"directory": "dist"
},
"repository": {
"type": "git",
"url": "https://github.com/constructive-io/constructive"
},
"bugs": {
"url": "https://github.com/constructive-io/constructive/issues"
},
"scripts": {
"clean": "makage clean",
"prepack": "npm run build",
"build": "makage build",
"build:dev": "makage build --dev",
"lint": "eslint . --fix",
"test": "jest --passWithNoTests",
"test:watch": "jest --watch"
},
"dependencies": {
"envalid": "^8.1.1"
},
"devDependencies": {
"@types/node": "^20.12.7",
"makage": "^0.1.10",
"ts-node": "^10.9.2"
},
"keywords": [
"environment",
"env",
"validation",
"12factor",
"secrets",
"kubernetes",
"docker",
"constructive"
]
}
Loading