dagger-env
Version:
A type-safe, reusable environment configuration abstraction for Dagger modules.
325 lines (248 loc) • 9.12 kB
Markdown
# dagger-env
A type-safe, reusable environment configuration abstraction for Dagger modules with full Zod v4 validation and 1Password integration.
## Features
- 🔒 **Type-safe**: Full TypeScript support with Zod v4 validation
- 🔄 **Reusable**: Create multiple environment configurations for different projects
- 🎯 **Consistent**: Standardized API across all Dagger modules
- 🛡️ **Validated**: Runtime validation of arguments, environment variables, and secrets
- 📦 **Modular**: Secret presets and derived environment variables
- 🔐 **1Password Integration**: Built-in command runner with `op run` support
- 🚀 **Easy to use**: Simple configuration-based setup
## Installation
```bash
npm install dagger-env zod
```
**Note:** The command runner functionality (`dagger-env/run`) requires the 1Password CLI (`op`) to be installed.
## Quick Start
```typescript
import { z } from 'zod/v4'
import { createDaggerEnv } from 'dagger-env'
// Define your environment configuration
const myDaggerEnv = createDaggerEnv({
args: z.object({
push: z.string().optional(),
environment: z.enum(['dev', 'staging', 'prod']).optional(),
}),
env: z.object({
CI: z.string().optional(),
NODE_ENV: z.string().optional(),
}),
secrets: z.object({
API_TOKEN: z.string(),
DATABASE_URL: z.string(),
REDIS_URL: z.string(),
}),
secretPresets: {
api: ['API_TOKEN', 'DATABASE_URL'],
cache: ['REDIS_URL'],
} as const,
derivedEnvVars: {
API_TOKEN: {
API_BASE_URL: 'https://api.example.com',
API_VERSION: 'v1',
},
DATABASE_URL: {
DB_POOL_SIZE: '10',
},
} as const,
})
// Use in your Dagger module
import { dag, Container, object, func, Secret } from '@dagger.io/dagger'
@object()
export class MyModule {
@func()
async build(options: Secret): Promise<Container> {
const opts = await myDaggerEnv.parseDaggerOptions(options)
const withEnv = await myDaggerEnv.getWithEnv(options, ['api'], ['REDIS_URL'])
return withEnv(dag.container().from('node:18')).withExec(['npm', 'run', 'build']).sync()
}
}
```
## Command Runner (1Password Integration)
For projects using 1Password for secret management, `dagger-env` provides a convenient command runner that integrates with `op run`:
```typescript
import { createDaggerEnv } from 'dagger-env'
import { createDaggerCommandRunner } from 'dagger-env/run'
import { z } from 'zod/v4'
// Create your DaggerEnv configuration
const myDaggerEnv = createDaggerEnv({
args: z.object({
environment: z.enum(['dev', 'staging', 'prod']).optional(),
}),
env: z.object({
CI: z.string().optional(),
NODE_ENV: z.string().optional(),
}),
secrets: z.object({
API_TOKEN: z.string(),
}),
secretPresets: {
api: ['API_TOKEN'],
} as const,
derivedEnvVars: {} as const,
})
// Create a command runner - simply pass your DaggerEnv instance
const runDaggerCommand = createDaggerCommandRunner({
opVault: 'your-vault-id',
opItem: 'your-item-id',
opSections: [
{
id: 'your-section-id',
label: 'Shared',
},
],
dockerCommands: ['build', 'deploy', 'test'],
daggerEnv: myDaggerEnv,
})
// Run a Dagger command
await runDaggerCommand('test', {
args: { environment: 'dev' },
env: { NODE_ENV: 'development' },
})
```
### Advanced Configuration
```typescript
// Advanced configuration with multiple sections and pre-command setup
const runDaggerCommand = createDaggerCommandRunner({
opVault: 'your-vault-id',
opItem: 'your-item-id',
opSections: [
{ id: 'shared-section-id', label: 'Shared' },
{ id: 'project-section-id', label: 'Project Specific' },
],
dockerCommands: ['build', 'deploy', 'test'],
beforeCommand: async () => {
// Setup vendor files, modules, etc.
console.log('Setting up environment...')
// await setupDaggerVendorFiles()
},
daggerEnv: myDaggerEnv,
})
```
## API Reference
### Environment Configuration
#### `createDaggerEnv(config)`
Creates a new DaggerEnv instance with the provided configuration.
**Parameters:**
- `config.args`: Zod schema for command-line arguments
- `config.env`: Zod schema for environment variables
- `config.secrets`: Zod schema for secrets
- `config.secretPresets`: Object mapping preset names to arrays of secret names
- `config.derivedEnvVars`: Object mapping secret names to derived environment variables
**Returns:** `DaggerEnv<T>` instance
### `daggerEnv.parseDaggerOptions(options: Secret)`
Parses and validates dagger options from a Secret containing JSON.
**Parameters:**
- `options`: Dagger Secret containing JSON options
**Returns:** `Promise<DaggerOptionsFromConfig<T>>` - Parsed and typed options object
### `daggerEnv.getWithEnv(options, secretPresets, secretNames?)`
Creates a function that applies environment variables and secrets to a container.
**Parameters:**
- `options`: Secret or parsed options object
- `secretPresets`: Array of preset names to include (e.g., `['api', 'cache']`)
- `secretNames`: Optional array of additional individual secret names
**Returns:** `Promise<(con: Container) => Container>` - Function that applies env vars and secrets
### `daggerEnv.getOptionsSchema()`
Returns the Zod schema for the complete options object. Primarily used internally by the command runner, but available for advanced use cases.
**Returns:** `ZodObject` - The combined schema for args, env, and secrets
### `daggerEnv.getSecretPresets()`
Returns array of available secret preset names.
**Returns:** `Array<string>` - Available preset names
### `daggerEnv.getPresetSecrets(preset)`
Returns array of secret names for a specific preset.
**Parameters:**
- `preset`: Name of the preset
**Returns:** `readonly string[]` - Secret names in the preset
### Command Runner
#### `createDaggerCommandRunner(config)`
Creates a function to run Dagger commands with 1Password integration.
**Parameters:**
- `config.opVault`: 1Password vault ID
- `config.opItem`: 1Password item ID
- `config.opSections`: Array of 1Password sections to include for secrets
- `config.dockerCommands`: Optional array of command names that should include Docker socket
- `config.beforeCommand`: Optional async function to run before executing the command
- `config.daggerEnv`: DaggerEnv instance for schema validation and type safety
**Returns:** `(commandName: string, options?: RunDaggerCommandOptions) => Promise<void>` - Function to execute Dagger commands
#### `RunDaggerCommandOptions`
Options for individual command execution:
- `args`: Optional record of arguments to pass to the Dagger command
- `env`: Optional record of additional environment variables
- `extraArgs`: Optional array of additional command-line arguments
## Configuration Examples
### Simple API Service
```typescript
const apiServiceEnv = createDaggerEnv({
args: z.object({
push: z.string().optional(),
}),
env: z.object({
CI: z.string().optional(),
}),
secrets: z.object({
API_TOKEN: z.string(),
DATABASE_URL: z.string(),
}),
secretPresets: {
api: ['API_TOKEN', 'DATABASE_URL'],
} as const,
derivedEnvVars: {
API_TOKEN: {
API_BASE_URL: 'https://api.example.com',
},
} as const,
})
```
### Multi-Environment Setup
```typescript
const multiEnvDaggerEnv = createDaggerEnv({
args: z.object({
environment: z.enum(['dev', 'staging', 'prod']),
push: z.string().optional(),
}),
env: z.object({
CI: z.string().optional(),
}),
secrets: z.object({
DEV_API_KEY: z.string(),
STAGING_API_KEY: z.string(),
PROD_API_KEY: z.string(),
}),
secretPresets: {
dev: ['DEV_API_KEY'],
staging: ['STAGING_API_KEY'],
prod: ['PROD_API_KEY'],
} as const,
derivedEnvVars: {
DEV_API_KEY: { API_URL: 'https://dev-api.example.com' },
STAGING_API_KEY: { API_URL: 'https://staging-api.example.com' },
PROD_API_KEY: { API_URL: 'https://api.example.com' },
} as const,
})
```
## Type Extraction
For advanced use cases where you need to extract TypeScript types:
```typescript
import { z } from 'zod/v4'
import type { DaggerOptionsFromConfig } from 'dagger-env'
// Extract the options type from your DaggerEnv configuration
type MyDaggerEnvConfig = typeof myDaggerEnv extends DaggerEnv<infer T> ? T : never
type MyOptions = DaggerOptionsFromConfig<MyDaggerEnvConfig>
// Access the schema if needed for validation or type extraction
const schema = myDaggerEnv.getOptionsSchema()
type SchemaOutput = z.output<typeof schema>
```
## Best Practices
1. **Use `as const`** for `secretPresets` and `derivedEnvVars` to ensure proper typing
2. **Group related secrets** into logical presets (e.g., `api`, `database`, `cache`)
3. **Validate early** by calling `parseDaggerOptions()` at the start of functions
4. **Reuse configurations** across multiple Dagger modules in the same project
5. **Document your schemas** with JSDoc comments for better developer experience
## Requirements
- Node.js 18+
- Dagger SDK
- Zod v4+
## License
MIT
## Contributing
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository.