angular-build

0.7.15 • Public • Published

Web Dev Build System

NPM version Downloads GitHub issues Build Status Dependency Status devDependency Status Code Climate Test Coverage Issue Count Gitter GitHub license

About

A Build System for Angular 2 web app development using the Gulp build system. This module forked from Modern Web Dev Build. All kudos goes to Sebastien, I have just added features I tend to use. You can see a seed project using this Build System.

Added Features

Proxy API Server

This was the primary feature I felt was missing, maybe I missed something and this isn't necessary but it is how I develop, so I forked the repository. The proxy server provides stubbed API calls to the back-end server. The proxy middleware is provided by http-proxy-middleware and is configured and developed by the application project, not this package. I tend to use express, but that isn't crucial. To setup the proxy, add configuration to the options object in the project gulpfile e.g.

options.proxy = {
    api: '/api',
    target: 'http://localhost',
    port: 8000
};

The api is the URL path to listen on e.g. /api/init, /api/login etc. The target and the port identify where to forward requests to. So, requests to http://localhost:8000/api/login would be sent to the proxy and the response from it returned to the application. This enables us to develop the front-end separately from the back-end server, all we have to do is agree an API.

The proxy server itself is defined by the project using this Build System but if the above configuration exists, then the proxy task must also be implemented, see an example of of such a task

Configure App Source Folder

Added ability to configure where the application code exists, this was previously hard-coded to the project root but that seemed limiting. To override the location add to the gulpfile e.g.

options.folders = {};
options.folders.app = './src/main/app';

SASS Linting

This seemed a very basic omission, I used gulp-sass-lint NPM package to provide the functionality, it is up to the application whether to override the default options or not by providing a sass-lint.yml file, the structure of which you can learn about at sass-lint.

Features from Modern Web Dev Build

  • ES2015 and TypeScript support
  • built-in HTTP server with live reloading & cross-device synchronization (BrowserSync)
    • configured to support CORS
  • awesome developer experience with a change detection mechanism that automagically:
    • transpiles TypeScript > ESx w/ sourcemaps (you choose the target version)
    • transpiles ES2015 > ESx w/ sourcemaps (you choose the target version)
    • transpiles SASS > CSS w/ sourcemaps
    • checks JavaScript/TypeScript code quality/style and report on the console (without breaking the build)
    • ...
  • production bundle creation support with:
    • CSS bundle creation
    • CSS optimization & minification
    • JS bundle creation
    • JS minification
    • HTML minification
    • images optimization
  • ...

Check out the change log

Embedded choices

As state above, some important technology choices are clearly embedded with this project. Here's a rundown of those choices:

  • TypeScript and ES2015 (although the final output is ES5 for wider compatibility)
  • SystemJS: module loader
  • JSPM to manage your application dependencies (through jspm.conf.js)
    • JSPM support can be disabled if you wish
  • Karma to run tests
  • SASS: who doesn't want variables and mixins?
  • component based code & assets organization (Angular friendly)
  • JSCS and included code style rules
  • JSHint and included code quality rules
  • TSLint and included code quality/style rules
  • BrowserSync development Web Server

Upgrade

Check out the upgrade page

Installation

General Pre-requisites

Before you install the build, you need to install some dependencies globally:

  • npm install --global gulp

Add to Projects

First configure the required dependencies in your package.json file:

  • add Build System to your devDependencies: npm install node-build-web-app --save-dev
  • execute npm install --no-optional

You should get warnings about missing peer dependencies. Those are dependencies that are required by the build but that you should add to your own project, install these one by one.

For now the required peer dependencies are as follows:

  • babel-core
  • gulp
  • jspm
  • nodemon (required by recommended npm scripts)
  • typescript

Next, check the minimal require file contents below!

Required folder structure and files

The build tries to provide a flexible structure, but given the technical choices that are embedded, some rules must be respected and the build expects certain folders and files to be present. In the future we'll see if we can make this more configurable.

Mandatory folder structure & files

Here's an overview of the structure imposed by this Build System.

Please make sure to check the file organization section for more background about the organization and usage guidelines.

  • project root
    • app: folder containing all the files of the application, defaults root, but is configurable, see above.
      • components: folder containing components of your application (e.g., login, menu, ...); basically reusable pieces
      • core: folder containing at least the entrypoint of your application
        • commons: folder containing common reusable code (e.g., base utilities)
        • services: folder containing generic services (e.g., for local storage)
        • boot.ts: the entrypoint of your application
        • app.ts: the application root
      • fonts: folder containing fonts of your application (if any)
      • images: folder for image assets
      • pages: folder for full-blown pages of your application
      • scripts: folder for scripts
      • styles: folder for the main stylesheets
        • main.scss: file used to import all application-specific stylesheets
        • vendor.scss: file used to import all third-party stylesheets
        • note that the goal isn't to put ALL your stylesheets in there, basically just the entrypoints and the generic parts (e.g., variables, mixins, responsive styles, ...)
      • index.html: the entrypoint of your application
    • .babelrc: Babel configuration file
    • .jscsrc: JSCS rule set to use while checking JavaScript code style
    • .jshintrc: JSHint rule set to use while checking JavaScript code quality
    • .jshintignore: files and folders to ignore while checking JavaScript code quality
    • gulpfile.babel.js: gulp configuration file
    • jspm.conf.js: SystemJS/JSPM configuration file
      • can have another name if you do not use JSPM (see options)
    • karma.conf.js: Karma configuration file (configuration of the test runner)
    • package.json: NPM configuration file (also used by JSPM)
    • tsconfig.json: TypeScript compiler configuration
    • tslint.json: TypeScript code quality/style rules

Minimal (build-related) required file contents

Although we want to limit this list as much as possible, for everything to build successfully, some files need specific contents:

.babelrc

{
    "presets": ["es2015"],
    "plugins": ["transform-es2015-modules-commonjs"],
    "comments": false
}

With the configuration above, Babel will transpile ES2015 code to ES5 commonjs. For that configuration to work, the following devDependencies must also be added to your project:

"babel-plugin-transform-es2015-modules-commonjs": "6.3.x",
"babel-preset-es2015": "6.3.x",

gulpfile.babel.js

In order to use this Build System, your gulpfile must at least contain the following. The code below uses ES2015 (via gulpfile.babel.js), but if you're old school you can also simply use a gulpfile.js with ES5.

Note that the build tasks provided are transpiled to ES5 before being published

"use strict";

import gulp from "gulp";

import build from "node-build-web-app";
let options = undefined; // no options are supported yet

//options.minifyHTML = false;
//...

build.registerTasks(gulp, options);

With the above, all the gulp tasks provided will be available to you. See a full example

.jscsrc

Valid configuration

.jshintrc

At least the following:

node_modules/**/*
jspm_packages/**/*
jspm.conf.js
dist/**/*
.tmp/**/*

jspm.conf.js

The SystemJS/JSPM configuration file plays a very important role;

  • it is where all your actual application dependencies are to be defined
  • it is where you can define your own 'paths', allowing you to load modules of your application easily without having to specify relative paths (i.e., create aliases for paths).

If you choose to use the default JSPM support, then you can add dependencies to your project using jspm install; check the official JSPM documentation to know more about how to install packages.

With the help of this configuration file, SystemJS will be able to load your own application modules and well as third party dependencies. In your code, you'll be able to use ES2015 style (e.g., import {x} from "y"). In order for this to work, you'll also need to load SystemJS and the SystemJS/JSPM configuration file in your index.html (more on this afterwards).

If you have disabled the use of JSPM by the build then you can rename that file if you wish and inform the build (see the options), BUT make sure that any reference in the various configuration files is updated (e.g., karma configuration, jshint configuration, etc). Only rename it if it is really really useful to you :)

Here's an example:

System.config({
  defaultJSExtensions: true,
  transpiler: false,
  paths: {
    "github:*": "jspm_packages/github/*",
    "npm:*": "jspm_packages/npm/*",
    "core/*": "./.tmp/core/*",
    "pages/*": "./.tmp/pages/*"
  }
});

In the above:

  • defaultJSExtensions: is mandatory so that extensions don't have to be specified when importing modules
  • transpiler: is set to false because we don't use in-browser transpilation
  • paths
    • core/, pages/ allow you to import modules from different parts of your codebase without having to specify relative or absolute paths. This is covered in the folder structure section above.
    • you can rename those if you wish

package.json

In addition to the dependencies listed previously, you also need to have the following in your package.json file, assuming that you want to use JSPM:

  "jspm": {
    "directories": {},
    "configFile": "jspm.conf.js",
    "dependencies": {
    },
    "devDependencies: {
    }
  }

This is where you let JSPM know where to save the information about dependencies you install. This is also where you can easily add new dependencies; for example: "angular2": "npm:angular2@^2.0.0-beta.1",. Once a dependency is added there, you can invoke jspm install to get the files and transitive dependencies installed and get an updated jspm.conf.js file.

tsconfig.json

Given that TypeScript is one of the (currently) embedded choices of this project, the TypeScript configuration file is mandatory.

The tsconfig.json file contains:

  • the configuration of the TypeScript compiler
  • TypeScript code style rules
  • the list of files/folders to include/exclude

Here's is the minimal required contents. Note that the outDir value is important as it tells the compiler where to write the generated code! Make sure that you also DO have the rootDir property defined and pointing to "./app", otherwise the build will fail (more precisely, npm run serve will fail).

The build depends on the presence of those settings.

{
    "version": "1.7.3",
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "declaration": false,
        "noImplicitAny": true,
        "suppressImplicitAnyIndexErrors": true,
        "removeComments": false,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "noEmitOnError": false,
        "preserveConstEnums": true,
        "inlineSources": false,
        "sourceMap": false,
        "outDir": "./.tmp",
        "rootDir": "./app",
        "moduleResolution": "node",
        "listFiles": false
    },
    "exclude": [
        "node_modules",
        "jspm_packages",
        "typings/browser",
        "typings/browser.d.ts"
    ]
}

Here's a more complete example including code style rules:

{
    "version": "1.7.3",
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "declaration": false,
        "noImplicitAny": true,
        "suppressImplicitAnyIndexErrors": true,
        "removeComments": false,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "noEmitOnError": false,
        "preserveConstEnums": true,
        "inlineSources": false,
        "sourceMap": false,
        "outDir": "./.tmp",
        "rootDir": "./app",
        "moduleResolution": "node",
        "listFiles": false
    },
    "formatCodeOptions": {
        "indentSize": 2,
        "tabSize": 4,
        "newLineCharacter": "\r\n",
        "convertTabsToSpaces": false,
        "insertSpaceAfterCommaDelimiter": true,
        "insertSpaceAfterSemicolonInForStatements": true,
        "insertSpaceBeforeAndAfterBinaryOperators": true,
        "insertSpaceAfterKeywordsInControlFlowStatements": true,
        "insertSpaceAfterFunctionKeywordForAnonymousFunctions": false,
        "insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": false,
        "placeOpenBraceOnNewLineForFunctions": false,
        "placeOpenBraceOnNewLineForControlBlocks": false
    },
    "exclude": [
        "node_modules",
        "jspm_packages",
        "typings/browser",
        "typings/browser.d.ts"
    ]
}

Note the exclusion that we have made, all of which are relevant and there to avoid known issues (e.g., https://github.com/typings/discussions/issues/6 if you are using typings).

tslint.json

tslint.json is the configuration file for TSLint.

Although it is not strictly mandatory (the build will work without this file), we heavily recommend you to use it as it is very useful to ensure a minimal code quality level and can help you avoid common mistakes and unnecessary complicated code:

Here's an example:

{
  "rules": {
    "class-name": true,
    "curly": true,
    "eofline": true,
    "forin": true,
    "indent": [false, "tabs"],
    "interface-name": false,
    "label-position": true,
    "label-undefined": true,
    "max-line-length": false,
    "no-any": false,
    "no-arg": true,
    "no-bitwise": true,
    "no-console": [false,
      "debug",
      "info",
      "time",
      "timeEnd",
      "trace"
    ],
    "no-construct": true,
    "no-debugger": true,
    "no-duplicate-key": true,
    "no-duplicate-variable": true,
    "no-empty": true,
    "no-eval": true,
    "no-imports": true,
    "no-string-literal": false,
    "no-trailing-comma": true,
    "no-unused-variable": false,
    "no-unreachable": true,
    "no-use-before-declare": null,
    "one-line": [true,
      "check-open-brace",
      "check-catch",
      "check-else",
      "check-whitespace"
    ],
    "quotemark": [true, "double"],
    "radix": true,
    "semicolon": true,
    "triple-equals": [true, "allow-null-check"],
    "variable-name": false,
    "no-trailing-whitespace": true,
    "whitespace": [false,
      "check-branch",
      "check-decl",
      "check-operator",
      "check-separator",
      "check-type",
      "check-typecast"
    ]
  }
}

karma.conf.js

Karma loads his configuration from karma.conf.js. That file contains everything that Karma needs to know to execute your unit tests.

Here's an example configuration file that uses Jasmine. Note that the main Karma dependencies including PhantomJS are included in the build. You only need to add a dependency to jasmine, karma-jasmine and karma-jspm for the following to work.

If you choose not to use JSPM, then you can use karma-systemjs instead: https://www.npmjs.com/package/karma-systemjs

Example:

// Karma configuration
// reference: http://karma-runner.github.io/0.13/config/configuration-file.html

module.exports = function (config) {
    config.set({

        // base path that will be used to resolve all patterns (eg. files, exclude)
        //basePath: ".tmp/",

        plugins: [
            "karma-jspm",
            "karma-jasmine",
            "karma-phantomjs-launcher",
            "karma-chrome-launcher",
            "karma-firefox-launcher",
            "karma-ie-launcher",
            "karma-junit-reporter",
            "karma-spec-reporter"
        ],

        // frameworks to use
        // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
        frameworks: [
            "jspm",
            "jasmine"
        ],

        // list of files / patterns to load in the browser (loaded before SystemJS)
        files: [],

        // list of files to exclude
        exclude: [],

        // list of paths mappings
        // can be used to map paths served by the Karma web server to /base/ content
        // knowing that /base corresponds to the project root folder (i.e., where this config file is located)
        proxies: {
            "/.tmp": "/base/.tmp" // without this, karma-jspm can't load the files
        },

        // preprocess matching files before serving them to the browser
        // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
        preprocessors: {},

        // test results reporter to use
        // possible values: 'dots', 'progress', 'spec', 'junit'
        // available reporters: https://npmjs.org/browse/keyword/karma-reporter
        // https://www.npmjs.com/package/karma-junit-reporter
        // https://www.npmjs.com/package/karma-spec-reporter
        reporters: ["spec"],

        // web server port
        port: 9876,

        // enable / disable colors in the output (reporters and logs)
        colors: true,

        // level of logging
        // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
        logLevel: config.LOG_INFO,

        // enable / disable watching file and executing tests whenever any file changes
        autoWatch: true,

        // start these browsers
        // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
        browsers: [
            "PhantomJS"
            //"Chrome",
            //"Firefox",
            //"PhantomJS",
            //"IE"
        ],

        // Continuous Integration mode
        // if true, Karma captures browsers, runs the tests and exits
        singleRun: false,

        junitReporter: {
            outputFile: "target/reports/tests-unit/unit.xml",
            suite: "unit"
        },

        // doc: https://www.npmjs.com/package/karma-jspm
        // reference config: https://github.com/gunnarlium/babel-jspm-karma-jasmine-istanbul
        jspm: {
            // Path to your SystemJS/JSPM configuration file
            config: "jspm.conf.js",

            // Where to find jspm packages
            //packages: "jspm_packages",

            // One use case for this is to only put test specs in loadFiles, and jspm will only load the src files when and if the test files require them.
            loadFiles: [
                // load all tests
                ".tmp/*.spec.js", // in case there are tests in the root folder
                ".tmp/**/*.spec.js"
            ],

            // Make additional files/a file pattern available for jspm to load, but not load it right away.
            serveFiles: [
                ".tmp/**/!(*.spec).js" // make sure that all files are available
            ],

            // SystemJS configuration specifically for tests, added after your config file.
            // Good for adding test libraries and mock modules
            paths: {}
        }
    });
};

Dev dependencies to add for the above Karma configuration:

    "jasmine": "...",
    "karma-jasmine": "...",
    "karma-jspm": "..."

Minimal (application-specific) required file contents

Although we want to limit this list as much as possible, for everything to build successfully, some files need specific contents:

core/app.ts

This should be the top element of your application. This should be loaded by core/boot.ts (see below).

"use strict";

export class App {
    ...
    constructor(){
        console.log("Hello world!");
    }
}

core/boot.ts

The boot.ts file is the entrypoint of your application. Currently, it is mandatory for this file to exist (with that specific name), although that could change or be customizable later.

The contents are actually not important but here's a starting point:

"use strict";

import {App} from "core/app";
// bootstrap your app

styles/main.scss

The main.scss file is where you should load all the stylesheets scattered around in your application.

Here's an example of a main.scss:

//
// Main stylesheet.
// Should import all the other stylesheets
//

// Variables, functions, mixins and utils
@import "base/variables";
@import "base/functions";
@import "base/mixins";
@import "base/utils";

// Base/generic style rules
@import "base/reset";
@import "base/responsive";
@import "base/fonts";
@import "base/typography";
@import "base/base";

// Layout
@import "layout/layout";
@import "layout/theme";
@import "layout/print";

// Components
@import "../components/posts/posts";

// Pages
@import "../pages/home/home";

In the example above, you can see that in the main.scss file, we import many other stylesheets (sass partials).

Here's another example, this time for vendor.scss:

//
// Includes/imports all third-party stylesheets used throughout the application.
// Should be loaded before the application stylesheets
//

// Nicolas Gallagher's Normalize.css
@import '../../jspm_packages/github/necolas/normalize.css@3.0.3/normalize.css'; // the path refers to the file at BUILD time

As you can see above, a third-party stylesheet is imported.

index.html

The index.html file is the entrypoint of your application. It is not mandatory per se, but when you run npm run serve, it'll be opened. Also, the html build task will try and replace/inject content in it.

Here's the minimal required contents for index.html (required for production builds with minification and bundling):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    ...
    <!-- Stylesheets -->
    <!-- build:css-vendor -->
    <link rel="stylesheet" href="styles/vendor.css">
    <!-- endbuild -->
    <!-- build:css-bundle -->
    <link rel="stylesheet" href="styles/main.css">
    <!-- endbuild -->
</head>
<body>

    <!-- build:js-app -->
    <!-- for production, this is all replaced by a minified bundle -->
    <script src="jspm_packages/system.src.js"></script>
    <script src="jspm.conf.js"></script>
    <script>
        System.import('core/core.bootstrap').catch(console.error.bind(console));
    </script>
    <!-- endbuild -->
</body>
</html>

In the above, the most important parts are:

  • for production, the contents of <!-- build:css-vendor --> ... <!-- endbuild --> will be replaced by the vendor bundle created by the build
  • for production, the contents of <!-- build:css-bundle --> ... <!-- endbuild --> will be replaced by the application's CSS bundle created by the build
  • for production, the contents of <!-- build:js-app --> ... <!-- endbuild --> will be replaced by the application's JS bundle created by the build

Also, note that during development, SystemJS is loaded (system.src.js), the JSPM configuration is loaded (jspm.conf.js) and SystemJS is used to load the entrypoint of the application (core/core.bootstrap).

Commands

Once you have added this build system to your project, you can list all the available commands using gulp help. The command will give you a description of each task. The most important to start discovering are:

  • gulp serve: start a Web server with live reload, watching files, transpiling, generating sourcemaps, etc
  • gulp serve-dist: same with the production version
  • gulp clean
  • gulp ts-lint: check TypeScript code quality/style
  • gulp check-js-quality: check JavaScript code quality
  • gulp check-js-style: check JavaScript code style
  • gulp prepare-test-unit: clean, compile and check quality/style
  • gulp test-unit: run unit tests using Karma (prereq: gulp prepare-test-unit

You can run the gulp -T command get an visual idea of the links between the different tasks.

Scripts

To make your life easier, you can add the following scripts to your package.json file. Note that if you have used the generator to create your project, you normally have these already:

  "scripts": {
    "tsc": "tsc",
    "typings": "typings",
    "clean": "gulp clean",
    "compile": "gulp",
    "build": "npm run compile && npm run test",
    "test": "gulp prepare-test-unit && gulp test-unit",
    "start": "npm run serve",
    "serve": "nodemon --watch gulpfile.js --watch gulpfile.babel.js --watch package.json --watch .jshintrc --watch .jscsrc --watch tsconfig.json --watch tslint.json --watch jspm.conf.js --exec gulp serve",
    "serve-dist": "nodemon --watch gulpfile.js --watch gulpfile.babel.js --watch package.json --watch .jshintrc --watch .jscsrc --watch tsconfig.json --watch tslint.json --watch jspm.conf.js --exec gulp serve-dist",
    "update": "npm install --no-optional && jspm update && jspm dl-loader && npm run typings-install",
    "outdated": "npm outdated",
    "help": "gulp help",
    "typings-install": "typings install",
    "setup": "npm install --no-optional && jspm install && jspm dl-loader && npm run typings-install"
  }

Options

The build can be customized by passing options. Defining options is done as in the following example gulpfile.babel.js:

"use strict";

import gulp from "gulp";

import build from "node-build-web-app";

let options = {};

options.distEntryPoint = "core/core.bootstrap";

Available options:

  • distEntryPoint
    • must be a relative path from .tmp/ to the file to use as entry point for creating the production JS bundle. The extension does not need to be specified (SystemJS is used to load the file)
    • by default, the following file is used: core/boot.js
  • minifyProductionJSBundle (default: true)
    • by default, the production JS bundle is minified
    • you can disable it by setting this option to false
  • mangleProductionJSBundle (default: true)
    • by default, the production JS bundle is mangled
    • you can disable it by setting this option to false
  • useJSPM (default: true)
    • by default, the production JS bundle is created using the JSPM API, which requires jspm configuration in your package.json
    • you can disable JSPM by setting this option to false
  • systemjsConfigurationFile (default: jspm.conf.js)
    • by default, if you disable JSPM usage by the build, it will expect to find 'jspm.conf.js' as your SystemJS configuration file
    • you can define this option to customize the file name
  • minifyProductionHTML (default: true)
    • by default, the production HTML is minified
    • you can disable it by setting this option to false
  • browserSync

FAQ

How can I inline some script in the production version of some HTML page?

  • add inline <path to the JS file> right above the script tag that you want to have inlined; this tells the gulp-inline-source plugin where to find the resource that should be inlined
  • add the inline attribute to the script tag itself: <script inline src="..."></script>

Example:

<!-- inline ../node_modules/angular2/bundles/angular2-polyfills.js -->
<script inline src="node_modules/angular2/bundles/angular2-polyfills.js"></script>

Note that the path specified in the <!-- inline ... comment is relative to the root of your project and NOT to the html file

Check out gulp-inline-source's documentation for more details.

Build dependencies

Contributing

  • Fork the project
  • Create a feature branch in your fork
  • Rebase if needed to keep the project history clean
  • Commit your changes & push to GitHub
  • Try and flood me with pull requests :)

Building from source

If you want to build from source, you need to:

  • install NodeJS and npm
  • install gulp: npm install --global gulp
  • clone this git repository
  • run npm run setup
  • run npm run build
  • start hacking :)

To clean, you can run npm run clean

Project configuration files

The project includes multiple configuration files. Here's some information about these:

  • gulpfile.babel.js: gulp's configuration file. This is where the build magic happens (more information: http://gulpjs.com/)
  • package.json: NPM's configuration file. This is where all dependencies are defined (more information: https://docs.npmjs.com/files/package.json)
  • npm-shrinkwrap.json: file created using npm shrinkwrap. Blocks dependency versions (including transitive ones), needed for build stability

Release Process

  • commit all changes to include in the release
  • edit the version in package.json
    • respect semver
  • update CHANGELOG.MD
  • commit
  • git tag
  • git push
  • git push --tags
  • draft the release on GitHub (add description, etc)
  • npm publish

Authors

Mark Small

License

This project and all associated source code is licensed under the terms of the MIT License.

Package Sidebar

Install

npm i angular-build

Weekly Downloads

1

Version

0.7.15

License

MIT

Last publish

Collaborators

  • aseales
  • jpinto
  • msmall
  • rvilla