# Architecture

This document attempts to document the high-level architecture of projen. This
could be useful if you're trying to contribute to projen, trying to debug an
error message, or if you're just curious!

## How are a project's files synthesized?

### Bird's eye view

When `npx projen` is run, the command-line process executes the project's
projenrc file. This is usually a file like `.projenrc.js` or `projenrc.java`.

> The "rc" in the name is a common convention for configuration files - see
https://en.wikipedia.org/wiki/Configuration_file.

projenrc files follow a general structure:

1. they define one or more `Project` instances
2. these projects are configured and customized
3. `project.synth()` is called on the root project

For simplicity, most of this document will just assume there is a single project
unless otherwise specified.

Steps 1 and 2 only serve to initialize an in-memory representation of the
project. projen runs on Node.js; so in the JavaScript runtime, these steps
create a hierarchy of objects (called `Component`'s), each with various fields
specifying the names of files, tasks, options, and so on. The data within each
component provides enough information to uniquely determine the structure and
contents of the files it is responsible for. Components can add other components
to the project, and even make changes to existing components through common
interfaces like `project.tasks`, `project.deps`, or
`project.tryFindObjectFile()`.

Step 3 is the only step that actually performs any changes to files in the
user's project / file system.

### Synthesizing actual files

The `synth()` method of `Project` performs the actual synthesizing (and
updating) of all configuration files managed by projen. This is achieved by
deleting all projen-managed files (if there are any), and then re-synthesizing
them based on the latest configuration specified by the user. In code, this
breaks down as follows (slightly simplified):

1. the project's `preSynthesize()` method is called
2. all components' `preSynthesize()` methods are called
3. all projen-synthesized files are cleaned up
4. all components' `synthesize()` methods are called (most files are generated)
5. all components' `postSynthesize()` methods are called
6. the project's `postSynthesize()` method is called

In the above list, step 3 is critical since it's important that only files that
are managed by projen get cleaned up - we don't want user source code to be
deleted! Moreover, if a file was synthesized by projen at one point in time, but
later a user changes their projenrc configuration so it is no longer necessary,
we want it to be automatically cleaned up.

Rather than manually keeping track of synthesized files with some form of stored
state (which could easily get desynced by tampering from users or other tools),
projen simply looks for files with the _magic string_ that you get by
concatenating `"~~ Generated by "` and `"projen"`, and removes them. See
[cleanup.ts](src/cleanup.ts).

Since any file with this string gets automatically cleaned up, you should not
include this magic string verbatim in source code files. If you are writing your
own projen project type or component, you can simply reference this magic string
via `FileBase.PROJEN_MARKER`.

----

Steps 1, 2, 4, 5, and 6 are more straightforward. `synthesize()` is used to
generate the actual files in the user's file system (including applying
appropriate read/write permissions). `preSynthesize()` and `postSynthesize()`
are complementary methods that can used to enable components to perform
additional logic before and after synthesis. See the source code of `Component`
and `FileBase` for more details.

> Note: in practice, there are many existing components for creating specific
> types of files (such as `JsonFile` and `TextFile`), so we recommend using
> these over hand-making components wherever possible. (Believe in the power of
> abstractions!)

Since `preSynthesize()` is called before any files are cleaned up, it can be
used for e.g. observing any changes made to a generated file, and then adjusting
how the file is re-synthesized based on those changes. (As an example, running
`npm install` or `yarn install` can change the dependencies listed in the
`package.json` file of JavaScript projects. The built-in `NodeProject` uses
`preSynthesize()` to automatically integrate these changes to the `package.json`
file synthesized by projen, instead of overriding them.)

## How can projenrc files be written in multiple languages?

The projen library is transpiled by [jsii](https://github.com/aws/jsii) so that
projenrc files can be written in languages besides JavaScript. Under the hood,
API calls made in projen's  Java/Python/etc. libraries communicate with a
JavaScript runtime to deliver the same behavior as if you wrote the code in
JavaScript. For more information, check out [jsii](https://github.com/aws/jsii).
