Builds node app into nuget packaged windows services
npm install window-painjson
{
"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.
`js
{
"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
`
$3
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.
$3
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
$3
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.
$3
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 [] []
`
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
`
$3
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
- Install Nupkg Phase
- Pull Config Phase
- Install Application Phase
- Install Config Phase
- Usage Phase
- Uninstall Application Phase
- Remove Nupkg Phase
--------------------------------------------------------------------------------
Build Phase
Build Phase consists of several steps:
- Cleanup Step
- Download Step
- Unzip Step
- Update Version Step
- Prepare to install npm dependencies step
- Install npm dependencies step
- Prepare resource files step
- Pack nupkg step
$3
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:
`sh
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",
...
`
$3
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}}"
`
$3
#### 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.
$3
#### 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:
filename | version | Description
------------|--------:|------------------------------------------
node.exe | 4.2.6 | Node.js main binary version
nuget.exe | 3.3.0 | Utility to manage Nuget packages
winsw.exe | 1.18 | utility used to manage Windows services
nssm.exe | 2.24 | alternate 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"
}
]
},
`
$3
#### 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"
}
]
},
`
$3
#### 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"
},
`
$3
#### 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.
$3
#### 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.
$3
#### 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"
}
]
},
`
$3
#### 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 " class="text-primary hover:underline" target="_blank" rel="noopener noreferrer">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
- string
Path to Package.nuspec file prepared in Prepare resource files step.
##### 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 - 'environment name i.e. subdirectory to use inside the repository
`
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.
$3
Usage:
`
install.cmd [ ...] [ ]
`
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
$3
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).
$3
`
installConfig.cmd"
`
`
- env_name - 'environment name i.e. subdirectory to use inside the repository
`
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%"
``