# Contributing to Endo ## Initial setup ```sh git clone git@github.com:endojs/endo.git cd endo yarn ``` Endo is a yarn workspaces repository. Running yarn in the root will install and hoist most dependencies up to the root `node_modules`. Note: running yarn `--ignore-scripts` will not complete the setup of SES. ### Action pinning GitHub Actions are pinned to commit SHAs. Run `node scripts/update-action-pins.mjs` to refresh patch/minor pins. Run `node scripts/update-action-pins.mjs --major` for major upgrades. Use `--min-age-days 0` to bypass the default 5-day age gate (for zero-day fixes). The updater reads the `# vX` comment on each `uses:` line. If no version comment exists, it infers the latest tag for that action. CI enforces pinning with `node scripts/update-action-pins.mjs --check-pins`. If this check fails, run the updater and commit the resulting changes. ## Validation Continuous Integration is comprehensive. Many issues can be anticipated locally by running: - `yarn` - `yarn format` for Prettier code formatting - `yarn docs`, because Typedoc has a holistic view of TypeScript definitions that lint in individual packages may not catch. - `yarn pack:all`: Packs every public workspace into `dist/*.tgz` via `ts-node-pack`, exercising the same code path as a real release. Uncovers issues with type definition generation, and (because each tarball is built from a temp staging copy rather than the source tree) ensures that type resolution in dependent packages works from the published state rather than whatever happens to be on the local filesystem. - `yarn smoketest:publish`: Boots a disposable Verdaccio registry, runs `yarn release:npm` against it, and installs a representative subset of the published tarballs into a fresh consumer under SES lockdown. This is what CI runs, and is the most thorough local check of the publish flow. ## Creating a new package Run [scripts/create-package.sh](./scripts/create-package.sh) $name, then update the resulting README.md, package.json (specifically setting `description` and [if appropriate] removing `"private": false`), index.js, and index.test.js files. ## Updating Workspace Dependencies If you've added, removed, or changed a dependency between workspaces, you'll want to regenerate the composite TypeScript build configs. Run `yarn build:types:gen` to regenerate the composite TypeScript build. See [TypeScript declarations](#typescript-declarations) for more details. ## Coding Style - Prefer `/** @import */` over dynamic `import()` in JSDoc type annotations. Use a top-level `/** @import {Foo} from 'bar' */` comment instead of inline `{import('bar').Foo}` in `@param`, `@type`, or `@returns` tags. ## Markdown Style Guide When writing Markdown documentation: - Wrap lines at 80 to 100 columns for readability in terminal editors. - Start each sentence on a new line. This ensures changes in one sentence do not cascade into the next in diffs. - Starting sentences on new lines also obviates any question of whether to use one or two spaces after a period. Example: ```markdown The Endo stack provides a layered solution through four packages. Each package has a specific role in enabling safe message passing. Together, they form the foundation of distributed computing. ``` This convention applies to all documentation files including README.md files, guides in the `docs/` directory, and package-specific documentation. **Exception:** Release notes in pull request descriptions and GitHub releases should use long lines (paragraphs joined without manual wrapping), as GitHub uses a different Markdown flavor for those contexts. ## TypeScript declarations TypeScript `.d.ts` declarations are generated as part of the publishing process, with each package's declarations created individually. However, when you need to link Endo against another project, you'll need to build the declarations for all of the relevant dependencies. To simplify this process, you can use the **composite TypeScript build**: ```sh yarn build:types # one-shot declaration build for all packages yarn build:types:watch # incremental watch (expect a ~10–30s cold start) ``` The `tsconfig.composite.json` files scattered across packages (and the root `tsconfig.composite.json`) are **generated** — do not edit them by hand. After adding, removing, or changing runtime dependencies of any workspace, run: ```sh yarn build:types:gen ``` CI checks that these files are in sync with the generator output. If the composite build complains about `TS5055` "would overwrite input file" errors, you have stale `.d.ts` outputs from a previous per-package build. Run `yarn build:types --clean` once to reset, then build normally. ## Rebuilding `ses` Changes to `ses` require a `yarn build` to be reflected in any dependency where `import 'ses';` appears. Use `yarn build` under `packages/ses` to refresh the build. Everything else is wired up thanks to workspaces, so no need to run installs in other packages. ## Using Changesets Endo uses [Changesets](https://github.com/changesets/changesets) to manage versioning and changelogs. A **changeset** is a Markdown file in the `.changeset/` directory that captures: - Which packages need to be released - The [semver](https://semver.org/) bump type (major, minor, or patch) - A changelog entry describing the change Changesets are "intents to release" that accumulate until maintainers cut a release. The changeset files themselves are temporary—when a release is cut, they are consumed and removed from version control, with their contents incorporated into each package's `CHANGELOG.md`. This approach automates version bumping across the monorepo (including internal dependency updates) and generates changelogs automatically, while keeping humans in the loop to review and edit release notes before publishing. Contributors make versioning decisions _at contribution time_ (i.e. _in the PR itself_), when the context is fresh. ### Adding a Changeset When your PR includes changes that should be released, add a changeset: 1. Run `yarn changeset` 2. Select the affected packages (use arrow keys to navigate, space to select, enter to confirm) 3. Choose the appropriate bump type for each package 4. Write a clear, complete description of the change—what changed, why, and any migration notes if needed. Consider security and performance implications. This text will appear verbatim in `CHANGELOG.md`, so make it useful for consumers of the package. 5. Commit the generated `.changeset/*.md` file with your PR > Do not be alarmed by the unique, auto-generated names of the changeset files! > This is expected. ### Editing a Changeset You typically want to do this _before_ your PR lands, but all you need to do is find the changeset file in `.changeset/`, edit it, and commit. ### Do I Need a Changeset? Generally, you need a changeset only if your PR contains **user-facing changes** to a package—bug fixes, new features, breaking changes, or other modifications that consumers of the package would notice. You typically **do not** need a changeset for: - Documentation-only changes - Test additions or fixes - CI/build configuration changes - Refactoring that doesn't change public behavior The helpful [changeset-bot](https://github.com/apps/changeset-bot) will comment on your PR if no changeset is present, but this won't block merging. > [!TIP] > > When in doubt, ask a friendly maintainer. Avoid the unfriendly ones. ### Release Workflow (For Maintainers) The release process works as follows: 1. As changesets accumulate on `master`, the `changesets/action` GitHub Action (see [.github/workflows/release.yml](.github/workflows/release.yml)) automatically creates and maintains a **Release PR** titled "Version Packages" 2. This Release PR applies all pending changesets; it bumps versions, updates `CHANGELOG.md` files, and deletes the consumed changeset files 3. Maintainers review the Release PR to verify versions and changelog entries look correct. Maintainer will approve and/or merge when ready. Merging will also create tags and GitHub Releases for each affected package. 4. After merging the Release PR, pull `master` and run `yarn release:npm` to publish the updated packages to npm. ## Running CI locally You can use [act](https://github.com/nektos/act) to run the CI locally. You'll need to make some changes, however, because Yarn will attempt to use the global cache, which is not available to the container. 1. Any job using `actions/setup-node` will need to have `cache: yarn` removed from the `with` section, as this overrides the behavior in `yarnrc.yml`. 2. Edit `yarnrc.yml` and add these following lines: ```yaml enableGlobalCache: false enableMirror: false globalFolder: .yarn/berry ``` By default, `act` will pull the `catthehacker/ubuntu:full-latest` image on every run. This can be slow, so you can pass `--pull=false` to `act` and only omit when you wish to update the image. ### Running on macOS If you're running `act` on macOS, you'll need to pass `--container-architecture linux/amd64` to the `act` command.