Skip to content

Commit

Permalink
chore(internal): improve CONTRIBUTING.md (#4355)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsinghvi committed Aug 20, 2024
1 parent 74f185c commit be386dc
Show file tree
Hide file tree
Showing 92 changed files with 181 additions and 127 deletions.
215 changes: 89 additions & 126 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,202 +1,165 @@
# Contributing

Thanks for being here! Fern gives a lot of importance to being a community project, and we rely on your help as much as you rely on ours. If you have any feedback on what we could improve, please [open an issue](https://github.com/fern-api/fern/issues/new) to discuss it!
Thanks for being here! This monorepo contains Fern's documentation, the Fern CLI, the Fern Definition, the OpenAPI importer, as well as all of our generators.

## Opening an issue
<br>

All contributions start with [an issue](https://github.com/fern-api/fern/issues/new). Even if you have a good idea of what the problem is and what the solution looks like, please open an issue. This will give us an opportunity to align on the problem and solution, and to deconflict in the case that somebody else is already working on it.
## Setup

## How can you help?
The Fern repo is primarily written in TypeScript and relies on [PnPm](https://pnpm.io/) for package management.

Review [our documentation](https://buildwithfern.com/docs)! We appreciate any help we can get that makes our documentation more digestible.
Once you have cloned or forked the repository, run through the steps below.

Talk about Fern in your local meetups! Even our users aren't always aware of some of our features. Learn, then share your knowledge with your own circles.
### Step 1: Install dependencies

Write code! We've got lots of open issues - feel free to volunteer for one by commenting on the issue.
```sh
pnpm install
```

## Writing Documentation
### Step 2: Compile

Our documentation is powered by Fern's Docs product. All of the configuration for the docs lives in
[docs.yml](./fern/docs.yml).
To compile all the packages in this monorepo, run `pnpm compile`.

To edit the docs, you can modify `docs.yml` or any of the markdown that it references.
To compile a single package, filter to the relevant package: `pnpm --filter @fern-api/openapi-parser compile`.

To validate that the docs, run:
### Step 3: Testing

```sh
npm install -g fern-api
fern check
```
This repo contains both unit tests and integration (end-to-end) tests.

When you make a PR to update the docs, a PR preview link will be generated which will allow you
to test if your changes came out as intended.
To run all the unit tests: `pnpm test`.

## Local Development
To run unit tests for a single package: `pnpm --filter @fern-api/openapi-parser test`

Our repo is a monorepo that relies on [Yarn workspaces](https://yarnpkg.com/features/workspaces) and [Yarn Plug'n'Play](https://yarnpkg.com/features/pnp) to run smoothly.
To run the integration tests: `pnpm test:ete`.

To get started:
Many of our tests rely on [snapshot testing](https://jestjs.io/docs/snapshot-testing). To rewrite snapshots, use `pnpm test:update` or `pnpm test:ete:update`.

**Step 1: Fork this repo**
<br>

Fork by clicking [here](https://github.com/fern-api/fern/fork).
## Repository Architecture

**Step 2: Clone your fork and open in VSCode**
Below we talk through the large components of this monorepo and how to contribute to each one.

```sh
git clone <your fork>
cd fern
code .
```
<br>

**Step 3: Install dependencies**
## Documentation

```sh
yarn
```
Fern's documentation is hosted live at the URL https://buildwithfern.com/learn. We appreciate any help we can get that makes our documentation more digestible.

**Step 4: Use the "workspace" version of Typescript**
If you find gaps within our documentation, please open an [issue](https://github.com/fern-api/fern/issues/new?assignees=&labels=documentation&projects=&template=documentation-suggestion.md&title=%5BFern%27s+Documentation%5D+)

1. Open any TypeScript file in VSCode
2. Open the Command Palette (Cmd+Shift+P on Mac) and select `Typescript: Select TypeScript Version...`
3. Choose `Use Workspace Version`
### Editing Documentation

This tells VSCode to rely on the version of TypeScript that lives in `.yarn/sdks/typescript`, which is modified to work with Yarn PNP.
Our documentation is powered by Fern's Docs product. All of the configuration for the docs lives in [docs.yml](./fern/docs.yml).

### Compiling
To edit the docs, you can modify `docs.yml` or any of the markdown that it references.

To validate that the docs, run:

To compile the packages in this monorepo, run `pnpm compile`.
```sh
fern check
```

### Tests
To preview the documentation, run:

This repo contains both unit tests and integration (end-to-end) tests.
```sh
fern docs dev
```

To run the unit tests: `yarn test`.
Finally, when you make a PR to update the docs, a PR preview link will be generated which will allow you
to test if your changes came out as intended. [Here](https://github.com/fern-api/fern/pull/4330) is a sample PR with a preview link.

To run the integration tests: `yarn test:ete`.
<br>

Many of our tests rely on [Jest snapshot testing](https://jestjs.io/docs/snapshot-testing). To rewrite snapshots, use `-u`: `yarn test -u` and `yarn test:ete -u`.
## Fern CLI

### CLI
The Fern CLI lives in a directory called [cli](./packages/cli/cli/) and the entrypoint is [cli.ts](./packages/cli/cli/src/cli.ts).

To build the CLI, run either:
### Building the CLI from source

- `pnpm dist:cli:dev`. This compiles and bundles a CLI that communicates with our dev cloud environment. The CLI is outputted to `packages/cli/cli/dist/dev/cli.cjs`.
For testing purposes, you can build a local version of the CLI by running `pnpm fern:build`. This compiles and builds a CLI
that communicates with our production cloud environment.

- `pnpm dist:cli:prod`. This compiles and bundles a CLI that communicates with our production cloud environment. The CLI is outputted to `packages/cli/cli/dist/prod/cli.cjs`.
The CLI is outputted to `packages/cli/cli/dist/prod/cli.cjs`.

To run the locally-generated CLI, run:
Once the CLI has been built, you can navigate to any `fern` folder and invoke it by running

```sh
FERN_NO_VERSION_REDIRECTION=true node <path to CLI> <args>
FERN_NO_VERSION_REDIRECTION=true node /<path to fern git repo>/packages/cli/cli/dist/prod/cli.cjs <args>
```

## Intermediate Representation

Fern generators read in IR (Intermediate Representation) and spit out
generated files. The IR is a JSON data structure that includes information
about your API and any additional information that may be convenient for a
code generator. For example, the IR includes all possible casings of every
string (e.g. `snake_case`, `camelCase`, `PascalCase`) so that the
generators don't need to implement this individually.

### IR Versioning
### Development CLI

As we add more features to the API definition, we introduce new versions
of the IR. For example, if we wanted to add a new auth mechanism, we would
eventually need to add it to the IR so that the generators could generate
relevant code.
To build a CLI that communicates with Fern's development cloud environment, run the command `pnpm fern-dev:build`.

Each generator is pinned to an IR Version. Different versions of the generator,
can dependend on differnt versions of the IR. For example, the Python SDK generator released
2 months ago depends on an older IR than the one released this week.
Once the CLI has been built, you can navigate to any `fern` folder and invoke it by running

> Note: The IR schema is modeled as a Fern Definition and you can see several
> versions of them in the `./fern` folder.
```sh
FERN_NO_VERSION_REDIRECTION=true node /<path to fern git repo>/packages/cli/cli/dist/dev/cli.cjsn <args>
```

The Fern CLI should be able to run old generators so whenver we
introduce a new IR version, we write a migration. In other words if you introduce IR V20, then
you will have to write a migration from IR V20 -> IR V19 so that any generator
that depends on a lower IR version can continue to be run from our CLI.
<br>

### How to add a new IR Version?
## Generators

**Step 1: Define the new IR**
All of Fern's generators live in a directory called [generators](./generators/). This directory contains generators for several languages such as
[typescript](./generators/typescript/), [python](./generators/python/), [go](./generators/go).

1. Create a new Fern Definition for the IR version `fern/ir-types-vXXX`.
Copy the latest IR Fern Definition as a starting point.
2. Introduce any changes you want in the new IR Fern Definition.
3. Generate a TypeScript SDK for the IR by running `fern generate --api ir-types-vXXX`
4. Update all `package.json` files to use new `ir-sdk` npm version.
Run `yarn install`
5. Run `pnpm compile`. You will see compile errors related to your schema changes.
Some of the generators are written in the language they generate (i.e. Python is written in python, Go is written in Go, and Java is written in Java).
We are moving to a world where each generator will be written in TypeScript so that we can share more utilities and enforce a consistent structure
in the generator.

**Step 2: Write a reverse migration**
### Generator Testing

In the `ir-migrations` package, introduce a new migration.
You can copy the latest migration as a starting point.
To test our generators we have built a CLI called seed.

## Generator Testing (Seed CLI)
Seed handles building the generators from source and running them against all of the
[test definitions](./test-definitions/fern/) that are present in the repository. Generated code is then stored in a directory named
[seed](./seed/).

To test our generators we have built a CLI tool called seed.
Each generator configures a `seed.yml`. For example, the TypeScript generator's configuration lives [here](./seed/ts-sdk/seed.yml).

Seed handles building the generators from source and running them against all of the
test definitions that are present in the repository. It also handles running scripts
against the generated code to make sure that all the generated code compiles and
works as intended.
Seed also handles running scripts against the generated code to make sure that the generated code compiles and works
as intended. For example, in the TypeScript generator seed runs `yarn install` and `yarn build` to compile the source code.

To build seed, simply run

```sh
yarn seed:build
pnpm seed:build
```

Each generator has a folder in the top level `seed` directory. For example, the folder
for the typescript sdk generator is `seed/ts-sdk`. This folder contains a config file
called `seed.yml` as well as all the generated code for each test case.
**Note**: If you make any changes to the seed [source code](./packages/seed/src/) then you will need to rerun `pnpm seed:build`.

To trigger seed tests on a specific generator run
To run seed, you can use the command:

```sh
yarn seed test --generator python-sdk --fixture file-download --skip-scripts
```
pnpm seed test [--generator <generator-id>] [--fixture <fixture-name>] [--skip-scripts] [--local]
```

Below are some examples of using the command.

You can specify as many fixtures as you want. If you don't specify one, it will
run on all the fields available.
- For a single generator: `pnpm seed test --generator python-sdk`
- For a single generator and test definition: `pnpm seed test --generator python-sdk --fixture file-download`
- For a single generator, test definition, and skipping scripts: `pnpm seed test --generator python-sdk --fixture file-download --skip-scripts`
- For running the generator locally (not on docker): `pnpm seed test --generator python-sdk`

### Running seed on a custom fixture
### Running seed against a custom fern definition

It may be valuable to run seed on a particular Fern definition or OpenAPI spec. To do this,
you can use the `seed run` command and point it at the fern folder:

```sh
yarn seed run --generator ts-sdk --path /Users/jdoe/fern
```

If the fern folder that you are pointing to has multiple APIs, then you must point it at the
specific API that you are looking to generate:

```sh
yarn seed run --generator ts-sdk --path /Users/jdoe/fern/apis/imdb
```

To run against a custom fixture with an audience, run

```sh
yarn seed run --generator ts-sdk --path /Users/jdoe/fern/apis/imdb --audience external
pnpm seed run [--generator <generator-id>] [--path /path/to/fern/folder] [--audience <audience>]
```

### Running generators from source
Below are some examples of using the command.

By default, seed will build the docker container for the generator and execute the docker. Building a docker
adds extra time to your iteration cycle so we also have a mode to run the generators directly from source. All you
have to do is use the `--local` flag.
- Pointed at a fern folder: `pnpm seed run --generator ts-sdk --path /Users/jdoe/fern --audience external`
- Pointed at a fern folder with an audience: `pnpm seed run --generator ts-sdk --path /Users/jdoe/fern`
- Pointed at a fern folder with multiple apis: `pnpm seed run --generator ts-sdk --path /Users/jdoe/fern/apis/<name-of-api>`

For example, to run the TypeScript SDK generator from source, you can:
<br>

```sh
yarn seed test --generator ts-sdk --fixture imdb --local
```
## Feedback

The local flag will only work if the generator has configured the `local` configuration in its seed.yml.
See [here](https://github.com/fern-api/fern/blob/bf3c28f1c08447e37949ba938f90228c575194d2/seed/ts-sdk/seed.yml#L7-L14).
If you have any feedback on what we could improve, please [open an issue](https://github.com/fern-api/fern/issues/new) to discuss it!
1 change: 1 addition & 0 deletions generators/commons/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"clean": "rm -rf ./lib && tsc --build --clean",
"compile": "tsc --build",
"test": "vitest --run",
"test:update": "vitest --run -u",
"lint:eslint": "eslint --max-warnings 0 . --ignore-path=../../.eslintignore",
"lint:eslint:fix": "yarn lint:eslint --fix",
"format": "prettier --write --ignore-unknown --ignore-path ../../shared/.prettierignore \"**\"",
Expand Down
1 change: 1 addition & 0 deletions generators/csharp/codegen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"clean": "rm -rf ./lib && tsc --build --clean",
"compile": "tsc --build",
"test": "vitest --run",
"test:update": "vitest --run -u",
"lint:eslint": "eslint --max-warnings 0 . --ignore-path=../../../.eslintignore",
"lint:eslint:fix": "yarn lint:eslint --fix",
"format": "prettier --write --ignore-unknown --ignore-path ../../../shared/.prettierignore \"**\"",
Expand Down
1 change: 1 addition & 0 deletions generators/csharp/model/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"clean": "rm -rf ./lib && tsc --build --clean",
"compile": "tsc --build",
"test": "jest --passWithNoTests",
"test:update": "jest --passWithNoTests -u",
"lint:eslint": "eslint --max-warnings 0 . --ignore-path=../../../.eslintignore",
"lint:eslint:fix": "yarn lint:eslint --fix",
"format": "prettier --write --ignore-unknown --ignore-path ../../../shared/.prettierignore \"**\"",
Expand Down
1 change: 1 addition & 0 deletions generators/csharp/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"clean": "rm -rf ./lib && tsc --build --clean",
"compile": "tsc --build",
"test": "jest --passWithNoTests",
"test:update": "jest --passWithNoTests -u",
"lint:eslint": "eslint --max-warnings 0 . --ignore-path=../../../.eslintignore",
"lint:eslint:fix": "yarn lint:eslint --fix",
"format": "prettier --write --ignore-unknown --ignore-path ../../../shared/.prettierignore \"**\"",
Expand Down
1 change: 1 addition & 0 deletions generators/openapi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"clean": "rm -rf ./lib && tsc --build --clean",
"compile": "tsc --build",
"test": "jest --passWithNoTests",
"test:update": "jest --passWithNoTests -u",
"lint:eslint": "eslint --max-warnings 0 . --ignore-path=../../.eslintignore",
"lint:eslint:fix": "yarn lint:eslint --fix",
"format": "prettier --write --ignore-unknown --ignore-path ../../shared/.prettierignore \"**\"",
Expand Down
1 change: 1 addition & 0 deletions generators/postman/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"clean": "rm -rf ./lib && tsc --build --clean",
"compile": "tsc --build",
"test": "jest --passWithNoTests",
"test:update": "jest --passWithNoTests -u",
"lint:eslint": "eslint --max-warnings 0 . --ignore-path=../../.eslintignore",
"lint:eslint:fix": "yarn lint:eslint --fix",
"format": "prettier --write --ignore-unknown --ignore-path ../../shared/.prettierignore \"**\"",
Expand Down
1 change: 1 addition & 0 deletions generators/ruby/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"clean": "rm -rf ./lib && tsc --build --clean",
"compile": "tsc --build",
"test": "jest --passWithNoTests",
"test:update": "jest --passWithNoTests -u",
"lint:eslint": "eslint --max-warnings 0 . --ignore-path=../../../.eslintignore",
"lint:eslint:fix": "yarn lint:eslint --fix",
"format": "prettier --write --ignore-unknown --ignore-path ../../../shared/.prettierignore \"**\"",
Expand Down
1 change: 1 addition & 0 deletions generators/ruby/codegen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"clean": "rm -rf ./lib && tsc --build --clean",
"compile": "tsc --build",
"test": "jest --passWithNoTests",
"test:update": "jest --passWithNoTests -u",
"lint:eslint": "eslint --max-warnings 0 . --ignore-path=../../../.eslintignore",
"lint:eslint:fix": "yarn lint:eslint --fix",
"format": "prettier --write --ignore-unknown --ignore-path ../../../shared/.prettierignore \"**\"",
Expand Down
1 change: 1 addition & 0 deletions generators/ruby/model/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"clean": "rm -rf ./lib && tsc --build --clean",
"compile": "tsc --build",
"test": "jest --passWithNoTests",
"test:update": "jest --passWithNoTests -u",
"lint:eslint": "eslint --max-warnings 0 . --ignore-path=../../../.eslintignore",
"lint:eslint:fix": "yarn lint:eslint --fix",
"format": "prettier --write --ignore-unknown --ignore-path ../../../shared/.prettierignore \"**\"",
Expand Down
1 change: 1 addition & 0 deletions generators/ruby/sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"clean": "rm -rf ./lib && tsc --build --clean",
"compile": "tsc --build",
"test": "jest --passWithNoTests",
"test:update": "jest --passWithNoTests -u",
"lint:eslint": "eslint --max-warnings 0 . --ignore-path=../../../.eslintignore",
"lint:eslint:fix": "yarn lint:eslint --fix",
"format": "prettier --write --ignore-unknown --ignore-path ../../../shared/.prettierignore \"**\"",
Expand Down
1 change: 1 addition & 0 deletions generators/swift/codegen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"clean": "rm -rf ./lib && tsc --build --clean",
"compile": "tsc --build",
"test": "vitest --run",
"test:update": "vitest --run -u",
"lint:eslint": "eslint --max-warnings 0 . --ignore-path=../../../.eslintignore",
"lint:eslint:fix": "yarn lint:eslint --fix",
"format": "prettier --write --ignore-unknown --ignore-path ../../../shared/.prettierignore \"**\"",
Expand Down
1 change: 1 addition & 0 deletions generators/template/codegen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"clean": "rm -rf ./lib && tsc --build --clean",
"compile": "tsc --build",
"test": "vitest --run",
"test:update": "vitest --run -u",
"lint:eslint": "eslint --max-warnings 0 . --ignore-path=../../../.eslintignore",
"lint:eslint:fix": "yarn lint:eslint --fix",
"format": "prettier --write --ignore-unknown --ignore-path ../../../shared/.prettierignore \"**\"",
Expand Down
1 change: 1 addition & 0 deletions generators/typescript/express/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"clean": "rm -rf ./lib && tsc --build --clean",
"compile": "tsc --build",
"test": "jest --passWithNoTests",
"test:update": "jest --passWithNoTests -u",
"lint:eslint": "eslint --max-warnings 0 . --ignore-path=../../../../.eslintignore",
"lint:eslint:fix": "yarn lint:eslint --fix",
"format": "prettier --write --ignore-unknown --ignore-path ../../../../shared/.prettierignore \"**\"",
Expand Down
Loading

0 comments on commit be386dc

Please sign in to comment.