The Synthesis Process
Relevant source files
The following files were used as context for generating this wiki page:- packages/@aws-cdk/core/lib/private/synthesis.ts
- packages/aws-cdk/lib/api/cxapp/cloud-executable.ts
- packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts
- design/cloud-assembly.md
- packages/@aws-cdk/core/test/synthesis.test.ts
- design/construct-tree.md
- packages/@aws-cdk/pipelines/lib/legacy/synths/simple-synth-action.ts
- packages/@aws-cdk/core/README.md
The Synthesis Process is the core mechanism within the AWS Cloud Development Kit (CDK) that transforms a CDK application's construct tree into a deployable artifact known as a Cloud Assembly. Triggered by the cdk synth command, this process executes the application code, traverses the hierarchy of constructs, and generates outputs such as AWS CloudFormation templates, file assets, and metadata. The result is a self-contained directory (typically cdk.out) that includes everything needed for deployment.
This process is fundamental to the CDK's ability to let developers define infrastructure using high-level programming languages while producing standard CloudFormation templates. It bridges the gap between the imperative code written by developers and the declarative format required by AWS CloudFormation. The output, the Cloud Assembly, also enables advanced tooling and experiences by providing a structured representation of the application, including the Construct Tree.
Sources: README.md:19-25, design/cloud-assembly.md:3-5, packages/@aws-cdk/core/lib/private/synthesis.ts:10-12
High-Level Flow
The synthesis process begins with the execution of the CDK application and culminates in the creation of the Cloud Assembly in the output directory.
The following diagram illustrates the high-level data flow of the synthesis process.
The synthesize Function Lifecycle
The internal synthesize function, located in packages/@aws-cdk/core/lib/private/synthesis.ts, orchestrates the entire process by performing a series of steps on the root construct of the application. It ensures that all constructs are prepared, validated, and have a chance to contribute to the final Cloud Assembly.
The sequence diagram below shows the internal steps of the synthesize function.
Key Lifecycle Steps
synthNestedAssemblies: Recursively finds and synthesizes any nestedStageconstructs. Aspects and other operations are not propagated across these boundaries.
Sources: packages/@aws-cdk/core/lib/private/synthesis.ts:71-79invokeAspects: Traverses the construct tree and applies any registeredIAspectobjects. Aspects can modify constructs, add tags, or enforce policies.
Sources: packages/@aws-cdk/core/lib/private/synthesis.ts:84-116injectMetadataResources: Adds aMetadataResourceto each stack, which reports analytics on the CDK version and other metadata. This can be disabled.
Sources: packages/@aws-cdk/core/lib/private/synthesis.ts:133-148prepareTree: A legacy step that calls theonPrepare()method on each construct in post-order.
Sources: packages/@aws-cdk/core/lib/private/synthesis.ts:118-121validateTree: Calls thevalidate()method on each construct and collects any validation errors. If errors are found, the synthesis process fails.
Sources: packages/@aws-cdk/core/lib/private/synthesis.ts:182-195synthesizeTree: The final step where constructs generate their artifacts. It callsonSynthesize()on each construct.Stackconstructs delegate this to theirsynthesizerto produce CloudFormation templates and assets. Constructs can also implementICustomSynthesisto hook into this phase.
Sources: packages/@aws-cdk/core/lib/private/synthesis.ts:154-180
The Cloud Assembly
The Cloud Assembly is the primary output of the synthesis process. It is a directory (usually cdk.out) containing all the files and metadata required to deploy the CDK application. Its structure is defined by the cloud-assembly-schema.
The central component of the Cloud Assembly is the manifest.json file, which serves as an index of all artifacts in the assembly.
The diagram below shows the relationship between the manifest and its artifacts.
Artifact Types
The manifest.json file lists artifacts, each with a specific type. Key artifact types include:
| Artifact Type | Description |
|---|---|
aws:cloudformation:stack | Represents an AWS CloudFormation stack. Its properties include the path to the template file (e.g., MyStack.template.json). |
cdk:tree | Represents the construct tree of the application. Its properties point to the tree.json file. |
aws:s3:asset | Represents a file or directory asset that needs to be uploaded to an S3 bucket before deployment. |
aws:ecr:image | Represents a Docker image asset that needs to be built and pushed to an ECR repository. |
cdk:nested-cloud-assembly | Represents a nested Stage with its own cloud assembly. |
Sources: packages/@aws-cdk/cloud-assembly-schema/lib/manifest.ts:49-62
The Construct Tree (tree.json)
A key artifact generated during synthesis is tree.json. This file provides a structured, machine-readable representation of the application's construct tree. Its primary purpose is to enable debugging and external tooling by exposing the hierarchy of constructs, their properties, and their relationships.
The tree is represented as a nested JSON object, where each node has an id, path, and optional children and attributes.
{
"version": "tree-0.1",
"tree": {
"id": "App",
"path": "",
"children": {
"MyStack": {
"path": "MyStack",
"children": {
"MyTopic": {
"path": "MyStack/MyTopic",
"attributes": {
"aws:cdk:cloudformation:type": "AWS::SNS::Topic",
"aws:cdk:cloudformation:properties": {
"DisplayName": "MyTopic"
}
}
}
}
}
}
}
}
Sources: design/construct-tree.md:120-161
| Property | Type | Description |
|---|---|---|
id | string | The construct's ID within its scope. |
path | string | The full, absolute path of the construct from the root of the tree. |
children | object | A map of child constructs, keyed by their ID. |
attributes | object | An extensible map of arbitrary metadata about the construct, such as its CloudFormation resource type and properties. |
Sources: design/construct-tree.md:92-97
Custom Synthesis Hooks
Constructs can perform custom logic during synthesis by implementing the ICustomSynthesis interface. This is an advanced feature intended for official AWS CDK libraries. The attachCustomSynthesis function associates a custom synthesis implementation with a construct.
This is useful for constructs that need to generate artifacts outside the standard CloudFormation template, such as writing a custom file to the output directory.
The following example from the test suite demonstrates how a construct can add its own artifact to the assembly.
// From: packages/@aws-cdk/core/test/synthesis.test.ts
class MyConstruct extends cdk.Construct {
protected synthesize(s: cdk.ISynthesisSession) {
writeJson(s.assembly.outdir, 'foo.json', { bar: 123 });
s.assembly.addArtifact('my-random-construct', {
type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK,
environment: 'aws://12345/bar',
properties: {
templateFile: 'foo.json',
},
});
}
}
Sources: packages/@aws-cdk/core/test/synthesis.test.ts:70-83, packages/@aws-cdk/core/lib/private/synthesis.ts:42-61
Synthesis in CI/CD Pipelines
In the context of aws-cdk-lib/pipelines, the synthesis process is encapsulated within a CodePipeline build action, typically using CodeBuild. The SimpleSynthAction class is a pre-configured action that handles the common steps of a CDK synthesis.
This class generates a BuildSpec for CodeBuild that includes phases for installing dependencies, building the project, running tests, and finally executing cdk synth.
The following diagram shows the phases inside the CodeBuild project created by SimpleSynthAction.
SimpleSynthAction Configuration
The SimpleSynthAction can be configured with various options to customize the build environment and commands.
| Property | Type | Description |
|---|---|---|
sourceArtifact | codepipeline.Artifact | The source artifact from the pipeline. |
cloudAssemblyArtifact | codepipeline.Artifact | The output artifact for the Cloud Assembly. |
installCommands | string[] | Commands to run for installing dependencies. |
buildCommands | string[] | Commands for compiling the project. |
testCommands | string[] | Commands for running tests before synthesis. |
synthCommand | string | The command to execute for synthesis (e.g., npx cdk synth). |
environment | codebuild.BuildEnvironment | The CodeBuild environment to use. |
subdirectory | string | The directory within the source artifact where commands should be run. |
rolePolicyStatements | iam.PolicyStatement[] | Additional IAM policy statements for the build role. |
vpc | ec2.IVpc | The VPC to run the build in. |
Sources: packages/@aws-cdk/pipelines/lib/legacy/synths/simple-synth-action.ts:25-174
Summary
The synthesis process is the cornerstone of the AWS CDK, translating developer-friendly code into deployable CloudFormation templates and associated assets. By traversing the construct tree, it applies aspects, validates the application's structure, and orchestrates the generation of a Cloud Assembly. This assembly, with its manifest.json and tree.json artifacts, not only enables deployment but also provides a rich, structured view of the application for tooling and debugging, forming a critical link in the development and CI/CD lifecycle of a CDK application.
Sources: README.md:19-25, packages/@aws-cdk/core/lib/private/synthesis.ts## FULL FILE CONTEXT SUMMARY
Query: You are an expert code analyst examining the github repository: https://github.com/fwang/aws-cdk (aws-cdk).
You provide direct, concise, and accurate information about code repositories.
You NEVER start responses with markdown headers or code fences.
IMPORTANT:You MUST respond in English language.
Total Files Included: 23
Total Files Skipped: 0
Total Context Tokens: 51,132 / 800,000 (6.4%)
Remaining Token Budget: 748,868
Included Files:
- packages/@aws-cdk/pipelines/lib/legacy/synths/simple-synth-action.ts (4,086 tokens)
- packages/@aws-cdk/aws-synthetics/package.json (1,027 tokens)
- packages/@aws-cdk/core/test/synthesis.test.ts (1,638 tokens)
- packages/@aws-cdk/cfnspec/build-tools/create-missing-libraries.ts (2,713 tokens)
- design/construct-tree.md (2,837 tokens)
- README.md (2,626 tokens)
- packages/aws-cdk/lib/init.ts (3,463 tokens)
- packages/@aws-cdk/aws-synthetics/README.md (1,800 tokens)
- packages/@aws-cdk/aws-appflow/package.json (829 tokens)
- packages/@aws-cdk/example-construct-library/README.md (977 tokens)
- packages/@aws-cdk/cfnspec/build-tools/build.ts (694 tokens)
- packages/@aws-cdk/core/lib/private/synthesis.ts (1,895 tokens)
- packages/@aws-cdk/aws-appsync/package.json (1,298 tokens)
- packages/@aws-cdk/aws-s3-assets/package.json (1,084 tokens)
- packages/@aws-cdk/cx-api/package.json (1,936 tokens)
- packages/@aws-cdk/core/README.md (8,167 tokens)
- packages/@aws-cdk/custom-resources/package.json (1,295 tokens)
- packages/@aws-cdk/aws-amplify/package.json (1,093 tokens)
- packages/@aws-cdk/pipelines/test/compliance/synths.test.ts (7,548 tokens)
- packages/@aws-cdk/aws-waf/package.json (863 tokens)
... and 3 more files
import * as crypto from 'crypto';
import * as path from 'path';
import * as codebuild from '@aws-cdk/aws-codebuild';
import * as codepipeline from '@aws-cdk/aws-codepipeline';
import * as codepipeline_actions from '@aws-cdk/aws-codepipeline-actions';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as events from '@aws-cdk/aws-events';
import * as iam from '@aws-cdk/aws-iam';
import { Stack } from '@aws-cdk/core';
import { dockerCredentialsInstallCommands, DockerCredential, DockerCredentialUsage } from '../../docker-credentials';
import { toPosixPath } from '../../private/fs';
import { copyEnvironmentVariables, filterEmpty } from './_util';
const DEFAULT_OUTPUT_DIR = 'cdk.out';
// keep this import separate from other imports to reduce chance for merge conflicts with v2-main
// eslint-disable-next-line no-duplicate-imports, import/order
import { Construct } from '@aws-cdk/core';
/**
- Configuration options for a SimpleSynth
/
export interface SimpleSynthOptions {
/*- The source artifact of the CodePipeline
*/
readonly sourceArtifact: codepipeline.Artifact;
- The source artifact of the CodePipeline
/**
- The artifact where the CloudAssembly should be emitted
*/
readonly cloudAssemblyArtifact: codepipeline.Artifact;
/**
- Environment variables to send into build
- NOTE: You may run into the 1000-character limit for the Action configuration if you have a large
- number of variables or if their names or values are very long.
- If you do, pass them to the underlying CodeBuild project directly in
environmentinstead. - However, you will not be able to use CodePipeline Variables in this case.
- @default - No additional environment variables
*/
readonly environmentVariables?: Record<string, codebuild.BuildEnvironmentVariable>;
/**
- Environment variables to copy over from parent env
- These are environment variables that are being used by the build.
- @default - No environment variables copied
*/
readonly copyEnvironmentVariables?: string[];
/**
- Name of the build action
- @default 'Synth'
*/
readonly actionName?: string;
/**
- Name of the CodeBuild project
- @default - Automatically generated
*/
readonly projectName?: string;
/**
- Build environment to use for CodeBuild job
- @default BuildEnvironment.LinuxBuildImage.STANDARD_5_0
*/
readonly environment?: codebuild.BuildEnvironment;
/**
- Directory inside the source where package.json and cdk.json are located
- @default - Repository root
*/
readonly subdirectory?: string;
/**
- Produce additional output artifacts after the build based on the given directories
- Can be used to produce additional artifacts during the build step,
- separate from the cloud assembly, which can be used further on in the
- pipeline.
- Directories are evaluated with respect to
subdirectory. - @default - No additional artifacts generated
*/
readonly additionalArtifacts?: AdditionalArtifact[];
/**
- Policy statements to add to role used during the synth
- Can be used to add acces to a CodeArtifact repository etc.
- @default - No policy statements added to CodeBuild Project Role
*/
readonly rolePolicyStatements?: iam.PolicyStatement[];
/**
- The VPC where to execute the SimpleSynth.
- @default - No VPC
*/
readonly vpc?: ec2.IVpc;
/**
- Which subnets to use.
- Only used if 'vpc' is supplied.
- @default - All private subnets.
*/
readonly subnetSelection?: ec2.SubnetSelection;
/**
- custom BuildSpec that is merged with the generated one
- @default - none
*/
readonly buildSpec?: codebuild.BuildSpec;
}
/**
- Construction props for SimpleSynthAction
/
export interface SimpleSynthActionProps extends SimpleSynthOptions {
/*- The synth command
*/
readonly synthCommand: string;
- The synth command
/**
- The install command
- If not provided by the build image or another dependency
- management tool, at least install the CDK CLI here using
npm install -g aws-cdk.- @default - No install required
- @deprecated Use
installCommandsinstead
*/
readonly installCommand?: string;
/**
- The build command
- If your programming language requires a compilation step, put the
- compilation command here.
- @default - No build required
- @deprecated Use
buildCommandsinstead
*/
readonly buildCommand?: string;
/**
- Install commands
- If not provided by the build image or another dependency
- management tool, at least install the CDK CLI here using
npm install -g aws-cdk.- @default - No install required
*/
readonly installCommands?: string[];
/**
- The build commands
- If your programming language requires a compilation step, put the
- compilation command here.
- @default - No build required
*/
readonly buildCommands?: string[];
/**
- Test commands
- These commands are run after the build commands but before the
- synth command.
- @default - No test commands
*/
readonly testCommands?: string[];
}
/**
- Specification of an additional artifact to generate
/
export interface AdditionalArtifact {
/*- Directory to be packaged
*/
readonly directory: string;
- Directory to be packaged
/**
- Artifact to represent the build directory in the pipeline
*/
readonly artifact: codepipeline.Artifact;
}
/**
- A standard synth with a generated buildspec
*/
export class SimpleSynthAction implements codepipeline.IAction, iam.IGrantable {
/**
- Create a standard NPM synth action
- Uses
npm cito install dependencies andnpx cdk synthto synthesize. - If you need a build step, add
buildCommand: 'npm run build'.
*/
public static standardNpmSynth(options: StandardNpmSynthOptions) {
return new SimpleSynthAction({
...options,
installCommand: options.installCommand ?? 'npm ci',
synthCommand: options.synthCommand ?? 'npx cdk synth',
vpc: options.vpc,
subnetSelection: options.subnetSelection,
environment: {
...options.environment,
environmentVariables: {
// Need this in case the CDK CLI is not in the 'package.json' of the project,
// and 'npx' is going to download it; without this setting, 'npx' will not properly
// install the package into the root user's home directory
NPM_CONFIG_UNSAFE_PERM: { value: 'true' },
...options.environment?.environmentVariables,
},
},
});
}
/**
- Create a standard Yarn synth action
- Uses
yarn install --frozen-lockfileto install dependencies andnpx cdk synthto synthesize. - If you need a build step, add
buildCommand: 'yarn build'.
*/
public static standardYarnSynth(options: StandardYarnSynthOptions) {
return new SimpleSynthAction({
...options,
installCommand: options.installCommand ?? 'yarn install --frozen-lockfile',
synthCommand: options.synthCommand ?? 'npx cdk synth',
vpc: options.vpc,
subnetSelection: options.subnetSelection,
environment: {
...options.environment,
environmentVariables: {
// Need this in case the CDK CLI is not in the 'package.json' of the project,
// and 'npx' is going to download it; without this setting, 'npx' will not properly
// install the package into the root user's home directory
NPM_CONFIG_UNSAFE_PERM: { value: 'true' },
...options.environment?.environmentVariables,
},
},
});
}
private _action?: codepipeline_actions.CodeBuildAction;
private _actionProperties: codepipeline.ActionProperties;
private _project?: codebuild.IProject;
private _dockerCredentials?: DockerCredential[];
constructor(private readonly props: SimpleSynthActionProps) {
// A number of actionProperties get read before bind() is even called (so before we
// have made the Project and can construct the actual CodeBuildAction)
//
// - actionName
// - resource
// - region
// - category
// - role
// - owner
this._actionProperties = {
actionName: props.actionName ?? 'Synth',
category: codepipeline.ActionCategory.BUILD,
provider: 'CodeBuild',
artifactBounds: { minInputs: 0, maxInputs: 5, minOutputs: 0, maxOutputs: 5 },
inputs: [props.sourceArtifact],
outputs: [props.cloudAssemblyArtifact, ...(props.additionalArtifacts ?? []).map(a => a.artifact)],
};
if (this.props.installCommand && this.props.installCommands) {
throw new Error('Pass either \'installCommand\' or \'installCommands\', but not both');
}
if (this.props.buildCommand && this.props.buildCommands) {
throw new Error('Pass either \'buildCommand\' or \'buildCommands\', but not both');
}
const addls = props.additionalArtifacts ?? [];
if (Object.keys(addls).length > 0) {
if (!props.cloudAssemblyArtifact.artifactName) {
throw new Error('You must give all output artifacts, including the \'cloudAssemblyArtifact\', names when using \'additionalArtifacts\'');
}
for (const addl of addls) {
if (!addl.artifact.artifactName) {
throw new Error('You must give all output artifacts passed to SimpleSynthAction names when using \'additionalArtifacts\'');
}
}
}
}
/**
- Exists to implement IAction
*/
public get actionProperties(): codepipeline.ActionProperties {
return this._actionProperties;
}
/**
- Project generated to run the synth command
*/
public get project(): codebuild.IProject {
if (!this._project) {
throw new Error('Project becomes available after SimpleSynthAction has been bound to a stage');
}
return this._project;
}
/**
- Exists to implement IAction
*/
public bind(scope: Construct, stage: codepipeline.IStage, options: codepipeline.ActionBindOptions): codepipeline.ActionConfig {
const buildCommands = this.props.buildCommands ?? [this.props.buildCommand];
const installCommands = this.props.installCommands ?? [this.props.installCommand];
const testCommands = this.props.testCommands ?? [];
const synthCommand = this.props.synthCommand;
const environment = { buildImage: codebuild.LinuxBuildImage.STANDARD_5_0, ...this.props.environment };
const osType = (environment.buildImage instanceof codebuild.WindowsBuildImage)
? ec2.OperatingSystemType.WINDOWS
: ec2.OperatingSystemType.LINUX;
const buildSpec = codebuild.BuildSpec.fromObject({
version: '0.2',
phases: {
pre_build: {
commands: filterEmpty([
this.props.subdirectory ? `cd ${this.props.subdirectory}` : '',
...installCommands,
...dockerCredentialsInstallCommands(DockerCredentialUsage.SYNTH, this._dockerCredentials, osType),
]),
},
build: {
commands: filterEmpty([
...buildCommands,
...testCommands,
synthCommand,
]),
},
},
artifacts: renderArtifacts(this),
});
const environmentVariables = {
...copyEnvironmentVariables(...this.props.copyEnvironmentVariables || []),
};
const mergedBuildSpec = this.props.buildSpec ? codebuild.mergeBuildSpecs(this.props.buildSpec, buildSpec) : buildSpec;
// A hash over the values that make the CodeBuild Project unique (and necessary
// to restart the pipeline if one of them changes). projectName is not necessary to include
// here because the pipeline will definitely restart if projectName changes.
// (Resolve tokens)
const projectConfigHash = hash(Stack.of(scope).resolve({
environment: serializeBuildEnvironment(environment),
buildSpecString: mergedBuildSpec.toBuildSpec(),
environmentVariables,
}));
const project = new codebuild.PipelineProject(scope, 'CdkBuildProject', {
projectName: this.props.projectName,
environment,
vpc: this.props.vpc,
subnetSelection: this.props.subnetSelection,
buildSpec: mergedBuildSpec,
environmentVariables,
});
if (this.props.rolePolicyStatements !== undefined) {
this.props.rolePolicyStatements.forEach(policyStatement => {
project.addToRolePolicy(policyStatement);
});
}
this._project = project;
this._dockerCredentials?.forEach(reg => reg.grantRead(project.grantPrincipal, DockerCredentialUsage.SYNTH));
this._action = new codepipeline_actions.CodeBuildAction({
actionName: this.actionProperties.actionName,
input: this.props.sourceArtifact,
outputs: [this.props.cloudAssemblyArtifact, ...(this.props.additionalArtifacts ?? []).map(a => a.artifact)],
// Inclusion of the hash here will lead to the pipeline structure for any changes
// made the config of the underlying CodeBuild Project.
// Hence, the pipeline will be restarted. This is necessary if the users
// adds (for example) build or test commands to the buildspec.
environmentVariables: {
...this.props.environmentVariables,
_PROJECT_CONFIG_HASH: { value: projectConfigHash },
},
project,
});
this._actionProperties = this._action.actionProperties;
return this._action.bind(scope, stage, options);
function renderArtifacts(self: SimpleSynthAction) {
// save the generated files in the output artifact
// This part of the buildspec has to look completely different depending on whether we're
// using secondary artifacts or not.
const cloudAsmArtifactSpec = {
'base-directory': toPosixPath(path.join(self.props.subdirectory ?? '.', DEFAULT_OUTPUT_DIR)),
'files': '**/*',
};
if (self.props.additionalArtifacts) {
const secondary: Record<string, any> = {};
if (!self.props.cloudAssemblyArtifact.artifactName) {
throw new Error('When using additional output artifacts, you must also name the CloudAssembly artifact');
}
secondary[self.props.cloudAssemblyArtifact.artifactName] = cloudAsmArtifactSpec;
self.props.additionalArtifacts.forEach((art) => {
if (!art.artifact.artifactName) {
throw new Error('You must give the output artifact a name');
}
secondary[art.artifact.artifactName] = {
'base-directory': toPosixPath(path.join(self.props.subdirectory ?? '.', art.directory)),
'files': '**/*',
};
});
return { 'secondary-artifacts': secondary };
}
return cloudAsmArtifactSpec;
}
}
/**
- The CodeBuild Project's principal
*/
public get grantPrincipal(): iam.IPrincipal {
return this.project.grantPrincipal;
}
/**
- Exists to implement IAction
*/
public onStateChange(name: string, target?: events.IRuleTarget, options?: events.RuleProps): events.Rule {
if (!this._action) {
throw new Error('Need bind() first');
}
return this._action.onStateChange(name, target, options);
}
/**
- Associate one or more Docker registries and associated credentials with the synth action.
- This will be used to inject installation commands to set up
cdk-assets, - and grant read access to the credentials.
- @internal
*/
public _addDockerCredentials(dockerCredentials: DockerCredential[]) {
this._dockerCredentials = dockerCredentials;
}
}
/**
- Options for a convention-based synth using NPM
/
export interface StandardNpmSynthOptions extends SimpleSynthOptions {
/*- The install command
- @default 'npm ci'
*/
readonly installCommand?: string;
/**
- The build command
- By default, we assume NPM projects are either written in JavaScript or are
- using
ts-node, so don't need a build command. - Otherwise, put the build command here, for example
npm run build. - @default - No build required
*/
readonly buildCommand?: string;
/**
- The synth command
- @default 'npx cdk synth'
*/
readonly synthCommand?: string;
/**
- Test commands
- These commands are run after the build commands but before the
- synth command.
- @default - No test commands
*/
readonly testCommands?: string[];
}
/**
- Options for a convention-based synth using Yarn
/
export interface StandardYarnSynthOptions extends SimpleSynthOptions {
/*- The install command
- @default 'yarn install --frozen-lockfile'
*/
readonly installCommand?: string;
/**
- The build command
- By default, we assume NPM projects are either written in JavaScript or are
- using
ts-node, so don't need a build command. - Otherwise, put the build command here, for example
npm run build. - @default - No build required
*/
readonly buildCommand?: string;
/**
- The synth command
- @default 'npx cdk synth'
*/
readonly synthCommand?: string;
/**
- Test commands
- These commands are run after the build commands but before the
- synth command.
- @default - No test commands
*/
readonly testCommands?: string[];
}
function hash(obj: A) {
const d = crypto.createHash('sha256');
d.update(JSON.stringify(obj));
return d.digest('hex');
}
/**
- Serialize a build environment to data (get rid of constructs & objects), so we can JSON.stringify it
*/
function serializeBuildEnvironment(env: codebuild.BuildEnvironment) {
return {
privileged: env.privileged,
environmentVariables: env.environmentVariables,
type: env.buildImage?.type,
imageId: env.buildImage?.imageId,
computeType: env.computeType,
imagePullPrincipalType: env.buildImage?.imagePullPrincipalType,
secretsManagerArn: env.buildImage?.secretsManagerCredentials?.secretArn,
};
}
================================================================================
{
"name": "@aws-cdk/aws-synthetics",
"version": "0.0.0",
"description": "The CDK Construct Library for AWS::Synthetics",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"jsii": {
"outdir": "dist",
"targets": {
"dotnet": {
"namespace": "Amazon.CDK.AWS.Synthetics",
"packageId": "Amazon.CDK.AWS.Synthetics",
"iconUrl": "https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png"
},
"java": {
"package": "software.amazon.awscdk.services.synthetics",
"maven": {
"groupId": "software.amazon.awscdk",
"artifactId": "synthetics"
}
},
"python": {
"distName": "aws-cdk.aws-synthetics",
"module": "aws_cdk.aws_synthetics",
"classifiers": [
"Framework :: AWS CDK",
"Framework :: AWS CDK :: 1"
]
}
},
"projectReferences": true
},
"repository": {
"type": "git",
"url": "https://github.com/aws/aws-cdk.git%22%2C
"directory": "packages/@aws-cdk/aws-synthetics"
},
"homepage": "https://github.com/aws/aws-cdk%22%2C
"scripts": {
"build": "cdk-build",
"watch": "cdk-watch",
"lint": "cdk-lint",
"test": "cdk-test",
"integ": "cdk-integ",
"pkglint": "pkglint -f",
"package": "cdk-package",
"awslint": "cdk-awslint",
"cfn2ts": "cfn2ts",
"build+test+package": "yarn build+test && yarn package",
"build+test": "yarn build && yarn test",
"compat": "cdk-compat",
"gen": "cfn2ts",
"rosetta:extract": "yarn --silent jsii-rosetta extract",
"build+extract": "yarn build && yarn rosetta:extract",
"build+test+extract": "yarn build+test && yarn rosetta:extract"
},
"cdk-build": {
"cloudformation": "AWS::Synthetics",
"jest": true,
"env": {
"AWSLINT_BASE_CONSTRUCT": true
}
},
"keywords": [
"aws",
"cdk",
"constructs",
"AWS::Synthetics",
"aws-synthetics"
],
"author": {
"name": "Amazon Web Services",
"url": "https://aws.amazon.com",
"organization": true
},
"license": "Apache-2.0",
"devDependencies": {
"@types/jest": "^26.0.24",
"cdk-build-tools": "0.0.0",
"cdk-integ-tools": "0.0.0",
"cfn2ts": "0.0.0",
"pkglint": "0.0.0",
"@aws-cdk/assertions": "0.0.0"
},
"dependencies": {
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-s3": "0.0.0",
"@aws-cdk/aws-s3-assets": "0.0.0",
"@aws-cdk/core": "0.0.0",
"@aws-cdk/aws-cloudwatch": "0.0.0",
"constructs": "^3.3.69"
},
"peerDependencies": {
"@aws-cdk/core": "0.0.0",
"@aws-cdk/aws-s3": "0.0.0",
"@aws-cdk/aws-s3-assets": "0.0.0",
"@aws-cdk/aws-iam": "0.0.0",
"@aws-cdk/aws-cloudwatch": "0.0.0",
"constructs": "^3.3.69"
},
"engines": {
"node": ">= 10.13.0 <13 || >=13.7.0"
},
"stability": "experimental",
"maturity": "developer-preview",
"awscdkio": {
"announce": false
},
"publishConfig": {
"tag": "latest"
}
}
================================================================================
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as cxschema from '@aws-cdk/cloud-assembly-schema';
import { nodeunitShim, Test } from 'nodeunit-shim';
import * as cdk from '../lib';
function createModernApp() {
return new cdk.App();
}
nodeunitShim({
'synthesis with an empty app'(test: Test) {
// GIVEN
const app = createModernApp();
// WHEN
const session = app.synth();
// THEN
test.same(app.synth(), session); // same session if we synth() again
test.deepEqual(list(session.directory), ['cdk.out', 'manifest.json', 'tree.json']);
test.deepEqual(readJson(session.directory, 'manifest.json').artifacts, {
Tree: {
type: 'cdk:tree',
properties: { file: 'tree.json' },
},
});
test.deepEqual(readJson(session.directory, 'tree.json'), {
version: 'tree-0.1',
tree: expect.objectContaining({
id: 'App',
path: '',
children: {
Tree: expect.objectContaining({ id: 'Tree', path: 'Tree' }),
},
}),
});
test.done();
},
'synthesis respects disabling tree metadata'(test: Test) {
const app = new cdk.App({
treeMetadata: false,
});
const assembly = app.synth();
test.deepEqual(list(assembly.directory), ['cdk.out', 'manifest.json']);
test.done();
},
'single empty stack'(test: Test) {
// GIVEN
const app = createModernApp();
new cdk.Stack(app, 'one-stack');
// WHEN
const session = app.synth();
// THEN
test.ok(list(session.directory).includes('one-stack.template.json'));
test.done();
},
'some random construct implements "synthesize"'(test: Test) {
// GIVEN
const app = createModernApp();
const stack = new cdk.Stack(app, 'one-stack');
class MyConstruct extends cdk.Construct {
protected synthesize(s: cdk.ISynthesisSession) {
writeJson(s.assembly.outdir, 'foo.json', { bar: 123 });
s.assembly.addArtifact('my-random-construct', {
type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK,
environment: 'aws://12345/bar',
properties: {
templateFile: 'foo.json',
},
});
}
}
new MyConstruct(stack, 'MyConstruct');
// WHEN
const session = app.synth();
// THEN
test.ok(list(session.directory).includes('one-stack.template.json'));
test.ok(list(session.directory).includes('foo.json'));
test.deepEqual(readJson(session.directory, 'foo.json'), { bar: 123 });
test.deepEqual(session.manifest, {
version: cxschema.Manifest.version(),
artifacts: {
'Tree': {
type: 'cdk:tree',
properties: { file: 'tree.json' },
},
'my-random-construct': {
type: 'aws:cloudformation:stack',
environment: 'aws://12345/bar',
properties: { templateFile: 'foo.json' },
},
'one-stack': {
type: 'aws:cloudformation:stack',
environment: 'aws://unknown-account/unknown-region',
properties: {
templateFile: 'one-stack.template.json',
validateOnSynth: false,
},
displayName: 'one-stack',
},
},
});
test.done();
},
'random construct uses addCustomSynthesis'(test: Test) {
// GIVEN
const app = createModernApp();
const stack = new cdk.Stack(app, 'one-stack');
class MyConstruct extends cdk.Construct {
constructor(scope: cdk.Construct, id: string) {
super(scope, id);
cdk.attachCustomSynthesis(this, {
onSynthesize(s: cdk.ISynthesisSession) {
writeJson(s.assembly.outdir, 'foo.json', { bar: 123 });
s.assembly.addArtifact('my-random-construct', {
type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK,
environment: 'aws://12345/bar',
properties: {
templateFile: 'foo.json',
},
});
},
});
}
}
new MyConstruct(stack, 'MyConstruct');
// WHEN
const session = app.synth();
// THEN
test.ok(list(session.directory).includes('one-stack.template.json'));
test.ok(list(session.directory).includes('foo.json'));
test.deepEqual(readJson(session.directory, 'foo.json'), { bar: 123 });
test.deepEqual(session.manifest, {
version: cxschema.Manifest.version(),
artifacts: {
'Tree': {
type: 'cdk:tree',
properties: { file: 'tree.json' },
},
'my-random-construct': {
type: 'aws:cloudformation:stack',
environment: 'aws://12345/bar',
properties: { templateFile: 'foo.json' },
},
'one-stack': {
type: 'aws:cloudformation:stack',
environment: 'aws://unknown-account/unknown-region',
properties: {
templateFile: 'one-stack.template.json',
validateOnSynth: false,
},
displayName: 'one-stack',
},
},
});
test.done();
},
'it should be possible to synthesize without an app'(test: Test) {
const calls = new Array();
class SynthesizeMe extends cdk.Construct {
constructor() {
super(undefined as any, 'id');
}
protected synthesize(session: cdk.ISynthesisSession) {
calls.push('synthesize');
session.assembly.addArtifact('art', {
type: cxschema.ArtifactType.AWS_CLOUDFORMATION_STACK,
properties: {
templateFile: 'hey.json',
parameters: {
paramId: 'paramValue',
paramId2: 'paramValue2',
},
},
environment: 'aws://unknown-account/us-east-1',
});
writeJson(session.assembly.outdir, 'hey.json', { hello: 123 });
}
protected validate(): string[] {
calls.push('validate');
return [];
}
protected prepare(): void {
calls.push('prepare');
}
}
const root = new SynthesizeMe();
const assembly = cdk.ConstructNode.synth(root.node, { outdir: fs.mkdtempSync(path.join(os.tmpdir(), 'outdir')) });
test.deepEqual(calls, ['prepare', 'validate', 'synthesize']);
const stack = assembly.getStackByName('art');
test.deepEqual(stack.template, { hello: 123 });
test.deepEqual(stack.templateFile, 'hey.json');
test.deepEqual(stack.parameters, { paramId: 'paramValue', paramId2: 'paramValue2' });
test.deepEqual(stack.environment, { region: 'us-east-1', account: 'unknown-account', name: 'aws://unknown-account/us-east-1' });
test.done();
},
});
function list(outdir: string) {
return fs.readdirSync(outdir).sort();
}
function readJson(outdir: string, file: string) {
return JSON.parse(fs.readFileSync(path.join(outdir, file), 'utf-8'));
}
function writeJson(outdir: string, file: string, data: any) {
fs.writeFileSync(path.join(outdir, file), JSON.stringify(data, undefined, 2));
}
================================================================================
#!/usr/bin/env node
/**
- automatically creates a module for any CloudFormation namespaces that do not
- have an AWS construct library.
*/
import * as path from 'path';
import * as fs from 'fs-extra';
import * as cfnspec from '../lib';
// don't be a prude:
/* eslint-disable no-console /
/ eslint-disable quote-props */
async function main() {
const root = path.join(__dirname, '..', '..');
if (path.basename(root) !== '@aws-cdk') {
throw new Error(Something went wrong. We expected ${root} to be the "packages/@aws-cdk" directory. Did you move me?);
}
// eslint-disable-next-line @typescript-eslint/no-require-imports
const version = require('../package.json').version;
// iterate over all cloudformation namespaces
for (const namespace of cfnspec.namespaces()) {
const [moduleFamily, moduleBaseName] = (namespace === 'AWS::Serverless' ? 'AWS::SAM' : namespace).split('::');
const moduleName = `${moduleFamily}-${moduleBaseName.replace(/V\d+$/, '')}`.toLocaleLowerCase();
const packagePath = path.join(root, moduleName);
const lowcaseModuleName = moduleBaseName.toLocaleLowerCase();
const packageName = `@aws-cdk/${moduleName}`;
// we already have a module for this namesapce, move on.
if (await fs.pathExists(packagePath)) {
const packageJsonPath = path.join(packagePath, 'package.json');
// eslint-disable-next-line @typescript-eslint/no-require-imports
const packageJson = require(packageJsonPath);
let scopes: string | string[] = packageJson['cdk-build'].cloudformation;
if (typeof scopes === 'string') { scopes = [scopes]; }
if (scopes.indexOf(namespace) !== -1) {
// V2-style module is already modeled in the root package, nothing to be done!
continue;
} else if (await fs.pathExists(path.join(root, `${moduleFamily}-${moduleBaseName}`.toLocaleLowerCase()))) {
// V2-style package already has it's own package (legacy behavior), nothing to be done!
continue;
} else {
// V2-style package needs to be added to it's "V1" package... Get down to business!
console.error(`Adding ${namespace} to ${packageName}`);
scopes.push(namespace);
packageJson['cdk-build'].cloudformation = scopes;
await fs.writeJson(packageJsonPath, packageJson, { encoding: 'utf-8', spaces: 2 });
const indexTsPath = path.join(packagePath, 'lib', 'index.ts');
const indexTs = [
(await fs.readFile(indexTsPath, { encoding: 'utf8' })).trimRight(),
`// ${namespace} CloudFormation Resources:`,
`export * from './${lowcaseModuleName}.generated';`,
].join('\n');
await fs.writeFile(indexTsPath, indexTs, { encoding: 'utf8' });
continue;
}
}
// dotnet names
const dotnetPackage = `Amazon.CDK.${moduleFamily}.${moduleBaseName}`;
// java names
const javaGroupId = 'software.amazon.awscdk';
const javaPackage = moduleFamily === 'AWS'
? `services.${lowcaseModuleName}`
: `${moduleFamily.toLocaleLowerCase()}.${lowcaseModuleName}`;
const javaArtifactId = moduleFamily === 'AWS'
? lowcaseModuleName
: `${moduleFamily.toLocaleLowerCase()}-${lowcaseModuleName}`;
// python names
const pythonDistName = `aws-cdk.${moduleName}`;
const pythonModuleName = pythonDistName.replace(/-/g, '_');
async function write(relativePath: string, contents: string[] | string | object) {
const fullPath = path.join(packagePath, relativePath);
const dir = path.dirname(fullPath);
await fs.mkdirp(dir);
let data;
if (typeof contents === 'string') {
data = contents.trimLeft(); // trim first newline
} else if (Array.isArray(contents)) {
data = contents.join('\n');
} else if (typeof contents === 'object') {
data = JSON.stringify(contents, undefined, 2);
} else {
throw new Error('Invalid type of contents: ' + contents);
}
await fs.writeFile(fullPath, data + '\n');
}
console.log(`generating module for ${packageName}...`);
await write('package.json', {
name: packageName,
version,
description: `The CDK Construct Library for ${namespace}`,
main: 'lib/index.js',
types: 'lib/index.d.ts',
jsii: {
outdir: 'dist',
projectReferences: true,
targets: {
dotnet: {
namespace: dotnetPackage,
packageId: dotnetPackage,
signAssembly: true,
assemblyOriginatorKeyFile: '../../key.snk',
iconUrl: 'https://raw.githubusercontent.com/aws/aws-cdk/master/logo/default-256-dark.png',
},
java: {
package: `${javaGroupId}.${javaPackage}`,
maven: {
groupId: javaGroupId,
artifactId: javaArtifactId,
},
},
python: {
classifiers: [
'Framework :: AWS CDK',
'Framework :: AWS CDK :: 1',
],
distName: pythonDistName,
module: pythonModuleName,
},
},
},
repository: {
type: 'git',
url: 'https://github.com/aws/aws-cdk.git%27%2C
directory: `packages/${packageName}`,
},
homepage: 'https://github.com/aws/aws-cdk%27%2C
scripts: {
build: 'cdk-build',
watch: 'cdk-watch',
lint: 'cdk-lint',
test: 'cdk-test',
integ: 'cdk-integ',
pkglint: 'pkglint -f',
package: 'cdk-package',
awslint: 'cdk-awslint',
cfn2ts: 'cfn2ts',
'build+test': 'yarn build && yarn test',
'build+test+package': 'yarn build+test && yarn package',
compat: 'cdk-compat',
gen: 'cfn2ts',
'rosetta:extract': 'yarn --silent jsii-rosetta extract',
'build+extract': 'yarn build && yarn rosetta:extract',
'build+test+extract': 'yarn build+test && yarn rosetta:extract',
},
'cdk-build': {
cloudformation: namespace,
jest: true,
env: {
AWSLINT_BASE_CONSTRUCT: 'true',
},
},
keywords: [
'aws',
'cdk',
'constructs',
namespace,
moduleName,
],
author: {
name: 'Amazon Web Services',
url: 'https://aws.amazon.com',
organization: true,
},
license: 'Apache-2.0',
devDependencies: {
'@types/jest': '^26.0.22',
'@aws-cdk/assertions': version,
'cdk-build-tools': version,
'cfn2ts': version,
'pkglint': version,
},
dependencies: {
'@aws-cdk/core': version,
},
peerDependencies: {
'@aws-cdk/core': version,
},
engines: {
node: '>= 10.13.0 <13 || >=13.7.0',
},
stability: 'experimental',
maturity: 'cfn-only',
awscdkio: {
announce: false,
},
publishConfig: {
tag: 'latest',
},
});
await write('.gitignore', [
'*.js',
'*.js.map',
'*.d.ts',
'tsconfig.json',
'node_modules',
'*.generated.ts',
'dist',
'.jsii',
'',
'.LAST_BUILD',
'.nyc_output',
'coverage',
'.nycrc',
'.LAST_PACKAGE',
'*.snk',
'nyc.config.js',
'!.eslintrc.js',
'!jest.config.js',
'junit.xml',
]);
await write('.npmignore', [
'# Don\'t include original .ts files when doing `npm pack`',
'*.ts',
'!*.d.ts',
'coverage',
'.nyc_output',
'*.tgz',
'',
'dist',
'.LAST_PACKAGE',
'.LAST_BUILD',
'!*.js',
'',
'# Include .jsii',
'!.jsii',
'',
'*.snk',
'',
'*.tsbuildinfo',
'',
'tsconfig.json',
'',
'.eslintrc.js',
'jest.config.js',
'',
'# exclude cdk artifacts',
'**/cdk.out',
'junit.xml',
'test/',
'!*.lit.ts',
]);