Skip to content

Commit 685c34b

Browse files
committed
Revise constructive-env skill per feedback: point to source, not duplicate
- env-vars.md: replaced duplicated env var tables with pointers to source files (pgpm/env/src/env.ts, graphql/env/src/env.ts, type definition files) - config-file.md: rewritten as 'last resort' reference, discouraging use unless specifically needed for shared workspace settings - SKILL.md: removed config file discovery section, updated reference table descriptions, added note that config files are rarely needed
1 parent 41688e4 commit 685c34b

3 files changed

Lines changed: 66 additions & 277 deletions

File tree

skills/constructive-env/SKILL.md

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,9 @@ Unified, type-safe environment configuration for all Constructive and PGPM proje
1616
Use this skill when:
1717
- Configuring database connections, server settings, CDN, SMTP, or jobs programmatically
1818
- Understanding how environment variables map to typed configuration objects
19-
- Setting up `pgpm.json` config files
2019
- Writing code that needs to read environment-aware configuration
2120
- Choosing between `@pgpmjs/env` and `@constructive-io/graphql-env`
22-
- Understanding the merge hierarchy (defaults → config file → env vars → overrides)
21+
- Understanding the merge hierarchy (defaults → env vars → runtime overrides)
2322

2423
## Architecture Overview
2524

@@ -53,10 +52,12 @@ Two packages form a layered configuration system:
5352
Options are merged in this order (later overrides earlier):
5453

5554
1. **Defaults**`pgpmDefaults` (+ `constructiveGraphqlDefaults` for GraphQL layer)
56-
2. **Config file**`pgpm.json` or `pgpm.config.js` discovered by walking up directories
55+
2. **Config file**`pgpm.json` or `pgpm.config.js` (rarely needed — see [references/config-file.md](references/config-file.md))
5756
3. **Environment variables** — parsed from `process.env`
5857
4. **Runtime overrides** — passed programmatically to `getEnvOptions()`
5958

59+
In practice, most projects only use layers 1, 3, and 4. Config files are a last resort for shared workspace settings.
60+
6061
Arrays are **replaced**, not concatenated — the later source wins completely.
6162

6263
## Quick Start
@@ -156,18 +157,6 @@ import { getNodeEnv } from '@pgpmjs/env';
156157
const env = getNodeEnv(); // 'development' | 'production' | 'test'
157158
```
158159

159-
### Config File Discovery
160-
161-
```typescript
162-
import { loadConfigSync, resolvePgpmPath } from '@pgpmjs/env';
163-
164-
// Load pgpm.json by walking up directory tree
165-
const config = loadConfigSync('/path/to/project');
166-
167-
// Find the pgpm config file path
168-
const pgpmPath = resolvePgpmPath('/path/to/project');
169-
```
170-
171160
## Anti-Patterns
172161

173162
**Never do this:**
@@ -199,9 +188,9 @@ const { isPublic } = opts.api;
199188

200189
| Reference | Topic | Consult When |
201190
|-----------|-------|--------------|
202-
| [references/env-vars.md](references/env-vars.md) | Complete environment variables reference | Looking up specific env var names, defaults, and which config key they map to |
191+
| [references/env-vars.md](references/env-vars.md) | Source file locations for env vars and types | Finding which source file defines a specific env var or type |
203192
| [references/defaults.md](references/defaults.md) | Default values for all configuration | Understanding what values are used when nothing is overridden |
204-
| [references/config-file.md](references/config-file.md) | pgpm.json configuration | Setting up project-level configuration files |
193+
| [references/config-file.md](references/config-file.md) | Config file reference (last resort) | Only when you specifically need a `pgpm.json` — this is rarely needed |
205194

206195
## Cross-References
207196

Lines changed: 16 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,133 +1,30 @@
1-
# Configuration File Reference
1+
# Config File Reference (Advanced / Last Resort)
22

3-
PGPM and Constructive support project-level configuration via `pgpm.json` or `pgpm.config.js` files.
3+
Config files (`pgpm.json` / `pgpm.config.js`) are supported but **should rarely be needed**. The preferred approach is to use environment variables and runtime overrides via `getEnvOptions()`.
44

5-
## Config File Discovery
5+
Config files exist primarily for workspaces that need shared base settings across many packages (e.g., a monorepo where every package shares the same DB extensions list).
66

7-
The system walks up the directory tree from the current working directory looking for:
7+
## When You Might Need a Config File
88

9-
1. `pgpm.config.js` (checked first — supports dynamic config)
10-
2. `pgpm.json` (static JSON config)
9+
- You have a large workspace where many packages share the same non-default settings
10+
- You need dynamic configuration that depends on runtime logic (`pgpm.config.js` only)
11+
- You're working with `pgpm init workspace` which generates one automatically
1112

12-
The first file found is used. If no config file is found, an empty object is returned (defaults still apply).
13+
**In most cases, env vars + `getEnvOptions()` overrides are sufficient.**
1314

14-
```
15-
project/
16-
├── packages/
17-
│ └── my-module/ ← cwd
18-
│ └── ...
19-
├── pgpm.json ← found by walking up
20-
└── ...
21-
```
22-
23-
## pgpm.json Format
24-
25-
```json
26-
{
27-
"pg": {
28-
"host": "localhost",
29-
"port": 5432,
30-
"user": "postgres",
31-
"password": "postgres",
32-
"database": "myproject"
33-
},
34-
"db": {
35-
"extensions": ["plpgsql", "uuid-ossp", "postgis"],
36-
"prefix": "test-"
37-
},
38-
"server": {
39-
"port": 3000,
40-
"host": "0.0.0.0"
41-
},
42-
"deployment": {
43-
"fast": true,
44-
"useTx": true
45-
},
46-
"cdn": {
47-
"provider": "minio",
48-
"bucketName": "uploads",
49-
"minioEndpoint": "http://localhost:9000"
50-
},
51-
"graphile": {
52-
"schema": ["app_public", "app_private"]
53-
},
54-
"features": {
55-
"simpleInflection": true,
56-
"postgis": false
57-
},
58-
"api": {
59-
"isPublic": true,
60-
"exposedSchemas": ["app_public"],
61-
"anonRole": "anonymous"
62-
}
63-
}
64-
```
15+
## How It Works
6516

66-
## pgpm.config.js Format
17+
`loadConfigSync(cwd)` walks up the directory tree from `cwd` looking for `pgpm.config.js` (checked first) or `pgpm.json`. The first found is used. If none is found, an empty object is returned and defaults still apply.
6718

68-
For dynamic configuration:
19+
Config file values sit in the merge hierarchy between defaults and env vars:
6920

70-
```javascript
71-
module.exports = {
72-
pg: {
73-
host: process.env.CUSTOM_HOST || 'localhost',
74-
port: 5432,
75-
},
76-
deployment: {
77-
fast: process.env.NODE_ENV === 'development',
78-
}
79-
};
8021
```
81-
82-
Or with ES module default export:
83-
84-
```javascript
85-
export default {
86-
pg: {
87-
database: 'myproject',
88-
},
89-
};
90-
```
91-
92-
## Workspace Resolution
93-
94-
The config system can also resolve workspace roots. These functions walk up the directory tree looking for workspace markers:
95-
96-
| Function | Looks For | Use Case |
97-
|----------|-----------|----------|
98-
| `resolvePgpmPath()` | `pgpm.config.js` or `pgpm.json` | Find PGPM workspace root |
99-
| `resolvePnpmWorkspace()` | `pnpm-workspace.yaml` | Find pnpm workspace root |
100-
| `resolveLernaWorkspace()` | `lerna.json` | Find Lerna workspace root |
101-
| `resolveNpmWorkspace()` | `package.json` with `workspaces` field | Find npm workspace root |
102-
| `resolveWorkspaceByType()` | Dispatches to the above based on type | Generic workspace resolution |
103-
104-
```typescript
105-
import { resolvePgpmPath, resolveWorkspaceByType } from '@pgpmjs/env';
106-
107-
const pgpmRoot = resolvePgpmPath('/path/to/nested/dir');
108-
// Returns '/path/to' if pgpm.json exists there
109-
110-
const workspaceRoot = resolveWorkspaceByType('/path/to/dir', 'pnpm');
111-
// Returns the pnpm workspace root
22+
defaults → config file → env vars → runtime overrides
11223
```
11324

114-
## Config File + Env Vars Interaction
115-
116-
Config file values override defaults but are overridden by environment variables:
25+
Env vars always override config file values.
11726

118-
```
119-
pgpmDefaults → { server: { port: 3000 } }
120-
pgpm.json → { server: { port: 4000 } } ← overrides default
121-
PORT=5000 → { server: { port: 5000 } } ← overrides config
122-
getEnvOptions({}) → { server: { port: 5000 } } ← final result
123-
```
124-
125-
For Constructive packages, GraphQL-specific keys in the config file (`graphile`, `features`, `api`) are also merged:
27+
## Source
12628

127-
```
128-
pgpmDefaults → base
129-
constructiveGraphqlDefaults → graphile/features/api defaults
130-
pgpm.json (graphile keys) → overrides graphql defaults
131-
GRAPHILE_SCHEMA=... → overrides config
132-
getEnvOptions({}) → final result
133-
```
29+
- **Config loading logic:** `pgpm/env/src/config.ts`
30+
- **Workspace resolution:** `pgpm/env/src/workspace.ts`
Lines changed: 44 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,143 +1,46 @@
11
# Environment Variables Reference
22

3-
Complete list of environment variables recognized by `@pgpmjs/env` and `@constructive-io/graphql-env`.
4-
5-
## PostgreSQL Connection (`opts.pg`)
6-
7-
| Env Var | Config Key | Type | Description |
8-
|---------|-----------|------|-------------|
9-
| `PGHOST` | `pg.host` | string | PostgreSQL server hostname |
10-
| `PGPORT` | `pg.port` | number | PostgreSQL server port |
11-
| `PGUSER` | `pg.user` | string | PostgreSQL username |
12-
| `PGPASSWORD` | `pg.password` | string | PostgreSQL password |
13-
| `PGDATABASE` | `pg.database` | string | Default database name |
14-
15-
## Database Test/Connection Options (`opts.db`)
16-
17-
| Env Var | Config Key | Type | Description |
18-
|---------|-----------|------|-------------|
19-
| `PGROOTDATABASE` | `db.rootDb` | string | Root database for admin operations |
20-
| `PGTEMPLATE` | `db.template` | string | Template database for creating test DBs |
21-
| `DB_PREFIX` | `db.prefix` | string | Prefix for generated database names |
22-
| `DB_EXTENSIONS` | `db.extensions` | string[] | Comma-separated list of extensions to install |
23-
| `DB_CWD` | `db.cwd` | string | Working directory for DB operations |
24-
| `DB_CONNECTION_USER` | `db.connection.user` | string | Legacy connection user |
25-
| `DB_CONNECTION_PASSWORD` | `db.connection.password` | string | Legacy connection password |
26-
| `DB_CONNECTION_ROLE` | `db.connection.role` | string | Legacy connection role |
27-
| `DB_CONNECTIONS_APP_USER` | `db.connections.app.user` | string | App-level connection user |
28-
| `DB_CONNECTIONS_APP_PASSWORD` | `db.connections.app.password` | string | App-level connection password |
29-
| `DB_CONNECTIONS_ADMIN_USER` | `db.connections.admin.user` | string | Admin connection user |
30-
| `DB_CONNECTIONS_ADMIN_PASSWORD` | `db.connections.admin.password` | string | Admin connection password |
31-
32-
## Server Options (`opts.server`)
33-
34-
| Env Var | Config Key | Type | Description |
35-
|---------|-----------|------|-------------|
36-
| `PORT` | `server.port` | number | HTTP server port |
37-
| `SERVER_HOST` | `server.host` | string | HTTP server host |
38-
| `SERVER_TRUST_PROXY` | `server.trustProxy` | boolean | Trust proxy headers |
39-
| `SERVER_ORIGIN` | `server.origin` | string | Server origin URL |
40-
| `SERVER_STRICT_AUTH` | `server.strictAuth` | boolean | Strict authentication mode |
41-
42-
## CDN / S3 Storage (`opts.cdn`)
43-
44-
| Env Var | Config Key | Type | Description |
45-
|---------|-----------|------|-------------|
46-
| `BUCKET_PROVIDER` | `cdn.provider` | `'s3'` \| `'minio'` | Storage provider |
47-
| `BUCKET_NAME` | `cdn.bucketName` | string | S3/MinIO bucket name |
48-
| `AWS_REGION` | `cdn.awsRegion` | string | AWS region |
49-
| `AWS_ACCESS_KEY` or `AWS_ACCESS_KEY_ID` | `cdn.awsAccessKey` | string | AWS access key |
50-
| `AWS_SECRET_KEY` or `AWS_SECRET_ACCESS_KEY` | `cdn.awsSecretKey` | string | AWS secret key |
51-
| `MINIO_ENDPOINT` | `cdn.minioEndpoint` | string | MinIO endpoint URL |
52-
53-
## Deployment Options (`opts.deployment`)
54-
55-
| Env Var | Config Key | Type | Description |
56-
|---------|-----------|------|-------------|
57-
| `DEPLOYMENT_USE_TX` | `deployment.useTx` | boolean | Wrap deployment in transaction |
58-
| `DEPLOYMENT_FAST` | `deployment.fast` | boolean | Skip verification after deploy |
59-
| `DEPLOYMENT_USE_PLAN` | `deployment.usePlan` | boolean | Use plan-based deployment |
60-
| `DEPLOYMENT_CACHE` | `deployment.cache` | boolean | Enable deployment caching |
61-
| `DEPLOYMENT_TO_CHANGE` | `deployment.toChange` | string | Deploy up to specific change |
62-
63-
## Migration Options (`opts.migrations`)
64-
65-
| Env Var | Config Key | Type | Description |
66-
|---------|-----------|------|-------------|
67-
| `MIGRATIONS_CODEGEN_USE_TX` | `migrations.codegen.useTx` | boolean | Wrap codegen migrations in transaction |
68-
69-
## Jobs Configuration (`opts.jobs`)
70-
71-
| Env Var | Config Key | Type | Description |
72-
|---------|-----------|------|-------------|
73-
| `JOBS_SCHEMA` | `jobs.schema.schema` | string | PostgreSQL schema for job tables |
74-
| `JOBS_SUPPORT_ANY` | `jobs.worker.supportAny` / `jobs.scheduler.supportAny` | boolean | Accept any job type |
75-
| `JOBS_SUPPORTED` | `jobs.worker.supported` / `jobs.scheduler.supported` | string[] | Comma-separated supported job types |
76-
| `INTERNAL_GATEWAY_URL` | `jobs.gateway.gatewayUrl` | string | Internal gateway URL for job dispatch |
77-
| `INTERNAL_JOBS_CALLBACK_URL` | `jobs.gateway.callbackUrl` | string | Callback URL for job completion |
78-
| `INTERNAL_JOBS_CALLBACK_PORT` | `jobs.gateway.callbackPort` | number | Callback server port |
79-
80-
## Error Output Options (`opts.errorOutput`)
81-
82-
| Env Var | Config Key | Type | Description |
83-
|---------|-----------|------|-------------|
84-
| `PGPM_ERROR_QUERY_HISTORY_LIMIT` | `errorOutput.queryHistoryLimit` | number | Max query history in errors |
85-
| `PGPM_ERROR_MAX_LENGTH` | `errorOutput.maxLength` | number | Max error message length |
86-
| `PGPM_ERROR_VERBOSE` | `errorOutput.verbose` | boolean | Verbose error output |
87-
88-
## SMTP Configuration (`opts.smtp`)
89-
90-
| Env Var | Config Key | Type | Description |
91-
|---------|-----------|------|-------------|
92-
| `SMTP_HOST` | `smtp.host` | string | SMTP server hostname |
93-
| `SMTP_PORT` | `smtp.port` | number | SMTP server port |
94-
| `SMTP_SECURE` | `smtp.secure` | boolean | Use TLS |
95-
| `SMTP_USER` | `smtp.user` | string | SMTP username |
96-
| `SMTP_PASS` | `smtp.pass` | string | SMTP password |
97-
| `SMTP_FROM` | `smtp.from` | string | Default from address |
98-
| `SMTP_REPLY_TO` | `smtp.replyTo` | string | Default reply-to address |
99-
| `SMTP_REQUIRE_TLS` | `smtp.requireTLS` | boolean | Require TLS connection |
100-
| `SMTP_TLS_REJECT_UNAUTHORIZED` | `smtp.tlsRejectUnauthorized` | boolean | Reject unauthorized TLS certs |
101-
| `SMTP_POOL` | `smtp.pool` | boolean | Use connection pooling |
102-
| `SMTP_MAX_CONNECTIONS` | `smtp.maxConnections` | number | Max pooled connections |
103-
| `SMTP_MAX_MESSAGES` | `smtp.maxMessages` | number | Max messages per connection |
104-
| `SMTP_NAME` | `smtp.name` | string | SMTP client name |
105-
| `SMTP_LOGGER` | `smtp.logger` | boolean | Enable SMTP logging |
106-
| `SMTP_DEBUG` | `smtp.debug` | boolean | Enable SMTP debug output |
107-
108-
## GraphQL-Specific Env Vars (from `@constructive-io/graphql-env`)
109-
110-
These are only available when using `@constructive-io/graphql-env`.
111-
112-
### Graphile Options (`opts.graphile`)
113-
114-
| Env Var | Config Key | Type | Description |
115-
|---------|-----------|------|-------------|
116-
| `GRAPHILE_SCHEMA` | `graphile.schema` | string \| string[] | Comma-separated schema names to expose |
117-
118-
### Feature Flags (`opts.features`)
119-
120-
| Env Var | Config Key | Type | Default | Description |
121-
|---------|-----------|------|---------|-------------|
122-
| `FEATURES_SIMPLE_INFLECTION` | `features.simpleInflection` | boolean | `true` | Use simple inflection |
123-
| `FEATURES_OPPOSITE_BASE_NAMES` | `features.oppositeBaseNames` | boolean | `true` | Use opposite base names |
124-
| `FEATURES_POSTGIS` | `features.postgis` | boolean | `true` | Enable PostGIS support |
125-
126-
### API Options (`opts.api`)
127-
128-
| Env Var | Config Key | Type | Default | Description |
129-
|---------|-----------|------|---------|-------------|
130-
| `API_ENABLE_SERVICES` | `api.enableServicesApi` | boolean | `true` | Enable services API routing |
131-
| `API_IS_PUBLIC` | `api.isPublic` | boolean | `true` | Public API mode (domain routing) |
132-
| `API_EXPOSED_SCHEMAS` | `api.exposedSchemas` | string[] | `[]` | Comma-separated schemas to expose |
133-
| `API_META_SCHEMAS` | `api.metaSchemas` | string[] | `['services_public', ...]` | Comma-separated metadata schemas |
134-
| `API_ANON_ROLE` | `api.anonRole` | string | `'administrator'` | Anonymous role name |
135-
| `API_ROLE_NAME` | `api.roleName` | string | `'administrator'` | Default role name |
136-
| `API_DEFAULT_DATABASE_ID` | `api.defaultDatabaseId` | string | `'hard-coded'` | Default database identifier |
137-
138-
## Type Parsing Rules
139-
140-
- **Boolean**: Accepts `'true'`, `'1'`, `'yes'` (case-insensitive) as `true`; everything else is `false`
141-
- **Number**: Uses `Number()` — returns `undefined` if `NaN`
142-
- **String Array**: Splits on comma, trims whitespace, filters empty strings
143-
- **Undefined**: If an env var is not set, that key is omitted from the options object (not set to `undefined`)
3+
The canonical source of truth for all recognized environment variables lives in the source code. Do not maintain a separate list — always refer to these files directly.
4+
5+
## Source Files
6+
7+
### Core PGPM Env Vars
8+
9+
**File:** `pgpm/env/src/env.ts` in the `constructive` repo
10+
11+
This file defines `getEnvVars()` which maps `process.env` keys to the `PgpmOptions` type. Every env var the system recognizes is defined here via conditional spreads:
12+
13+
```typescript
14+
// Pattern used throughout env.ts:
15+
...(process.env.PGHOST && { host: process.env.PGHOST }),
16+
...(process.env.PGPORT && { port: parseEnvNumber(process.env.PGPORT) }),
17+
```
18+
19+
Covers: `PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`, `PGDATABASE`, `DB_*`, `SERVER_*`, `PORT`, `BUCKET_*`, `AWS_*`, `MINIO_*`, `DEPLOYMENT_*`, `JOBS_*`, `SMTP_*`, `PGPM_ERROR_*`
20+
21+
### GraphQL-Specific Env Vars
22+
23+
**File:** `graphql/env/src/env.ts` in the `constructive` repo
24+
25+
This file defines `getGraphQLEnvVars()` which adds GraphQL/Constructive-specific env vars on top of the core PGPM set.
26+
27+
Covers: `GRAPHILE_SCHEMA`, `FEATURES_*`, `API_*`
28+
29+
## Type Definitions
30+
31+
The TypeScript interfaces that define the shape of these options:
32+
33+
- **`PgpmOptions`**`pgpm/types/src/options.ts`
34+
- **`ConstructiveOptions`**`graphql/types/src/constructive.ts`
35+
- **`GraphileOptions`**`graphql/types/src/graphile.ts`
36+
- **`PgConfig`**`pgpm/types/src/pg.ts`
37+
38+
## Type Parsing
39+
40+
The env parsing uses three type-safe parsers (defined in `pgpm/env/src/env.ts`):
41+
42+
- **`parseEnvBoolean(val)`** — Accepts `'true'`, `'1'`, `'yes'` (case-insensitive) as `true`
43+
- **`parseEnvNumber(val)`** — Uses `Number()`, returns `undefined` if `NaN`
44+
- **`parseEnvStringArray(val)`** — Splits on comma, trims whitespace, filters empty strings
45+
46+
If an env var is not set, that key is omitted entirely from the options object — it does not get set to `undefined`. This allows the merge hierarchy to fall through to defaults or config file values.

0 commit comments

Comments
 (0)