format-package
    TypeScript icon, indicating that this package has built-in type declarations

    6.1.0 • Public • Published

    Format Package codecov

    Greenkeeper badge

    yarn add -D format-package prettier@^2.0.0
    Table of Contents

    Getting started

    package.json files are notorious for becoming large and overwhelming. When working in teams, this can make it hard to know how to structure the file or where to find certain configurations or scripts - especially since everyone has their own preferences.

    And manually going through and organizing the file seems as painful as doing formatting checks by hand in PRs.

    format-package solves these problems by allowing the file to be sorted and formatted in a consistent and automated manner.

    It is configurable to allow teams to pick the order that work best for them, and includes transformations that can be applied to a value in the package.json (such as logically sorting scripts).

    Requirements

    • node: >=10

    Command Line

    This module provides a simple CLI:

    ./node_modules/.bin/format-package --help

    If combined with Yarn, it can be run as:

    yarn format-package --help

    It can also be used as part of an npm script:

    {
      "scripts": {
        "format:pkg": "format-package -w"
      },
      "devDependencies": {
        "format-package": "latest"
      }
    }
    yarn format:pkg

    Module

    The module exports an asynchronous format function that takes the contents of package.json and a set of options.

    It returns a newly sorted and formatted package.json string.

    #!/usr/bin/env node
     
    const fs = require('fs');
    const format = require('format-package');
    const pkg = require('<path-to-package.json>');
     
    async function formatPackage(pkg, filePath) {
      const formattedPkg = await format(pkg, options);
     
      return new Promise((resolve, reject) => {
        fs.writeFile('<path-to-package.json>', formattedPkg, (error) => {
          if (error) {
            reject(error);
            return;
          }
     
          resolve();
        });
      });
    }
     
    formatPackage(pkg).catch((error) => {
      console.error(error);
      process.exit(1);
    });

    Options

    There are three options:

    • order (Array)
    • transformations (Object)
    • formatter (Function)

    Options are expected to be passed in as a keyed object:

    const format = require('format-package');
    const pkg = require('<path-to-package.json>');
     
    const options = {
      order: [],
      transformations: {},
      formatter: (v) => v.toString(),
    };
     
    format(pkg, options).then((formattedPkg) => console.log(formattedPkg));

    Defaults

    The format-package module also exports its defaults to help with configuration:

    const format = require('format-package');
    const pkg = require('<path-to-package.json>');
     
    const {
      defaults: { order: defaultOrder },
    } = format;
     
    // Move `...rest` to the bottom of the default order list
    const restIndex = defaultOrder.indexOf(sort, '...rest');
    let order = [...defaultOrder];
     
    if (restIndex !== -1) {
      order.splice(restIndex, 1);
    }
     
    order.push('...rest');
     
    format(pkg, {
      order,
    }).then((formattedPkg) => console.log(formattedPkg));

    order

    The most meaningful part of this utility is an ordered array of keys that are used to order the contents of package.json.

    The default order is:

    [
      "name",
      "version",
      "description",
      "license",
      "private",
      "engines",
      "os",
      "cpu",
      "repository",
      "bugs",
      "homepage",
      "author",
      "contributors",
      "keywords",
      "bin",
      "man",
      "main",
      "module",
      "browser",
      "files",
      "directories",
      "workspaces",
      "config",
      "publishConfig",
      "scripts",
      "husky",
      "lint-staged",
      "...rest",
      "dependencies",
      "peerDependencies",
      "devDependencies",
      "optionalDependencies",
      "bundledDependencies"
    ]

    The ...rest value is considered special. It marks the location where the remaining package.json keys that are not found in this ordered list will be placed in alphabetical order.

    Note: if a ...rest string is not found in the provided order list, it will be appended to the bottom.

    const format = require('format-package');
    const pkg = require('<path-to-package.json>');
    const options = {
      order: [
        'name',
        'version',
        'scripts',
        'jest',
        'dependencies',
        'peerDependencies',
        'devDependencies',
        'optionalDependencies',
        '...rest',
      ],
    };
     
    format(pkg, options).then((formattedPkg) =>
      Object.keys(JSON.parse(formattedPkg))
    );
    /*
    [ 'name',
    'version',
    'scripts',
    'dependencies',
    'devDependencies',
    'optionalDependencies',
    'author',
    'bin',
    'bugs',
    'description',
    'engines',
    'homepage',
    'license',
    'lint-staged',
    'main',
    'repository' ]
    */

    transformations

    transformations is a map of package.json keys and corresponding synchronous or asynchronous functions that take a key and value and return a key and value to be written to package.json.

    The default transformations map has a scripts method that sorts the scripts in a sensical way using 'sort-scripts'.

    import * as sortScripts from 'sort-scripts';
    import { Transformations } from '../transform';
     
    const transformations: Transformations = {
      scripts(key, prevValue) {
        const nextValue = sortScripts(prevValue).reduce(
          (obj, [name, value]) => ({ ...obj, [name]: value }),
          {}
        );
     
        return [key, nextValue];
      },
    };
     
    export { transformations as default };

    Notes: Any package.json property that is an object and does not have a defined transformation will be alphabetically sorted.

    Additional transformations or overrides can be passed in:

    const format = require('format-package');
    const pkg = require('<path-to-package.json>');
     
    const options = {
      transformations: {
        // This reverses all the keys in dependencies
        dependencies(key, value) {
          return [
            key,
            Object.keys(value)
              .sort()
              .reverse()
              .reduce((obj, k) => {
                obj[k] = value[k];
                return obj;
              }, {}),
          ];
        },
      },
    };
     
    format(pkg, options);

    formatter

    The formatter is the function used to prepare the contents before being returned.

    A custom synchronous or asynchronous formatter can be supplied that will process the resulting package contents.

    By default, the formatter will try to use prettier if it is installed, and will fallback to JSON.stringify if the peer dependency is not found:

    import * as path from 'path';
     
    async function formatter(obj: any, filePath?: string): Promise<string> {
      const content = JSON.stringify(obj, null, 2);
     
      // Try to use prettier if it can be imported,
      // otherwise add a new line at the end
      let prettier;
      try {
        prettier = require('prettier');
      } catch (error) {
        return `${content}\n`;
      }
     
      let config = await prettier.resolveConfig(
        filePath ? path.dirname(filePath) : process.cwd()
      );
      if (!config) {
        config = {};
      }
     
      return prettier.format(content, {
        ...config,
        parser: 'json',
        printWidth: 0,
      });
    }
     
    export { formatter as default };

    CLI

    The CLI accepts a series of files or globs to be formatted, as well as a set of options.

    yarn format-package "**/package.json"

    Options can also be passed as environment variables and are used in the following order of precedence:

    1. Command line options
    2. Env vars
    FORMAT_PACKAGE_VERBOSE=true
    Option Alias ENV Description Default
    config c FORMAT_PACKAGE_CONFIG Path to a custom configuration to use. This configuration can be JavaScript, JSON, or any other format that your configuration of node can require. The default configuration can be found here.
    write w FORMAT_PACKAGE_WRITE Write the output to the location of the found package.json false
    ignore i FORMAT_PACKAGE_IGNORE Patterns for ignoring matching files ['**/node_modules/**']
    verbose v FORMAT_PACKAGE_VERBOSE Print the output of the formatting false
    help h Print help menu

    You can also see the available options in the terminal by running:

    yarn format-package --help

    Configuration Files

    format-package will search for a valid configuration file in the following order of precedence.

    1. If the option --config [path | module id] or a FORMAT_PACKAGE_CONFIG environment variable is provided:

       a. check if the value resolves to a module id, else
       b. check if value resolves to an existing path
      

      If either a or b are valid configuration, then use the configuration, else return the default configuration.

    If neither a --config or a FORMAT_PACKAGE_CONFIG environment variable is provided, search for configurations in the following places:

    1. format-package.js
    2. format-package.yaml or format-package.yml
    3. format-package.json
    4. format-package.config.js
    5. format-package.config.yaml or format-package.config.yml
    6. format-package property in package.json

    If there are no configuration from the above search places, format-package will move up one directory level and try again.

    format-package will continue searching until arriving at the home directory.

    If no configuration is found, then the default configuration is used.

    Configuration Schema

    const JoiConfigSchema = Joi.object({
      order: Joi.array().min(1).unique().optional(),
      transformations: Joi.object().optional(),
      formatter: Joi.func().optional(),
    });

    Configuration Examples

    Supported configuration formats: JSON, JSON5, JS, and YAML.

    with package.json

    {
      "order": ["name", "version"]
    }

    with format-package.json

    {
      "order": ["name", "description", "..."]
    }

    with format-package.js or format-package.config.js

    module.exports = {
      order: ['name', 'description', '...'],
    };

    with format-package.{yml,yaml}, format-package.config.{yml,yaml}

    order:
      - name
      - description
      - ...

    Integrating

    An effective integration of this plugin could look like this:

    {
      "lint-staged": {
        "package.json": ["format-package -w", "git add"]
      },
      "husky": {
        "hooks": {
          "pre-commit": "lint-staged"
        }
      },
      "devDependencies": {
        "format-package": "latest",
        "husky": "latest",
        "lint-staged": "latest"
      }
    }

    This configuration combines:

    Together, these modules ensure the package.json file is automatically formatted if it changes and provides an easy package.json script for manual use:

    yarn format:pkg

    Development

    Clone the repo and install dependencies to get started with development:

    git clone git@github.com:camacho/format-package.git
    yarn install

    Scripts

    These scripts can be run via yarn or npm run:

    Script Description
    prebuild clean the build directory to prevent dangling artifacts
    build transpile TypeScript files in the src directory into JavaScript files in the build directory
    postbuild make build/cli/index.js file executable
    clean remove build and node_modules directories
    clean-build remove build directory
    clean-packages rimraf ./node_modules
    dev run ts-node-dev with src/cli/index.ts entrypoint
    docs update auto-generated-content blocks in Markdown files
    format format application code
    format-docs format documentation
    format-package format package.json files
    format-source format source content using prettier
    gamut run the full gamut of checks - reset environment, generate docs, format and lint code, run tests, and build
    lint lint the application code
    prepublishOnly make sure the package is in good state before publishing
    reset clean build directory and reset the node_modules dependencies
    start run the cli from build directory
    test run tests for the application
    type-check check source types
    watch run ts-node-dev with provided entrypoint, e.g. yarn watch src/cli/index.ts

    Note - This repo depends on husky and lint-staged to automatically format code and update documents. If these commands are not run, code changes will most likely fail.

    Install

    npm i format-package

    DownloadsWeekly Downloads

    2,857

    Version

    6.1.0

    License

    MIT

    Unpacked Size

    346 kB

    Total Files

    55

    Last publish

    Collaborators

    • camacho