Checks can created to standardize any existing configuration or workflow.

All checks have two required properties:

  1. message: The default message that will be shown for the check in the CLI and Commonality Studio
  2. validate: A function that returns a boolean or a promise that resolves to a boolean. This function is used to determine if the check passes or fails.

Check context

Checks are run within the context of each package directory. In the example below, we’re accessing the context object that contains metadata about the package to validate that it has a codeowner.

.commonality/has-codeowner.ts
import type { Check } from 'commonality';

export default {
  message: 'Package has least one codeowner',
  validate: (ctx) => ctx.codeowners.length > 0,
} satisfies Check;

Checking files

Commonality provides utilities like json and text that make it easy to read and write to files within checks.

Below is an example of a check that ensures a package has a build script with a value of tsc --build in it’s package.json.

.commonality/has-build-script.ts
import type { Check } from 'commonality';
import { json } from 'commonality';

export default {
  message: 'Package must have a "build" script',
  validate: async (ctx) => {
    return json(ctx.package.path, 'package.json').contains({
      scripts: { build: 'tsc --build' },
    });
  },
} satisfies Check;

The following output will be shown if the check fails:

✓ fail Package has a "build" script

Customizing messages

The default message can be overriden by returning an object from the validate function to provide additional context about a failed check.

We can modify the previous example to provide a more detailed error messages.

.commonality/has-build-script.ts
import type { Check } from 'commonality';
import { json, diff } from 'commonality';

export default {
  message: 'Package must have a "build" script',
  validate: async (ctx) => {
    const packageJson = await json(ctx.package.path, 'package.json').get();

    if (!packageJson) {
      return {
        message: 'package.json does not exist',
        path: 'package.json',
      };
    }

    if (packageJson?.scripts?.build !== 'tsc --build') {
      return {
        message:
          'package.json does not have a "build" script with value "tsc --build"',
        path: 'package.json',
        suggestion: diff(
          { scripts: packageJson.scripts },
          { scripts: { build: 'tsc --build' } },
        ),
      };
    }

    return true;
  },
} satisfies Check;

The diff utility can be used to suggest file edits needed to pass checks. The suggestion property is also useful for showing how a fix function will modify a file.

The diff function will return a pretty printed string where unique properties and values in the second argument appear in red and are prefixed with +.”

The following output will be shown if the check fails:

✓ warn package.json does not have a "build" script with value "tsc --build"
│      packages/pkg-a/package.json
│        Object {
│            "scripts": {
│              "lint": "eslint .",
│      +       "build": "tsc --build",
│        }

Composing checks

Since checks are just objects, you can create functions that return checks to make them re-usable in a variety of different scenarios.

Previously, we wrote a lot of code just to validate a single script existed, now we can re-use this check to validate the existence of any script.

.commonality/has-script.ts
import type { Check } from 'commonality';
import { json, diff } from 'commonality';

export const hasScript = (name: string, value: string): Check =>
  ({
    message: `Package must have a "${name}" script`,
    validate: async (ctx) => {
      const packageJson = await json(ctx.package.path, 'package.json').get();

      if (!packageJson) {
        return {
          message: 'package.json does not exist',
          path: 'package.json',
        };
      }

      if (packageJson?.scripts[name] !== value) {
        return {
          message: `package.json does not have a "${name}" script with value "${value}"`,
          path: 'package.json',
          suggestion: diff(
            { scripts: packageJson.scripts },
            { scripts: { [name]: value } },
          ),
        };
      }

      return true;
    },
  }) satisfies Check;

Auto-fixable checks

Check failures help enforce standardization at scale but can add friction to developer workflows. To solve this, you can make any check auto-fixable, making it easy for developers to adhere to standards without needing to manually fix issues.

To make a check fixable just add a fix property to your check. This function will only run against a package if the result of validate is not true.

Commonality provides utilities like json, and text to make it easy to read and write files within fix functions.

.commonality/has-script.ts
import type { Check } from 'commonality';
import { json, diff } from 'commonality';

export const hasScript = (name: string, value: string): Check =>
  ({
    // ...check logic,
    fix: async (ctx) => {
      await json(ctx.package.path, 'package.json').merge({
        scripts: {
          [name]: value,
        },
      });
    },
  }) satisfies Check;