mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
fix(env): trim environment syntax prefix before casting (#21218)
* fix(env): respect environment syntax prefix * added changeset * Update changeset * Base prefix removal on substring, don't require key * Update uses of cast to match updated type sig --------- Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>
This commit is contained in:
5
.changeset/khaki-moose-sing.md
Normal file
5
.changeset/khaki-moose-sing.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@directus/env': minor
|
||||
---
|
||||
|
||||
Fixed an issue that would prevent prefix-based casted values in environment variables not to be extracted properly.
|
||||
50
packages/env/src/lib/cast.test.ts
vendored
50
packages/env/src/lib/cast.test.ts
vendored
@@ -22,17 +22,39 @@ describe('Type extraction', () => {
|
||||
test('Uses cast flag if exists', () => {
|
||||
vi.mocked(getCastFlag).mockReturnValue('string');
|
||||
|
||||
cast('key', 'value');
|
||||
cast('string:value', 'key');
|
||||
|
||||
expect(getCastFlag).toHaveBeenCalledWith('value');
|
||||
expect(getCastFlag).toHaveBeenCalledWith('string:value');
|
||||
expect(toString).toHaveBeenCalledWith('value');
|
||||
});
|
||||
|
||||
test('Uses cast flag for array with nested cast flags if exists', () => {
|
||||
vi.mocked(getCastFlag).mockImplementation((val) => {
|
||||
if (String(val).startsWith('array')) return 'array';
|
||||
if (String(val).startsWith('string')) return 'string';
|
||||
return 'number';
|
||||
});
|
||||
|
||||
vi.mocked(toString).mockReturnValue('hey');
|
||||
vi.mocked(toNumber).mockReturnValue(1);
|
||||
vi.mocked(toArray).mockReturnValue(['string:hey', 'number:1']);
|
||||
|
||||
const res = cast('array:string:hey,number:1', 'key');
|
||||
|
||||
expect(getCastFlag).toHaveBeenNthCalledWith(1, 'array:string:hey,number:1');
|
||||
expect(getCastFlag).toHaveBeenCalledWith('string:hey');
|
||||
expect(getCastFlag).toHaveBeenCalledWith('number:1');
|
||||
expect(toString).toHaveBeenCalledWith('hey');
|
||||
expect(toArray).toHaveBeenCalledWith('string:hey,number:1');
|
||||
expect(toNumber).toHaveBeenCalledWith('1');
|
||||
expect(res).toEqual(['hey', 1]);
|
||||
});
|
||||
|
||||
test('Uses type map entry if cast flag does not exist', () => {
|
||||
vi.mocked(getCastFlag).mockReturnValue(null);
|
||||
vi.mocked(getTypeFromMap).mockReturnValue('string');
|
||||
|
||||
cast('key', 'value');
|
||||
cast('value', 'key');
|
||||
|
||||
expect(getTypeFromMap).toHaveBeenCalledWith('key');
|
||||
expect(toString).toHaveBeenCalledWith('value');
|
||||
@@ -43,7 +65,7 @@ describe('Type extraction', () => {
|
||||
vi.mocked(getTypeFromMap).mockReturnValue(null);
|
||||
vi.mocked(guessType).mockReturnValue('string');
|
||||
|
||||
cast('key', 'value');
|
||||
cast('value', 'key');
|
||||
|
||||
expect(guessType).toHaveBeenCalledWith('value');
|
||||
expect(toString).toHaveBeenCalledWith('value');
|
||||
@@ -55,39 +77,45 @@ describe('Casting', () => {
|
||||
vi.mocked(getCastFlag).mockReturnValue('string');
|
||||
|
||||
vi.mocked(toString).mockReturnValue('cast-value');
|
||||
expect(cast('key', 'value')).toBe('cast-value');
|
||||
expect(cast('value')).toBe('cast-value', 'key');
|
||||
});
|
||||
|
||||
test('Uses toNumber for number types', () => {
|
||||
vi.mocked(getCastFlag).mockReturnValue('number');
|
||||
|
||||
vi.mocked(toNumber).mockReturnValue(123);
|
||||
expect(cast('key', 'value')).toBe(123);
|
||||
expect(cast('value')).toBe(123, 'key');
|
||||
});
|
||||
|
||||
test('Uses toBoolean for number types', () => {
|
||||
vi.mocked(getCastFlag).mockReturnValue('boolean');
|
||||
|
||||
vi.mocked(toBoolean).mockReturnValue(false);
|
||||
expect(cast('key', 'value')).toBe(false);
|
||||
expect(cast('value')).toBe(false, 'key');
|
||||
});
|
||||
|
||||
test('Uses RegExp for regex types', () => {
|
||||
vi.mocked(getCastFlag).mockReturnValue('regex');
|
||||
expect(cast('key', 'value')).toBeInstanceOf(RegExp);
|
||||
expect(cast('value')).toBeInstanceOf(RegExp, 'key');
|
||||
});
|
||||
|
||||
test('Uses toArray for array types', () => {
|
||||
vi.mocked(getCastFlag).mockReturnValue('array');
|
||||
vi.mocked(getCastFlag).mockImplementation((v) => {
|
||||
if (String(v).startsWith('array')) return 'array';
|
||||
return null;
|
||||
});
|
||||
|
||||
vi.mocked(guessType).mockReturnValue('number');
|
||||
vi.mocked(toNumber).mockImplementation((v) => v);
|
||||
vi.mocked(toArray).mockReturnValue([1, 2, 3]);
|
||||
expect(cast('key', 'value')).toEqual([1, 2, 3]);
|
||||
|
||||
expect(cast('array:value')).toEqual([1, 2, 3], 'key');
|
||||
});
|
||||
|
||||
test('Uses tryJson for json types', () => {
|
||||
vi.mocked(getCastFlag).mockReturnValue('json');
|
||||
|
||||
vi.mocked(tryJson).mockReturnValue('cast-value');
|
||||
expect(cast('key', 'value')).toBe('cast-value');
|
||||
expect(cast('value')).toBe('cast-value', 'key');
|
||||
});
|
||||
});
|
||||
|
||||
11
packages/env/src/lib/cast.ts
vendored
11
packages/env/src/lib/cast.ts
vendored
@@ -5,8 +5,13 @@ import { guessType } from '../utils/guess-type.js';
|
||||
import { getCastFlag } from '../utils/has-cast-prefix.js';
|
||||
import { tryJson } from '../utils/try-json.js';
|
||||
|
||||
export const cast = (key: string, value: unknown) => {
|
||||
const type = getCastFlag(value) ?? getTypeFromMap(key) ?? guessType(value);
|
||||
export const cast = (value: unknown, key?: string): unknown => {
|
||||
const castFlag = getCastFlag(value);
|
||||
const type = castFlag ?? getTypeFromMap(key) ?? guessType(value);
|
||||
|
||||
if (typeof value === 'string' && castFlag) {
|
||||
value = value.substring(castFlag.length + 1); // Type length + 1 for `:` character
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'string':
|
||||
@@ -18,7 +23,7 @@ export const cast = (key: string, value: unknown) => {
|
||||
case 'regex':
|
||||
return new RegExp(String(value));
|
||||
case 'array':
|
||||
return toArray(value);
|
||||
return toArray(value).map((v) => cast(v));
|
||||
case 'json':
|
||||
return tryJson(value);
|
||||
}
|
||||
|
||||
4
packages/env/src/lib/create-env.test.ts
vendored
4
packages/env/src/lib/create-env.test.ts
vendored
@@ -28,7 +28,7 @@ let processConfig: Record<string, string>;
|
||||
let fileConfig: Record<string, unknown>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.mocked(cast).mockImplementation((_key, value) => value);
|
||||
vi.mocked(cast).mockImplementation((value) => value);
|
||||
|
||||
processConfig = { PROCESS: 'test-process' };
|
||||
fileConfig = { FILE: 'test-file' };
|
||||
@@ -117,7 +117,7 @@ test('Throws error if file could not be read', () => {
|
||||
});
|
||||
|
||||
test('Casts regular values', () => {
|
||||
vi.mocked(cast).mockImplementation((_key, value) => `cast-${value}`);
|
||||
vi.mocked(cast).mockImplementation((value) => `cast-${value}`);
|
||||
|
||||
const env = createEnv();
|
||||
|
||||
|
||||
2
packages/env/src/lib/create-env.ts
vendored
2
packages/env/src/lib/create-env.ts
vendored
@@ -27,7 +27,7 @@ export const createEnv = (): Env => {
|
||||
throw new Error(`Failed to read value from file "${value}", defined in environment variable "${key}".`);
|
||||
}
|
||||
} else {
|
||||
output[key] = cast(key, value);
|
||||
output[key] = cast(value, key);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,3 +20,8 @@ test('Returns null if key does not exist', () => {
|
||||
const res = getTypeFromMap('non-existing');
|
||||
expect(res).toBe(null);
|
||||
});
|
||||
|
||||
test('Returns null if key is undefined', () => {
|
||||
const res = getTypeFromMap(undefined);
|
||||
expect(res).toBe(null);
|
||||
});
|
||||
|
||||
4
packages/env/src/utils/get-type-from-map.ts
vendored
4
packages/env/src/utils/get-type-from-map.ts
vendored
@@ -1,7 +1,9 @@
|
||||
import { TYPE_MAP } from '../constants/type-map.js';
|
||||
import type { EnvType } from '../types/env-type.js';
|
||||
|
||||
export const getTypeFromMap = (key: string): EnvType | null => {
|
||||
export const getTypeFromMap = (key: string | undefined): EnvType | null => {
|
||||
if (!key) return null;
|
||||
|
||||
const type = TYPE_MAP[key];
|
||||
|
||||
if (type !== undefined) {
|
||||
|
||||
Reference in New Issue
Block a user