Skip to content

Latest commit

 

History

History
703 lines (503 loc) · 66.1 KB

CONTRIBUTING.md

File metadata and controls

703 lines (503 loc) · 66.1 KB

Contributing

Thank you for reading the contributing guidelines for this repository. Since this repository is maintained primarily by a solo developer, this file also serves as a single source of truth style and usage guide for the repository. Amongst other things, this includes information about releasing new versions, code style, repository configuration, build scripts etc., version control standards, and directory structure. If you are contributing to this repository, and part or all of your changes are not covered by the notes in this file, then please try to either match your changes to the existing code style of this repository, or follow general best practices for the language.

Table of Contents

Pull Requests

If you would like to make changes or additions to this repository, please make a pull request. Your pull request should follow the pull request template found when submitting your changes, and for extra brownie points, your changes should adhere to the contributing guidelines in this file, particularly the sections on git, code style, and script format. See the following list for a summary of steps required for making a successful pull request (where "should" is used to describe a step, that step is not strictly required, but its completion will make merging the changes faster):

  1. Fork repository and pull to your local machine
  2. Make a new branch based on the notes in the Branch Naming section
  3. Checkout new branch and make all changes on that branch
    1. Changes should follow contributing notes in the code style and script format sections
    2. Commits should follow contributing notes in the git section
    3. Changes should pass all tests in the repository test suite
    4. Changes should update any required documentation or tests
    5. Changes on the new branch must not be merged into the main branch before making a pull request
  4. Make a pull request which must complete the pull request template provided for this repository

Please note that this repository is maintained primarily by a solo developer. As such, it may take longer than expected for pull requests to be reviewed. If your changes are important and/or time critical for your personal use case, please consider forking this repository and maintaining that fork instead.

Issues

If you encounter an issue whilst using this repository or any software released by it, please submit an issue using the issue tracker for this repository. Issues for this repository are tracked using the default github issue tracker (see here for more information on managing repository issues on github).

Git

This repository uses git for version control. Please consult the following subheadings for information on the preferred git commit format used in this repository, and for other guidelines to consider when making a pull request with changes you have made to this repository. The following notes form the git "best practices" for this repository.

Lightweight Commit Format

This repository uses the lightweight commit format for formatting git commits. The lightweight commit format is largely based on the conventional commit format with modifications to allow for terser commit titles. Where possible, contributors should familiarise themselves with the lightweight commit format and use it to format their commits, however the format is not a strict requirement for making contributions.

If you are not intending on contributing to this repository regularly, then feel free to use commits using another standard such as the conventional commit format mentioned above or these commit message guidelines, making sure that your commits meet the following minimum standards:

  1. Commits must be formatted consistently for all your changes
  2. Any prose title description must start with an imperative form verb, for example Add feature or Fix bug
  3. Any prose title description should start with a capital letter, and should not end with a period

Branch Naming

If you are contributing to this repository, and are intending on making a pull request, please first make a new branch. Any changes should be made on this new branch. All branches in this repository should be named according to the following rules:

  1. Words in each part of the branch name must be separated by a - (i.e. each part of the branch name must be in lower-kebab-case)
  2. Each part of the branch name must be separated by a /
  3. Branch names must be formed of some or all of the following parts in order:
    1. Type of changes in the branch (see table below for options)
    2. Reference number for branch if applicable (for instance issue number if branch resolves an issue)
    3. Author name or reference (for example github username or name given when calling git config --get user.name)
    4. Brief branch description

For example feature/123/jimbob3806/implement-slider

Branch Type Description
feature Branch which implements a new or changed feature.
bug Branch which fixes a reported or observed bug.
performance Branch which fixes a performance issue.
security Branch which fixes a security vulnerability.
dependency Branch which updates a dependency, and changes code as required depending on the new version of the dependency.
refactor Branch which refactors a part of the codebase (predominantly changes which do not change how code functions).
rewrite Branch which rewrites a part of the codebase (significant changes to how the code functions).
remove Branch which removes a section of the codebase, and changes code as required to facilitate the removal.
docs Branch which only updates documentation.
test Branch which only updates the test suite.
other Any other Branch.

Developer Environments

Developer environment files such as a workspace ./.vscode directory must not be committed to the repository. Additionally the .gitignore in the root of the repository should not be responsible for excluding developer environments. This is in the interest of preventing one-off entries in the .gitignore file for uncommon developer environment files etc.

Preferably developers contributing to this repository should avoid the use of workspace configuration files for their developer environment, favouring instead a global user configuration file such as a .vimrc file. If you are contributing to this repository and have to use workspace configuration files for your developer environment, please ensure that you follow the following steps:

  1. Make new branch for all your changes
  2. Before your first commit on new branch, update the .gitignore file to ensure that no configuration files appear in the git history, and commit these changes with a commit title of cfg~ Modify .gitignore
  3. Make your changes as required on your new branch
  4. After your last commit, remove your workspace configuration files, revert the .gitignore file, and commit these changes with a commit title of cfg~ Modify .gitignore

Code Style

Primarily this repository uses a custom configuration of ESLint to enforce code style. Other elements of code style and formatting not enforced by ESLint are listed in the subheadings below. For any specific code style or formatting which is not enforced by ESLint and not listed below, please try to match the general code style of the repository, or follow best practices for the language.

ESLint

This repository uses ESLint as the primary tool for the standardisation of JavaScript and TypeScript source code files. Note that ESLint is primarily responsible for source code formatting within this repository, although some linting rules are also intended for preventing code which may give rise to logical errors.

Configuration

The custom ESLint configuration for the source code files in this repository can be found in the .eslintrc and .eslintignore files. Both files are well documented with links to existing ESLint documentation for more information on the given configuration options. Particularly for layout rules, no justification for configuration is provided, as each choice is usually driven by maintainer preference rather than any objective benefits associated with that rule. Additionally the repository package.json file specifies scripts to run ESLint. To run ESLint manually across the source code files in this repository, please see the npm scripts reference, or run npm run lint:check. Similarly to fix linting errors which can be automatically resolved by ESLint, please see the npm scripts reference, or run npm run lint:fix.

Overrides

Any ESLint rule may be overridden with an appropriate directive. Due to the inclusion of the eslint comments plugin, the use of such directives within source code files in this repository is restricted as follows:

  • Any eslint-disable directive which is not scoped to a single line must have a matching eslint-enable directive to re-enable the rule
  • Duplicate eslint-disable directives are not permitted if those lines of code already have the given rule disabled
  • eslint-disable directive must specify one or more rule names which should be disabled (universal directives are not permitted)
  • All eslint-disable directives must be used (i.e. at least one error must be prevented by any given directive)
  • All eslint-disable directives must have a description explaining why the directive is required

Additionally, although not enforced by the eslint comments plugin,

Please see the following code block for examples of preferred methods of overriding ESLint rules:

// This method is preferred when the rule needs to be off for multiple lines.
/* eslint-disable id-match -- Invalid IDs. */
let bad_name_A
let bad_name_B
/* eslint-enable id-match -- Close disable-enable pair. */

// This method is preferred when a single line needs to be ignored.
/* eslint-disable-next-line id-match  -- Invalid ID. */
let bad_name_A

// This method is allowed but not preferred.
let bad_name_A /* eslint-disable-line id-match  -- Invalid ID. */

Ternaries

The preferred format for ternaries in this repository is the Haskell-like ternary format which mimics a Haskell guard statement in syntax, replacing the | (pipe) guard character with the JavaScript ternary : (colon), and replacing the = (equals) assignment operator with the JavaScript ternary ? (question mark). This is the same format suggested under the conditional chains section of the mdn page on ternaries.

In its multiline ternary rule configuration, ESLint does not currently support this exact multiline ternary format where the ternary : operator begins each line. The ESLint configuration for this repository therefore does not strictly enforce the desired ternary format, and instead uses a mix of indentation rules etc. which allow the desired ternary format but cannot necessarily enforce it. See the following code block for examples of the preferred single and multiline ternary formats:

// Consider the following examples of preferred single and multiline ternaries
// where caseA to caseZ are all functions which return boolean results, and
// resultA to resultZ are all variable values of the same template type.

// Single line ternary format.
const resultA = caseA() ? resultA : resultB

// Standard Haskell-style multiline line ternary format.
const resultB = caseA() ? resultA
    : caseB() ? resultB
    : caseC() ? resultC
    : resultD // Default case.

// Multiline ternary format with long cases and/or statements causing wrapping.
const resultC = caseA() ? resultA
    : caseB() // Case length causes result statement to exceed max line width.
        ? resultB // Wrapped result should be indented.
    : caseC() // Case length causes result statement to exceed max line width.
        ? resultC // Wrapped result should be indented.
    : resultD // Default case.

Identifier Naming

Identifiers in source files in this repository should follow the lowerCamelCase naming convention with the following exceptions:

  • Class and type names should follow the UpperCamelCase naming convention
  • Global variables such as environment variables or top level configuration variables may use UPPER_SNAKE_CASE where appropriate

Please see the instructions below on how to construct compliant identifiers, read the google specification, or read this response summarising the algorithm:

  1. Convert identifier name to an ASCII prose form of the same name, for example player ID:
    1. Remove any apostrophes etc.
    2. Turn accented letters into standard letter groups (for example the German might become sz)
  2. Divide the result from step 1 into individual words:
    1. Split on spaces or any remaining punctuation such as hyphens
    2. Split any conventional abbreviations into their own separate words (for instance AdWords would become ad words)
  3. Turn everything into lowercase, including all acronyms (for example ID will become id, XML will become xml etc.)
  4. Turn first letter of every word to uppercase for UpperCamelCase, or exclude the first word to yield lowerCamelCase
  5. Concatenate everything from the previous step into one word to get your camel case identifier

Note that these rules are particularly useful for standardising identifiers containing acronyms (only the first letter of an acronym is capitalised regardless of length). The method produces consistent identifiers when compared to standards which make exceptions for short acronyms such as the microsoft abbreviations standard, which suggests capitalising acronyms consisting of only two characters.

Especially when concatenating multiple adjacent acronyms, the google specification produces clearer identifiers. For example with a prose identifier such as player id url, we yield the identifier playerIdUrl. Compared to an identifier such as playerIDUrl using the microsoft abbreviations standard, which not only looks inconsistent, but also raises ambiguity as to where some acronyms begin and/or end.

Paths

Where possible, a leading ./ path prefix is preferred for accessing relative scripts etc. from source code files, this applies to any statement where a path to a file is supplied in a string. Please see the following code block for clarification:

// Preferred path format for relative files.
const file = fs.readFileSync("./relative/path/to/file")

// Valid format, but not preferred.
const file = fs.readFileSync("relative/path/to/file")

Imports and Exports

The type field of the package.json file in this repository declares the scripts in this repository as ESM modules by default, therefore any module imports and exports in a given script must use ES6 module syntax unless the script uses the .cjs extension to declare a CommonJS script.

All import paths should be relative paths, and where possible code and directory structure should be built to avoid long relative path imports. Imports should also prioritise loading the index.js file of a given folder when importing members from module scripts within that folder. This tends to simplify the import path, and allows for less import statements when importing members from multiple different module scripts in a given folder.

Similarly, to facilitate this behaviour, each folder should generally include an index.js file which exports all of the public members of the module scripts contained within that folder. Not all members of a given module script must be made public, and not all exported members of a module script must be exported by the folder index.js file; ultimately the index.js file for a given folder (and the the package entrypoint index.js file) is responsible for exposing only those members which should be made public to other parts of the code (or made available for import in another package). Note that for importing members from scripts in the same folder, members must be imported directly from the given script instead of the folder index.js file in order to avoid circular imports.

If an import must come from a script which has a long or unreadable relative path, then it may be appropriate to use an absolute import. This may be achieved either by using a local path as a dependency, or by using the subpath imports feature of node. See the package imports section for more information on subpath imports and why they are generally avoided in this repository.

// Prefer relative path for importing members from folder index.js file.
import { member } from "./relative/path/to/index.js"

// Import members from the same folder directly to avoid circular imports.
import { member } from "./sibling-script.js"

// Avoid absolute path imports defined in package.json.
import { member } from "#internal/subpath/import"

Comment Grammar

All code comments should be written in clear, concise, and good quality written English. For the sake of consistency, code comments should also follow sentence case. That is to say that any comment, regardless of how small, should start with a capital letter on the first word, and should end with a . (period). Whilst especially short code comments are ideal for not following sentence case, the cutoff between where a comment becomes detailed enough to need one or more full sentences is arbitrary. By following sentence case all the time, code comments are more likely to appear consistent, and less likely to turn into inappropriately long statements that would benefit from sentence breaks. Please see the code block below for an explicit example on the preferred comment format, or see here for more discussion on why code comments should form readable, complete sentences.

// Bad:
// filter array for even numbers

// Preferred:
// Filter array for even numbers.

Script Format

To maintain consistency across the repository, every script follows the same overall format with respect to filenames and the order of certain parts of each script such as imports, exports, and file descriptions. The general format of each script consists of a brief license text, a JSDoc comment containing a file description tag, and author tags for that file, a @ts-check directive, import statements, the body of the script, and finally export statements.

Naming

Each script in this repository should be named using lower-kebab-case (all lowercase words separated by hyphens), or if the main exported member of the script is a class or type, then the script should be named using UpperCamelCase (all uppercase words without separation). Please consider the following points when naming a new script:

  • Script names must minimally indicate the main functionality of the script
  • Script names should be one or two words, longer names may indicate the need for further refactoring into more scripts or folders
  • Script directory paths must be considered as context when naming the script (i.e. container/primary.js rather than container/primary-container.js)
  • Standard scripts must be named using lower-kebab-case unless the script's main export is a class or a type
  • Scripts whose main export is a class or a type must be named using UpperCamelCase, with the name reflecting the main exported class or type
  • Folders may also use lower-kebab-case to have multiple words in the folder name

Header

Each script in this repository starts with a header section containing a license text, information about the script, and directives. Please see the following list for the required elements and order for the header section of each script:

  1. Brief license text listing the type of license which the script is released under, the location of the license in the root of the repository, and links to bare templates of that license on the internet
  2. JSDoc comment containing at a minimum the following tags:
    1. @file tag with a description of the main purpose of the file, for example the main exports
    2. @author tag(s) indicating the main authors of that file
  3. @ts-check directive comment to enforce strict type checking

Please see the template section below for an example empty script containing the required header text and all of the custom maker tags. A blank line separates each section of the script, for example the license text is the first commented lines at the top of the sample script until the first blank line.

Custom Marker Tags

Custom marker tags are single line comments beginning with @@, and mark specific parts of each script. Please see the following table for a list of available marker tags. Every script must include one of each type of tag (imports, body and exports). If a section of the script is empty, for instance if the script does not import anything, mark the section with the no-<tag> variant of the marker tag. Note that imports-<type> tags are preferred over using a single imports tag in order to increase the organisation of import statements at the top of each file. Any tag marked as unique in the following table may only be used once per script.

Marker Tag Unique Description
no-<tag> By tag Indicates that this part of the script is empty. Multiple no-<tag> tags allowed if using different <tag> value.
imports Yes Script imports. Do not use an imports marker tag in conjunction with imports-<type> tags.
imports-<type> By type Script imports of a given type (see table below). Multiple imports-<type> tags allowed if using different <type> value.
body Yes Script body containing the code which will be executed or exported.
exports Yes Script exports.

Please see the following table for the available import types. If multiple import types are required, please use multiple import-<type> marker tags as required. The order of the import tags should follow the order shown in the table below (i.e. node imports should come before dependency imports etc.). This order is chosen to reflect the increasing locality of imports going down the table (i.e. module scripts are in the same folder, package scripts are in a parent folder, external dependencies are in the repository ./node_modules directory etc.). Type imports are an exception to this rule, and always appear at the bottom of the imports section of a script.

Import Type Description
node Native dependencies which are a part of node.
dependencies External dependencies pointing to ./node_modules (i.e. listed in package.json).
utils Generic repository utilities which could be split from the main package scripts or functionality.
package Imports from parent folder within the repository.
module Imports from the same folder.
submodule Imports from a child folder within the repository.
types Type imports.

Template

To speed up the creation of correctly formatted new scripts, this repository offers a script template which may be instantiated using the command npm run admin:plop and then following the directions in the terminal for creating a script from the supplied template. Please see the code block below for an example a script created from this template. For more information on the admin:plop script, please see the npm scripts section.

// Copyright (c) 2023 James Reid. All rights reserved.
//
// This source code file is licensed under the terms of the MIT license, a copy
// of which may be found in the LICENSE.md file in the root of this repository.
//
// For a template copy of the license see one of the following 3rd party sites:
//      - <https://opensource.org/licenses/MIT>
//      - <https://choosealicense.com/licenses/mit>
//      - <https://spdx.org/licenses/MIT>

/**
 * @file This is an empty template script with markers for where the different
 *      parts of the script should be written.
 * @author James Reid
 */

// @ts-check

// @@no-imports

// @@no-body

// @@no-exports

Getting Around

This repository is generated from a template repository which may be viewed here. Please see the following subheadings for descriptions of the purpose and use of the root directories included in the template repository. Note that other directories and architectures may be added by the maintainer(s) after duplicating the template. These additions will obviously have their own purpose(s), some of which may not be described below.

.cache

The ./.cache directory is included in this repository by use of a .gitkeep file, otherwise the contents of the ./.cache directory are untracked by the .gitignore file. The ./.cache directory is reserved for cache files of repository build scripts etc. These include the cache for ESLint, parcel bundler, and any other script which needs to cache local data in order to make subsequent executions of that script faster, or consume fewer system resources.

.github

The .github directory contains some or all files relating to github and the management of the repository community (think code of conduct, templates for issues and pull requests etc.). Primarily this repository uses the ./.github directory for specific issue templates (see below) and for a specific pull request template. For more information specifically on adding a pull request template for a github repository, see here. For more information on the general usage of the ./.github directory in a repository hosted on github, please see this article. Note that although it may be included in the ./.github directory, in the case of this repository, the CONTRIBUTING.md file is not stored in the ./.github directory. Instead the CONTRIBUTING.md file may be found in the root of the repository. This is simply because the CONTRIBUTING.md file is a more universal file which may be expected to be found in the root of any repository regardless of where that repository may be hosted.

.github/ISSUE_TEMPLATE

Issue templates are stored in the ./.github/ISSUE_TEMPLATE subdirectory. Each yaml file in this directory creates an issue template which consumers of the repository may use to report a bug, security vulnerability etc. For more information on creating issue templates, see the github documentation here. For information relating specifically to the syntax for the github form schema, please see here.

admin

The ./admin directory contains all build scripts etc., template files, and configuration files which do not need to be located in the root of the repository. All scripts in the ./admin directory do not form any part of the code exported by this repository. Please see the following subheadings to see how the directory is structured.

admin/assets

The ./assets subdirectory is used to store all repository assets. This includes images, vector files, fonts, and any other media that features in repository documentation or is used in demos of repository functionality. Note that in the event that the default favicon.ico file is not is deleted and no other assets are included in the repository, the ./assets folder will still be included in git history of this repository by use of a .gitkeep file.

admin/config

The ./admin/config subdirectory contains all configuration files for tools which do not require configuration files in the root of the repository. These tools include build tools such as rollup, generators such as jsdoc and plop etc. Other configuration files such as tsconfig.json and .eslintrc must be located in the root of the repository in order to be found by tsserver, npm etc., and are therefore not found in the ./admin/config subdirectory.

admin/scripts

The ./admin/scripts subdirectory contains all custom scripts written by the maintainer for common repository tasks such as changelog generation upon release of a new issue. Most of these scripts can be called using npm run admin:<script-name>. See the npm scripts section for more information on all of the default available scripts.

admin/templates

The ./admin/templates subdirectory contains all templates used by plop for automatic generation of new scripts or documentation files, and by the changelog generator to render new release note prompts. Note that template files are all saved as .hbs (handlebars) files for syntax highlighting purposes in vscode, although some of the template files may be limited to the simpler mustache syntax (this applies mainly to the templates used by the changelog generator).

admin/web

The ./admin/web subdirectory contains files which should be copied to the ./dist/web directory when building a site using the appropriate build command. Primarily this directory contains public record files including a CNAME record file (required for gh-pages to correctly set custom domain when deploying site to github pages), a robots.txt file, and a sitemap.xml file, both of which help search engines to crawl the generated site. For references on how to write and update these files, please see the following links:

build

The ./build directory is included in this repository by use of a .gitkeep file, otherwise the contents of the ./build directory are untracked by the .gitignore file. The ./build directory is reserved for development build outputs. The relevant npm build scripts will output packaged scripts etc. to subdirectories in the ./build directory. These subdirectories will correspond to the subdirectories in the ./src directory (i.e. ./build/bin, ./build/package, and ./build/web). Auto generated JSDoc documentation output will also be found in the ./build directory in the ./build/docs subdirectory. Please see the src section for more information on the purpose of each subdirectory.

dist

The ./dist directory is reserved for production build outputs. The relevant npm build scripts will output packaged scripts etc. to subdirectories in the ./dist directory. These subdirectories will correspond to the subdirectories in the ./src directory (i.e. ./build/bin, ./build/package, and ./build/web). Please see the src section for more information on the purpose of each subdirectory. The ./dist directory may also contain any other production related data files etc., and is included by default in this repository by use of a .gitkeep file.

docs

The ./dist directory is reserved for custom, author generated, documentation files. Primarily these will be markdown files which will be included in the tutorials section of the auto-generated JSDoc documentation output. End users may also browse these markdown documentation files on github etc. The ./docs directory may also contain any other documentation related data files etc., and is included by default in this repository by use of a .gitkeep file.

src

The ./src directory contains all source files which form part of the exported software of this repository. This includes scripts for any CLI tools provided by this repository, any scripts forming the exported package of this repository, and any scripts or markup files for the static demo site of this repository. Please see the following subheadings to see how the directory is structured.

src/bin

The ./src/bin subdirectory contains source files for any CLIs which are provided by this repository, and is included by default in this repository by use of a .gitkeep file. The standard entrypoint for any CLI provided by this repository is ./src/bin/index.js, although rollup may build multiple executable scripts from other entrypoints if required (for instance if multiple CLI tools are provided by the repository). For each executable script which rollup produces, the package.json file bin object must be updated with an entry to point to the new script.

src/package

The ./src/package subdirectory contains source files for the package which this repository exports, and is included by default in this repository by use of a .gitkeep file. The standard entrypoint for any package provided by this repository is ./src/package/index.js, although rollup may build multiple bundles from other entrypoints if required (for instance if the exported package is split into many subpath exports to allow only parts of the package to be imported later). Each entrypoint must export only those members which should be made available for import within another package, and must not blindly export all members etc. from all source files. For each bundled script which rollup produces, the package.json file subpath exports object must be updated with an entry to point to the new bundled script.

src/web

The ./src/web subdirectory contains markup and source files for the static demo site of this repository. Its default subdirectories are included in this repository by use of .gitkeep files. The ./src/web subdirectory is structured like a standard vanilla static site, with a ./src/web/pages subdirectory for all .html page files, a ./src/web/scripts subdirectory for all .js script files, and a ./src/web/styles subdirectory for all .css stylesheet files. The standard entrypoint for the static demo site of this repository is ./src/web/pages/index.html. The parcel bundler will create its bundled output from this file.

When deployed, this page will be found at a URL such as <subdomain>.blameitonyourisp.com (gh-pages looks for index.html file by default). Other pages can be added to the static site by adding them to the ./src/web/pages subdirectory, and linking them from the index.html page. For instance the file ./src/web/pages/about.html would be found at a URL such as <subdomain>.blameitonyourisp.com/about.html, or the file ./src/web/pages/docs/index.html would be found at a URL such as <subdomain>.blameitonyourisp.com/docs.

Most commonly this directory will be used to demonstrate functionality of the package exported by this repository, by importing and using the production build of the package from the ./dist directory. Where appropriate, the static site may also include some author generated documentation pages.

Package

The properties of the package.json file of this repository are generally ordered as follows:

  1. Properties which must be updated when duplicating the template repository such as name, and version
  2. Properties which may be updated directly by the maintainer during the life of the repository such as engines, and scripts
  3. Properties which are generally not updated directly by the maintainer such as dependencies, and devDependencies

Within each group, properties are loosely logically grouped: name, version, description, and keywords are grouped together since they describe the package; homepage, repository, and bugs are grouped together since they refer to the remote repository of the package. Please see the following subheadings for information on usage of specific properties of the package.json file.

Imports

Generally, this repository discourages the use of the subpath imports object for creating absolute path imports in favour of using absolute paths, and structuring code in such a way as to avoid long import paths where possible. This is because:

  • The location of an absolute path import is not always immediately clear compared to the location of a relative path import, especially when the relative path is in the same or an adjacent directory
  • The absolute path of an imported member will not always be inferred by tsserver solely from the subpath imports object of the package.json file, and instead requires an additional entry in the paths object of the tsconfig.json file

If a particularly long import path is unavoidable, then an entry in the subpath imports object of the package.json file may be appropriate at the discretion of the maintainer. If a subpath import is required, and tsserver is not correctly finding the absolute path to the imported member, please see the following code blocks for how to explicitly point tsserver to the absolute path:

Configuration in package.json file:

"imports": {
    "#feature": "./src/feature/index.js"
}

Configuration in tsconfig.json file:

"paths": {
    "#feature": [ "./src/feature/index.js", "./src/feature/*.js" ]
}

Since node now natively supports subpath imports, this repository also strongly discourages implementing absolute path imports by using package.json file dependencies.

Exports

Where appropriate, this repository encourages splitting the package exports by creating bundles from multiple rollup entrypoints, and using the package.json file subpath exports object to specify a path for each bundle. This allows only specific parts of the package to be imported by any dependent packages if desired.

Splitting exports is particularly useful for isolating parts of the package which are expected to be commonly used, or providing separate exports for parts of a package which differ in functionality, instead of providing only one bundle. For instance a package containing disparate code snippets and utilities may have subpath exports such as ./math, and ./terminal etc.

Please note that even if all of the public members of a package are exported across multiple different subpath exports, it is still advisable to provide a main entrypoint which exports all public members of the package from a single bundled script. Additionally, for backwards compatibility with CommonJS imports, this repository also provides a subpath export reserved for the exported package bundled for CommonJS.

See the following code block for an example subpath exports object containing the default exports and an additional export for a specific, commonly used feature:

"exports": {
    ".": "./dist/package/index.js",
    "./feature": "./dist/package/feature.js",
    "./COMMON_JS": "./dist/package/index.cjs"
}

These subpath exports may then be imported as follows (this example assumes that the feature member was exported using a named export rather than a default export):

// ESM imports.
import { feature } from "<package-name>/feature"

// CJS imports.
const { feature } = require("<package-name>/COMMON_JS")

For more information on importing ESM modules into CommonJS scripts, please see here

NPM Scripts

This repository largely follows the npm script conventions from ESLint. Scripts in this repository must follow the following rules:

  1. Script names must contain only lowercase English words (abbreviations are acceptable where they are obvious)
  2. A colon (:) must be used to separate nested categories of each script
  3. A hyphen (-) must be used to separate words
  4. Scripts must be ordered alphabetically, using categories where necessary to group scripts logically

For more information on npm scripts in general, checkout the docs, review this npm style guide, or read this article. Please see the following table for all available scripts in the package.json file and a description of their functionality:

Script Name Description
admin:deploy Deploy static website to gh-pages branch after the site has been built with the appropriate build command.
admin:plop Create or modify a script or other file with plop.
admin:tokei 1 Count approximate lines of code written by the author, and generate a json endpoint for a shields badge.
admin:update-labels Update github issue labels for repository (requires access token in ./.env file).
build Run build:dev script.
build:dev Run all build:dev:<target> scripts.
build:dev:bin Output development build of all CLI tools provided by repository to ./build directory.
build:dev:package Output development build of repository package to ./build directory.
build:dev:web Run start:web script.
build:dist Run all build:prod:<target> scripts.
build:dist:bin Output production build of all CLI tools provided by repository to ./dist directory.
build:dist:package Output production build of repository package to ./dist directory.
build:dist:web Output production build of repository static demo site to ./dist directory.
docs Run docs:jsdoc script.
docs:changelog Autogenerate new changelog entry with prompts from each relevant commit since last version.
docs:jsdoc Build autogenerated docs using JSDoc.
lint Run lint:check script.
lint:check Check source files for linting errors.
lint:fix Fix linting errors in source files which can be automatically resolved.
lint:fix-dry Fix linting errors without saving to filesystem.
postversion Push new version and tag to remote following npm version <semver> command.
preversion Run build:prod prior to npm version <semver> command.
start Run start:web script.
start:docs Run docs:jsdoc, start server hosting the autogenerated docs, and watch ./src directory for changes.
start:web Bundle demo static site to ./build directory using parcel, and start development server.
test Run all test:<suite> scripts.
test:bin Run available tests for all CLI tools provided by repository.
test:package Run available tests for repository package.
test:web Run available tests for repository static demo site.
types Run types:check script.
types:check Check types using tsc
types:declaration Output declaration file for repository package to ./dist directory.
watch Watch ./src/bin and ./src/package directories, and run development build on change.
watch:bin Watch ./src/bin directory, and run development build on change.
watch:package Watch ./src/package directory, and run development build on change.

Wiki

This repository favours documentation which is not platform specific. This means a good quality README.md file, additional author generated markdown documentation files, and custom documentation page(s) on the demo website where such extra documentation would be useful (for instance when documenting an API). Additionally this repository offers auto-generated documentation from JSDoc code comments. As such, the github wiki for this repository should be switched off by default.

Enable

If you wish to add a wiki to this repository, you must first re-enable the wiki in the repository settings. With the wiki re-enabled, you can now add content to the wiki either by cloning the repository in a separate local directory, or by adding the wiki repository as a subtree of the main repository.

When using subtrees, the git command will generally be git subtree <COMMAND> --prefix <PATH> <REMOTE_URL> <REMOTE_BRANCH> --squash where:

  • <COMMAND> is a git operation such as pull, push etc.
  • <PATH> is the relative path to where the subtree is located from the root of the main repository
  • <REMOTE_URL> is the URL of the repository which is to be included as a subtree of the main repository
  • <REMOTE_BRANCH> is the branch which should be fetched

The --squash flag is optional, but recommended when using the pull commands as it will squash the history of the fetched branch into only one commit, preventing cluttering of the main repository git history. Please see the following code blocks for information on how to edit the wiki repository as a subtree of the main repository:

# Add wiki as new remote.
git remote add wiki $WIKI_URL

# Add subtree in ./.github/wiki directory.
git subtree add --prefix .github/wiki wiki master --squash
git subtree pull --prefix .github/wiki wiki master --squash

Note that for any repository hosted on github, the wiki for that repository is a second standalone repository. The general form of the URL of the wiki repository will be https://github.com/<REPO_OWNER>/<REPO_NAME>.wiki.git (i.e. append .wiki.git to the main repository name).

Update

Once you have added the wiki repository as a subtree of the main repository, you can change the contents of the wiki by editing the contents of the ./.github/wiki directory. Please see here for documentation on how to format markdown documentation files for github repository wikis. To commit changes to the wiki repository please see the following code block:

# Commit changes to local repo, then push subtree to remote.
git commit ./github/wiki
git subtree push --prefix .github/wiki wiki master

Disable

To disable a wiki repository after enabling it and adding content, please follow the list below in order:

  1. Remove wiki subtree and optionally remove wiki content (see code blocks below)
  2. Disable wiki in repository settings

To only remove subtree from main repository, but preserve the wiki content in case it is required in the future, please see the following code block:

# Delete subtree directory and commit changes.
rm -rf ./github/wiki
git commit ./github/wiki

# Remove wiki remote *without* pushing removed directory to wiki remote.
git remote remove wiki

To remove subtree and remove all content from the wiki repository, please see the following code block:

# Delete subtree directory and commit changes.
rm -rf ./github/wiki
git commit ./github/wiki

# Push changes to wiki remote.
git subtree push --prefix .github/wiki wiki master

# Remove wiki remote.
git remote remove wiki

To remove all wiki content if the wiki repository has not been added as a subtree of the main repository, please see the following code block:

# Clone and open wiki repository.
git clone https://github.com/$REPO_OWNER/$REPO_NAME.wiki.git
cd $REPO_NAME.wiki

# Remove wiki content and commit changes.
git rm *.md
git commit -m "Remove wiki content"

# Push changes.
git push

To remove all wiki content and delete git history, please see the following code block (this process is irreversible, proceed with caution):

# Clone and open wiki repository.
git clone https://github.com/$REPO_OWNER/$REPO_NAME.wiki.git
cd $REPO_NAME.wiki

# Delete git history using new orphaned branch.
git checkout --orphan empty
git rm --cached -r .
git commit --allow-empty -m "Remove wiki content"

# Force push changes which overrides existing git history.
git push origin empty:master --force

CAUTION Please note that this will completely delete all data for any repository. Proceed with caution, ensuring that you have cloned the correct repository, and are sure that you want to overwrite its history. This process is irreversible!

Releasing

In order to ensure that versions of software released by this repository are released in a complete and consistent manner, please use the following checklist when creating a new version or release:

  1. Test source files using the appropriate test command (only accept failed tests if the test is known to be not required or incorrect):
    1. Run npm run test to run all available test suites
    2. Run npm run test:bin to only run CLI source file tests
    3. Run npm run test:package to only run package source file tests
    4. Run npm run test:web to only run static web source file tests
  2. Build production bundles of source files:
    1. If required, update public record files found at ./admin/web:
      1. Update URLs in public record files to ensure the correct domain and/or subdomain is used
      2. Update sitemap.xml with new pages added etc., optionally use a sitemap generator to create a new sitemap
      3. See the admin/web section for more information on updating public record files
    2. Build type declarations with typescript by running npm run types:declaration (note that this command is also run automatically when building the package)
    3. Build production bundles of the binary, package, and or web source files by running the appropriate build command:
      1. Run npm run build:prod to build a production bundle with all available source files
      2. Run npm run build:prod:bin to build only the CLI source files
      3. Run npm run build:prod:package to build only the released package source files
      4. Run npm run build:prod:web to build only the static web source files
    4. If required, deploy static site to gh-pages by running npm run admin:deploy
  3. Update package.json file:
    1. Do not update the version field as this will be updated by using the npm version <semver> command
    2. Update bin object with any changed executable names or paths
    3. Update subpath exports object with any additional exports provided by the package
    4. Update engines object if the minimum required node or npm version has changed
    5. Update scripts object with any new or changed scripts
  4. Update README.md file:
    1. Update basic and specific usage instructions as required
    2. Update roadmap with new features, and remove features which have now been implemented
    3. Update attributions with any new 3rd party assets
  5. Update CONTRIBUTING.md file:
    1. Update the getting around section with any significant new or changed directory structures
    2. Update the npm scripts section with any new or changed scripts
  6. Update CHANGELOG.md file:
    1. Run npm run docs:changelog to generate a new section in the CHANGELOG.md file with prompts for each relevant commit since the last version
    2. Edit each prompt as required so that the new section in the CHANGELOG.md file reflects the changes since the last version in a concise, continuous and human readable fashion
  7. Update software versions for dropdowns in issue templates:
    1. Add the new version number of the package to the package version dropdown
    2. Add any new LTS version of node released since the last version to the node LTS version dropdown
  8. Commit all changes in the working tree, or stash uncommitted changes such that the working tree is clean prior to updating the version using the npm version <semver> command
  9. Update version by running npm version <semver> where <semver> is either major, minor, or patch depending on the changes made 2

Indexing

When publishing a new site, it may be useful to help search engine crawlers by explicitly submitting your sitemap.xml file for faster indexing. Note that this is step is not required for a site to be indexed, as search engines will usually find any given site organically unless the site's robots.txt file prevents this. To find out which page(s) are currently indexed by google for a given site, simply search for site:<url>. This query will return all pages, posts etc. associated with that url which are currently indexed on google.

For information on how to submit your sitemap.xml file for indexing on google or different search engines, please see the following resources:

Note that each subdomain of a given domain should have its own sitemap.xml file and associated public records. As discussed in these forum threads (webmaster stack exchange and google webmasters), search engines generally consider subdomains as being standalone sites, and all URLs listed in a sitemap.xml file must reside on the same host as the given sitemap.xml file (i.e. a sitemap at https://www.example.com/sitemap.xml cannot include URLs from https://subdomain.example.com). Additionally, modern search engines are usually capable of inferring from content which subdomains should be indexed as independent sites, and which subdomains should be associated with the main domain.

Footnotes

  1. Please note that this script relies on tokei, a CLI application written in Rust. If tokei is not installed on the system, then this script will not run. At the time of writing, the tokei endpoint for dynamic badges/shields is intermittent and unreliable, showing a 502 bad gateway error. This error also prevents the shields lines of code badge from rendering correctly, causing all repos to show as having 0 lines of code. The format for the tokei badge URL may be found here. To circumvent this, lines of code are being counted "manually" using the tokei rust CLI and a json endpoint to generate the badge. As such the lines of code badge may be out of sync with the latest commit, although given that this is a "just for fun" metric, it is not of importance.

  2. Note that the npm preversion script is configured to run the build:prod script in case the build step is forgotten, the .npmrc file is configured to add a git tag to the generated new version commit, and the npm postversion script is configured to push the new commit and tag to the remote repository. For more information on the npm version command, please see this cheatsheet.