Thanks for taking the time to contribute! 🎉 We've established a set of community guidelines to enable as many people as possible to contribute to and benefit from HASH. Please follow these when interacting with this repo.
If you'd like to make a significant change or re-architecture to this repository or any project within, please first open a discussion or create an issue to get feedback before spending too much time.
We also have a developer website at hash.dev, containing developer tutorials, guides and other resources.
This repository is HASH's public monorepo. It contains many different projects, the vast majority of which are open-source, in line with our commitment as a company. While each project has its own license, our contribution policies are consistent across this whole repository.
To ascertain the license and contributing policy for any given project, check out the LICENSE.md and CONTRIBUTING.md files in its root (or the license information in the file itself, which takes precedence where present).
All externally-contributed pull requests which modify code should, at minimum, be covered by one or more unit tests (colocated alongside the code). Other kinds of tests, including integration tests (found in the /tests directory) may also be required.
Please see the /tests README for more information about testing in HASH.
We use Yarn Workspaces to work with multiple packages in a single repository. Turborepo is used to cache script results and thus speed up their execution.
Authoring new packages
New local packages should follow these rules:
- Anything which is imported or consumed by something else belongs in
libs/and have apackage.json"name":- beginning with
@local/for non-published JavaScript dependencies - identical to their
npmname for published JavaScript dependencies - begin with
@rust/for Rust dependencies
- beginning with
- Things which are executed belong in
apps/, and are named `@apps/app-name - Packages which aren't published to
npmshould have"private": truein theirpackage.json - All TypeScript packages should be
"type": "module" - ESLint and TypeScript configuration should all extend the base configs (see existing examples in other packages). Don't modify or override anything unless necessary.
Read the next section to understand how to configure compilation for packages.
The package resolution setup is designed to meet two goals:
- Enable the local dependency graph for any application to be executed directly as TypeScript code during development, whilst
- Enabling it to be run as transpiled JavaScript in production.
This is achieved by maintaining two parallel exports definitions for each package:
- The
exportsfield inpackage.jsonshould point to the transpiled JavaScript (andtypesVersionsto the type definition files) - The
pathsmap in the base TSConfig should map the same import paths to their TypeScript source
During development (e.g. running yarn dev for an application), the paths override will be in effect, meaning that the source TypeScript
is being run directly, and modifying any dependent file in the repo will trigger a reload of the application (assuming tsx watch or equivalent is used).
For production builds, where they are created, a tsconfig.build.json in the package is used which overwrites the paths field in the root config,
meaning that the imports will resolve to the transpiled JavaScript (usually in a git-ignored dist/ folder).
Creating a production build should be done by running turbo run build, so that turbo takes care of building its dependencies first.
Running yarn build may not work as expected, as the built JavaScript for its dependencies may be (a) missing or (b) out of date.
If a bundler is used rather than tsc, the paths override needs to be translated into the appropriate configuration for the bundler.
For webpack, this is automated by adding the TsconfigPathsPlugin to the configuration's resolve field (search existing examples in repo).
New packages which are to be built as JavaScript, whether as an app or dependency, must follow these rules:
- They must have a
tsconfig.jsonwhich extends the base config and sets"module": "NodeNext"and"moduleResolution": "NodeNext" - Imports within a package must use relative imports and not the package's name (they will not be resolved when built otherwise)
- Relative imports within a package must have a
.jsfile extension (tscwill enforce this) - They must have a
tsconfig.build.jsonwhich overrides thepathsfield ("paths": {}) - They must have a
buildcommand which uses this file (typicallyrimraf ./dist/ && tsc -p tsconfig.build.json) - They must specify the paths exposed to consumers in
exportsandtypesVersionsinpackage.json, andpathsin the base TSConfig - They must have a
turbo.jsonwhich extends the root and specifies theoutputsfor caching (see existing examples)
Authoring patches
Patches to JavaScript packages are managed by Yarn, using the yarn patch command.
yarn patch <package>
# ➤ YN0000: Package <package>@npm:<version> got extracted with success!
# ➤ YN0000: You can now edit the following folder: /private/var/folders/lk/j93xz9pd7nqgd5_2wlyxmbh00000gp/T/xfs-df787c87/user
# ➤ YN0000: Once you are done run yarn patch-commit -s /private/var/folders/lk/j93xz9pd7nqgd5_2wlyxmbh00000gp/T/xfs-df787c87/user and Yarn will store a patchfile based on your changes.
# ➤ YN0000: Done in 0s 702msOnce you have completed your changes, run the command that was output to commit the patch:
yarn patch-commit -s /private/var/folders/lk/j93xz9pd7nqgd5_2wlyxmbh00000gp/T/xfs-df787c87/userThis will automatically create a patch file and put it into the .yarn/patches directory. If you're modifying a direct dependency in any workspace it will replace the package.json entry with a patch: reference to the patch file. In case you're patching an indirect dependency a new resolutions entry will be added to the root workspace package.json.
You will need to run yarn install for the patch to be installed and applied to the lockfile.
The procedure to modify an existing patch is very similar, but instead of running yarn patch <package> you will need to run yarn patch -u <package>. This will apply existing patches and then extract the package for you to modify.
yarn patch -u <package>
# ➤ YN0000: Package <package>@npm:<version> got extracted with success along with its current modifications!
# ➤ YN0000: You can now edit the following folder: /private/var/folders/lk/j93xz9pd7nqgd5_2wlyxmbh00000gp/T/xfs-d772c076/user
# ➤ YN0000: Once you are done run yarn patch-commit -s /private/var/folders/lk/j93xz9pd7nqgd5_2wlyxmbh00000gp/T/xfs-d772c076/user and Yarn will store a patchfile based on your changes.
# ➤ YN0000: Done in 1s 455msOnce you have completed your changes, run the command that was output to commit the patch:
yarn patch-commit -s /private/var/folders/lk/j93xz9pd7nqgd5_2wlyxmbh00000gp/T/xfs-d772c076/userThis will automatically update the patch file with your changes. Do not forget to run yarn install for the patch to be installed and applied to the lockfile.
Locate any patch: protocol entries in any workspace package.json and remove them. The entry will look somewhat similar to: patch:@changesets/assemble-release-plan@npm%3A5.2.4#~/.yarn/patches/@changesets-assemble-release-plan-npm-5.2.4-2920e4dc4c.patch, to remove the patch simply extract out the package (everything after the patch: and before #) and url-decode it and extract the version from it, so for the example it would be 5.2.4. You should not completely remove the line from the package.json.
In case the patch has been applied in the resolutions field you should also check if the resolution is made redundant. This is the case if the left side is the same as the right, e.g. "react@npm:18.2.0": "18.2.0" is redundant, same as "react@npm:18.2.0": "npm:18.2.0", or "react@npm:18.2.0": "npm:react@18.2.0", but "react": "npm:react@18.2.0" is not redundant.
A resolution specifier like
"react": "npm:react@18.2.0",is also correct. Simply meaning that the react package should be resolved to the npm packagereact@18.2.0, in fact"react": "18.2.0"is simply a shorthand for"react": "npm:react@18.2.0".If the left hand of a resolution has no version specifier it is assumed to be
npm:*, e.g."react": "18.2.0"is equivalent to"react@npm:*": "18.2.0"(replace react with version18.2.0regardless of the dependency requirement).For more examples see the yarn documentation
Then run yarn install to remove the patch.
You can then safely remove the patch file from .yarn/patches.
Yarn currently does not provide a command to remove a patch, so you will need to do this manually.
eslint `parserOptions.project`
There is a mismatch between VSCode's eslint plugin and the eslint cli tool. Specifically the option
parserOptions.project is not interpreted the same way as reported
in this issue. If VSCode complains about
a file not being "on the project" underlining an import statement, try to add the following to the
plugin's settings:
"eslint.workingDirectories": [
{ "directory": "apps/hash-api", "!cwd": true }
]Services not launched because ports busy
Make sure that ports 3000, 3333, 3838, 5001, 5432, 6379 and 9200 are not used by any other processes. You can test this by running:
lsof -n -i:PORT_NUMBERTODO: replace
lsofwithnpx ??? A,B,...Nfor a better DX. Suggestions welcome!
User registration fails (WSL users)
If you're running the application on Windows through Windows Subsystem for Linux (WSL) you might need to
change the registration url in apps/hash-external-services/docker-compose.yml from
http://host.docker.internal:5001/kratos-after-registration to http://{WSL_IP}:5001/kratos-after-registration,
where WSL_IP is the IP address you get by running:
wsl hostname -IThe kratos and kratos-migrate services will need to be restarted/rebuilt for the change to take effect.
These apply across all projects:
- Before undertaking any significant work, please share your proposal with us: we don't want you to invest your time on changes we are already working on ourselves, or have different plans for. You can suggest changes as a discussion if it's a feature proposal, or an issue if it's a bug you intend to fix. If you're only fixing a typo or making a minor change to documentation, don't worry about this step (just go ahead and open a Pull Request on this repository).
- When submitting a pull request, please fill out any sections of the provided template you feel able to. If you are unsure or don't feel a section is relevant, please say so.
- Always include a link to the issue or discussion proposing the change.
- Write tests to accompany your PR, or ask for help/guidance if this is a blocker.
- Make sure that your PR doesn’t break existing tests.
- The repository follows a set of linting rules. Many of them can be applied automatically by running
yarn installandyarn fix. - Sign our Contributor License Agreement at the CLA Assistant's prompting. (To learn more, read why we have a CLA)
- Once you have receive a pull request review, please bear the following in mind:
- reviewers may make suggestions for optional changes which are not required to get your code merged. It should be obvious which suggestions are optional, and which are required changes. If it is not obvious, ask for clarification.
- please do not resolve comment threads unless you opened them - leave it to the person who raised a comment to decide if any further discussion is required (GitHub will also automatically resolve any code suggestions you click 'commit' on). Comment threads may also be left open so that they can be linked to later.
- We perform automated linting and formatting checks on pull requests using GitHub Actions. As part of our Continuous Integration (CI) setup, when a pull request is created or updated, GitHub Actions will run a series of checks. This includes running
ESLint,TSC,Biome,Markdownlint,rustfmt, and a few other tools. Some checks may be skipped depending on the files that have been changed in the pull request. First-time contributors need to wait for a HASH maintainer to manually launch CI checks.
Existing issues can provide a good source of inspiration for potential contributions. The issue tags E-help-wanted and E-good-first-issue flag some of the lower-hanging fruit that are available for people (including first-time contributors) to work on, without necessarily requiring prior discussion. If you're willing to contribute, we'd love to have you!
There are a number of reasons why otherwise sound contributions might not find their way into the main branch of our repo. Ultimately, we reserve the right to reject PRs for any reason. In a bid to minimize wasted time and effort, here are some possible reasons for rejection:
- PRs that introduce new functionality without proper tests will not be accepted. You should write meaningful tests for your code.
- PRs that fail to pass tests will not be merged. If your PR doesn’t pass our Continuous Integration tests, it won’t be merged.
- PRs that duplicate functionality which already exist in HASH, but outside of the project you're introducing them in. For example, recreating functionality provided in one package directly within another.
- PRs that duplicate workstreams already under development at HASH may be rejected, or alternatively integrated into working branches other than those intended by the contributor. For more on these, see our public roadmap.
- PRs that add functionality that is only useful to a subset of users, which may increase maintenance overheads on the product. We know it can be frustrating when these sorts of PRs are rejected, and it can sometimes seem arbitrary. We’ll do our best to communicate our rationale clearly in such instances and are happy to talk it out. It's impossible to forecast all of the possible use-cases of a product or feature, and we try to keep an open posture towards such contributions.
- PRs that introduce architectural changes to the project (without prior discussion and agreement) will be rejected.
- PRs that don’t match the syntax, style and formatting of the project will be rejected. See: maintainability.
We're continuously headhunting for full-time roles. However, as outlined on our careers site, you can't apply to work at HASH.. Instead, we use the technology we've developed at HASH to scour the web for people we think would be a good fit to join us, and we reach out to them, rather than accept inbound applications. Nevertheless, a great (and guaranteed) way to get on our radar is to contribute to any of our open-source repositories, in particular this one. If and when a good fit opens up, we may invite you to interview. If your contact email address or other information aren't accessible via your profile, we invite you to tell us about yourself nevertheless.