diff --git a/graphql/env/__tests__/__snapshots__/merge.test.ts.snap b/graphql/env/__tests__/__snapshots__/merge.test.ts.snap index 02c79c085..636127402 100644 --- a/graphql/env/__tests__/__snapshots__/merge.test.ts.snap +++ b/graphql/env/__tests__/__snapshots__/merge.test.ts.snap @@ -12,10 +12,6 @@ exports[`getEnvOptions merges pgpm defaults, graphql defaults, config, env, and ], "isPublic": true, "metaSchemas": [ - "config_meta", - "services_public", - "metaschema_public", - "metaschema_modules_public", "env_meta1", "env_meta2", ], @@ -75,9 +71,6 @@ exports[`getEnvOptions merges pgpm defaults, graphql defaults, config, env, and "graphileBuildOptions": {}, "overrideSettings": {}, "schema": [ - "config_schema", - "env_schema_a", - "env_schema_b", "override_schema", ], }, diff --git a/graphql/env/__tests__/merge.test.ts b/graphql/env/__tests__/merge.test.ts index 4cc6e240f..edbc3f6df 100644 --- a/graphql/env/__tests__/merge.test.ts +++ b/graphql/env/__tests__/merge.test.ts @@ -84,8 +84,8 @@ describe('getEnvOptions', () => { expect(result).toMatchSnapshot(); }); - it('dedupes graphql array fields across config, env, and overrides', () => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'graphql-env-dedupe-')); + it('replaces graphql array fields with later values (overrides win)', () => { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'graphql-env-replace-')); writeConfig(tempDir, { graphile: { schema: ['config_schema', 'shared_schema'] @@ -116,25 +116,9 @@ describe('getEnvOptions', () => { testEnv ); - expect(result.graphile?.schema).toEqual([ - 'config_schema', - 'shared_schema', - 'env_schema', - 'override_schema' - ]); - expect(result.api?.exposedSchemas).toEqual([ - 'public', - 'shared', - 'env_schema', - 'override_schema' - ]); - expect(result.api?.metaSchemas).toEqual([ - 'metaschema_public', - 'services_public', - 'config_meta', - 'metaschema_modules_public', - 'env_meta', - 'override_meta' - ]); + // Arrays are replaced, not merged - overrides win completely + expect(result.graphile?.schema).toEqual(['override_schema', 'shared_schema']); + expect(result.api?.exposedSchemas).toEqual(['public', 'override_schema']); + expect(result.api?.metaSchemas).toEqual(['env_meta', 'override_meta']); }); }); diff --git a/graphql/env/src/merge.ts b/graphql/env/src/merge.ts index c9e1e235a..669c75269 100644 --- a/graphql/env/src/merge.ts +++ b/graphql/env/src/merge.ts @@ -1,6 +1,6 @@ import deepmerge from 'deepmerge'; import { ConstructiveOptions, constructiveGraphqlDefaults } from '@constructive-io/graphql-types'; -import { getEnvOptions as getPgpmEnvOptions, loadConfigSync, mergeArraysUnique } from '@pgpmjs/env'; +import { getEnvOptions as getPgpmEnvOptions, loadConfigSync, replaceArrays } from '@pgpmjs/env'; import { getGraphQLEnvVars } from './env'; /** @@ -47,7 +47,7 @@ export const getEnvOptions = ( graphqlEnvOptions, overrides ], { - arrayMerge: mergeArraysUnique + arrayMerge: replaceArrays }) as ConstructiveOptions; }; diff --git a/pgpm/env/__tests__/merge.test.ts b/pgpm/env/__tests__/merge.test.ts index f6db3d26e..c880841c5 100644 --- a/pgpm/env/__tests__/merge.test.ts +++ b/pgpm/env/__tests__/merge.test.ts @@ -180,8 +180,8 @@ describe('getEnvOptions', () => { expect(result).toMatchSnapshot(); }); - it('dedupes array fields across config, env, and overrides', () => { - tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pgpm-env-dedupe-')); + it('replaces array fields with later values (overrides win)', () => { + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pgpm-env-replace-')); writeConfig(tempDir, { db: { extensions: ['uuid', 'postgis'] @@ -219,25 +219,10 @@ describe('getEnvOptions', () => { const result = getEnvOptions(overrides, tempDir, testEnv) as PgpmOptionsWithPackages; - expect(result.db?.extensions).toEqual([ - 'uuid', - 'postgis', - 'pgcrypto', - 'hstore' - ]); - expect(result.jobs?.worker?.supported).toEqual([ - 'alpha', - 'beta', - 'gamma', - 'delta', - 'epsilon' - ]); - expect(result.jobs?.scheduler?.supported).toEqual([ - 'beta', - 'gamma', - 'delta', - 'zeta' - ]); - expect(result.packages).toEqual(['testing/*', 'packages/*', 'extensions/*']); + // Arrays are replaced, not merged - overrides win completely + expect(result.db?.extensions).toEqual(['uuid', 'hstore']); + expect(result.jobs?.worker?.supported).toEqual(['delta', 'epsilon']); + expect(result.jobs?.scheduler?.supported).toEqual(['gamma', 'zeta']); + expect(result.packages).toEqual(['testing/*', 'extensions/*']); }); }); diff --git a/pgpm/env/src/index.ts b/pgpm/env/src/index.ts index 66fb144f2..9ec47ea63 100644 --- a/pgpm/env/src/index.ts +++ b/pgpm/env/src/index.ts @@ -11,6 +11,6 @@ export { } from './config'; export type { WorkspaceType } from './config'; export { getEnvVars, getNodeEnv, parseEnvBoolean, parseEnvNumber } from './env'; -export { walkUp, mergeArraysUnique } from './utils'; +export { walkUp, replaceArrays } from './utils'; export type { PgpmOptions, PgTestConnectionOptions, DeploymentOptions } from '@pgpmjs/types'; diff --git a/pgpm/env/src/merge.ts b/pgpm/env/src/merge.ts index 1e0a08074..d0e920db3 100644 --- a/pgpm/env/src/merge.ts +++ b/pgpm/env/src/merge.ts @@ -2,7 +2,7 @@ import deepmerge from 'deepmerge'; import { pgpmDefaults, PgpmOptions, PgTestConnectionOptions, DeploymentOptions } from '@pgpmjs/types'; import { loadConfigSync } from './config'; import { getEnvVars } from './env'; -import { mergeArraysUnique } from './utils'; +import { replaceArrays } from './utils'; /** * Get core PGPM environment options by merging: @@ -26,7 +26,7 @@ export const getEnvOptions = ( const envOptions = getEnvVars(env); return deepmerge.all([pgpmDefaults, configOptions, envOptions, overrides], { - arrayMerge: mergeArraysUnique + arrayMerge: replaceArrays }); }; diff --git a/pgpm/env/src/utils.ts b/pgpm/env/src/utils.ts index 083be7831..2b8cba957 100644 --- a/pgpm/env/src/utils.ts +++ b/pgpm/env/src/utils.ts @@ -26,11 +26,14 @@ export const walkUp = (startDir: string, filename: string): string => { throw new Error(`File "${filename}" not found in any parent directories.`); }; -export const mergeArraysUnique = ( - target: T[], +/** + * Array merge strategy that replaces target array with source array. + * This allows later configuration sources to completely override earlier ones. + */ +export const replaceArrays = ( + _target: T[], source: T[], _options?: unknown ): T[] => { - const merged = [...target, ...source]; - return [...new Set(merged)]; + return source; };