Publishing npm packages to multiple registries with Projen

Construct Hub is a web portal to collect the constructs for AWS CDK, CDK8s and CDKtf. The construct could support multiple programming languages, such as Javascript/TypeScript, Python, Java and C#. Actually the construct is developed by TypeScript, then it's compiled as across languages library by jsii! Any npm/pypi package with certain tags will be discovered by Construct Hub, the package will be automatically recognized as construct package and listed in Construct Hub.

Projen is a project generator to create project with simplifying the project configuration to support dependencies management, building, unit testing, code style linting, CI/CD via Github actions PR and actions. So projen supports the construct project out of box, which configures construct project with jsii configuration that build the construct to across languages library, though publish the packages to kinds of package registries, such as npmjs, pypi and maven central.

Projen provides a Publishing capability to publish construct library to supported package managers. For example, npm for JavaScript/TypeScript, it could publish the package to several npm registries, for example, npm public registry, Github packages, AWS CodeArtifact and any public accessible private npm registry.

However the projen only supports publishing the package to single npm registry by default, how about you would like to publish your package to both npm public registry and Github packages?

There is no mature way to archive it, but projen is a flexible tool, we can hack it like below to add multiple npm registries support to publish the package to both npm public registry and Github packages,

 1const target = 'js';
 2const REPO_TEMP_DIRECTORY = '.repo';
 3const options = {
 4  registry: 'npm.pkg.github.com',
 5  prePublishSteps: [
 6    {
 7      name: 'Prepare Repository',
 8      run: `mv ${project.artifactsDirectory} ${REPO_TEMP_DIRECTORY}`,
 9    },
10    {
11      name: 'Install Dependencies',
12      run: `cd ${REPO_TEMP_DIRECTORY} && ${project.package.installCommand}`,
13    },
14    {
15      // remove this if your package name already has scope
16      name: 'Update package name',
17      run: `cd ${REPO_TEMP_DIRECTORY} && sed -i "1,5s/\\"packagename\\"/\\"@scope\\/packagename\\"/g" package.json`,
18    },
19    {
20      name: `Create ${target} artifact`,
21      run: `cd ${REPO_TEMP_DIRECTORY} && npx projen package:js`,
22    },
23    {
24      name: `Collect ${target} Artifact`,
25      run: `mv ${REPO_TEMP_DIRECTORY}/${project.artifactsDirectory} ${project.artifactsDirectory}`,
26    },
27  ],
28};
29project.release.publisher.addPublishJob((_branch, branchOptions) => {
30  return {
31    name: 'npm_github',
32    publishTools: {},
33    prePublishSteps: options.prePublishSteps ?? [],
34    run: project.release.publisher.publibCommand('publib-npm'),
35    registryName: 'npm-github',
36    env: {
37      NPM_DIST_TAG: branchOptions.npmDistTag ?? options.distTag ?? 'latest',
38      NPM_REGISTRY: options.registry,
39    },
40    permissions: {
41      contents: github.workflows.JobPermission.READ,
42      packages: github.workflows.JobPermission.WRITE,
43    },
44    workflowEnv: {
45      NPM_TOKEN: '${{ secrets.YOUR_GITHUB_REGISTRY_TOKEN }}',
46      // if we are publishing to AWS CodeArtifact, pass AWS access keys that will be used to generate NPM_TOKEN using AWS CLI.
47      AWS_ACCESS_KEY_ID: undefined,
48      AWS_SECRET_ACCESS_KEY: undefined,
49      AWS_ROLE_TO_ASSUME: undefined,
50    },
51  };
52});

Above code snippet adds an additonal step in release workflow of Github action that is managed by projen, which publishes the package to Github packages.

HAPPY Projen!