|
| 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