@aligent/nx-cdk 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -21,6 +21,13 @@ npx create-nx-workspace@latest --preset=@aligent/nx-cdk
21
21
  | `name` | string | Yes | - | The name of the project/application (alphanumeric and dashes only) |
22
22
  | `nodeVersion` | string | No | `24.11.0` | The target Node.js version for the project (must be valid semver, e.g., 22.10.0) |
23
23
 
24
+ #### Post-generation setup
25
+
26
+ After running the preset generator, review these defaults before committing:
27
+
28
+ - **AWS profile** — The `pg:synth`, `pg:deploy`, `pg:diff`, and `pg:destroy` scripts in `package.json` use `--profile playground`. This assumes a profile named `playground` exists in your `~/.aws/config`. Update the profile name in the `pg:*` scripts if yours differs.
29
+ - **`.github/CODEOWNERS`** — The generated file references Aligent's GitHub teams (`@aligent/microservices`, `@aligent/devops`). Replace these with your organisation's team handles.
30
+
24
31
  #### What it creates
25
32
 
26
33
  The preset generator scaffolds:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aligent/nx-cdk",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "type": "commonjs",
5
5
  "main": "./src/index.js",
6
6
  "typings": "./src/index.d.ts",
@@ -26,7 +26,7 @@ exports.NX_JSON = {
26
26
  cache: true,
27
27
  dependsOn: ['^build'],
28
28
  inputs: ['production', '^production'],
29
- outputs: ['{projectRoot}/dist'],
29
+ outputs: ['{projectRoot}/dist'], // FIXME: It seems that this doesn't cache correctly. Investigate this.
30
30
  },
31
31
  lint: { cache: true, inputs: ['default', '^production'] },
32
32
  test: {
@@ -37,5 +37,7 @@ exports.NX_JSON = {
37
37
  configurations: { coverage: { coverage: true } },
38
38
  },
39
39
  typecheck: { cache: true, inputs: ['default', '^production'] },
40
+ cdk: { dependsOn: [{ target: 'build', params: 'forward', projects: '@services/*' }] },
41
+ pg: { dependsOn: [{ target: 'build', params: 'forward', projects: '@services/*' }] },
40
42
  },
41
43
  };
@@ -10,17 +10,18 @@ export declare const PACKAGE_JSON: {
10
10
  readonly 'lint:all': "nx run-many -t lint";
11
11
  readonly typecheck: "nx affected -t typecheck";
12
12
  readonly 'typecheck:all': "nx run-many -t typecheck";
13
- readonly postinstall: "[ -d .git ] && git config core.hooksPath '.git-hooks' && chmod +x .git-hooks/* || true";
14
- readonly 'pg:synth': "nx run application:cdk synth 'dev/**' --exclusively --bundle-mode=dev --profile=playground";
15
- readonly 'pg:deploy': "nx run application:cdk deploy 'dev/**' --method=direct --require-approval=never --exclusively --bundle-mode=dev --profile=playground";
16
- readonly 'pg:destroy': "nx run application:cdk destroy 'dev/**' --profile playground";
17
13
  readonly audit: "nx run-many -t lint typecheck test --configuration coverage --skip-nx-cache";
14
+ readonly 'pg:synth': "nx run application:pg:synth --bundle-mode=dev --profile playground";
15
+ readonly 'pg:deploy': "nx run application:pg:deploy --bundle-mode=dev --method=direct --require-approval never --profile playground";
16
+ readonly 'pg:diff': "nx run application:pg:diff --profile playground";
17
+ readonly 'pg:destroy': "nx run application:pg:destroy --profile playground";
18
+ readonly postinstall: "[ -d .git ] && git config core.hooksPath '.git-hooks' && chmod +x .git-hooks/* || true";
18
19
  };
19
20
  readonly dependencies: {
20
21
  readonly '@aligent/microservice-util-lib': "^1.2.0";
21
22
  };
22
23
  readonly devDependencies: {
23
- readonly '@aligent/cdk-aspects': "^0.2.0";
24
+ readonly '@aligent/cdk-aspects': "^0.4.0";
24
25
  readonly '@aligent/cdk-step-function-from-file': "^0.3.2";
25
26
  readonly '@aligent/nx-openapi': "^1.0.0";
26
27
  readonly '@aligent/ts-code-standards': "^4.1.0";
@@ -13,17 +13,18 @@ exports.PACKAGE_JSON = {
13
13
  'lint:all': 'nx run-many -t lint',
14
14
  typecheck: 'nx affected -t typecheck',
15
15
  'typecheck:all': 'nx run-many -t typecheck',
16
- postinstall: `[ -d .git ] && git config core.hooksPath '.git-hooks' && chmod +x .git-hooks/* || true`,
17
- 'pg:synth': `nx run application:cdk synth 'dev/**' --exclusively --bundle-mode=dev --profile=playground`,
18
- 'pg:deploy': `nx run application:cdk deploy 'dev/**' --method=direct --require-approval=never --exclusively --bundle-mode=dev --profile=playground`,
19
- 'pg:destroy': "nx run application:cdk destroy 'dev/**' --profile playground",
20
16
  audit: 'nx run-many -t lint typecheck test --configuration coverage --skip-nx-cache',
17
+ 'pg:synth': 'nx run application:pg:synth --bundle-mode=dev --profile playground',
18
+ 'pg:deploy': 'nx run application:pg:deploy --bundle-mode=dev --method=direct --require-approval never --profile playground',
19
+ 'pg:diff': 'nx run application:pg:diff --profile playground',
20
+ 'pg:destroy': 'nx run application:pg:destroy --profile playground',
21
+ postinstall: `[ -d .git ] && git config core.hooksPath '.git-hooks' && chmod +x .git-hooks/* || true`,
21
22
  },
22
23
  dependencies: {
23
24
  '@aligent/microservice-util-lib': '^1.2.0',
24
25
  },
25
26
  devDependencies: {
26
- '@aligent/cdk-aspects': '^0.2.0',
27
+ '@aligent/cdk-aspects': '^0.4.0',
27
28
  '@aligent/cdk-step-function-from-file': '^0.3.2',
28
29
  '@aligent/nx-openapi': '^1.0.0',
29
30
  '@aligent/ts-code-standards': '^4.1.0',
@@ -18,11 +18,12 @@ export declare function constructPackageJsonFile(input: PackageJsonInput): {
18
18
  readonly 'lint:all': "nx run-many -t lint";
19
19
  readonly typecheck: "nx affected -t typecheck";
20
20
  readonly 'typecheck:all': "nx run-many -t typecheck";
21
- readonly postinstall: "[ -d .git ] && git config core.hooksPath '.git-hooks' && chmod +x .git-hooks/* || true";
22
- readonly 'pg:synth': "nx run application:cdk synth 'dev/**' --exclusively --bundle-mode=dev --profile=playground";
23
- readonly 'pg:deploy': "nx run application:cdk deploy 'dev/**' --method=direct --require-approval=never --exclusively --bundle-mode=dev --profile=playground";
24
- readonly 'pg:destroy': "nx run application:cdk destroy 'dev/**' --profile playground";
25
21
  readonly audit: "nx run-many -t lint typecheck test --configuration coverage --skip-nx-cache";
22
+ readonly 'pg:synth': "nx run application:pg:synth --bundle-mode=dev --profile playground";
23
+ readonly 'pg:deploy': "nx run application:pg:deploy --bundle-mode=dev --method=direct --require-approval never --profile playground";
24
+ readonly 'pg:diff': "nx run application:pg:diff --profile playground";
25
+ readonly 'pg:destroy': "nx run application:pg:destroy --profile playground";
26
+ readonly postinstall: "[ -d .git ] && git config core.hooksPath '.git-hooks' && chmod +x .git-hooks/* || true";
26
27
  } | {
27
28
  readonly '@aligent/microservice-util-lib': "^1.2.0";
28
29
  } | {
@@ -1,3 +1,5 @@
1
- # Both Microservice and DevOps teams own everything in this repo
2
- # We'll review this in few months time and adjust when needed
3
- * @aligent/microservices @aligent/devops
1
+ # Default owners
2
+ * @aligent/microservices
3
+
4
+ # DevOps owns files inside .github/
5
+ .github/** @aligent/devops
@@ -12,6 +12,6 @@ jobs:
12
12
  with:
13
13
  deploy: true
14
14
  deploy-command: yarn nx run application:cdk deploy
15
- extra-arguments: --exclusively
15
+ environment-target: prd
16
16
  github-environment: production
17
17
  secrets: inherit
@@ -12,6 +12,6 @@ jobs:
12
12
  with:
13
13
  deploy: true
14
14
  deploy-command: yarn nx run application:cdk deploy
15
- extra-arguments: --exclusively
15
+ environment-target: stg
16
16
  github-environment: staging
17
17
  secrets: inherit
@@ -48,6 +48,25 @@ nvm use
48
48
  yarn install
49
49
  ```
50
50
 
51
+ ### Initial Setup Checklist
52
+
53
+ Before committing this repository, review and update the following generated defaults:
54
+
55
+ - **`.github/CODEOWNERS`** — The default file references Aligent's GitHub teams (`@aligent/microservices`, `@aligent/devops`). Replace these with your own organisation's team handles.
56
+ - **`package.json` `pg:*` scripts** — The playground scripts use `--profile playground`. Ensure you have a matching profile in your `~/.aws/config` file:
57
+
58
+ ```ini
59
+ [profile playground]
60
+ source_profile = default
61
+ role_arn = arn:aws:iam::11111111111:role/RoleName
62
+ ```
63
+
64
+ If your profile has a different name, update the `pg:*` scripts in the root `package.json` to match.
65
+
66
+ ### Deployment
67
+
68
+ For deployment instructions (synthesize, deploy, destroy), see the [CDK Application README](./application/README.md).
69
+
51
70
  ## Contributing
52
71
 
53
72
  We follow [trunk-based development framework](https://trunkbaseddevelopment.com/) as a start and will evolve depending on our need.
@@ -6,12 +6,44 @@ This application manages the deployment of microservices and their shared infras
6
6
 
7
7
  ### Using yarn script from workspace root (Recommended)
8
8
 
9
+ ##### Synthesize
10
+
9
11
  ```bash
10
- # Synthesize templates
12
+ # Synthesize for default environment/stage (dev)
11
13
  yarn pg:synth
12
14
 
13
- # Deploy to playground
15
+ # Synthesize for a custom environment/stage
16
+ yarn pg:synth --args="plg/** -c environment=plg"
17
+ ```
18
+
19
+ ##### Deploy
20
+
21
+ ```bash
22
+ # Deploy for default environment/stage (dev)
14
23
  yarn pg:deploy
24
+
25
+ # Deploy to a custom environment/stage
26
+ yarn pg:deploy --args="plg/** -c environment=plg"
27
+ ```
28
+
29
+ ##### Diff
30
+
31
+ ```bash
32
+ # Diff checking for default environment/stage (dev)
33
+ yarn pg:diff
34
+
35
+ # Diff checking for a custom environment/stage
36
+ yarn pg:diff --args="-c environment=plg"
37
+ ```
38
+
39
+ ##### Destroy
40
+
41
+ ```bash
42
+ # Destroy default environment/stage (dev)
43
+ yarn pg:destroy
44
+
45
+ # Destroy a custom environment/stage
46
+ yarn pg:destroy --args="-c environment=plg"
15
47
  ```
16
48
 
17
49
  ### Direct Nx Commands
@@ -4,66 +4,94 @@ import {
4
4
  LogGroupDefaultsAspect,
5
5
  MicroserviceChecks,
6
6
  NodeJsFunctionDefaultsAspect,
7
+ ResourcePrefixAspect,
7
8
  StepFunctionsDefaultsAspect,
8
9
  } from '@aligent/cdk-aspects';
9
- import { App, Aspects, Tags } from 'aws-cdk-lib';
10
- import { Runtime } from 'aws-cdk-lib/aws-lambda';
10
+ import { App, AspectOptions, AspectPriority, Aspects, IAspect, Runtime, Tags } from 'aws-cdk-lib';
11
11
  import { ApplicationStage } from '../lib/service-stacks.js';
12
12
 
13
- const APPLICATION_CONTEXT = { NAME: '<%= name %>-int', OWNER: 'aligent' } as const;
14
- const STAGE_NAMES = { DEV: 'dev', STG: 'stg', PRD: 'prd' } as const;
15
-
13
+ const APPLICATION_CONTEXT = { NAME: '<%= name %>-int', OWNER: 'Aligent' } as const;
16
14
  const app = new App({ context: APPLICATION_CONTEXT });
17
- Tags.of(app).add('OWNER', APPLICATION_CONTEXT.OWNER);
18
15
 
19
16
  /**
20
- * Apply Application-Level Aspects
21
- * These aspects are applied to all resources across all stages
22
- */
23
- Aspects.of(app).add(new NodeJsFunctionDefaultsAspect({ runtime: Runtime.NODEJS_<%= nodeRuntime %> }));
24
- Aspects.of(app).add(new StepFunctionsDefaultsAspect());
25
- Aspects.of(app).add(new MicroserviceChecks());
26
-
27
- /**
28
- * Static Stage Creation Approach
29
- *
30
- * We use static stage creation (explicitly defining dev, stg, prd) for several reasons:
17
+ * Dynamic Stage (Environment) Creation
31
18
  *
32
- * 1. **Predictability**: All stages are known at synthesis time, making the CDK app
33
- * behavior deterministic and easier to reason about.
19
+ * The stage (environment) name is provided via CDK context: `cdk synth -c environment=dev`
34
20
  *
35
- * 2. **Type Safety**: Static definitions provide better IDE support, autocompletion,
36
- * and compile-time type checking for stage-specific configurations.
37
- *
38
- * 3. **Explicit Configuration**: Each stage's unique settings (e.g., log retention
39
- * durations, aspects) are clearly visible in the code without needing to trace
40
- * through dynamic logic.
41
- *
42
- * 4. **Simpler Deployment**: CDK can synthesize all stages in a single pass without
43
- * requiring runtime context lookups or conditional logic.
44
- *
45
- * 5. **CI/CD Integration**: Static stages integrate seamlessly with standard CI/CD
46
- * pipelines where environment configuration is managed externally.
21
+ * - Stage name must be exactly 3 lowercase alphanumeric characters (e.g., dev (default), stg, prd)
22
+ * - Production-specific aspects (versioning, long log retention) are applied only to `prd`
23
+ * - Non-production stages get shorter log retention and debug-level logging
47
24
  *
48
25
  * @see {@link https://dev.to/aws-heroes/how-to-use-aws-cdk-stage-and-when-to-choose-static-vs-dynamic-stack-creation-35h|Static vs Dynamic Stack Creation}
49
- *
50
- * @remarks
51
- * Aspects visit all constructs in the tree after synthesis and can modify them
52
- * (e.g., applying log retention policies, enabling tracing).
53
26
  */
27
+ const stageName = app.node.tryGetContext('environment') ?? 'dev';
54
28
 
55
- const dev = new ApplicationStage(app, STAGE_NAMES.DEV);
56
- Aspects.of(dev).add(new LogGroupDefaultsAspect({ duration: 'SHORT' }));
29
+ if (typeof stageName !== 'string' || !/^[a-z0-9]{3}$/.test(stageName)) {
30
+ throw new Error(
31
+ `Invalid stage (environment) name: "${stageName}". Stage must be exactly 3 lowercase alphanumeric characters (e.g., dev, stg, prd)`
32
+ );
33
+ }
57
34
 
58
- const stg = new ApplicationStage(app, STAGE_NAMES.STG);
59
- Aspects.of(stg).add(new LogGroupDefaultsAspect({ duration: 'MEDIUM' }));
35
+ const stage = new ApplicationStage(app, stageName);
36
+ Tags.of(stage).add('APPLICATION', APPLICATION_CONTEXT.NAME);
37
+ Tags.of(stage).add('OWNER', APPLICATION_CONTEXT.OWNER);
38
+ Tags.of(stage).add('STAGE', stageName);
60
39
 
61
40
  /**
62
- * Production Stage
63
- * - Long log retention (2 years) for compliance and debugging
64
- * - Resources retained on stack deletion
65
- * - Lambda functions and Step Functions are automatically versioned, enabled for zero-downtime deployments
41
+ * Common aspects applied to every stage.
42
+ *
43
+ * Aspects must be registered on each `Stage` individually — a `Stage` creates a
44
+ * synthesis boundary that prevents `App`-level aspects from traversing into it.
45
+ *
46
+ * Order matters: `ResourcePrefixAspect` runs first (priority 100) so that
47
+ * resource names are finalised before the versioning aspect reads them.
66
48
  */
67
- const prd = new ApplicationStage(app, STAGE_NAMES.PRD);
68
- Aspects.of(prd).add(new LogGroupDefaultsAspect({ duration: 'LONG' }));
69
- Aspects.of(prd).add(new LambdaAndStepFunctionVersioningAspect());
49
+ const commonAspectsWithOptions: Array<{ aspect: IAspect; options: AspectOptions }> = [
50
+ {
51
+ aspect: new ResourcePrefixAspect({ prefix: `${APPLICATION_CONTEXT.NAME}-${stageName}` }),
52
+ options: { priority: 100 },
53
+ },
54
+ {
55
+ aspect: new NodeJsFunctionDefaultsAspect({ runtime: Runtime.NODEJS_<%= nodeRuntime %> }),
56
+ options: { priority: AspectPriority.MUTATING },
57
+ },
58
+ { aspect: new StepFunctionsDefaultsAspect(), options: { priority: AspectPriority.MUTATING } },
59
+ { aspect: new MicroserviceChecks(), options: { priority: AspectPriority.READONLY } },
60
+ ];
61
+
62
+ /**
63
+ * Apply common aspects to the stage, then add stage-specific aspects.
64
+ *
65
+ * @remarks
66
+ * Aspects visit every construct in the tree after synthesis and may mutate them
67
+ * (e.g. setting log retention, enabling X-Ray tracing).
68
+ *
69
+ * Stage-specific behaviour:
70
+ * - `prd`: long log retention (2 years) for compliance; Lambda and Step Function
71
+ * versioning enabled for zero-downtime deployments.
72
+ * - `stg`: medium log retention.
73
+ * - all others: short log retention.
74
+ */
75
+ commonAspectsWithOptions.forEach(({ aspect, options }) => {
76
+ Aspects.of(stage).add(aspect, options);
77
+ });
78
+
79
+ switch (stageName) {
80
+ case 'prd':
81
+ Aspects.of(stage).add(new LogGroupDefaultsAspect({ duration: 'LONG' }), {
82
+ priority: AspectPriority.MUTATING,
83
+ });
84
+ Aspects.of(stage).add(new LambdaAndStepFunctionVersioningAspect(), {
85
+ priority: AspectPriority.MUTATING,
86
+ });
87
+ break;
88
+ case 'stg':
89
+ Aspects.of(stage).add(new LogGroupDefaultsAspect({ duration: 'MEDIUM' }), {
90
+ priority: AspectPriority.MUTATING,
91
+ });
92
+ break;
93
+ default:
94
+ Aspects.of(stage).add(new LogGroupDefaultsAspect({ duration: 'SHORT' }), {
95
+ priority: AspectPriority.MUTATING,
96
+ });
97
+ }
@@ -1,5 +1,5 @@
1
1
  import { SharedInfraStack } from '@libs/infra';
2
- import { Stage, Tags, type StageProps } from 'aws-cdk-lib';
2
+ import { Stage, type StageProps } from 'aws-cdk-lib';
3
3
  import type { Construct } from 'constructs';
4
4
 
5
5
  /**
@@ -15,7 +15,6 @@ import type { Construct } from 'constructs';
15
15
  export class ApplicationStage extends Stage {
16
16
  constructor(scope: Construct, id: string, props?: StageProps) {
17
17
  super(scope, id, props);
18
- Tags.of(this).add('STAGE', id);
19
18
 
20
19
  const sharedInfra = new SharedInfraStack(this, 'shared-infra', props);
21
20
 
@@ -14,14 +14,28 @@
14
14
  "color": true,
15
15
  "command": "cdk",
16
16
  "cwd": "{projectRoot}"
17
- },
18
- "dependsOn": [
19
- {
20
- "target": "build",
21
- "params": "forward",
22
- "projects": "@services/*"
17
+ }
18
+ },
19
+ "pg": {
20
+ "executor": "nx:run-commands",
21
+ "configurations": {
22
+ "synth": {
23
+ "command": "cdk synth"
24
+ },
25
+ "deploy": {
26
+ "command": "cdk deploy"
27
+ },
28
+ "diff": {
29
+ "command": "cdk diff"
30
+ },
31
+ "destroy": {
32
+ "command": "cdk destroy"
23
33
  }
24
- ]
34
+ },
35
+ "options": {
36
+ "color": true,
37
+ "cwd": "{projectRoot}"
38
+ }
25
39
  }
26
40
  }
27
41
  }
@@ -56,6 +56,6 @@ Resources that are used by multiple services are created once in the shared infr
56
56
 
57
57
  The `SharedInfraStack` must be instantiated within a CDK Stage to properly resolve the stage name for resource naming and tagging.
58
58
 
59
- ### Application Context
59
+ ### Resource Naming
60
60
 
61
- Requires the `NAME` context variable to be set for proper resource naming conventions.
61
+ Resource prefixes are automatically applied via a CDK aspect, ensuring consistent naming conventions across all resources without manual configuration.
@@ -1,36 +1,32 @@
1
1
  import { Stack, Stage, type StackProps } from 'aws-cdk-lib';
2
2
  import { Secret } from 'aws-cdk-lib/aws-secretsmanager';
3
- import { StringParameter } from 'aws-cdk-lib/aws-ssm';
3
+ import { IStringParameter, StringParameter } from 'aws-cdk-lib/aws-ssm';
4
4
  import type { Construct } from 'constructs';
5
5
 
6
6
  export interface SharedInfraProps {
7
- eCommerceBaseUrl: string;
7
+ eCommerceBaseUrl: IStringParameter;
8
8
  eCommerceCredentials: Secret;
9
9
  }
10
10
 
11
11
  export class SharedInfraStack extends Stack {
12
- readonly eCommerceBaseUrl: string;
12
+ readonly eCommerceBaseUrl: IStringParameter;
13
13
  readonly eCommerceCredentials: Secret;
14
14
 
15
15
  constructor(scope: Construct, id: string, props?: StackProps) {
16
16
  super(scope, id, props);
17
17
 
18
- const STAGE = Stage.of(this)?.stageName;
19
- if (!STAGE) {
18
+ if (!Stage.of(this)?.stageName) {
20
19
  throw new Error('This construct must be used within a CDK Stage');
21
20
  }
22
21
 
23
- const applicationName = this.node.tryGetContext('NAME');
24
-
25
22
  this.eCommerceBaseUrl = StringParameter.fromStringParameterName(
26
23
  this,
27
24
  'ECommerceBaseUrl',
28
- `/${applicationName}/e-commerce/base-url`
29
- ).stringValue;
25
+ `/e-commerce/base-url`
26
+ );
30
27
 
31
28
  // Create secrets once in the shared stack
32
29
  this.eCommerceCredentials = new Secret(this, 'ECommerceCredentials', {
33
- secretName: `${applicationName}/e-commerce-credentials`,
34
30
  description: 'E-Commerce API credentials shared across services',
35
31
  });
36
32
  }