How to simplify your next software project set-up
You are about to start a new software project. What do you do?
Chances are that you create a new (git, subversion) repository, and copy various configurations, scripts and other things from similar projects that were useful.
Maybe your company has some template repositories to start from?
Either way, setting up a new project can be time-consuming. You need some CICD pipeline configuration, build and test scripts, linter setup. There are many things that are useful and add quality to the developer workflow, but require time and work to get right.
I have done this countless times, and even if I sometimes could get up to speed fairly quickly, there was always some additional tweaking needed – and trying to figure out the details of a specific tool configuration which I have not touched for at least six months.
These issues were the reason that a tool called Projen piqued my interest.
Projen
Projen is a scaffolding tool that uses programming language code to generate different configurations for your project. There are several tools that do that. It does not just generate the initial setup and let you figure out the rest for yourself, though.
Instead, it actively maintains the various project tool configurations you use throughout the life cycle of the project. It even makes configuration files read-only to emphasize that you should not touch these files directly.
It uses code to make a simplified, opinionated, and typed interface for various types of software projects. From this code, it generates all the various configuration files, scripts, etc that it supports. Most times it comes with some sane defaults, so you kind of get batteries included experience.
In my work, I do a lot of work with the AWS Cloud Development Kit (AWS CDK). It was through this context that I discovered Projen. The creator of Projen, Elad Ben-Israel, is one of the key developers of AWS CDK. He described it as “CDK for software projects”.
Here is a video where Elad introduces Projen from last year.
I did not think so much of it initially, but when I tried it out earlier this year, I got hooked.
When I first created a new AWS CDK project with it, it added many bells & whistles, a sane project structure, and a single fairly simple file with which I configured everything.
But enough talk, let us get practical!
Set up a project with Projen
Projen itself is written in Typescript, and you will need to have Node.js installed on your computer to run it. If you already have Node.js installed on your computer, then that is great! If you do not have Node.js, you can download the Node.js runtime from the Node.js website. I would recommend to use the current long-term support of Node.js, at the time of writing that is version 14.
You can also install Node.js via different package managers, depending on the computer environment you are working with. See the Node.js package manager page for suggestions. You can also download tools there etc manage multiple separate versions of Node.js, should you need that.
Depending on what kind of project you set up, you may need additional software installed. I am going to show two examples:
- An AWS CDK app project, using Typescript
- A Java project
To see the different project types you have available in Projen itself, you can just run the command
npx projen new
It will show a list of project types, like this:
❯ npx projen new npx: installed 64 in 3.622s projen new [PROJECT-TYPE-NAME] [OPTIONS] Creates a new Projen project Commands: projen new awscdk-app-ts AWS CDK app in TypeScript. projen new awscdk-construct AWS CDK construct library project. projen new cdk8s-app-ts CDK8s app in TypeScript. projen new cdk8s-construct CDK8s construct library project. projen new cdktf-construct CDKTF construct library project. projen new java Java project. projen new jsii Multi-language jsii library project. projen new nextjs Next.js project without TypeScript. projen new nextjs-ts Next.js project with TypeScript. projen new node Node.js project. projen new project Base project. projen new python Python project. projen new react React project without TypeScript. projen new react-ts React project with TypeScript. projen new typescript TypeScript project. projen new typescript-app TypeScript app.
The command npx is part of the Node.js runtime installation. It will perform a temporary installation of Projen to run it.
We are going to use the project type options awscdk-app-ts and java.
Set up an AWS CDK Typescript project
To set up the AWS CDK App project, we run
npx projen new awscdk-app-ts
and get the following result:
❯ npx projen new awscdk-app-ts npx: installed 64 in 3.027s ✨ Project definition file was created at /Users/erikl/Documents/Dev/repos/my-awscdk-app/.projenrc.js ✨ Synthesizing project... 🤖 yarn install --check-files yarn install v1.22.11 info No lockfile found. [1/4] 🔍 Resolving packages... [2/4] 🚚 Fetching packages... [3/4] 🔗 Linking dependencies... [4/4] 🔨 Building fresh packages... success Saved lockfile. ✨ Done in 30.42s. ✨ Synthesis complete 🤖 git init Initialized empty Git repository in /Users/erikl/Documents/Dev/repos/my-awscdk-app/.git/ 🤖 git add . 🤖 git commit --allow-empty -m "chore: project created with projen" [master (root-commit) c14eb25] chore: project created with projen 21 files changed, 7226 insertions(+) create mode 100644 .eslintrc.json create mode 100644 .gitattributes create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/stale.yml create mode 100644 .github/workflows/upgrade.yml create mode 100644 .gitignore create mode 100644 .mergify.yml create mode 100644 .npmignore create mode 100644 .projen/deps.json create mode 100644 .projen/tasks.json create mode 100644 .projenrc.js create mode 100644 LICENSE create mode 100644 README.md create mode 100644 cdk.json create mode 100644 package.json create mode 100644 src/main.ts create mode 100644 test/main.test.ts create mode 100644 tsconfig.dev.json create mode 100644 tsconfig.json create mode 100644 yarn.lock 🤖 git branch -M main ❯
There are a few things that happened here. The key file here is the file .projenrc.js. This is a Javascript file, which is the place where you handle all the project settings for your project. The default here is to use Javascript, but other languages and formats are supported as well, including Typescript, JSON, Python, Java, etc. You should be able to use a language/format that makes sense to you.
Other bits and pieces you may see here include initialization of a Git repository, Github workflows for dependency management, builds, pull request handling, Typescript configurations, with ESlint setup, ignore files, CDK setup in place. It also defaults to the Apache 2 open source license file, which you can change to another license, or disable completely.
This project setup defaults to using Yarn as a package manager, which I already had installed. If you prefer NPM instead, that is fine – change the configuration in .projenrc.js and it will happily use NPM instead.
Let us have a brief look at .projenrc.js itself:
const { AwsCdkTypeScriptApp } = require('projen'); const project = new AwsCdkTypeScriptApp({ cdkVersion: '1.95.2', defaultReleaseBranch: 'main', name: 'my-awscdk-app', // cdkDependencies: undefined, /* Which AWS CDK modules (those that start with "@aws-cdk/") this app uses. */ // deps: [], /* Runtime dependencies of this module. */ // description: undefined, /* The description is just a string that helps people understand the purpose of the package. */ // devDeps: [], /* Build dependencies for this module. */ // packageName: undefined, /* The "name" in package.json. */ // release: undefined, /* Add release management to this project. */ }); project.synth();
This is a piece of Javascript code that creates an object of type AwsCdkTypeScriptApp, and passes some options to it. There are plenty of options to set, but most of them are optional. If you have worked with AWS CDK in the past, you can see that it includes a specific field to define which version to use. This provides a single place to set what version you want to use, instead of updating a potentially long list of dependencies, whenever you want to upgrade.
The code statement projen.synth() at the end of the file causes the project setup to update. If you change the settings here, you process these by simply running:
npx projen
This will update any settings you have changed in .projenrc.js.
A project set up with Projen also includes several commands you can perform, which depend on the project type. You can see the list of commands available by running the command
npx projen —help.
❯ npx projen --help projen [command] Commands: projen new [PROJECT-TYPE-NAME] [OPTIONS] Creates a new projen project projen clobber hard resets to HEAD of origin and cleans the local repo projen compile Only compile projen test Run tests projen build Full release build (test+compile) projen test:watch Run jest in watch mode projen test:update Update jest snapshots projen default projen watch Watch & compile in the background projen eslint Runs eslint against the codebase projen synth Synthesizes your cdk app into cdk.out (part of "yarn build") projen deploy Deploys your CDK app to the AWS cloud projen destroy Destroys your cdk app in the AWS cloud projen diff Diffs the currently deployed app against your code projen upgrade upgrade dependencies projen upgrade-projen upgrade projen projen completion generate completion script
You can also define your own project commands in .projenrc.js.
A similar article that covers using Projen with AWS CDK is How to simplify project setup with Projen.
Let us move on to set up a Python project.
Set up a Java project
The java project type sets up a generic Java project. We can set that up with npx projen new java:
❯ npx projen new java npx: installed 64 in 2.923s ✨ Project definition file was created at /Users/erikl/Documents/Dev/repos/my-java-project/src/test/java/projenrc.java ✨ Synthesizing project... ✨ Synthesis complete 🤖 git init Initialized empty Git repository in /Users/erikl/Documents/Dev/repos/my-java-project/.git/ 🤖 git add . 🤖 git commit --allow-empty -m "chore: project created with projen" [master (root-commit) 527cc13] chore: project created with projen 10 files changed, 415 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/workflows/stale.yml create mode 100644 .gitignore create mode 100644 .projen/deps.json create mode 100644 .projen/tasks.json create mode 100644 README.md create mode 100644 pom.xml create mode 100644 src/main/java/org/acme/Main.java create mode 100644 src/test/java/org/acme/MyTest.java create mode 100644 src/test/java/projenrc.java 🤖 git branch -M main ❯
Similar to the AWS CDK Typescript app project, Projen sets up a Git repository, adds Github workflows, adds ignore file. It also adds a Maven project file (pom.xml) and some sample code. Notice here that the Projen configuration is now a Java file, in
src/test/java/projenrc.java.
A brief look into this file we see this setup:
import io.github.cdklabs.projen.java.JavaProject; import io.github.cdklabs.projen.java.JavaProjectOptions; public class projenrc { public static void main(String[] args) { JavaProject project = new JavaProject(JavaProjectOptions.builder() .artifactId("my-app") .groupId("org.acme") .name("my-java-project") .version("0.1.0") .build()); project.synth(); } }
The principle is the same as for the AWS CDK Typescript project, with just different syntax. You run
npx projen
whenever you have done an update in projenrc.java, and it updates the settings.
Set project defaults
If you are not happy with the defaults provided for a project type, you can change them in the file, or even directly from the command line. To see what options to can change from the command line, you can run the —help option for the project type, for example:
❯ npx projen new java --help npx: installed 64 in 3.094s projen new java Java project. Required: --artifact-id The artifactId is generally the name that the project is known by [string] [required] [default: "my-app"] --group-id This is generally unique amongst an organization or a project [string] [required] [default: "org.acme"] --name This is the name of your project [string] [required] [default: "my-java-project"] --version This is the last piece of the naming puzzle [string] [required] [default: "0.1.0"] Optional: --clobber Add a `clobber` task which resets the repo to origin [default: true] [boolean] --description Description of a project is always good [string] --dev-container Add a VSCode development environment (used for GitHub Codespaces) [default: false] [boolean] --distdir Final artifact output directory [default: "dist/java"] [string] --github Enable GitHub integration [default: true] [boolean] --gitpod Add a Gitpod development environment [default: false] [boolean] --junit Include junit tests [default: true] [boolean] --mergify Whether mergify should be enabled on this repository or not [default: true] [boolean] --outdir The root directory of the project [default: "."] [string] --packaging Project packaging format [default: "jar"] [string] --project-type Which type of project this is (library/app) [default: ProjectType.UNKNOWN] [string] --projenrc-java Use projenrc in java [default: true] [boolean] --projenrc-json Generate (once) .projenrc.json (in JSON). Set to `false` in order to disable .projenrc.json generation [default: false] [boolean] --sample Include sample code and test if the relevant directories don't exist [boolean] --sample-java-package The java package to use for the code sample [default: "org.acme"] [string] --stale Auto-close of stale issues and pull request [default: true] [boolean] --url The URL, like the name, is not required [string] --vscode Enable VSCode integration [default: true] [boolean] Positionals: PROJECT-TYPE-NAME optional only when --from is used and there is a single project type in the external module [string] Options: --post Run post-synthesis steps such as installing dependencies. Use --no-post to skip [boolean] [default: true] -w, --watch Keep running and resynthesize when projenrc changes [boolean] [default: false] --debug Debug logs [boolean] [default: false] --rc path to .projenrc.js file [string] [default: "/Users/erikl/Documents/Dev/repos/my-java-project/.projenrc.js"] --help Show help [boolean] --synth Synthesize after creating .projenrc.js [boolean] [default: true] --comments Include commented out options in .projenrc.js (use --no-comments to disable) [boolean] [default: true] -f, --from External jsii npm module to create project from. Supports any package spec supported by npm (such as "my-pack@^2.0") [string] --git Run `git init` and create an initial commit (use --no-git to disable) [boolean] [default: true] ❯
Final thoughts
I really like Projen, and it has been quite helpful, I think. But to be honest, some areas are more fleshed out than others and my primary use case is for AWS CDK-based solutions.
The tool is also still in a 0.x release, so the interface may change from time to time. There are frequent updates, so check the Github repository for the latest updates.
You can also define your own project types and use them besides the bundled project types. This area is out of scope for this article, but you can, for example, read more about that here.
Try it out and see what you think!