Skip to content
Quickly create a Node.js package with a Github repository.
JavaScript CSS HTML Dockerfile
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.documentary/section-breaks
.vscode
build
doc
documentary
src
test
website
.alamoderc.json
.eslintignore
.eslintrc
.gitignore
CHANGELOG.md
LICENSE
README.md
package.json
t.js
type.js
yarn.lock

README.md

mnp

npm version

mnp aka My New Package is a Node.js CLI binary that allows to quickly create a new Node.js package with a default structure (src, test, package.json, etc) and initialise a GitHub repository. It provides a number of essential structures for web development.

Table Of Contents

CLI: mnp my-new-package

The default mode is to start creating a package. If package-name is not passed, the program will run in interactive mode and ask to enter details.

mnp [package-name] [-D description] [-s structure] [--init|I] [-chdn@]

To use the binary, enter mnp cool-package-name, or just mnp to be asked for the name. mnp will check if the directory does not exist and not in a git path, create a Github repository, star it, clone it to the local filesystem, and fill in the default Node.js package structure.

Creating Packages
Creating a new package.

-I, --init: Configure

When launched for the first time, mnp will ask to complete the set-up process and create a .mnprc file in the directory from which it was called. It is possible to create a default .mnprc in the HOME directory to remember the token, and then initialise mnp in other directories, when it will reuse the token from the HOME config, but ask for more details for the current folder. This way, it is easy to manage different organisations and scopes, while reusing the access token.

Field Description
token A GitHub developer token.
organisation An optional GitHub organisation name to create repositories for. A personal GitHub account is used if not supplied.
name, email Author's name and email to set in the package.json file, and in the project directory's git config. Default values are read from the global git config.
scope A scope with which to create packages.
website A link location in the copyright section of the README file.
trademark A display text for the website link in the README.
legal name A legal name placed in the LICENCE file.
Initialising configuration: mnp -I.
Initialising the configuration.

-h, --help: Show Help

MNP: create My New Package.
 If no package name is given as the first argument, the program will ask
 for it in the CLI. A GitHub repository for each new package will be
 created automatically, and a GitHub token can be generated at:
 https://github.com/settings/tokens for the use in this application.
 The token is saved in the CWD/.mnprc file along with other configuration,
 including organisation name etc. Different types of packages, with a
 modern Node.js library by default are available, including:

+ package:	a modern Node.js package to publish on npm (default);
+ idio:		a JSX-powered Koa2 + React-Redux universal website;
+ structure:	an mnp template to create new structures.

  mnp [package-name] [-D description] [-s structure] [-cIhdv]

	package-name 	Name of the new or checked package.
	-D, --desc   	Description of the software.
	-s structure 	Which structure to use (package, idio, structure).
	-c, --check  	Check if the package name has been taken or not.
	-h, --help   	Print this information and quit.
	-d, --delete 	Delete a repository. Useful in testing.
	-v, --version	Show mnp version.
	--init, -I   	Initialise configuration in the local .mnprc file.

  Example:

    mnp my-new-package -s idio

-c, --check: Check Exists

Check if the package name is already taken or not.

Command Output
mnp taken -c taken output
mnp isfree -c free output

-d, --delete: Delete Repository

Delete specified repository from GitHub. Useful when a package was created for testing. The organisation name will be read from the configuration.

mnp package -d

-@, --scope: Set Scope

When a particular scope needs to be specified for the package, the -@ option can be used.

mnp package -@ superscope

-n, --no-scope: Disable Scope

If the settings read from .mnprc contained an NPM scope, but it is not needed for the particular package, it can be disabled with this option.

mnp package -n

Structures

There are a number of structures available. The default one is the package structure.

Name Description Link
package Art Deco Node.js Package. It has everything needed to create high-quality modern application with testing, building and documentation facilities. @mnpjs/package
idio The @idio/core web server structure. Fast backend with Facebook auth and dotenv support. @mnpjs/idio
azure The Azure functions app structure for creating serverless APIs. @mnpjs/azure
structure The metastructure for creating new structures with mnp. @mnpjs/structure

Scripts

At certain stages during the creation of a new package, mnp will run scripts specified in the package.json of a structure. For example, the package structure will run yarn to install dependencies right after the package is created.

Scripts should be specified in the mnp field of the package.json file either as a string, or an array, for example:

{
  "name": "@mnpjs/package",
  "mnp": {
    "onCreate": [
      "yarn"
    ]
  }
}

If a script is given as a .js file which exists in the structure directory, it will be executed with Node.js:

{
  "name": "@mnpjs/structure",
  "mnp": {
    "onCreate": "scripts/mkdir.js"
  }
}

Package Structure

The default package structure is an up-to-date template of a modern Node.js application.

node_modules/@mnpjs/package/structure
├── .alamoderc.json
├── .documentary
│   ├── cache
│   │   ├── fork.json
│   │   └── modules.json
│   └── section-breaks
│       ├── -1.svg
│       ├── 0.svg
│       ├── 1.svg
│       └── 2.svg
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .vscode
│   └── launch.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── build
│   └── index.js
├── documentary
│   ├── 1-API
│   │   └── index.md
│   ├── footer.md
│   └── index.md
├── example
│   └── index.js
├── package.json
├── src
│   └── index.js
├── test
│   ├── context
│   │   └── index.js
│   ├── fixture
│   │   └── test.txt
│   ├── mask
│   │   └── default.js
│   ├── result
│   │   └── default.md
│   └── spec
│       └── default.js
├── types
│   ├── externs.js
│   ├── index.js
│   └── index.xml
└── yarn.lock

It also includes yarn.lock file to speed up the installation process.

Main Function

Every package will have a main file specified in the main field in the package.json file, unless they have a bin field otherwise (in other words, if package does not provide a Node.js API, and only CLI usage). This structure has a minimum example of working function which is exported with export default keyword, and documented with JSDoc. It's important to document the config argument in a typedef so that other developers are able to see the autocompletion hints when trying to use the function.

import { debuglog } from 'util'

const LOG = debuglog('my-new-package')

/**
 * {{ description }}
 * @param {_my-new-package.Config} [config] Options for the program.
 * @param {boolean} [config.shouldRun=true] A boolean option. Default `true`.
 * @param {string} config.text A text to return.
 */
export default async function myNewPackage(config = {}) {
  const {
    shouldRun = true,
    text,
  } = config
  if (!shouldRun) return
  LOG('my-new-package called with %s', text)
  return text
}

/* documentary types/index.xml */
/**
 * @suppress {nonStandardJsDocs}
 * @typedef {_my-new-package.Config} Config Options for the program.
 */
/**
 * @suppress {nonStandardJsDocs}
 * @typedef {Object} _my-new-package.Config Options for the program.
 * @prop {boolean} [shouldRun=true] A boolean option. Default `true`.
 * @prop {string} text A text to return.
 */

Config Api Type

Test Suites

The tests are found in the test/spec directory, and all necessary infrastructure in the test dir, including a fixture directory and optionally a snapshot directory if the package is using snapshot testing.

node_modules/@mnpjs/package/structure/test
├── context
│   └── index.js
├── fixture
│   └── test.txt
├── mask
│   └── default.js
├── result
│   └── default.md
└── spec
    └── default.js

The tests are written with zoroaster framework, which expects a file to export a test suite as an object, containing tests as its properties. Tests can be both asynchronous and synchronous, and zoroaster/assert includes a throws method to assert that the function throws, as well as deepEqual with color difference.

import { equal, ok } from '@zoroaster/assert'
import Context from '../context'
import myNewPackage from '../../src'

/** @type {Object.<string, (c: Context)>} */
const T = {
  context: Context,
  'is a function'() {
    equal(typeof myNewPackage, 'function')
  },
  async 'calls package without error'() {
    await myNewPackage()
  },
  async 'gets a link to the fixture'({ fixture }) {
    const text = fixture`text.txt`
    const res = await myNewPackage({
      text,
    })
    ok(res, text)
  },
}

export default T

If snapshot-testing is required, it can be additionally installed with yarn add -DE snapshot-context. This will allow to write snapshot tests.

Testing Context

The structure uses a test context -- a feature of Zoroaster that lets separate the set-up and tear-down methods from the test implementations. All common methods, e.g., reading a fixture file, should be implemented in the context and accessed via the destructuring capabilities of the JavaScript language. All clean-up code such as destroying a server, can be done in the _destroy method of the class.

import { join } from 'path'
import { debuglog } from 'util'

const LOG = debuglog('my-new-package')

/**
 * A testing context for the package.
 */
export default class Context {
  async _init() {
    LOG('init context')
  }
  /**
   * Example method.
   */
  example() {
    return 'OK'
  }
  /**
   * A tagged template that returns the relative path to the fixture.
   * @param {string} file
   * @example
   * fixture`input.txt` // -> test/fixture/input.txt
   */
  fixture(file) {
    const f = file.raw[0]
    return join('test/fixture', f)
  }
  async _destroy() {
    LOG('destroy context')
  }
}

When a context is used in tests, there's an access to the test context API:

JSDoc In Destructuing A FIXTURE Path Via Context

Context testing also allows to split files into mulitple sub-directories much easier.

Documentation with doc

The documentation is pre-processed with documentary which simplifies working on the README.md file by allowing to split files, and inserting examples and output text in the docs.

node_modules/@mnpjs/package/structure/documentary
├── 1-API
│   └── index.md
├── footer.md
└── index.md

To process documentation, the yarn doc command can be run.

Examples Embedding

The examples are extremely useful for people reading the documentation, and they also allow developers to manually check that everything works correctly in the package. documentary supports embedding of examples and their output, eliminating the need to copy those by hand. The examples are put in the example directory, and embedded in the README file with the following snippet:

%EXAMPLE: example/example, ../src => mnp%

The paths to JS and JSX files will be resolved automatically, however to embed the source of other files, the extension should be passed. To specify the code block language in output markdown, it can be passed at the end, otherwise it will be determined from the file extension:

%EXAMPLE: example/config.yml, yaml%

The output can be printed with the FORK command:

%FORK-json example/example%
/* alanode example/ */
import myNewPackage from '../src'

(async () => {
  const res = await myNewPackage({
    text: 'example',
  })
  console.log(res)
})()

Because the examples are written using import and export syntax, ÀLaMode transpiler bundlers alanode binary, which is added as an alias with yarn e command, e.g., yarn e example/ or yarn e example/second. However, this is not required to fork examples for documentation, because Documentary will take care of the import and export statements by using ÀLaMode internally. To disable that, the _FORK should be used instead.

Forking also supports caching, so that examples don't have to be rerun each time the documentation changes in a different place. This allows to recompile the output README.md much faster. Caches include module's mtime and its dependencies' versions if they are packages, or mtimes if they are other modules. To disable caching globally, the doc command can be run with -c, or !FORK should be specified for individual forks. Because caches rely on mtime, they are not submitted to git.

To provide a quick way to run examples, each of them needs to be created a script for in the package.json.

Scripts in Package.json

The scripts are useful for testing, running in debugger, building and building documentation.

{
  "name": "my-new-package",
  "version": "0.0.0-pre",
  "description": "{{ description }}",
  "main": "build/index.js",
  "module": "src/index.js",
  "scripts": {
    "t": "zoroaster -a",
    "test": "yarn t test/spec test/mask",
    "spec": "yarn t test/spec",
    "mask": "yarn t test/mask",
    "test-build": "ALAMODE_ENV=test-build yarn test",
    "lint": "eslint .",
    "doc": "NODE_DEBUG=doc doc -o README.md",
    "b": "alamode src -o build -s",
    "d": "yarn-s d1 externs",
    "d1": "typal types/index.js src -c -t types/index.xml",
    "externs": "typal types/externs.js",
    "build": "yarn-s d b doc",
    "e": "alanode"
  },
  "files": [
    "build",
    "src",
    "types/externs.js"
  ],
  "externs": "types/externs.js",
  "repository": {
    "type": "git",
    "url": "{{ git_url }}"
  },
  "keywords": [
    "{{ keywords }}"
  ],
  "author": "{{ author_name }} <{{ author_email }}>",
  "license": "MIT",
  "bugs": {
    "url": "{{ issues_url }}"
  },
  "homepage": "{{ readme_url }}",
  "devDependencies": {
    "alamode": "^2.3.4",
    "documentary": "^1.27.4",
    "eslint-config-artdeco": "1.0.1",
    "yarn-s": "1.1.0",
    "zoroaster": "^4.1.1-alpha"
  }
}

The description of each script is as follows:

Script Meaning Description
t Test a single file or directory. To run: yarn t test/spec/lib.js.
b Build With À La Mode. The package uses alamode to allow writing import and export statements.
doc Document With Documentary. Is run with yarn doc, but is also a part of the build script.
build Run b and doc in series. Builds source code into the build directory, and compiles documentation to the README.md file.
test Test With Zoroaster. Run all tests, yarn test.
test-build Test build files. Run all tests by requiring all files from the build directory and not the src. This is possible with the babel-plugin-transform-rename-import which changes ../src to ../build (also as part of a bigger path such as ../../src/lib).
e Run an example file. Run specified example, e.g., yarn e example/test.js.
example/ Run a particular example. A job specifically created as a short-hand for a particular example.
lint Check code style. eslint is not installed as a dependency, because it can be installed globally easily. It will also work in the IDE if installed globally fine. However, eslint-config-artdeco config is specified as a dependency.

.alamoderc.json

alamode is a fast Regex-based JavaScript transpiler which is capable of transforming import and export statements into require calls and module.export assignments. Because the stable Node.js contains most in not all of the features that could be wanted by developers, except for the ECMAScript 6 modules, the code inside packages is transpiled with alamode either during the build process, or via a require hook. It also allows to substitute the path to the source directory, e.g., when testing the build with the test-build command when ALAMODE_ENV is set to test-build.

{
  "env": {
    "test-build": {
      "import": {
        "replacement": {
          "from": "^((../)+)src",
          "to": "$1build"
        }
      }
    }
  }
}

launch.json Debugging

Debugging is very useful and almost always required way to progress with a development of a software program. A new functionality can be introduced by writing the tests first, and then running them against the source code. That is, the TDD approach to testing can be summarised as having to somehow run the code being tested first, and the best place to put it in is a test file. By providing a quick sketch of tests, the program can then be debugged to see whether the execution flows as expected, and adjust it on-the-fly.

This explains the structure of the launch.json file, which will have a configuration to start Zoroaster testing in watch mode, so that it is not necessary to restart the Node process every time. Also, if changes are made during a pause at a breakpoint, the execution will need finish running completely first before a changed version can be run.

{
  // Use IntelliSense to learn about possible attributes.
  // Hover to view descriptions of existing attributes.
  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Launch Zoroaster",
      "program": "${workspaceFolder}/node_modules/.bin/zoroaster",
      "env": {
        "NODE_DEBUG": "my-new-package",
      },
      "console": "integratedTerminal",
      "args": [
        "test/spec",
        "test/mask",
        "-a", "-w", "-t", "9999999"
      ],
      "skipFiles": [
        "<node_internals>/**/*.js"
      ]
    },
    {
      "type": "node",
      "request": "launch",
      "name": "Launch Example",
      "program": "${workspaceFolder}/node_modules/.bin/alanode",
      "console": "integratedTerminal",
      "args": [
        "example"
      ],
      "skipFiles": [
        "<node_internals>/**/*.js"
      ]
    }
  ]
}

Copyright

(c) Art Deco 2019

You can’t perform that action at this time.