0.7.23 • Published 7 years ago

window-pain v0.7.23

Weekly downloads
464
License
SEE LICENSE IN LI...
Repository
github
Last release
7 years ago

window-pain

For those with a node app that needs to be packaged as nuget and run on Windows as a service.


Features

  • Generates a nupkg containing your node app, without you having to create a nuspec file.
  • Bundles all your node module dependencies into the nupkg.
  • Bundles 32-bit and 64-bit versions of node.exe into the nupkg.
  • Generates install.cmd file that determines the correct node.exe to use on the target server, copies the package files to a target install location, installs your app as a Windows service, and optionally starts the service.
  • Generates an uninstall.cmd that stops the Windows service, uninstalls it, and deletes all nupkg files.
  • Generates an installConfig.cmd that pulls configuration for your app from a git repo and installs it on the target server.
  • Generates an deployment script for developer use to validate the build process has worked by deploying the app.

Caveats

  • You are responsible for publishing the nuget package to whatever artifact repository you are using.
  • Looks for node and other binaries in standard public repositories, but you can supercede these with your own sources if you want.
  • Assumes you nuget install the package into a temporary workspace (such as a Jenkins slave workspace).

Quick Start

Add a config.windowPain property to your to your package.json file as follows. Also note use of the npm script build, which servers to encapsulate the command line that runs the window-pain build script.

{
    "version": "0.0.0",
    "scripts": {"build": "node node_modules/window-pain/cli.js"},
    "devDependencies": {"window-pain": "latest"},
    "config": {
        "windowPain": {
            "nuget": {"name": "AppNugetPackageName"},
            "windowsService": {"name": "AppServiceName"},
            "install": {
                "target": {
                    "processorArchitecture32bit": "c:\\Program Files (x86)\\systems\\sys\\apps\\app\\code",
                    "processorArchitecture64bit": "c:\\Program Files\\systems\\sys\\apps\\app\\code"
                },
                "configTarget": {
                    "processorArchitecture32bit": "c:\\Program Files (x86)\\systems\\sys\\apps\\app\\_config",
                    "processorArchitecture64bit": "c:\\Program Files\\systems\\sys\\apps\\app\\_config"
                }
            }
        }
    }
}

Then run the following commands to build and install your app (this example assumes your app root c:\Development\app, your installation workspace is c:\Jenkins\workspace\apps, and you are on a 64 bit Windows). Note the version=1.2.3 command line argument, which is optional and if provided will override the version in package.json.

> cd c:\Development\app
> npm run build version=1.2.3
> cd c:\Jenkins\workspace\apps
> rd /S /Q AppNugetPackageName
> nuget.exe install AppNugetPackageName -Source c:\Development\app\build\nupkg -ExcludeVersion
> AppNugetPackageName\content\tools\install.cmd
> cd c:\Program Files\systems\sys\apps\app\code
> dir
> sc query "AppServiceName"

At this point the dir should show you your app root, and the sc query should show you the service with a state of running. Now unsinstall the app by running the following:

> cd c:\Jenkins\workspace\apps
> AppNugetPackageName\content\tools\install.cmd
> cd c:\Program Files\systems\sys\apps\app\code
> dir
> sc query "AppServiceName"

At this point the dir should return with something like "The system cannot find the path" because the files have been removed, and the sc query should return something like "The specified service does not exist".

Overview

Your app folder/file structure can be anything you want, except it must not have the following to folders at the app root: build, and dist. The build process creates a folder named build, and the install process creates a folder named dist. Your app will of course have a dependency on window-pain. In your package.json you need to provide some configuration data for window-pain.

Build Script

The script that does all the "heavy lifting" is implemented inside the window-pain module at window-pain/cli.js. The command line syntax is:

cli.js [version]

The optional version argument is a name=value pair where name is "version" and value is a sematic version number. If version is provided it will override the version porperty in package.json.

All you need to do to build a nuget package for you app is run the window-pain build script:

> node node_modules/window-pain/cli.js

Or, to override the package.json version:

> node node_modules/window-pain/cli.js version=1.2.3

The window-pain build script creates a folder named build in your app root folder, so your app must not have a folder named build. The nupkg will contain your app, plus all of its production node module dependencies, plus the binary dependencies needed for any node app running as a Windows service. The nupkg will also contain three Windows command scripts:

  • install.cmd, copies the node app files to a target location and installs the node app as a Windows service.
  • installConfig.cmd, install application configuration files from a GIT repository.
  • uninstall.cmd, uninstalls the node app (deletes the Windows service and removes the deployed files)

npm uninstall script

window-pain also has a script at window-pain/tools/npmUninstall.js to remove all your app's node module dependencies. The reason for this is to ensure the package you build only has depencencies included that are specified in your package.json.

> node node_modules/window-pain/tools/npmUninstall.js

Building on a "generic" windows server

The assumption here is that the build server has minimal tools, e.g., can run a windows batch file. So there is a script provided that will pull all the necessary tools onto the build server before running the Build Script. This script is named prebuild.bat, and is located in tools folder.

The prebuild.bat file takes one optional environment variable named primaryNuGetRepository. If set the script will look in that repository for the ConDel-Build package (which contains the means to pull all the build tools onto the generic windows server). If not specified then the script looks in the public NuGet repository.

You can run this script from any working directory because it sets the working directory to the app root (three levels up from the bat file location). This script is intended to be called from a bat file in the app itself. For example your continuous delivery/integration orchestrator would call a build.bat file such as:

REM this script file is in ./.build folder relative to app root.
@echo off
set primaryNuGetRepository=https://artifactory.mattersight.local/artifactory/api/nuget/NuGet-gallery
call ..\node_modules\window-pain\tools\prebuild.bat

package.json

In your app's package.json file include the window-pain module as a development dependency. To be able to build your app with an npm command (e.g., npm run build), in package.json create an npm script item that references the build script, and one that references the npm uninstall script. In the example below the npm script runs npm install to bring in all node dependencies then runs the build.js script.

{
	"scripts": {
		...
		"_npmUninstall": "node node_modules/window-pain/tools/npmUninstall.js",
		"build": "npm run _npmUninstall && npm install && node tools/build.js",
        ...
	},
	...
	"devDependencies": {
		...
		"window-pain": "^0.0.0",
	},
	...
}

Building the nuget package

To generate the nuget package go to the app root and run the npm script named build:

> npm run build version=1.2.3

Publishing the nuget package

The presumtion is that the built package will be published to an artifact/package repository of some sort. This step is to be done by whatever your continuous integration/delivery orchestrator is, or manually, or whatever.

deploy.cmd

This is a script provided as a tool for the developer to deploy a built package on outside of whatever the "production" build process/orchestrator is.

> app/build/nupkg/deploy.cmd

Installing the nuget package

nuget install

To install your app from the nuget package, first step is to get it from the artifact repository. Go to whatever your working folder is (e.g., c:\Jenkins\workspace\job), run nuget install:

> cd c:\Jenkins\workspace\job
> nuget.exe install app -source repository -ExcludeVersion

The nuget install simply uncompresses the nupkg into a folder in the parent folder (e.g., c:\Jenkins\workspace\job\app). Your app's files will all be under the app\content folder, as is standard for nuget packages.

install.cmd

Next step is to run the install command script install.cmd which is dynamically generated during npm run build for the application. The target path (and some other parameters) for the installation are hardcoded in it. Running install.cmd will will do following:

  • copies workspace app/content folder to target directory for 32 and 64 bit systems (e.g., C:\ProgramFiles\systems\system\apps\app\code). The target location is configured in your app's package.json file (config.windowPain.install.target).
  • determines which node.exe you need, 32 bit or 64 bit and copies the right node.exe to the dist folder in your target location.
  • copies nssm.exe and winsw.exe to the dist folder in teh target location.
  • installs the app as a Windows service, using 'winsw.exe' or nssm.exe' (according to command line switches).
  • optionally (according to command line '-Start'/'-NoStart' switch) starts the service.

Usage

install.cmd must be started from same directory in which nuget install command was run.

> app/content/tools/install.cmd [<switches>] [<parameters>]

See Install Application Phase for detailed description of switches and parameters

Example: Install, don't start the service, run as local user named test with password test

app/content/tools/install.cmd -NoStart . test test

installConfig.cmd

If you are using the concept of deployable configuration, windows-pain has created an installConfig.cmd that will deploy configuration from a Git repository. For more info please refer to Install Configuration Phase.


Lifecycle of window-pain Application

Typical lifecycle of the application built using window-pain consists of following phases:


Build Phase

Build Phase consists of several steps:

Starting the build

Build process is triggered by running cli.js script in root directory of window-pain package.

If window-pain is installed in the application as a root dependency, location of this script is node_modules/window-pain/cli.js relative to root application directory. Full command line to start the script from root application directory is:

node node_modules/window-pain/cli.js

To add ability to build the application using npm run build command line you need to add following linea into scripts section of application' package.json:

  ...
  "_npmUninstall": "node node_modules/window-pain/tools/npmUninstall.js",
  "build": "npm run _npmUninstall && npm install && npm run __build",
  "__build": "node node_modules/window-pain/cli.js",
  ...

Build configuration

Build phase configuration is based upon following config files and parameters: 0. configuration passed via command line as name=value pairs (version is only supported name) 0. configuration passed via command line using NODE_CONFIG parameter 0. config.windowPain section of package.json of window-pain module itself 0. config.windowPain section of package.json of the application

All the above sources of configuration are merged (earlier in above list supercede later in the list) to get the final configuration. So configuration passed from command line has the highest priority, while package config has the lowest priority.

  • command line version name=value pair overrides the app's package.json version property.
  • NODE_CONFIG overrides the config.windowPain property in the app and window-pain package.json files.

Examples

To set the version of the app (including the version of the nuget package) use the command line name=value pair:

npm run build version=1.2.3

To override any configuration parameter in the package.json files you may use the NODE_CONFIG command line option. Following examples represents how to disable strict SSL on the download of binary dependencies:

> npm run build -- --NODE_CONFIG="{\"download\":{\"strictSSL\":true}}"

Cleanup Step

Description

Content of following directories is cleaned during this step (relatively to project's root directory):

  • build/dependencies/
  • build/resources/
  • build/nupkg

Configuration

There are no configurable parameters for this step.

Download Step

Description

window-pain allows to automatically download files from external source to be used in a package.

By default it is configured to download following binaries:

filenameversionDescription
node.exe4.2.6Node.js main binary version
nuget.exe3.3.0Utility to manage Nuget packages
winsw.exe1.18utility used to manage Windows services
nssm.exe2.24alternate utility to manage Windows services

Configuration

All the options including URLs and filenames are configured in download section of the config.

Parameters:

overwrite
  • [boolean=true]
    If true, overwrite file if already exist, if false, skip download if file already exists.
strictSSL
  • [boolean=true]
    If false, disables server certificates check. Useful when servers certificates are expired or not recoginzed by Node. Setting it to false lowers security and may result in downloading of maliciouos software. Use wtih care.
dest
  • string Base directory to save downloaded files
files
  • Object[] Array of file download definitions

Each file download definition consists of following properties:

  • name - string - filename to save downloaded file (overrides server-provided filename);
  • path - string - subdirectory of base directory to download controlled by dest parameter to place downloaded file;
  • url || urls - string || string[] - single url to download ot array of urls (try to download from first one, if fails then try second etc)

Example:

  "download": {
    "overwrite": true,
    "strictSSL": true,
    "dest": "build/bin",
    "files": [
      { "url": "https://repo.jenkins-ci.org/releases/com/sun/winsw/winsw/1.18/winsw-1.18-bin.exe",
        "path": "winsw",
        "name": "winsw.exe"
      },
      { "url": "https://nodejs.org/dist/v4.2.6/win-x64/node.exe",
        "path": "v4.2.6/win-x64"
        "name": "node.exe"
      },
      { "url": "https://nodejs.org/dist/v4.2.6/win-x86/node.exe",
        "path": "v4.2.6/win-x86"
        "name": "node.exe"
      },
      { "url": "https://dist.nuget.org/win-x86-commandline/v3.3.0/nuget.exe",
        "path": "nuget"
        "name": "nuget.exe"
      },
      { "url": "https://nssm.cc/release/nssm-2.24.zip",
        "path": "nssm"
        "name": "nssm-2.24.zip"
      }
    ]
  },

Unzip Step

Description

During this step window-pain automatically uncompresses zipped files

Configuration

Options for this phase are configured in unzip section of the config.

src
  • string base directory for zipped files
dst
  • string base directory to output unzipped files
files
  • Object[] Array of object containing source and target pathnames.

Properties of each object are following:

  • src - source pathname
  • dst - destination pathname

Example:

  "unzip": {
    "src": "build/bin",
    "dest": "build/bin",
    "files": [
      {
        "src": "nssm/nssm-2.24.zip",
        "dest": "nssm"
      }
    ]
  },

Update Version Step

Description

window-pain allows autoincrement version patch number in package.json file (disabled by default).

Configuration

Options for this phase are configured in version section of the config.

autoincrement
  • [boolean=false] Setting this parameter to true to enables automatic increment version patch number on each build. Note. As this operation modifies the source files (package.json), the changes must be later commit-ed and push-ed to Git repository or revert-ed.
backup
  • string Directory to backup package.json file before modification.

Example

  "version": {
    "autoincrement": false,
    "backup": "build/backup"
  },

Prepare to install npm dependencies

Description

This step is inteded to prepare environment to install dependencies listed as production in project's package.json from NPM repositroy into separate directory.

This step copies project's package.json into build/dependencies/ directory.

Configuration

There are no configurable parameters for this step.

Install npm dependencies

Description

This step is inteded to install dependencies listed as production in project's package.json from NPM repository into separate directory.

This step executes npm install --production in build/dependencies/ directory.

Configuration

There are no configurable parameters for this step.

Prepare resource files step

Description

This step is intended to prepare different scripts and configuration files to be bundled into nupkg for use on further lifecycle phases. Main purpose is to set proper file locations and options.

There are two types of files handled by this step:

  • configuration files - (i.e. Package.nuspec)
  • start script - i.e. entry point for main script

Each file is prepared basing on template. Templates are written using EJS syntax. Inside the template following namespaces are accessible:

  • appPkg - application' package.json
  • pkgPkg - window-pain module' package.json
  • config - actual configuration merged from command line, window-pain package.json, application' package.json.

All the templates are located in subdirectories of app/templates/ directory:

  • app/templates/resources/ - configuration files (Package.nuspec)
  • app/templates/tools/ - scripts (install.cmd, pullConfig.cmd, installConfig.cmd, uninstall.cmd)

Result files are used by next steps of the build phase and expected to be located in corresponding build/templates/resources and build/template/tools directories.

Following file are handled:

  • Package.nuspec - Nuspec file for the nuget utility. It defines all the files to be put into nupkg and their corresponding source and target locations;
  • install.cmd - start script to run main installation scripts;
  • pullConfig.cmd - start script to run scripts to pull additional config;
  • installConfig.cmd - start script to run scripts to install additional config;
  • uninstall.cmd - start script to run main uninstall scripts.

Configuration

Options for this phase are configured in resources section of the config. This section has the only property templates containing array of the definitions to prepare the files. Each definition consists of following properties:

  • src - string - pathname of the template
  • target - string - pathname of resulting file

NOTE. Some other sections of config, application package.json and window-pain package.json maybe be used inside a templates. Examples are:

  • files section is used when generating Package.nuspec
  • install section is used when generating install.cmd, installConfig.cmd, pullConfig.cmd, uninstall.cmd. Please, refer to Install Application Phase and Install Config Phase section of this readme for more info.
  • version parameter is used when generating Package.nuspec As whole content of application package.jsonand window-pain module' package.json are passed to the templates, any other parameters also may be involved in template processing.

Example

  "resources": {
    "templates": [
      {
        "src": "app/templates/resources/Package.nuspec",
        "target": "build/templates/resources/Package.nuspec"
      },
      {
        "src": "app/templates/tools/install.cmd",
        "target": "build/templates/tools/install.cmd"
      },
      {
        "src": "app/templates/tools/installConfig.cmd",
        "target": "build/templates/tools/installConfig.cmd"
      },
      {
        "src": "app/templates/tools/pullConfig.cmd",
        "target": "build/templates/tools/pullConfig.cmd"
      },
      {
        "src": "app/templates/tools/uninstall.cmd",
        "target": "build/templates/tools/uninstall.cmd"
      }
    ]
  },

Pack nupkg step

Description

This step is intended to bundle all the files prepared on previous steps into single nupkg. To make a nupkg it runs nuget.exe utility downloaded in Download Step. nuget.exe utility builds nupkg package basing on the parameters passed through command line and configuration in file Package.nuspec.

For more info on nuget.exe utility please refer to official Nuget docs: https://docs.nuget.org/

Configuration

name
  • string Path to output nupkg file.
nupkgPath
  • string Path to output nupkg file.
exe
  • string Path to nuget.exe utility.
nuspec
files
  • object[] Array of file definition objects

Properties of each object are following:

  • src - source pathname files pattern
  • target - target pathname
  • exclude - exclude pathname pattern For more info on these parameters please refer to official Nuget docs: https://docs.nuget.org/ Parameter files is inserted into Package.nuspec during Prepare resource files step and is used by nuget.exe utility.
exclude
  • string[] List of additional files/directories to exclude.
excludeDevDependencies
  • boolean DEPRECATED Exclude all files listed in devDependencies section of application's package.json. Usage not recommended.

Example

  "nuget": {
    "name": null,
    "nupkgPath": "build\\nupkg",
    "exe": "build\\bin\\nuget\\nuget.exe",
    "nuspec": "build\\templates\\resources\\Package.nuspec",
    "files": [
      {
        "src": "..\\..\\..\\**\\*.*",
        "target": "content",
        "exclude": "..\\..\\..\\.git\\**;..\\..\\..\\build\\**;..\\..\\..\\log\\**;..\\..\\..\\tmp\\**;..\\..\\..\\coverage\\**;..\\..\\..\\test\\**;..\\..\\..\\node_modules\\**"
      },
      {
        "src": "..\\..\\..\\build\\dependencies\\node_modules\\**\\*.*",
        "target": "content\\node_modules"
      },
      {
        "src": "..\\..\\..\\build\\bin\\**\\*.*",
        "target": "content\\dist"
      },
      {
        "src": "..\\..\\..\\build\\templates\\tools\\**\\*.*",
        "target": "content\\tools"
      },
      {
        "src": "..\\..\\..\\node_modules\\window-pain\\app\\source\\**\\*.*",
        "target": "content"
      }
    ],
    "exclude": [],
    "excludeDevDependencies": false
  },

Install Nupkg Phase

By default, the nupkg generated by window-pain is located in build\nupkg directory (relative to application root)

To install nupkg following command to be run from command line:

nuget.exe install CompanyName.%app_name% -source "%app_src_dir%\build\nupkg" -ExcludeVersion

Example for ApplicationName:

nuget.exe install CompanyName.ApplicationName -source c:\app\ApplicationName\build\nupkg" -ExcludeVersion

This command will unpack the nupkg into current directory.

Pull Config Phase

Prerequisites: Nupkg is installed into local directory

To pull the additional config following command to be run from command line:

pullConfig.cmd" <env_name> <git_repo_name> <repo_url> <repo_branch_or_tag> <repo_username> <repo_password>
- env_name           - 'environment name` i.e. subdirectory to use inside the repository 
- repo_name          - Git repository name
- repo_url           - Git repository URL
- repo_branch_or_tag - Git repository Branch or Tag name
- repo_username      - Git repository username
- repo_password      - Git repository password
CALL "%nuget_install_dir%\content\tools\pullConfig.cmd" "%env_name%" "%git_repo_name%" "github.com/CompanyName/config.git" "%git_branch_or_tag%" "%git_username%" %repo_pass%

Install Application Phase

install.cmd utility is used to install the application to target directory, install it as windows service and optionally to start.

install.cmd

Usage:

install.cmd [<switch> ...] [<domain> <user> <password>]

install.cmd has following optional switches:

  • -NoStart || -Start - windows service autostart option (default is to start). When using installConfig.cmd to install additional config, you must use -NoStart to avoid starting with inappropriate config.
  • -winsw || -nssm - windows service control utility (deafault is winsw)
  • -robocopy || -xcopy - file copy utility to use (default is robocopy)

install.cmd has following optional parameters:

  • - optional, user domain for the service
  • - optional, user name for the service
  • - optional, user password for the service

Example:

CALL "%nuget_install_dir%\content\tools\install.cmd"

Configuration

During Prepare resource files step following parameters from install section of the config are used to set destination paths for the application

      "install": {
        "target": {
          "processorArchitecture32bit": "c:\\target",
          "processorArchitecture64bit": "c:\\target"
        },
        "configTarget": {
          "processorArchitecture32bit": "c:\\target",
          "processorArchitecture64bit": "c:\\target"
        }
      }

Install Config Phase

Description

During Install Config Phase you can install additional config (usually pulled from Git repository during Pull Config Phase) which may have priority over default application config. Also, this config will not be touched by install.cmd and uninstall.cmd scripts and and will survive application upgrade. Example for the locations are:

  • Code directory: C:\Program Files\apps\approot\code
  • App directory: C:\Program Files\apps\approot
  • Outside config directory (must not be touched by install/uninstall):C:\Program Files\apps\approot\_config
  • Inside config directory (must be created/removed during install/uninstall): C:\Program Files\apps\approot\code\_config Root application directory may also depend on 32/64 system type (e.g. c:\Program Files (x86)\ and C:\Program Files\ and is controlled by install.target.* and install.configTarget.* parameters of the config (see below).

installConfig.cmd command line parameters

installConfig.cmd" <env_name> <git_repo_name> <repo_url> <repo_branch_or_tag> <repo_username> <repo_password>
- env_name           - 'environment name` i.e. subdirectory to use inside the repository 
- repo_name          - Git repository name
- repo_url           - Git repository URL
- repo_branch_or_tag - Git repository Branch or Tag name
- repo_username      - Git repository username
- repo_password      - Git repository password
CALL "%nuget_install_dir%\content\tools\installConfig.cmd" "%env_name%" "%git_repo_name%" "github.com/CompanyName/config.git" "%git_branch_or_tag%" "%git_username%" %repo_pass%

Configuration

During Prepare resource files step following parameters from install section of the config are used to set destination paths for the application

      "install": {
        "target": {
          "processorArchitecture32bit": "c:\\target",
          "processorArchitecture64bit": "c:\\target"
        },
        "configTarget": {
          "processorArchitecture32bit": "c:\\target",
          "processorArchitecture64bit": "c:\\target"
        }
      }

Usage Phase

...

Uninstall Application Phase

Following command tries to stop the serves and removes the installed application.

CALL "%nuget_install_dir%\content\tools\uninstall.cmd"

This command does not removes the installed nupkg.

Remove Nupkg Phase

To remove nupkg you need to manually delete its directory. Following command may be used:

RMDIR /S /Q "%nuget_install_dir%"

Change Log

version 0.7.23

  1. Switch from node v6.11.2 to node v6.11.3

Prior versions

  1. Determines latest patch level from the configured artifact repository
  2. Removed prebuild.bat script
  3. Fixed working directory location in deploy.cmd script
  4. Added check for lock on target directory to install.cmd
  5. Added script for manual install
  6. Minor change to install script display
  7. Improved _uninstall.cmd
  8. Robocopy more strict; return code must be 1 rather than less than 8
  9. Clean up the _install and _uninstall scripts
  10. Renaming install.cmd to install.code.cmd, same pattern for others
  11. Forgot to include pull.config.cmd
  12. Changing references to new script names
  13. Check that service is not stopped before stopping. This enables hard failure on error.
  14. Improve error checking on install scripts
  15. Switch administrator privileges check from NET SESSION to OPENFILES
  16. Stop logging npm install output
  17. Build using version 0.0.0 when unable to connect with artifact repository
0.7.23

7 years ago

0.7.22

7 years ago

0.7.21

7 years ago

0.7.20

7 years ago

0.7.19

7 years ago

0.7.18

7 years ago

0.7.17

7 years ago

0.7.16

7 years ago

0.7.15

7 years ago

0.7.14

7 years ago

0.7.13

7 years ago

0.7.12

7 years ago

0.7.11

7 years ago

0.7.10

7 years ago

0.7.9

7 years ago

0.7.8

7 years ago

0.7.7

7 years ago

0.7.6

7 years ago

0.7.5

7 years ago

0.7.4

7 years ago

0.7.3

7 years ago

0.7.2

7 years ago

0.7.1

7 years ago

0.7.0

7 years ago

0.6.0

7 years ago

0.5.0

7 years ago

0.4.0

7 years ago

0.3.37

8 years ago

0.3.36

8 years ago

0.3.35

8 years ago

0.3.34

8 years ago

0.3.33

8 years ago

0.3.32

8 years ago

0.3.31

8 years ago

0.3.30

8 years ago

0.3.29

8 years ago

0.3.28

8 years ago

0.3.27

8 years ago

0.3.26

8 years ago

0.3.25

8 years ago

0.3.24

8 years ago

0.3.23

8 years ago

0.3.22

8 years ago

0.3.21

8 years ago

0.3.20

8 years ago

0.3.19

8 years ago

0.3.18

8 years ago

0.3.17

8 years ago

0.3.16

8 years ago

0.3.15

8 years ago

0.3.14

8 years ago

0.3.13

8 years ago

0.3.12

8 years ago

0.3.11

8 years ago

0.3.10

8 years ago

0.3.9

8 years ago

0.3.8

8 years ago

0.3.7

8 years ago

0.3.6

8 years ago

0.3.5

8 years ago

0.3.4

8 years ago

0.3.3

8 years ago

0.3.2

8 years ago

0.3.1

8 years ago

0.3.0

8 years ago

0.2.2

8 years ago

0.2.1

8 years ago

0.2.0

8 years ago

0.1.27

8 years ago

0.1.26

8 years ago

0.1.25

8 years ago

0.1.24

8 years ago

0.1.23

8 years ago

0.1.22

8 years ago

0.1.21

8 years ago

0.1.20

8 years ago

0.1.19

8 years ago

0.1.18

8 years ago

0.1.17

8 years ago

0.1.16

8 years ago

0.1.15

8 years ago

0.1.14

8 years ago

0.1.13

8 years ago

0.1.12

8 years ago

0.1.11

8 years ago

0.1.10

8 years ago

0.1.9

8 years ago

0.1.8

8 years ago

0.1.7

8 years ago

0.1.6

8 years ago

0.1.5

8 years ago

0.1.4

8 years ago

0.1.3

8 years ago

0.1.2

8 years ago

0.1.1

8 years ago

0.1.0

8 years ago

0.0.18

8 years ago

0.0.17

8 years ago

0.0.16

8 years ago

0.0.15

8 years ago

0.0.14

8 years ago

0.0.13

8 years ago

0.0.12

8 years ago

0.0.11

8 years ago

0.0.10

8 years ago

0.0.9

8 years ago

0.0.8

8 years ago

0.0.7

8 years ago

0.0.6

8 years ago

0.0.5

8 years ago

0.0.4

8 years ago

0.0.3

8 years ago

0.0.2

8 years ago

0.0.1

8 years ago

0.0.0

8 years ago