Full working typescript example can be found on my GitHub.

projen

I’ve written about projen previously and I’m still using it heavily. It’s super quick to stand up a project, explicitly declare and maintain dependencies (especially for the AWS CDK) and extend by creating your own custom projects. Here I will show you how to deploy a custom project internally to AWS CodeArtifact. This example is for a custom project, however, it could be used to publish a CDK construct, a typescript app, react component - whatever you want!

AWS CodeArtifact

To quote AWS, “CodeArtifact is a fully managed artifact repository service…”. It supports pulling from upstream repositories as well as configuring and storing your own internal packages.

Setup

Things we will need to setup: 1. AWS CodeArtifact Domain 1. AWS CodeArtifact Repository 1. AWS CodeCommit Repository 1. AWS CodePipeline Pipeline

I’ve setup the example using the new projen composite-project type. It contains the jsii project (that we will be publishing) and an awscdk-app-ts to deploy the resources we just mentioned. Feel free to use that as a starting place or you can create them manually, create a separate Cloudformation stack, or however else you’d like to create the resources. If you use the cdk project I have provided then it will create your buildspec for you! No need to grab all the values and insert them.

One suggestion - don’t turn on upstream repository access unless it’s what you want. For demo purposes or small team I’d leave it off. It will sync all upstream packages into your repository. To avoid this, leave it off and use a namespace like @mycoolcompany so that npm will only pull those scoped packages from your AWS CodeArtifact repository.

Currently internally at Voxi I’m using: CodeCommit main branch -> CodePipeline package publish

Login

To login to aws codeartifact on your machine you’ll need to make sure you have the proper IAM permissions.

Example: * namespace: @mycoolcompany * repository: company-artifacts * domain: company-artifacts-domain * accountID: 1234567890

- aws codeartifact login --tool npm --namespace @mycoolcompany --repository company-artifacts --domain company-artifacts-domain --domain-owner 1234567890

projen Project Initialization

Start with npx projen new composite. We will be creating two sub projects - JsiiProject and AwsCdkTypeScriptApp. Since we are using AWS CodeArtifact you can turn off all the Github workflow settings, and go ahead and add in a few of the dependencies we will need like so:

const { CompositeProject, JsiiProject, AwsCdkTypeScriptApp } = require('projen');

const disableGithub = {
  buildWorkflow: false,
  releaseWorkflow: false,
  pullRequestTemplate: false,
  dependabot: false,
  mergify: false
}

const project = new CompositeProject({
  projects: [
    {
      path: 'demo-project',
      project: new JsiiProject({
        authorName: 'Ken Winner',
        authorAddress: 'kcswinner@gmail.com',
        name: "@demo/demo-project",
        repository: "https://github.com/kcwinner/advocacy.git",
        minNodeVersion: '10.17.0',
        devDeps: [
          '@types/fs-extra@^8', // This will break if it's on 9
          'fs-extra'
        ],
        deps: ['projen'],
        peerDeps: ['projen'],

        ...disableGithub
      }),
    },
    {
      path: 'pipeline',
      project: new AwsCdkTypeScriptApp({
        appEntrypoint: 'main.ts',
        defaultReleaseBranch: 'main',
        cdkVersion: '1.73.0',
        cdkDependencies: [
          '@aws-cdk/core',
          '@aws-cdk/aws-codeartifact',
          '@aws-cdk/aws-codebuild',
          '@aws-cdk/aws-codecommit',
          '@aws-cdk/aws-codepipeline',
          '@aws-cdk/aws-codepipeline-actions',
          '@aws-cdk/aws-iam',
        ],
        devDeps: [
          'parcel@2.0.0-beta.1' // So we can bundle locally instead of using a container
        ],

        ...disableGithub
      }),
    },
  ]
});

// We have to dig deep for the jsii project type
// We need to add the publish config
const jsiiProject = project.projects['demo-project'].options.project;
jsiiProject.addFields({
  'publishConfig': { "registry": "https://<ARTIFACT_DOMAIN_NAME>-<ACCOUNT_ID>.d.codeartifact.<REGION>.amazonaws.com/npm/<ARTIFACT_EPOSITORY_NAME>/" }
})

project.synth();

Important You’ll need to drop in your values in the .projenrc.js for the publishConfig. You’ll need your artifact domain name, account id, region, and artifact repository name. If you forget this part you’ll get an error like so:

npm publish Unable to authenticate, need: Bearer realm="demo-domain/demo-test"

At this point it’s safe to run npx projen to synthesize the project. If you have not done so already it is nice to alias npx projen to pj. It’s a command you’ll be using frequently when updating your .projenrc.js

Create The Custom Project

Next you’ll need to update your demo-project/index.ts with your custom project. I’ve chosen to re-use this project I already created to demonstrate. Feel free to use the code from the advocacy repo or use either one as a guide for creating your own!

If you leave jest enabled (it is by default) then projen will expect tests to be available. If not, it will regenerate the test file. My simple example makes sure the cdk version is what we would expect and that the cdk-appsync-transformer dependency is added.

Build

Once your project is complete you are ready to build! Go ahead and throw a yarn build. This should run your tests and generate your lib/ and dist/ directories.

Test Locally

I always make sure to test creation of a custom project locally before publishing. Pop into a clean directory and run:

npx projen new --from /path/to/your/project/dist/js/project-name@0.0.0.jsii.tgz

This will generate the and synthesize your custom project! Make sure it worked correctly by running yarn build

Publish to AWS CodeArtifact

I’ve added a sample buildspec for AWS CodePipeline that will publish the package. You can always run this locally as well to make sure it’s working before you deploy.

To ensure it was published successfully run an npm view @mycoolcompany/package-name. This should return something like:

% npm view @mycoolcompany/package-name

@mycoolcompany/package-name@0.0.0 | Apache-2.0 | deps: 1 | versions: 2
## Using AWS CodeArtifact
https://github.com/mycoolcompany/package-name#readme

Potential Improvements

Since AWS CodePipeline doesn’t provide the git information when it pulls your package you have to do some additional work to have it available. I didn’t include that as an example, therefore my buildspec doesn’t include the yarn bump and git push steps that you will see in the default projen release workflow.

Since projen composite projects are fairly new there are a few things that could help the workflow a bit. Over time this should improve but since this feature is brand new I wanted to give it a shot!

The pipeline’s IAM permissions are extremely too permissive (administrator access). I did this for simplifying the demo but these should be dialed in to only what is needed.

Helpful Additions

I’ve found it helpful to alias the aws codeartifact login command like so:

alias pnpm="aws codeartifact login --tool npm --namespace @mycoolcompany --repository company-artifacts --domain company-artifacts-domain --domain-owner 1234567890"

References