diff --git a/package-lock.json b/package-lock.json index fe810787..99b0c6f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12696,7 +12696,7 @@ "devDependencies": { "@types/chalk": "^2.2.0", "@types/debug": "^4.1.4", - "esbuild": "^0.17.19" + "esbuild": "^0.17.4" }, "engines": { "node": ">=14.16" @@ -15610,7 +15610,7 @@ "@types/debug": "^4.1.4", "chalk": "^4.1.0", "debug": "^4.1.1", - "esbuild": "0.17.19" + "esbuild": "^0.17.4" } }, "@pgtyped/wire": { diff --git a/packages/cli/src/config.test.ts b/packages/cli/src/config.test.ts new file mode 100644 index 00000000..fe09b21d --- /dev/null +++ b/packages/cli/src/config.test.ts @@ -0,0 +1,104 @@ +import { DBConfigArgs, getEnvDBConfig } from './config'; + +describe('getEnvDBConfig', () => { + const env = { + ...process.env, + }; + + beforeEach(() => { + // Default PG + process.env.PGHOST = 'pg_host'; + process.env.PGUSER = 'pg_user'; + process.env.PGPASSWORD = 'pg_password'; + process.env.PGDATABASE = 'pg_db_name'; + process.env.PGPORT = '1111'; + process.env.PGURI = 'pg_uri'; + process.env.DATABASE_URL = 'pg_url'; + + // Custom + process.env.URI_ENV = 'host_from_env'; + process.env.HOST_ENV = 'host_from_env'; + process.env.USER_ENV = 'user_from_env'; + process.env.PASSWORD_ENV = 'password_from_env'; + process.env.DB_NAME_ENV = 'db_name_from_env'; + process.env.PORT_ENV = '2222'; + process.env.URI_ENV = 'uri_from_env'; + }); + + afterEach(() => { + process.env = env; + }); + + test('Parses template ENV', () => { + const dbConfig: Partial = { + host: '{{HOST_ENV}}', + user: '{{USER_ENV}}', + password: '{{PASSWORD_ENV}}', + dbName: '{{DB_NAME_ENV}}', + port: '{{PORT_ENV}}', + uri: '{{URI_ENV}}', + }; + const parsedConfig = getEnvDBConfig(dbConfig); + + expect(parsedConfig).toEqual({ + host: 'host_from_env', + user: 'user_from_env', + password: 'password_from_env', + dbName: 'db_name_from_env', + port: 2222, + uri: 'uri_from_env', + }); + }); + + test('Parses default ENV', () => { + const dbConfig: Partial = {}; + const parsedConfig = getEnvDBConfig(dbConfig); + + expect(parsedConfig).toEqual({ + host: 'pg_host', + user: 'pg_user', + password: 'pg_password', + dbName: 'pg_db_name', + port: 1111, + uri: 'pg_uri', + }); + }); + + test('Parses default ENV port=DATABASE_URL', () => { + process.env.PGURI = undefined; + + const dbConfig: Partial = {}; + const parsedConfig = getEnvDBConfig(dbConfig); + + expect(parsedConfig).toEqual({ + host: 'pg_host', + user: 'pg_user', + password: 'pg_password', + dbName: 'pg_db_name', + port: 1111, + uri: 'pg_url', + }); + }); + + test('Parses default ENV with invalid templates', () => { + // All invalid templates + const dbConfig: Partial = { + host: '{{{HOST_ENV}}', + user: '{{USER_ENV}}}', + password: 'invalid', + dbName: '', + port: '1234', + uri: '_', + }; + const parsedConfig = getEnvDBConfig(dbConfig); + + expect(parsedConfig).toEqual({ + host: undefined, + user: undefined, + password: 'pg_password', + dbName: 'pg_db_name', + port: 1111, + uri: 'pg_uri', + }); + }); +}); diff --git a/packages/cli/src/config.ts b/packages/cli/src/config.ts index 7a73eae1..663283d6 100644 --- a/packages/cli/src/config.ts +++ b/packages/cli/src/config.ts @@ -45,7 +45,7 @@ const configParser = t.type({ t.type({ host: t.union([t.string, t.undefined]), password: t.union([t.string, t.undefined]), - port: t.union([t.number, t.undefined]), + port: t.union([t.number, t.string, t.undefined]), user: t.union([t.string, t.undefined]), dbName: t.union([t.string, t.undefined]), ssl: t.union([t.UnknownRecord, t.boolean, t.undefined]), @@ -67,8 +67,42 @@ const configParser = t.type({ ]), }); +export type DBConfigArgs = { + host: string; + user: string; + password: string; + dbName: string; + port: number | string; + ssl: tls.ConnectionOptions | boolean; + uri: string; +}; + export type IConfig = typeof configParser._O; +function parseEnvTemplate(input?: string): string | undefined { + const templateStringRegex = new RegExp('{{\\w+}}', 'g'); + const result = input ? templateStringRegex.exec(input) : undefined; + return result?.input?.substring(2, result.input.length - 2); +} + +export function getEnvDBConfig(dBConfig: Partial) { + const host = parseEnvTemplate(dBConfig.host) ?? 'PGHOST'; + const user = parseEnvTemplate(dBConfig.user) ?? 'PGUSER'; + const password = parseEnvTemplate(dBConfig.password) ?? 'PGPASSWORD'; + const dbName = parseEnvTemplate(dBConfig.dbName) ?? 'PGDATABASE'; + const port = parseEnvTemplate(dBConfig?.port?.toString()) ?? 'PGPORT'; + const uri = parseEnvTemplate(dBConfig.uri) ?? 'PGURI'; + + return { + host: process.env[host], + user: process.env[user], + password: process.env[password], + dbName: process.env[dbName], + port: process.env[port] ? Number(process.env[port]) : undefined, + uri: process.env[uri] ?? process.env.DATABASE_URL, + }; +} + export interface ParsedConfig { db: { host: string; @@ -162,15 +196,6 @@ export function parseConfig( port: 5432, }; - const envDBConfig = { - host: process.env.PGHOST, - user: process.env.PGUSER, - password: process.env.PGPASSWORD, - dbName: process.env.PGDATABASE, - port: process.env.PGPORT ? Number(process.env.PGPORT) : undefined, - uri: process.env.PGURI ?? process.env.DATABASE_URL, - }; - const { db = defaultDBConfig, dbUrl: configDbUri, @@ -182,6 +207,8 @@ export function parseConfig( typesOverrides, } = configObject as IConfig; + const envDBConfig = getEnvDBConfig(db); + // CLI connectionUri flag takes precedence over the env and config one const dbUri = argConnectionUri || envDBConfig.uri || configDbUri; @@ -196,7 +223,18 @@ export function parseConfig( ); } - const finalDBConfig = merge(defaultDBConfig, db, urlDBConfig, envDBConfig); + // The port may be a template string + const dbConfig = { + ...db, + port: typeof db.port === 'string' ? Number(db.port) : db.port, + }; + + const finalDBConfig = merge( + defaultDBConfig, + dbConfig, + urlDBConfig, + envDBConfig, + ); const parsedTypesOverrides: Record> = {};