Skip to content

Commit

Permalink
feat: config utility
Browse files Browse the repository at this point in the history
  • Loading branch information
Flavio Stutz committed Jan 11, 2024
1 parent 73652e5 commit 6ffe6f3
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# cdk-practical-constructs

A collection of CDK constructs for making the development of AWS based applications easier and safer in a practical way.
A collection of CDK constructs and utilities for making the development of AWS based applications easier and safer in a practical way.

See examples for BaseNodeJsLambda and OpenApiGatewayLambda below.

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"fs": "0.0.1-security",
"npm-which": "^3.0.1",
"openapi3-ts": "^4.2.1",
"scoperjs": "^1.0.0",
"tmp": "^0.2.1",
"zod": "^3.22.4"
},
Expand Down
7 changes: 7 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions src/config/configs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* eslint-disable camelcase */
import { RetentionDays } from 'aws-cdk-lib/aws-logs';

import { LambdaConfig } from '..';

import { StageConfig, StagesConfig, resolveStageConfig } from './configs';

type TestConfig = StageConfig & {
lambda: LambdaConfig;
};

const testStageConfigs: StagesConfig<TestConfig> = {
default: {
lambda: {
allowAllOutbound: true,
logRetention: RetentionDays.ONE_WEEK,
},
},
dev: {
lambda: {
logRetention: RetentionDays.ONE_DAY,
bundling: {
sourceMap: true,
},
},
},
prd: {
lambda: {
logRetention: RetentionDays.SIX_MONTHS,
},
},
};

describe('configs', () => {
it('resolve config with dev overrides', async () => {
const stageConfig = resolveStageConfig<TestConfig>('dev', testStageConfigs);
expect(stageConfig.lambda.allowAllOutbound).toBeTruthy();
expect(stageConfig.lambda.logRetention).toBe(RetentionDays.ONE_DAY);
});

it('resolve stage "dev-pr-123" with same contents as stage "dev"', async () => {
const stageConfig = resolveStageConfig<TestConfig>('dev-pr-123', testStageConfigs);
expect(stageConfig.lambda.allowAllOutbound).toBeTruthy();
expect(stageConfig.lambda.logRetention).toBe(RetentionDays.ONE_DAY);
});

it('resolve prd config with defaults', async () => {
const stageConfig = resolveStageConfig<TestConfig>('tst', testStageConfigs);
expect(stageConfig.lambda.allowAllOutbound).toBeTruthy();
expect(stageConfig.lambda.logRetention).toBe(RetentionDays.ONE_WEEK);
});

it('resolve acc config with defaults', async () => {
const stageConfig = resolveStageConfig<TestConfig>('prd', testStageConfigs);
expect(stageConfig.lambda.allowAllOutbound).toBeTruthy();
expect(stageConfig.lambda.logRetention).toBe(RetentionDays.SIX_MONTHS);
});
});
46 changes: 46 additions & 0 deletions src/config/configs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { Scoper } from 'scoperjs';

// https://stackoverflow.com/questions/43159887/make-a-single-property-optional-in-typescript
export type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

export type StagesConfig<T extends StageConfig> = {
default: T;
[stage: string]: T;
};

export type StageConfig = {
stage?: string;
};

/**
* Returns the prefix for a stage name identified by a '-'. e.g: for 'dev-pr-123', it returns 'dev'
* @param stageName Full stage name. e.g: dev-pr-123, dev-testing-deploy, acc-frozen
* @returns Prefix of stage name. e.g: 'dev'
*/
export const getStagePrefix = (stage: string): string => {
return stage.replaceAll(/-.*/g, '');
};

/**
* Resolves a stage configuration by merging global configs with specific stage configs
* @param {string} stage such as 'dev', 'tst', 'acc', 'prd', 'dev-pr-123'. The actual stage name for getting the config will be the stage name prefixed by "-" if exists. For example, for 'dev-pr-123', it will be used 'dev'
* @param {StagesConfig<T>} stagesConfig Configuration for all stages along with default values if the stage doesn't define a specific value
* @returns {StageConfig}
*/
export function resolveStageConfig<T extends StageConfig>(
stage: string,
stagesConfig: StagesConfig<T>,
): T {
const contexter = Scoper.create<T>(stagesConfig.default);
// eslint-disable-next-line no-restricted-syntax
for (const [stagek, config] of Object.entries(stagesConfig)) {
contexter.setScopeValue(stagek, config);
}

// get stage context by stage prefix name. e.g: context 'dev' when stage is 'dev-pr-123'
const stagePrefix = getStagePrefix(stage);

const stageValue = contexter.getValue(stagePrefix);
stageValue.stage = stage;
return stageValue;
}

0 comments on commit 6ffe6f3

Please sign in to comment.