Publishing Node.js Modules on AWS CodeArtifact using AWS CodePipeline
Code reuse is hard. When writing code for Node.js, developers can take advantage of the Node.js Package Manager (NPM) Command Line Interface (CLI) that enables packaging code into modules which can then reused across projects. These modules or packages can be stored in AWS CodeArtifact and used throughout Node.js applications running on AWS. This pattern provides a continuous integration - continuous deployment pipeline via AWS CodePipeline to lint, test, and publish a new version of a Node.js module into a AWS CodeArtifact Repository. The publish stage of the pipeline uses semantic-release, a fully automated version management and package publishing tool. Semantic release automatically creates a CHANGELOG.md based off your commit messages and updates the version in your package.json via semantic version guidelines. The pipeline and supporting infrastructure is defined via the AWS Cloud Development Kit (CDK).
See the associated Amazon Prescriptive Guidance (APG) Pattern here for further information.
Prerequisites
- AWS Command Line Interface (AWS CLI), installed and configured. For more information, see Installing, updating, and uninstalling the AWS CLI.
- Node.js and NPM installed. For more information, see Node.js Downloads.
- AWS Cloud Development Kit (CDK) Toolkit, installed and configured. For more information, see AWS CDK Toolkit (cdk command).
- An AWS Account bootstrapped with the AWS CDK. For more information, see Bootstrapping.
- Git, installed and configured. For more information, see Git.
Limitations
- AWS CodeCommit is the only source control management service compatible with this pattern. GitHub or GitLab cannot be used.
- Node.js packages are the only package format compatible with this pattern. PyPi packages cannot be used.
Product versions
- AWS CLI Version 2 or greater
- Node.js 14.X or greater
- AWS CDK v2 or greater
- Git 2.X or greater
Architecture
Tools
Steps
Setup Domain and Repository
Create the AWS CodeArtifact Domain and Repository which will store packages. Change the variables in the script below and then run the script in your terminal. Make sure you follow the naming constraints for the domain and repository. To be safe, use kebab-case-like-this.
CA_DOMAIN=my-domain
CA_REPO=my-repo
aws codeartifact create-domain --domain $CA_DOMAIN
aws codeartifact create-repository --domain $CA_DOMAIN --repository $CA_REPO
Login to AWS CodeArtifact Repository by running the script below in your terminal. Note, it is a convention to prefix your namepsace with @.
CA_NAMESPACE=@my-namespace
aws codeartifact login --tool npm --domain $CA_DOMAIN --repository $CA_REPO --namespace $CA_NAMESPACE
This updates your ~/.npmrc telling NPM to look for packages with a namespace (prefix) of $CA_NAMESPACE
within the AWS CodeArtifact Repository $CA_REPO
.
Publish the CDK Construct Pipeline
Clone the repository from https://github.com/aws-samples/aws-codepipeline-publish-nodejs-modules and install dependencies.
git clone https://github.com/aws-samples/aws-codepipeline-publish-nodejs-modules.git
cd aws-codepipeline-publish-nodejs-modules
npm install
Deploy the AWS infrastructure required for the pipeline
cdk deploy
Push the code to CodeCommit. Note, the pipeline ran upon deploying in the previous step and failed because the repository wasn't initialized. Pushing code to the repository will cause the pipeline to run.
git add package-lock.json
git commit -m "feat: initial commit"
git remote set-url origin https://git-codecommit.us-east-1.amazonaws.com/v1/repos/nodejs-pkg-pipeline
git push origin --set-upstream main
Create Node.js Module to Publish
Initialize the CDK Construct with the CDK Toolkit in a new directory
cd ..
mkdir my-bucket
cd my-bucket
cdk init --language typescript
Delete the folders: bin, lib, and test and the file: .npmignore. Add the following file to build your bucket. This example is trivial but you can add more features later on.
// lib/my-bucket.ts
import { Bucket, BucketProps } from "aws-cdk-lib/aws-s3";
import { Construct } from 'constructs';
interface MyBucketProps extends BucketProps {}
export class MyBucket extends Construct {
constructor(scope: Construct, id: string, props?: MyBucketProps) {
super(scope, id);
new Bucket(this, "MyBucket", props);
}
}
First, install the module you created earlier with the command npm install @my-namespace/nodejs-pkg-pipeline
which will install the module from CodeArtifact into your node_modules folder. If you get a 404 error, make sure the pipeline created in the previous epic has completed. Second, add the following file:
// bin/pkg-pipeline.ts
import { App, Stack } from "aws-cdk-lib";
import { Construct } from "constructs";
import { PkgPipeline } from "@my-namespace/nodejs-pkg-pipeline";
const app = new App();
const name = "my-bucket";
class PipelineStack extends Stack {
constructor(scope: Construct, id: string) {
super(scope, id);
new PkgPipeline(this, "MyBucketPipeline", {
name: id,
repoDescription: "My Bucket",
codeArtifactNamespace: "@my-namespace",
codeArtifactRepo: "my-repo",
codeArtifactDomain: "my-domain",
});
}
}
new PipelineStack(app, name);
Make sure you change "@my-namespace", "my-repo", and "my-domain" in the above code snippet to your values.
Third, update the app key of the cdk.json file so the CDK knows which stack to deploy.
// cdk.json
{
"app": "npx ts-node --prefer-ts-exts bin/pkg-pipeline.ts",
...
}
Update the package.json to include the following:
{
"name": "@my-namespace/my-bucket",
"version": "0.1.0",
"files": ["dist/**/*", "lib/**/*"],
"main": "dist/my-bucket.js",
"types": "dist/my-bucket.d.ts",
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"test": "echo \"TODO\"",
"cdk": "cdk",
"lint": "echo \"TODO\""
},
...
Update the tsconfig.json to include the following:
{
"compilerOptions": {
...
"declarationMap": true,
"outDir": "dist",
},
"include": ["lib"],
}
Also add dist
to your .gitignore.
Finally, add a .releaserc.js which will tell semantic-release how to release your package
// .releaserc.js
module.exports = {
branches: ["main"],
plugins: [
"@semantic-release/commit-analyzer", // analyzes commits with conventional-changelog standrd
"@semantic-release/release-notes-generator", // generates changelog content based on conventional-changelog standard
"@semantic-release/changelog", // updates CHANGELOG.md
"@semantic-release/npm", // updates version in package.json based on commits
"@semantic-release/git"
],
};
IMPORTANT: Since the default .gitignore ignores all .js files, you'll need to add !.releaserc.js to your .gitignore so it's pushed up to your repository.
Publish my-bucket
Deploy my-bucket pipeline with the CDK:
cdk deploy
Push the code to CodeCommit.
git add -A
git commit -m "feat: initial commit"
git remote add origin https://git-codecommit.us-east-1.amazonaws.com/v1/repos/my-bucket
git push origin --set-upstream main
Congratulations! You've created a Node.js package within CodeArtifact. Go to CodeArtifact in your console to view the package.
Update my-bucket
After a couple minutes when the pipeline completes for the first time, run git pull and look at the CHANGELOG.md and the version of your package.json to see the automatic updates.
Add default encryption to your bucket managed by S3.
// lib/my-bucket.ts
import { Bucket, BucketProps, BucketEncryption } from "aws-cdk-lib/aws-s3";
import { Construct } from 'constructs';
interface MyBucketProps extends BucketProps {}
export class MyBucket extends Construct {
constructor(scope: Construct, id: string, props?: MyBucketProps) {
super(scope, id);
const defaultProps = { encryption: BucketEncryption.S3_MANAGED };
const newProps = props ? { ...defaultProps, ...props } : defaultProps;
new Bucket(this, "MyBucket", newProps);
}
}
Commit your changes with a commit message that follows Conventional Commits. Using a commit message prefix of "feat" will create a feature release of your package incrementing Y in the version X.Y.Z. Note, if you needed to make a fix to your bucket you could prefix your commit message with "fix" which will create a patch release of your package incrementing Z in the version X.Y.Z.
git add lib/my-bucket.ts
git commit -m "feat: add default encryption on bucket"
git push
After a couple minutes when the pipeline completes, run git pull
and look at the CHANGELOG.md and the version of your package.json to see the automatic updates.
You can now install my-bucket into another Node.js project with peace of mind that your bucket has default encryption. Install my-bucket by running npm install @my-namespace/my-bucket
. Note, if you try to install the module more than 12 hours after you logged into AWS CodeArtifact initially, you'll need to login again with: aws codeartifact login --tool npm --domain $CA_DOMAIN --repository $CA_REPO --namespace $CA_NAMESPACE
Related Resources
Additional Information
Package Workflow: Produce to Consume
- Package producer pushes code and updates default branch of AWS CodeCommit repository.
- Amazon EventBridge event is triggered and sent to AWS Lambda function.
- AWS Lambda function triggers AWS CodePipeline to begin execution except for code updates with a commit message include "[skip ci]". Skipping pipeline executions with this message is important. because otherwise the pipeline would be triggered multiple times as semantic-release creates a new commit message on your default branch each time a new version is released.
- AWS CodePipeline begins the "Source" stage which downloads the code from AWS CodeCommit.
- AWS CodePipeline begins the "Lint" stage which runs the command npm run lint within AWS CodeBuild. This typically runs a static code analysis tool like ESLint ensuring high code quality.
- AWS CodePipeline begins the "Test" stage which runs the command npm run test within AWS CodeBuild. This typically starts a test runner like Jest to run unit tests.
- AWS CodePipeline begins the "Publish" stage which runs the command npx semantic-release within AWS CodeBuild. This kicks off a package release workflow that determines the next version number, generates release notes, and publishes the package to AWS CodeArtifact. See semantic-release for more information.
- Package consumer installs the updated package version into their Node.js project with npm install @my-namespace/my-package.
Additional Security Considerations:
- Use AWS CodeCommit with interface VPC endpoints for network level access control. More information here.
- Use AWS CodeArtifact with interface VPC endpoints for network level access control. More information here.
- Connect to CodeCommit using git-remote-codecommit
Security
See CONTRIBUTING for more information.
License
This library is licensed under the MIT-0 License. See the LICENSE file.