Skip to content

Commit 365b3ea

Browse files
committed
docs(core): add contributing.md
1 parent a2e459d commit 365b3ea

1 file changed

Lines changed: 163 additions & 0 deletions

File tree

CONTRIBUTING.md

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
# Contributing
2+
3+
Syncpack is a Rust binary crate that creates a command line application for ensuring consistency in the contents of multiple package.json files, particularly focusing on dependency versions.
4+
5+
It is deployed to the npm registry as `syncpack` in the version range `syncpack@14.0.0-alpha.*`. It is an in-development replacement for `syncpack@latest` which is currently on version `13.0.4`.
6+
7+
## Git branches
8+
9+
`main` - The most recently published version of the Rust v14 alpha version of the codebase.
10+
`v14-alpha` - A development branch for the next version of the Rust v14 alpha version of the codebase.
11+
`13.x.x` - The most recently published version of the TypeScript v13 version of the codebase which is being replaced.
12+
13+
## Folder structure
14+
15+
- All source code is located in files at `./src/**/*.rs`
16+
- All tests are located in files at `./src/**/*_test.rs`
17+
- The documentation website is located in `./site/src/**`
18+
- The `./fixtures/fluid-framework` directory is an example project which can be used to run the local development version of syncpack against for testing locally.
19+
- The `./npm` directory contains files used when deploying syncpack to npm, a rust binary for each major OS needs publishing as npm packages which are then set as optionalDependencies of the main syncpack package, which then has a small node.js script to run the appropriate binary. This is not needed during local development.
20+
21+
## Documentation website
22+
23+
The source code for the documentation website is located in `./site/src/**`, the sitemap for the published website is located at https://jamiemason.github.io/syncpack/sitemap.xml. Read this sitemap to find what documentation is available to help you with a given topic.
24+
25+
## Development scripts
26+
27+
Important commands:
28+
29+
- `just test` - Run all tests
30+
- `just lint` - Run all linting checks
31+
- `just coverage` - Run all tests and generate a coverage report, this can help find unused code or identify real world use cases we do not have tests for.
32+
- `just benchmark` - When making performance improvements, run this command before and after each change to compare the performance of the current version of syncpack with the previous version.
33+
- `just format` - Fix formatting, indentation etc of all files
34+
35+
Run `just` to see a list of all other available commands and their descriptions.
36+
37+
## Running syncpack locally
38+
39+
When deployed and installed globally, the end user help documentation for syncpack as a whole can be found by running:
40+
41+
```bash
42+
syncpack --help
43+
```
44+
45+
The equivalent command when running a local development version of syncpack is:
46+
47+
```bash
48+
cargo run -- --help
49+
```
50+
51+
To view the help documentation for each command:
52+
53+
```bash
54+
cargo run -- lint --help
55+
cargo run -- fix --help
56+
cargo run -- format --help
57+
cargo run -- update --help
58+
cargo run -- list --help
59+
cargo run -- json --help
60+
```
61+
62+
## Writing tests
63+
64+
### Test Structure
65+
66+
- Unit tests are co-located with source files as `*_test.rs` and tend to test complex functions in isolation.
67+
- The preferred tests are those at `src/visit_packages/**/*_test.rs` and `src/visit_formatting/**/*_test.rs` as they are integration tests which resemble real world use cases.
68+
- Integration tests use the builder pattern in `src/test/builder.rs` via the `TestBuilder` struct. The `TestBuilder` struct provides a fluent API for creating test cases that consist of package.json files, syncpack configuration files, and command line inputs.
69+
- The `TestBuilder` has a `.build()` method which returns a `Context` struct in the correct state to reproduce the required test scenario. There is also a `build_and_visit_packages()` method which returns a `Context` struct which has also been passed through `visit_packages()` for convenience.
70+
- Mock utilities are available in `src/test/mock.rs`
71+
- The `expect` function at `src/test/expect.rs` receives a `Context` struct and asserts that it is in the expected state. the `Vec` it receives is a list which must contain every expected package.json file that should be present in the context after the test has been run, in the expected state.
72+
- Examples of good tests to emulate can be found in `src/visit_packages/banned_test.rs`.
73+
74+
## High-level architecture and data flow
75+
76+
Every syncpack command follows the same pattern:
77+
78+
1. [Create Context](#create-context)
79+
2. [Inspect Context](#inspect-context)
80+
3. [Run Command](#run-command)
81+
82+
### 1. Create context
83+
84+
This phase is read only and must happen in this order:
85+
86+
1. Nothing can happen until the command line arguments are known
87+
2. We can then use that information to locate the configuration file
88+
3. Only then can we know which package.json files to read
89+
4. When the package.json files are read, we can collect all of their versions and dependencies, and assign them to the appropriate version and semver groups defined in the user's configuration.
90+
91+
More information on each of these steps is as follows:
92+
93+
#### 1a. Parse CLI input
94+
95+
Determine which command and each CLI options were chosen and collect them into a `Cli` struct. Any options which were not provided are assigned default values.
96+
97+
- src/cli.rs is responsible for this.
98+
99+
#### 1b. Read config
100+
101+
1. Determine path to config file, first one wins:
102+
1. `--config` CLI Option
103+
2. Search in the root directory of the project for the first file whose name matches a specific list of config file names
104+
2. Once a config file path has been determined, read its contents. It must be one of:
105+
- TypeScript
106+
- JavaScript
107+
- YAML
108+
- JSON
109+
110+
- src/rcfile.rs is responsible for finding and reading the config file as an `Rcfile` struct.
111+
- src/config.rs defines a `Config` struct which combines the `Cli` and `Rcfile` structs into one.
112+
113+
#### 1c. Read package.json files
114+
115+
Now that we have a `Config` struct, we can use it to get paths to package.json files:
116+
117+
1. Find globs to package.json files, first one wins:
118+
1. `--source` CLI Options
119+
2. Syncpack config
120+
3. npm workspace config in the root package.json file
121+
4. pnpm workspace config in pnpm's config file
122+
5. Yarn workspace config in the root package.json file
123+
6. Lerna workspace config in Lerna's config file
124+
7. Syncpack defaults
125+
2. Resolve globs
126+
3. Read and Parse package.json files
127+
128+
- src/packages.rs is responsible for reading and parsing package.json files into a `Packages` struct containing each `PackageJson` struct for each package.json file.
129+
130+
#### 1d. Collect project dependencies
131+
132+
Now that we have a `Config` struct and `Packages` struct, we can collect the project's dependencies and assign them to version groups.
133+
134+
1. Partition the monorepo by versioning policy ("version groups")
135+
2. Load every "instance" (eg. @effect/schema in devDependencies of @effect/platform-node) of every "dependency" (eg. @effect/schema)
136+
1. Parse and tag its version specifier (eg. `"1.2.1"`, `"workspace:*"`, `"catalog:"`, `"git://github.com/user/repo.git"`)
137+
3. Assign every instance to one version group, first one wins
138+
139+
- src/context.rs is responsible for collecting project dependencies and assigning them to version groups. These are all returned in a `Context` struct alongside all other data we have collected such as the `Config` and `Packages` structs.
140+
141+
### 2. Inspect context
142+
143+
In terms of Rust's ownership and borrowing, the `Context` struct has ownership of all of the data related to the project being operated on. The `Context` struct is given in its entirety to either the `visit_packages` or `visit_formatting` functions.
144+
145+
#### `visit_packages`
146+
147+
Located at src/visit_packages.rs, this function will:
148+
149+
1. Visit each version group, each dependency within it, and each instance within that.
150+
2. Tag every instance with an instance of an `InstanceState` enum to describe if it is valid, or specifically how it is not.
151+
3. Return ownership of the `Context` struct.
152+
153+
#### `visit_formatting`
154+
155+
Located at src/visit_formatting.rs, this function will:
156+
157+
1. Visit each package.json file
158+
2. Tag every package.json file with multiple status codes describing if its formatting is valid, or specifically how it is not
159+
3. Return ownership of the `Context` struct
160+
161+
### 3. Run command
162+
163+
Finally, the command chosen by the user is passed the `Context` struct and has full ownership of it. Each command will perform its own side effects such as updating or synchronising the project's dependencies, or formatting the project's files. Every command must finish by returning and exit code of 1 or 0 to exit the program with.

0 commit comments

Comments
 (0)