diff --git a/.agents/skills/pgpm/references/environment-configuration.md b/.agents/skills/pgpm/references/environment-configuration.md index dcf11b734c..c74d49b82a 100644 --- a/.agents/skills/pgpm/references/environment-configuration.md +++ b/.agents/skills/pgpm/references/environment-configuration.md @@ -246,23 +246,15 @@ const envOptions = getEnvVars(); const envOptions = getEnvVars(process.env); ``` -### getNodeEnv() +### Shared environment parsers -Get normalized NODE_ENV: +Generic string-to-value parsing is provided by the neutral `12factor-env` +package rather than `@pgpmjs/env`: -```typescript -import { getNodeEnv } from '@pgpmjs/env'; - -const env = getNodeEnv(); -// Returns 'development' | 'production' | 'test' -``` - -### parseEnvBoolean() - -Parse boolean environment variable: +#### parseEnvBoolean() ```typescript -import { parseEnvBoolean } from '@pgpmjs/env'; +import { parseEnvBoolean } from '12factor-env/parsers'; parseEnvBoolean('true'); // true parseEnvBoolean('1'); // true @@ -271,18 +263,25 @@ parseEnvBoolean('false'); // false parseEnvBoolean(undefined); // undefined ``` -### parseEnvNumber() - -Parse numeric environment variable: +#### parseEnvNumber() ```typescript -import { parseEnvNumber } from '@pgpmjs/env'; +import { parseEnvNumber } from '12factor-env/parsers'; parseEnvNumber('5432'); // 5432 parseEnvNumber('invalid'); // undefined parseEnvNumber(undefined); // undefined ``` +#### parseEnvStringArray() + +```typescript +import { parseEnvStringArray } from '12factor-env/parsers'; + +parseEnvStringArray('jobs,email'); // ['jobs', 'email'] +parseEnvStringArray(undefined); // undefined +``` + ## pgpm.json Configuration Example `pgpm.json` with environment options: diff --git a/functions/send-email/package.json b/functions/send-email/package.json index 3b35a85b50..0d1403ec87 100644 --- a/functions/send-email/package.json +++ b/functions/send-email/package.json @@ -33,9 +33,9 @@ "makage": "^0.3.0" }, "dependencies": { + "12factor-env": "workspace:^", "@constructive-io/knative-job-fn": "workspace:^", "@constructive-io/postmaster": "workspace:^", - "@pgpmjs/env": "workspace:^", "@pgpmjs/logger": "workspace:^", "simple-smtp-server": "workspace:^" } diff --git a/functions/send-email/src/index.ts b/functions/send-email/src/index.ts index 197afd2b6e..ed27adfedf 100644 --- a/functions/send-email/src/index.ts +++ b/functions/send-email/src/index.ts @@ -1,7 +1,7 @@ import { createJobApp } from '@constructive-io/knative-job-fn'; import { send as sendSmtp } from 'simple-smtp-server'; import { send as sendPostmaster } from '@constructive-io/postmaster'; -import { parseEnvBoolean } from '@pgpmjs/env'; +import { parseEnvBoolean } from '12factor-env/parsers'; import { createLogger } from '@pgpmjs/logger'; type SimpleEmailPayload = { diff --git a/functions/send-verification-link/package.json b/functions/send-verification-link/package.json index ef17b3781c..ef1545426f 100644 --- a/functions/send-verification-link/package.json +++ b/functions/send-verification-link/package.json @@ -33,11 +33,11 @@ "makage": "^0.3.0" }, "dependencies": { + "12factor-env": "workspace:^", "@constructive-io/knative-job-fn": "workspace:^", "@constructive-io/postmaster": "workspace:^", "@launchql/mjml": "0.1.1", "@launchql/styled-email": "0.1.0", - "@pgpmjs/env": "workspace:^", "@pgpmjs/logger": "workspace:^", "graphql-request": "^7.1.2", "graphql-tag": "^2.12.6", diff --git a/functions/send-verification-link/src/index.ts b/functions/send-verification-link/src/index.ts index b7046b472a..a7149207b1 100644 --- a/functions/send-verification-link/src/index.ts +++ b/functions/send-verification-link/src/index.ts @@ -4,7 +4,7 @@ import gql from 'graphql-tag'; import { generate } from '@launchql/mjml'; import { send as sendPostmaster } from '@constructive-io/postmaster'; import { send as sendSmtp } from 'simple-smtp-server'; -import { parseEnvBoolean } from '@pgpmjs/env'; +import { parseEnvBoolean } from '12factor-env/parsers'; import { createLogger } from '@pgpmjs/logger'; const isDryRun = parseEnvBoolean(process.env.SEND_VERIFICATION_LINK_DRY_RUN ?? process.env.SEND_EMAIL_LINK_DRY_RUN) ?? false; diff --git a/graphql/env/README.md b/graphql/env/README.md index e4f4139e40..0e6356352d 100644 --- a/graphql/env/README.md +++ b/graphql/env/README.md @@ -56,6 +56,8 @@ PGPM and Constructive defaults In addition to the PostgreSQL/PGPM values supplied by `@pgpmjs/env`, this package parses the Constructive-owned variables below. +The generic boolean, number, and comma-separated string conversions come from `12factor-env/parsers`. GraphQL env continues to own the variable names, defaults, output structure, and merge behavior listed here. + ### Server - `PORT` @@ -139,3 +141,5 @@ Constructive defaults are provided by `@constructive-io/graphql-types`. They inc ## Follow-up boundary This refactor moves only server, CDN/storage, jobs, SMTP, and `getNodeEnv()` ownership out of PGPM. It does not consolidate environment variables currently managed by Mailgun providers, individual functions, Knative runtimes, LLM integrations, observability, CAPTCHA, Graphile runtime helpers, or test harnesses. Existing behavior in those areas remains unchanged and can be addressed separately. + +Common parser functions are centralized in `12factor-env`, including the compatible boolean helper used by several jobs and function runtimes. That shared mechanism does not transfer ownership of those runtimes' environment variables to GraphQL env or `12factor-env`. diff --git a/graphql/env/package.json b/graphql/env/package.json index bdb6647f78..7b92af1492 100644 --- a/graphql/env/package.json +++ b/graphql/env/package.json @@ -29,6 +29,7 @@ "test:watch": "jest --watch" }, "dependencies": { + "12factor-env": "workspace:^", "@constructive-io/graphql-types": "workspace:^", "@pgpmjs/env": "workspace:^", "deepmerge": "^4.3.1" diff --git a/graphql/env/src/env.ts b/graphql/env/src/env.ts index c903d26328..b895e0535f 100644 --- a/graphql/env/src/env.ts +++ b/graphql/env/src/env.ts @@ -2,28 +2,11 @@ import { BucketProvider, ConstructiveOptions } from '@constructive-io/graphql-types'; - -/** - * Parse GraphQL-related environment variables. - * These are the env vars that Constructive packages need but pgpm doesn't. - */ -const parseEnvBoolean = (val?: string): boolean | undefined => { - if (val === undefined) return undefined; - return ['true', '1', 'yes'].includes(val.toLowerCase()); -}; - -const parseEnvNumber = (val?: string): number | undefined => { - const num = Number(val); - return !isNaN(num) ? num : undefined; -}; - -const parseEnvStringArray = (val?: string): string[] | undefined => { - if (!val) return undefined; - return val - .split(',') - .map(s => s.trim()) - .filter(Boolean); -}; +import { + parseEnvBoolean, + parseEnvNumber, + parseEnvStringArray +} from '12factor-env/parsers'; type NodeEnv = 'development' | 'production' | 'test'; @@ -34,6 +17,9 @@ export const getNodeEnv = (): NodeEnv => { }; /** + * Parse GraphQL-related environment variables. + * These are the env vars that Constructive packages need but pgpm doesn't. + * * @param env - Environment object to read from (defaults to process.env for backwards compatibility) */ export const getGraphQLEnvVars = (env: NodeJS.ProcessEnv = process.env): Partial => { diff --git a/jobs/job-utils/package.json b/jobs/job-utils/package.json index 38194f6064..304fa2fa0a 100644 --- a/jobs/job-utils/package.json +++ b/jobs/job-utils/package.json @@ -36,9 +36,9 @@ "makage": "^0.3.0" }, "dependencies": { + "12factor-env": "workspace:^", "@constructive-io/graphql-env": "workspace:^", "@constructive-io/graphql-types": "workspace:^", - "@pgpmjs/env": "workspace:^", "@pgpmjs/logger": "workspace:^", "pg-cache": "workspace:^", "pg-env": "workspace:^" diff --git a/jobs/job-utils/src/runtime.ts b/jobs/job-utils/src/runtime.ts index c59365b854..30cc591f12 100644 --- a/jobs/job-utils/src/runtime.ts +++ b/jobs/job-utils/src/runtime.ts @@ -1,15 +1,10 @@ import { getEnvOptions, getNodeEnv } from '@constructive-io/graphql-env'; import { jobsDefaults, type ConstructiveOptions } from '@constructive-io/graphql-types'; -import { parseEnvBoolean } from '@pgpmjs/env'; +import { parseEnvBoolean } from '12factor-env/parsers'; import { defaultPgConfig, getPgEnvVars, type PgConfig } from 'pg-env'; import { buildConnectionString, getPgPool } from 'pg-cache'; import type { Pool } from 'pg'; -type Maybe = T | null | undefined; - -const toStrArray = (v: Maybe): string[] | undefined => - v ? v.split(',').map(s => s.trim()).filter(Boolean) : undefined; - // ---- PG config ---- export const getJobPgConfig = (): PgConfig => { const opts: ConstructiveOptions = getEnvOptions(); diff --git a/jobs/knative-job-service/package.json b/jobs/knative-job-service/package.json index d26b18be3e..90a6942234 100644 --- a/jobs/knative-job-service/package.json +++ b/jobs/knative-job-service/package.json @@ -56,6 +56,7 @@ "ts-node": "^10.9.2" }, "dependencies": { + "12factor-env": "workspace:^", "@constructive-io/job-pg": "workspace:^", "@constructive-io/job-scheduler": "workspace:^", "@constructive-io/job-utils": "workspace:^", @@ -64,7 +65,6 @@ "@constructive-io/knative-job-worker": "workspace:^", "@constructive-io/send-email-fn": "workspace:^", "@constructive-io/send-verification-link-fn": "workspace:^", - "@pgpmjs/env": "workspace:^", "@pgpmjs/logger": "workspace:^", "async-retry": "1.3.3", "pg": "8.21.0" diff --git a/jobs/knative-job-service/src/index.ts b/jobs/knative-job-service/src/index.ts index 106053501b..d0454e5231 100644 --- a/jobs/knative-job-service/src/index.ts +++ b/jobs/knative-job-service/src/index.ts @@ -10,7 +10,10 @@ import { getSchedulerHostname, getWorkerHostname } from '@constructive-io/job-utils'; -import { parseEnvBoolean } from '@pgpmjs/env'; +import { + parseEnvBoolean, + parseEnvStringArray +} from '12factor-env/parsers'; import { Logger } from '@pgpmjs/logger'; import retry from 'async-retry'; import { Client } from 'pg'; @@ -289,14 +292,6 @@ export class KnativeJobsSvc { } } -const parseList = (value?: string): string[] => { - if (!value) return []; - return value - .split(',') - .map((item) => item.trim()) - .filter(Boolean); -}; - const parsePortMap = (value?: string): Record => { if (!value) return {}; @@ -339,7 +334,7 @@ const buildFunctionsOptionsFromEnv = (): KnativeJobsSvcOptions['functions'] => { return { enabled: true }; } - const names = parseList(rawFunctions) as FunctionName[]; + const names = (parseEnvStringArray(rawFunctions) ?? []) as FunctionName[]; if (!names.length) return undefined; const services: FunctionServiceConfig[] = names.map((name) => ({ diff --git a/packages/12factor-env/README.md b/packages/12factor-env/README.md index d6d381530c..033bff2cc3 100644 --- a/packages/12factor-env/README.md +++ b/packages/12factor-env/README.md @@ -50,6 +50,44 @@ console.log(config.DATABASE_URL); // Validated string console.log(config.PORT); // Validated number ``` +## Permissive Parsers + +For callers that already decide whether a value is required and only need the +historical Constructive conversion semantics, the package provides three pure +parsers: + +```ts +import { + parseEnvBoolean, + parseEnvNumber, + parseEnvStringArray, +} from '12factor-env/parsers'; + +parseEnvBoolean('yes'); // true +parseEnvNumber('3000'); // 3000 +parseEnvStringArray('email, thumbnail'); // ['email', 'thumbnail'] +``` + +They are also exported from the package root. The `parsers` entry has no access +to `process.env`, the filesystem, secret files, or envalid; it only converts the +value passed by the caller. + +These parsers are deliberately permissive to preserve existing runtime +behavior: + +- `parseEnvBoolean()` returns `undefined` only for an absent value. It returns + `true` for `true`, `1`, or `yes` (case-insensitive), and `false` for every + other defined string. +- `parseEnvNumber()` uses JavaScript `Number()` conversion and returns + `undefined` only when the result is `NaN`. +- `parseEnvStringArray()` splits on commas, trims entries, and removes empty + entries. + +Use the envalid-backed validators such as `bool()`, `num()`, and `port()` when +invalid or missing configuration must be rejected. Use the pure parsers when +the caller owns defaults and validation and compatibility with the permissive +conversion rules is required. + ## Secret File Support This library supports reading secrets from files, which is useful for Docker secrets and Kubernetes secrets that are mounted as files. diff --git a/packages/12factor-env/__tests__/package-exports.test.ts b/packages/12factor-env/__tests__/package-exports.test.ts new file mode 100644 index 0000000000..a80fd54c28 --- /dev/null +++ b/packages/12factor-env/__tests__/package-exports.test.ts @@ -0,0 +1,66 @@ +import { execFileSync } from 'child_process'; +import { mkdirSync, mkdtempSync, rmSync, symlinkSync } from 'fs'; +import { tmpdir } from 'os'; +import { join, resolve } from 'path'; + +describe('published package exports', () => { + const packageRoot = resolve(__dirname, '..'); + const distRoot = join(packageRoot, 'dist'); + let fixtureRoot: string; + + beforeAll(() => { + execFileSync( + process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm', + ['run', 'build'], + { cwd: packageRoot, stdio: 'pipe' } + ); + + fixtureRoot = mkdtempSync(join(tmpdir(), '12factor-env-exports-')); + const nodeModules = join(fixtureRoot, 'node_modules'); + mkdirSync(nodeModules); + symlinkSync( + distRoot, + join(nodeModules, '12factor-env'), + process.platform === 'win32' ? 'junction' : 'dir' + ); + }); + + afterAll(() => { + rmSync(fixtureRoot, { recursive: true, force: true }); + }); + + it('loads the root and parser entries through CommonJS', () => { + execFileSync( + process.execPath, + [ + '-e', + [ + "const root = require('12factor-env');", + "const parsers = require('12factor-env/parsers');", + "if (typeof root.env !== 'function') process.exit(1);", + "if (parsers.parseEnvBoolean('YES') !== true) process.exit(1);", + "require.resolve('12factor-env/index.js');", + "require.resolve('12factor-env/esm/index.js');", + ].join('\n'), + ], + { cwd: fixtureRoot, stdio: 'pipe' } + ); + }); + + it('loads the root and parser entries through native ESM', () => { + execFileSync( + process.execPath, + [ + '--input-type=module', + '-e', + [ + "import { env } from '12factor-env';", + "import { parseEnvStringArray } from '12factor-env/parsers';", + "if (typeof env !== 'function') process.exit(1);", + "if (parseEnvStringArray('a, b').join(':') !== 'a:b') process.exit(1);", + ].join('\n'), + ], + { cwd: fixtureRoot, stdio: 'pipe' } + ); + }); +}); diff --git a/packages/12factor-env/__tests__/parsers.test.ts b/packages/12factor-env/__tests__/parsers.test.ts new file mode 100644 index 0000000000..17ea611f3a --- /dev/null +++ b/packages/12factor-env/__tests__/parsers.test.ts @@ -0,0 +1,98 @@ +import { + parseEnvBoolean as parseEnvBooleanFromRoot, + parseEnvNumber as parseEnvNumberFromRoot, + parseEnvStringArray as parseEnvStringArrayFromRoot, +} from '../src'; +import { + parseEnvBoolean, + parseEnvNumber, + parseEnvStringArray, +} from '../src/parsers'; + +describe('pure environment parsers', () => { + it('re-exports the parser functions from the package root', () => { + expect(parseEnvBooleanFromRoot).toBe(parseEnvBoolean); + expect(parseEnvNumberFromRoot).toBe(parseEnvNumber); + expect(parseEnvStringArrayFromRoot).toBe(parseEnvStringArray); + }); + + describe('parseEnvBoolean', () => { + it.each(['true', 'TRUE', 'True', '1', 'yes', 'YES'])( + 'parses %p as true', + (value) => { + expect(parseEnvBoolean(value)).toBe(true); + } + ); + + it.each([ + 'false', + 'FALSE', + '0', + 'no', + 'on', + 'off', + 'anything', + '', + ' true ', + ])('parses defined non-true value %p as false', (value) => { + expect(parseEnvBoolean(value)).toBe(false); + }); + + it('preserves an absent value as undefined', () => { + expect(parseEnvBoolean(undefined)).toBeUndefined(); + }); + }); + + describe('parseEnvNumber', () => { + it.each([ + ['42', 42], + ['-3.5', -3.5], + ['0', 0], + ['', 0], + [' ', 0], + ['0x10', 16], + ['Infinity', Infinity], + ])('parses %p with Number semantics', (value, expected) => { + expect(parseEnvNumber(value)).toBe(expected); + }); + + it.each([undefined, 'not-a-number', '12px'])( + 'returns undefined for %p', + (value) => { + expect(parseEnvNumber(value)).toBeUndefined(); + } + ); + }); + + describe('parseEnvStringArray', () => { + it('splits commas, trims entries, and removes empty entries', () => { + expect(parseEnvStringArray(' alpha, beta ,, gamma ')).toEqual([ + 'alpha', + 'beta', + 'gamma', + ]); + }); + + it('preserves order and duplicate values', () => { + expect(parseEnvStringArray('alpha,beta,alpha')).toEqual([ + 'alpha', + 'beta', + 'alpha', + ]); + }); + + it.each([undefined, ''])( + 'returns undefined for an absent source value %p', + (value) => { + expect(parseEnvStringArray(value)).toBeUndefined(); + } + ); + + it.each([' ', ','])( + 'returns an empty array for non-empty blank input %p', + (value) => { + expect(parseEnvStringArray(value)).toEqual([]); + } + ); + }); +}); diff --git a/packages/12factor-env/jest.config.js b/packages/12factor-env/jest.config.js index e301e43aa5..73a144bb52 100644 --- a/packages/12factor-env/jest.config.js +++ b/packages/12factor-env/jest.config.js @@ -12,6 +12,9 @@ module.exports = { ] }, transformIgnorePatterns: ['/node_modules/*'], + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1' + }, testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$', moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], modulePathIgnorePatterns: ['dist/*'] diff --git a/packages/12factor-env/package.json b/packages/12factor-env/package.json index 66b26e5a6c..e625f49c37 100644 --- a/packages/12factor-env/package.json +++ b/packages/12factor-env/package.json @@ -6,6 +6,27 @@ "main": "index.js", "module": "esm/index.js", "types": "index.d.ts", + "exports": { + ".": { + "types": "./index.d.ts", + "import": "./index.js", + "require": "./index.js", + "default": "./index.js" + }, + "./parsers": { + "types": "./parsers.d.ts", + "import": "./parsers.js", + "require": "./parsers.js", + "default": "./parsers.js" + }, + "./index": "./index.js", + "./index.js": "./index.js", + "./parsers.js": "./parsers.js", + "./esm": "./esm/index.js", + "./esm/index": "./esm/index.js", + "./esm/index.js": "./esm/index.js", + "./package.json": "./package.json" + }, "homepage": "https://github.com/constructive-io/constructive", "license": "MIT", "publishConfig": { diff --git a/packages/12factor-env/src/index.ts b/packages/12factor-env/src/index.ts index 07226da47f..6068d15cb2 100644 --- a/packages/12factor-env/src/index.ts +++ b/packages/12factor-env/src/index.ts @@ -174,3 +174,9 @@ export { }; export type { ValidatorSpec, CleanedEnv }; + +export { + parseEnvBoolean, + parseEnvNumber, + parseEnvStringArray, +} from './parsers.js'; diff --git a/packages/12factor-env/src/parsers.ts b/packages/12factor-env/src/parsers.ts new file mode 100644 index 0000000000..9b2df5c885 --- /dev/null +++ b/packages/12factor-env/src/parsers.ts @@ -0,0 +1,33 @@ +/** + * Parse the permissive boolean syntax historically used by Constructive env + * resolvers. + * + * Only `true`, `1`, and `yes` (case-insensitive) are true. Every other defined + * string is false; an absent value remains undefined. Input is intentionally + * not trimmed so existing behavior is preserved. + */ +export const parseEnvBoolean = (value?: string): boolean | undefined => { + if (value === undefined) return undefined; + return ['true', '1', 'yes'].includes(value.toLowerCase()); +}; + +/** + * Parse an environment value with JavaScript's permissive `Number` semantics. + * Values whose conversion is NaN are treated as unset. + */ +export const parseEnvNumber = (value?: string): number | undefined => { + const number = Number(value); + return !isNaN(number) ? number : undefined; +}; + +/** + * Parse a comma-separated environment value, trimming entries and removing + * empty entries. An absent or empty source value is treated as unset. + */ +export const parseEnvStringArray = (value?: string): string[] | undefined => { + if (!value) return undefined; + return value + .split(',') + .map((item) => item.trim()) + .filter(Boolean); +}; diff --git a/packages/smtppostmaster/__tests__/test-send.ts b/packages/smtppostmaster/__tests__/test-send.ts index c002700df1..20045f3368 100644 --- a/packages/smtppostmaster/__tests__/test-send.ts +++ b/packages/smtppostmaster/__tests__/test-send.ts @@ -1,13 +1,9 @@ import { getEnvOptions } from '@constructive-io/graphql-env'; import type { SmtpOptions } from '@constructive-io/graphql-types'; +import { parseEnvBoolean } from '12factor-env/parsers'; import { send } from '../src/index'; import { createSmtpCatcher } from './smtp-catcher'; -const parseEnvBoolean = (val?: string): boolean | undefined => { - if (val === undefined) return undefined; - return ['true', '1', 'yes'].includes(val.toLowerCase()); -}; - const main = async () => { const useCatcher = parseEnvBoolean(process.env.SMTP_TEST_USE_CATCHER) ?? false; const catcher = useCatcher ? await createSmtpCatcher() : null; diff --git a/packages/smtppostmaster/package.json b/packages/smtppostmaster/package.json index e1327e394c..89656e3e29 100644 --- a/packages/smtppostmaster/package.json +++ b/packages/smtppostmaster/package.json @@ -36,6 +36,7 @@ "nodemailer": "^6.9.13" }, "devDependencies": { + "12factor-env": "workspace:^", "@types/nodemailer": "^7.0.11", "@types/smtp-server": "^3.5.13", "makage": "^0.3.0", diff --git a/pgpm/env/README.md b/pgpm/env/README.md index 357206c3b3..0746deb0a8 100644 --- a/pgpm/env/README.md +++ b/pgpm/env/README.md @@ -16,6 +16,8 @@ Lower-level environment management for PostgreSQL and the PGPM toolchain. It res `@pgpmjs/env` owns database connections, test database settings, workspace/package configuration, deployment, migrations, and PGPM error output. HTTP server, CDN/storage, jobs, and SMTP configuration are owned by the upper-level `@constructive-io/graphql-env` resolver. +Generic string-to-value conversion is provided by `12factor-env/parsers`. PGPM env still owns which PGPM variables are read and how their parsed values map into `PgpmOptions`. + ## Features - Config file discovery using `walkUp` utility @@ -39,3 +41,5 @@ For a complete Constructive configuration, including PGPM values plus GraphQL ru ## Ownership change This refactor moves only the existing server, CDN/storage, jobs, and SMTP configuration, plus `getNodeEnv()`, from PGPM env to GraphQL env. It does not consolidate other environment variables currently read by Mailgun providers, functions, Knative runtimes, LLM integrations, or other packages; those remain follow-up work. + +The shared boolean and number parser implementations are now owned by the neutral `12factor-env` mechanism layer rather than being public PGPM utilities. This changes parser ownership without moving any additional environment variables into or out of PGPM env. diff --git a/pgpm/env/package.json b/pgpm/env/package.json index 99680818b6..f0be58ad96 100644 --- a/pgpm/env/package.json +++ b/pgpm/env/package.json @@ -29,6 +29,7 @@ "test:watch": "jest --watch" }, "dependencies": { + "12factor-env": "workspace:^", "@pgpmjs/types": "workspace:^", "deepmerge": "^4.3.1" }, diff --git a/pgpm/env/src/env.ts b/pgpm/env/src/env.ts index 64eb07a8db..5b656fbd77 100644 --- a/pgpm/env/src/env.ts +++ b/pgpm/env/src/env.ts @@ -1,14 +1,5 @@ import { PgpmOptions } from '@pgpmjs/types'; - -export const parseEnvNumber = (val?: string): number | undefined => { - const num = Number(val); - return !isNaN(num) ? num : undefined; -}; - -export const parseEnvBoolean = (val?: string): boolean | undefined => { - if (val === undefined) return undefined; - return ['true', '1', 'yes'].includes(val.toLowerCase()); -}; +import { parseEnvBoolean, parseEnvNumber } from '12factor-env/parsers'; /** * Parse core PGPM environment variables. diff --git a/pgpm/env/src/index.ts b/pgpm/env/src/index.ts index 9055bc1aee..98b6823e1f 100644 --- a/pgpm/env/src/index.ts +++ b/pgpm/env/src/index.ts @@ -15,7 +15,7 @@ export { resolveWorkspaceByType } from './config'; export type { WorkspaceType } from './config'; -export { getEnvVars, parseEnvBoolean, parseEnvNumber } from './env'; +export { getEnvVars } from './env'; export { walkUp, replaceArrays } from './utils'; export type { PgpmOptions, PgTestConnectionOptions, DeploymentOptions } from '@pgpmjs/types'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index aedf9ffe42..f40e7f372f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -262,15 +262,15 @@ importers: functions/send-email: dependencies: + 12factor-env: + specifier: workspace:^ + version: link:../../packages/12factor-env/dist '@constructive-io/knative-job-fn': specifier: workspace:^ version: link:../../jobs/knative-job-fn/dist '@constructive-io/postmaster': specifier: workspace:^ version: link:../../packages/postmaster/dist - '@pgpmjs/env': - specifier: workspace:^ - version: link:../../pgpm/env/dist '@pgpmjs/logger': specifier: workspace:^ version: link:../../pgpm/logger/dist @@ -285,6 +285,9 @@ importers: functions/send-verification-link: dependencies: + 12factor-env: + specifier: workspace:^ + version: link:../../packages/12factor-env/dist '@constructive-io/knative-job-fn': specifier: workspace:^ version: link:../../jobs/knative-job-fn/dist @@ -297,9 +300,6 @@ importers: '@launchql/styled-email': specifier: 0.1.0 version: 0.1.0(@babel/core@7.29.0)(encoding@0.1.13)(react-dom@19.2.5(react@19.2.5))(react-is@19.2.6)(react@19.2.5) - '@pgpmjs/env': - specifier: workspace:^ - version: link:../../pgpm/env/dist '@pgpmjs/logger': specifier: workspace:^ version: link:../../pgpm/logger/dist @@ -1356,6 +1356,9 @@ importers: graphql/env: dependencies: + 12factor-env: + specifier: workspace:^ + version: link:../../packages/12factor-env/dist '@constructive-io/graphql-types': specifier: workspace:^ version: link:../types/dist @@ -2061,15 +2064,15 @@ importers: jobs/job-utils: dependencies: + 12factor-env: + specifier: workspace:^ + version: link:../../packages/12factor-env/dist '@constructive-io/graphql-env': specifier: workspace:^ version: link:../../graphql/env/dist '@constructive-io/graphql-types': specifier: workspace:^ version: link:../../graphql/types/dist - '@pgpmjs/env': - specifier: workspace:^ - version: link:../../pgpm/env/dist '@pgpmjs/logger': specifier: workspace:^ version: link:../../pgpm/logger/dist @@ -2152,6 +2155,9 @@ importers: jobs/knative-job-service: dependencies: + 12factor-env: + specifier: workspace:^ + version: link:../../packages/12factor-env/dist '@constructive-io/job-pg': specifier: workspace:^ version: link:../job-pg/dist @@ -2176,9 +2182,6 @@ importers: '@constructive-io/send-verification-link-fn': specifier: workspace:^ version: link:../../functions/send-verification-link/dist - '@pgpmjs/env': - specifier: workspace:^ - version: link:../../pgpm/env/dist '@pgpmjs/logger': specifier: workspace:^ version: link:../../pgpm/logger/dist @@ -2669,6 +2672,9 @@ importers: specifier: ^6.9.13 version: 6.10.1 devDependencies: + 12factor-env: + specifier: workspace:^ + version: link:../12factor-env/dist '@types/nodemailer': specifier: ^7.0.11 version: 7.0.11 @@ -2870,6 +2876,9 @@ importers: pgpm/env: dependencies: + 12factor-env: + specifier: workspace:^ + version: link:../../packages/12factor-env/dist '@pgpmjs/types': specifier: workspace:^ version: link:../types/dist @@ -3122,6 +3131,10 @@ importers: publishDirectory: dist postgres/pg-env: + dependencies: + 12factor-env: + specifier: workspace:^ + version: link:../../packages/12factor-env/dist devDependencies: makage: specifier: ^0.3.0 diff --git a/postgres/pg-env/package.json b/postgres/pg-env/package.json index 59a6d2f6f3..7c84dd7fa4 100644 --- a/postgres/pg-env/package.json +++ b/postgres/pg-env/package.json @@ -35,6 +35,9 @@ "config", "constructive" ], + "dependencies": { + "12factor-env": "workspace:^" + }, "devDependencies": { "makage": "^0.3.0" } diff --git a/postgres/pg-env/src/env.ts b/postgres/pg-env/src/env.ts index ef7a83081d..7d56e51a0f 100644 --- a/postgres/pg-env/src/env.ts +++ b/postgres/pg-env/src/env.ts @@ -1,9 +1,5 @@ import { defaultPgConfig,PgConfig } from './pg-config'; - -const parseEnvNumber = (val?: string): number | undefined => { - const num = Number(val); - return !isNaN(num) ? num : undefined; -}; +import { parseEnvNumber } from '12factor-env/parsers'; export const getPgEnvVars = (): Partial => { const { @@ -48,4 +44,4 @@ export function getSpawnEnvWithPg( ...baseEnv, ...toPgEnvVars(config) }; -} \ No newline at end of file +}