---
description: This documentation is intended to provide comprehensive guidelines for contributing to the codebase.
files: */..
---
This documentation is intended to provide comprehensive guidelines for contributing to the codebase. It outlines the tools, environment, coding standards, and best practices that ensure our code remains clean, maintainable, and efficient.
- Write elegant, concise, and readable code
- Prefer
const
overlet
(never usevar
) - Use kebab-case for file and directory names
- Use clear, descriptive names for variables, functions, and components
- Always use ESM
import
andexport
(never use CJSrequire
)- File imports should end with
.js
(NOT.ts
or.tsx
). Module or subpath imports don't need the extension. - GOOD examples:
import { Foo } from './foo.js';
import { type Route } from './+types/root.js';
import zod from 'zod';
import { logger } from '@dx/core-utils';
- BAD examples:
import { Foo } from './foo';
(missing.js
extension)import { type Route } from './+types/root';
(missing.js
extension)import { Foo } from './foo.ts';
(should be.js
not.ts
)import { AnInternalClass } from 'zod/dist/internals';
(shouldn't import internals)import { logger } from '@dx/core-utils/src/logger.js';
(shouldn't import source files or use.js
for packages)
- File imports should end with
- Always prefer named exports over default exports
- It's OK to use a default export in
.tsx
files (like Remix/React Router routes) - It's OK to use default exports in the CLI because it is required by oclif
- It's OK to use a default export in
All packages must follow these package.json
rules:
type
must be set tomodule
exports
should be used instead ofmain
ormodule
- ALL source code should be exported from the root (
.
) path - Any test-specific code should be exported from a
test
path - Exports should use the
.ts
extension - Good example:
"exports": "./src/index.ts"
"exports": { ".": "./src/index.ts", "./test": "./src/test/index.ts" }
- BAD example:
"main": "./src/index.ts"
(don't usemain
ormodule
)"exports": "./src/index.js"
(don't use.js
for exports)"exports": { ".": "./src/index.ts", "./test": "./src/test/index.ts", "./test/mocks": "./src/test/mocks.ts" }
(don't export multiple paths: "./test" and "./test/mocks")
- ALL source code should be exported from the root (
types
should be used instead oftypings
- NEVER use TypeScript path resolution (
paths
intsconfig.json
)- Use ESM subpath imports instead if this is desired (eg:
"imports": { "#/ui": "./ui/index.ts" }
)
- Use ESM subpath imports instead if this is desired (eg:
All packages must be placed in packages/<package-name>/
. Each package should have the following:
- A
src/
directory containing the core source code. - A single entrypoint in
src/index.ts
that exports the package's public API. - Place test helpers, utilities, or mocks in
src/test/
with a single entrypoint insrc/test/index.ts
. - Tests should be placed beside source code like
src/my-file.ts
andsrc/my-file.test.ts
(NOTsrc/test/my-file.test.ts
ortest/my-file.test.ts
). - A standard set of config files (like
package.json
andtsconfig.json
) to ensure consistency. - The
package.json
must define an "exports" object with"." : "./src/index.ts"
and optionally"./test": "./src/test/index.ts"
, ensuring clearly defined entrypoints for production and test.
Example:
packages/foo/package.json
packages/foo/tsconfig.json
packages/foo/src/index.ts
packages/foo/src/bar.ts
packages/foo/src/bar.test.ts
packages/foo/test/index.ts
packages/foo/test/setup.ts
- Eg: `export async function loader() {` and `export async function action() {` don't need return types
- NEVER use
any
/unknown
or cast values like(value as any)
orvalue!
in TypeScript outside of test files e.g.*.test.ts
or test fixtures e.g.**/test-data.ts
. - Don't rely on
typeof
,ReturnType<>
,Awaited<>
, etc for complex type inference (it's ok for simple types) - Use
as const
for better type inference - Use type guards to narrow types in conditional blocks
- Create custom types for complex data structures used throughout the application
- Utilize TypeScript's utility types (e.g.,
Partial
,Pick
,Omit
) to manipulate existing types - Never use
React.FC
. Use a function declaration instead - Functions should accept an object parameter (like
args
orprops
) instead of multiple parameters- Good examples:
function myFunction(args: { foo: boolean; bar: string }) {} function VideoPlayer(props: { sid: string }) {}
- Bad examples:
function myFunction(foo: boolean, bar: string, baz: number) {}
- Good examples:
- Arguments should be destructured in the function body, not the function definition. It's ok for React components to destructure props in the function definition.
- Good example:
function myFunction(args: { foo: boolean; bar: string }) { const { foo, bar } = args; }
- Bad example:
function myFunction({ foo, bar }: { foo: boolean; bar: string });
- Good example:
- Zod should be used to parse untrusted data, but not for data that is trusted like function arguments
- Zod unions should always be used instead of enums
- For example, this union
z.union([z.literal('youtube'), z.literal('spotify')])
is better than this enumz.enum(['youtube', 'spotify'])
- For example, this union
- Promises (and
async
functions which implicitly create Promises) must always be properly handled, either via:- Using
await
to wait for the Promise to resolve successfully - Using
.then
or.catch
to handle Promise resolution - Returning a Promise to a calling function which itself has to handle the Promise. If you can't infer this from the available context, add a warning that the promise may not be handled properly.
- Using
Server-side code is written using Bun. Use native Bun (and the Node APIs it supports) when possible.
- Use standard lib modules like
Bun.file
,$
shell commands,Glob
, etc - Prefer standard lib modules over third-party alternatives
- Utilize the
node:
protocol when importing Node.js modules (e.g.,import fs from 'node:fs/promises'
) - Prefer the promise-based APIs over Node's legacy sync methods
- Use
Error
objects for operational errors, and consider extendingBaseError
for specific error types - Use environment variables for configuration and secrets (avoid hardcoding sensitive information)
Always prefer using standard web APIs like fetch
, WebSocket
, and ReadableStream
when possible. Avoid Node.js-specific modules (like Buffer
) or redundant libraries (like node-fetch
).
- Prefer the
fetch
API for making HTTP requests instead of Node.js modules likehttp
orhttps
- Use the native
fetch
API instead ofnode-fetch
or polyfilledcross-fetch
- Use the
ky
library for HTTP requests instead ofaxios
orsuperagent
- Use the native
- Use the WHATWG
URL
andURLSearchParams
classes instead of the Node.jsurl
module - Use
Request
andResponse
objects from the Fetch API instead of Node.js-specific request and response objects - Utilize
Blob
andFile
APIs for handling binary data when possible - Use
TextEncoder
andTextDecoder
for encoding and decoding text
- Prefer
async
/await
over.then()
and.catch()
- Always handle errors correctly (eg:
try
/catch
or.catch()
) - Implement React Error Boundaries to catch and handle errors in component trees
- Use Remix Error Boundaries for handling errors in Remix routes
- Avoid swallowing errors silently; always log or handle caught errors appropriately
Comments should be used to document and explain code. They should complement the use of descriptive variable and function names and type declarations.
- Add comments to explain complex sections of code
- Add comments that will improve the autocompletion preview in IDEs (eg: functions and types)
- Don't add comments that just reword symbol names or repeat type declarations
- Use JSDoc formatting for comments (not TSDoc or inline comments)
- Common JSDoc tags to use:
@param
: define a parameter on a function@returns
: define the return type of a function@throws
: define an exception that can be thrown by a function@example
: provide an example of how to use a function or class@deprecated
: mark a function or class as deprecated@see
: define an external reference related to the symbol{@link}
: create a link to the namepath or URL@TODO
: mark a task that needs to be completed
- DO NOT use the following tags:
@file
,@async
- Always use an instance of the
pino
logger from@dx/core-utils
for logging. - NEVER use
console
orpino
directly.
- All unit tests should Vitest
- DO NOT attempt to install or use other testing libraries like Jest
- Write unit tests for individual components and utility functions
- Test files should be named
[target].test.ts
and placed in the same directory as the code they are testing (NOT a separate directory)- Good example:
src/my-file.ts
andsrc/my-file.test.ts
- Bad example:
src/my-file.ts
andsrc/test/my-file.test.ts
ortest/my-file.test.ts
orsrc/__tests__/my-file.test.ts
- Good example:
- Tests should be run with
bun run test
(you can't do justbun test
) - It's acceptable to use
any
/unknown
in test files (such as*.test.ts
) or test fixtures (like**/test-data.ts
) to facilitate mocking or stubbing external modules or partial function arguments, referencing the usage guidelines in the TypeScript section. However, do not useany
orunknown
in production code.
- Test critical business logic and edge cases
- Don't add tests for trivial code or just to increase test coverage
- Don't make tests too brittle or flaky by relying on implementation details
- Branch names should start with
bot-123-the-issue-name
wherebot-123
is the Linear issue identifier associated with the branch andthe-issue-name
is the name of the issue (or a shortened version) - Commit messages should be short, concise, and descriptive
- Pull requests should have a short descriptive title and a description that explains the changes made
- PR titles and descriptions must accurately reflect the changes made and should be kept up to date if the PR changes significantly
- Linear identifiers (eg: BOT-123) should not be included in PR titles (they should be in the branch name)
- Proper capitalization and punctuation should always be used in commit messages and pull request titles and descriptions
- When a new package (
/packages/<package-name>
) or app/service (/apps/<app-name>
) is added to the repository, a corresponding entry should be added to the.github/CODEOWNERS
and thereadme.md
file in the root directory.