# projen: every build script in the repo is itself a projen task

> A configuration generator whose own manifest routes thirty-one commands through its own task runner, whose generated files are made read-only and policed by an anti-tamper check in CI, which tells you to run it through pnpm while shipping an npm lockfile, and whose documented config file is JavaScript while the repository's own is TypeScript.

**projen/projen** — Rapidly build modern applications with advanced configuration management

- Repository: https://github.com/projen/projen
- Website: https://projen.io
- Stars: 2,956 · Forks: 414
- Language: TypeScript
- License: Apache-2.0
- Published: 2026-09-24 · Updated: 2026-09-24 · Language: en
- Canonical page: https://hysenlabs.com/projects/projen-projen

## Generated files are read-only and a build check fails if they move

The central rule is stated three times in slightly different ways, and the enforcement is the interesting part. Synthesised files should never be edited by hand, and the page adds in parentheses that the tool enforces this. The generated files are described as an implementation detail. To change your project setup you edit a definition and re-run the tool.

How that is enforced is worth spelling out, because it is not just documentation. Most generated files are marked read-only at the filesystem level, so an editor that respects the bit refuses to save over them. And a check called anti-tamper is configured into the continuous integration workflow, so that if a build ever modifies one of those files the build fails. That second mechanism is the real teeth: a generated file drifting from what the definition says becomes a broken pipeline rather than a silent divergence.

For a team, the practical effect is that the cost of using this tool is paid up front. Every future change to your project configuration goes through the definition file, and there is no supported path for the exception you need today.

```js
const project = new JsiiProject({
  // ...
  publishToPypi: {
    distName: "mydist",
    module: "my_module",
  }
});
```

## Thirty-one build scripts, all of them the tool's own tasks

The most convincing argument for this project is in its own manifest. Read the scripts block and every entry has the same shape: a command that invokes one file in the repository root and passes it a task name. Build, test, compile, package, bump, bump the releasable commits, bundle the task runner, check licences, lint, generate documentation, audit dependencies, dedupe, compare compatibility, eject, set up the development environment, render the readme macros. Then integration suites per language, one per packaging target, and package-everything. Thirty-one visible entries, none of which is a direct command.

The tool configures itself. Its own definition file sits at the root, its own generated state directory is committed, and the release workflow badge points at a file whose content is the tool's own output. So the claims about generated files being read-only and policed by a build check are not aspirations; they are properties of this repository, which has been running them on itself continuously.

Two of those task names deserve attention. One ejects the configuration, which is the escape hatch for when you no longer want the tool in charge. Another hard resets the working copy to the remote's head and cleans the local repository. Both ship in every generated project.

## Told to use one package runner, shipping the other one's lockfile

The getting started section makes a deliberate choice and gives its reason. The tool does not need to be installed, because you run it through a one-off runner that handles all required setup. The alternative also works, but the page says the preferred runner avoids the supply-chain risk of phantom dependency resolution in the other one. That is a specific, technical objection about transitive dependency resolution, and it is stated once, without elaboration, and never repeated.

The repository then does something slightly awkward. There is no lockfile for the preferred runner in the tree; what is committed is a lockfile for the other one. So the project that argues against one runner's resolution behaviour is pinned with the other runner's lockfile.

There is a smaller version of the same mismatch in the configuration file. The documentation teaches JavaScript, and the sample definition file uses a CommonJS require. The repository's own definition file is TypeScript. Given that the tool's whole argument is about strongly-typed configuration, having its own configuration in the other language is the sort of detail a contributor notices on day one.

```bash
alias pj='pnpm dlx projen'
```

## One object literal adds a manifest section and a workflow step

The clearest illustration of what the tool is for is a short example about publishing to a package index. You add an option to the project constructor naming the distribution and the module, run the tool, and then two things have changed: the project manifest now contains a section for that ecosystem inside its configuration block, and the release workflow now includes a publishing step for it.

One property in a JavaScript object produced an edit to a JSON file and an edit to a continuous integration pipeline. No YAML was opened, no marketplace credential was pasted into a workflow by hand, and neither of those files is the place to look next time, because both are outputs.

The page then recommends putting the run command in your shell profile so you can type a two-letter alias after every edit to the definition file. That recommendation comes with a grammatical slip, recommending to putting rather than recommending putting, which is a small thing but it is in the sentence a reader copies.

## Twenty built-in types, one external, and a list that generates itself

The supported types are listed as twenty built-ins plus one external. The built-ins cluster by ecosystem: four for one cloud toolkit, three for its Kubernetes counterpart, two for a third, and then singles and pairs for Java, a multi-language library type, Next.js in two languages, Node, Python, React in two languages, TypeScript in two forms, and a bare base project.

The list is wrapped in a comment marker naming a script in the repository's scripts directory, with a matching closing marker. So the list is generated from the code and the readme carries a task for rendering its own macros. That is why it cannot drift out of date, and it is the mechanism behind a claim the page does not make explicitly: the twenty names in that list are the twenty names the binary accepts.

External types are installed with a flag instead, and there is exactly one of them, listed with a link to a file on another repository's main branch. Documentation links for the built-ins are grouped by API area rather than by type name, and one of them is filed under a group whose name does not match its type.

```shell
pnpm dlx projen
```

## The task list runs local commands and CI steps from one definition

Most generated projects arrive with a set of tasks covering development activities from compiling through to publishing. The design point is that a task can be run as a local command or turned into a continuous integration workflow, so the two do not drift, because there is only one definition.

The sentence describing composition has a stray word in it, saying tasks can be and composed together, which is presumably where was meant. It is a small blemish on the one paragraph that explains the composition model.

The listing of available tasks is printed by the help flag, and the visible portion is worth reading as a product decision rather than a command reference. Creating a new project is one entry. There is an entry that only compiles, one that runs tests, and one that does a full release build described as test plus compile. There is one that hard resets to the remote head and cleans the local repository. And the listing in the page stops partway through the next entry, so the total number of shipped tasks is not something the documentation tells you.

## Two one-line descriptions that do not match

The project describes itself twice in two different places with two different sentences. The repository description says it builds modern applications rapidly with advanced configuration management. The description inside the package manifest says it is a cloud development kit for software projects. Both are accurate in their own way and neither is wrong, but a reader arriving from a package index and a reader arriving from a repository page are being told they are looking at two different products.

The introduction has its own small wobbles. It says users interact with rich strongly-typed class, in the singular, and it says they execute the tool where it means executed. And the sentence about scaling to many repositories ends with a parenthetical that is not sure of itself, saying dozens or hundreds with a question mark inside the brackets.

None of that is disqualifying. It does mean the front page of this repository is written quickly and reviewed lightly, which is a reasonable trade for a tool whose selling point is that the interesting configuration lives somewhere other than the readme.

## Conclusion

projen is worth adopting when your configuration is large enough that hand-editing it across a repository is a real cost, which for a project with a package manifest, a compiler config, a lint config, a test config and several workflows is early. Buy into the model completely or not at all, because the value comes from regenerating rather than patching, and a single hand edit will be reverted or will fail the build. Read the escape hatch before you commit, since one of the shipped commands hard resets your working copy. And notice what the tool's own repository reveals about its tradeoffs: it prefers a package runner that avoids transitive resolution surprises, yet ships an npm lockfile itself, and it teaches JavaScript while configuring itself in TypeScript.

## FAQ

### how to install projen

It is not installed. You run it through a one-off package runner, which takes care of the required setup steps. The page says the alternative runner also works, but that the preferred one avoids the supply-chain risk of phantom dependency resolution in the other.

### what is projen

It synthesizes project configuration files such as package.json, tsconfig.json, .gitignore, GitHub workflows, eslint and jest from a well-typed definition written in JavaScript. Unlike a one-off scaffolder, it is re-run: you edit the definition and regenerate, and the generated files should never be edited by hand.

### How do I change a file that projen generated?

You do not change it. You edit the project's definition file and re-run the tool. Generated files are treated as an implementation detail, most are marked read-only, and an anti-tamper check in the CI workflow fails the build if a generated file changes during it.

### Which project types does projen support?

Twenty built-in types, covering a cloud toolkit and its Kubernetes counterpart in several languages, Java, a multi-language library type, Next.js, Node, Python, React, TypeScript and a base project. External types are added with a from flag, and running the new command with no type prints the full list. The list in the readme is generated from the code by a script.

### What tasks does projen provide in a generated project?

An assortment covering development activities from compiling to publishing, where each task can be run as a local command or turned into a GitHub workflow from one definition. You list them with the help flag. One shipped task, clobber, hard resets to the remote head and cleans the local repository.

## Sources

- [License: Apache-2.0](https://github.com/projen/projen/blob/main/LICENSE)
- [Project website](https://projen.io)
- [projen/projen on GitHub](https://github.com/projen/projen)
- [README](https://github.com/projen/projen/blob/main/README.md)
- [Releases](https://github.com/projen/projen/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/projen-projen
