Deployment
Wins
- Manifest-driven application packaging requires no additional tooling.
- Built-in deployments via
tibet build
,tibet release
andtibet deploy
. - Pre-configured support for running
shipit
-based deployments. - Fully extensible via
tibet deploy
command plugins ortibet make
. - Integrated test, build, deploy support directly from TIBET Lama™.
Contents
Concepts
Cookbook
- installing
shipit
- deploying via
shipit
- advanced
shipit
deployment - deploying to cloud PAAS environments via
docker
- deploying via
electron-builder
- deploying via
tibet make
- extending
tibet deploy
Code
Concepts
Building
TIBET projects support building (packaging and minification) out of the box using TIBET-specific tooling optimized for creating deployable TIBET applications that conform to your requirements.
Unlike systems which rely on source code analysis TIBET leverages the same application package files it uses to define your application for loading, testing, and other tasks. By using a common package description format TIBET lets you define exactly how you want your application be configured in a single, consistent fashion regardless of the task at hand.
When you launch a TIBET application the TIBET Loader will
automatically attempt to load a "built" project configuration. In other words,
launching via a URL such as http://127.0.0.1:1407
will try to find and load a
built package. When you first create a new project this package won't exist
until the first time you execute tibet build
for that project.
Once you've built your project at least once TIBET will load the last built
package for your project unless you provide it with boot parameters to target a
more dynamic development configuration. This is, in fact, why development with
TIBET usually happens from a URL of the form:
http://127.0.0.1:1407#?boot.profile=development
.
Below is the output of tibet build
after working through the TIBET Quickstart
and TIBET Essentials guides. We specify --minify
here for the build so that it
can load properly using a 'production target' configuration (which tries to load
.min
files by default):
$ tibet build --minify
Delegating to 'tibet make build'
building app...
checking for lint...
checked 13 of 15 total files
(2 filtered, 0 unchanged)
0 errors, 0 warnings. Clean!
verifying packages...
verifying ~app_cfg/main.xml@base
scanning for unresolved/unlisted files...
no unresolved files found in project.
processing resources...
generating resources...
# Loading TIBET platform at 2021-04-20T02:02:07.640Z
# TIBET reflection suite loaded and active in 7142ms
filtering 2 computed and 9 specified resources...
building 11 concrete resources...
writing package resource entries...
<script src="~app_build/app_tags-APP.helloworld.app-APP.helloworld.app.xhtml.js"/> (added)
<script src="~app_build/app_tags-APP.helloworld.app-APP.helloworld.app.css.js"/> (added)
<script src="~app_build/app_styles-app.css.js"/> (added)
<script src="~app_build/app_styles-index.css.js"/> (added)
<resource href="~app_media/apple-touch-icon-precomposed.png" no-inline="true"/> (added)
<resource href="~app_media/apple-touch-icon.png" no-inline="true"/> (added)
<resource href="~app_media/favicon.ico" no-inline="true"/> (added)
<script src="~app_build/app_dat-keyrings.xml.js"/> (added)
<script src="~app_build/app_dat-vcards.xml.js"/> (added)
New configuration entries created. Review/Rebuild as needed.
rolling up assets...
writing 10447 chars to ~app_build/app_base.js
writing 5172 chars to ~app_build/app_base.min.js
linking build targets...
skipping link for missing file '~app_build/app_base.min.js.gz'
skipping link for missing file '~app_build/app_base.min.js.br'
build assets linked successfully.
building project documentation...
processing helloworld-sample.1
Task complete: 12640ms.
As you can see, tibet build
invokes the TIBET Loader to load your application
in a headless context, reflect on it for resource utilization information, and
then use that information to not only bundle your source code but also any
styles, images, and other resources it can detect.
Experimental features of this load/reflect process include running your tests while maintaining a list of all functions invoked, allowing future `tibet build` processing to optionally strip your application package to the minimum size possible...only the code you actually invoke.
The output of the build process is a set of files specific to your application code. This is the "app" portion of TIBET's "app vs. lib" separation. There's nothing special to do to get TIBET to factor out lib from application code.
Building A Non-Default Target
Sometimes you need to build a 'package@config' combination that's not the
default (main@base
). In that case you can use --profile=
to provide both or
you can use --package
and/or --config
to define what you need.
In the example below we tell tibet build
we want to use the baseui
target so
we can pull the xctrls
namespace files into our built package:
$ tibet build --minify --config=baseui
Delegating to 'tibet make build'
building app...
checking for lint...
checked 0 of 24 total files
(8 filtered, 16 unchanged)
0 errors, 0 warnings. Clean!
verifying packages...
verifying ~app_cfg/main.xml@baseui
scanning for unresolved/unlisted files...
no unresolved files found in project.
processing resources...
generating resources...
# Loading TIBET platform at 2021-04-20T02:04:13.165Z
# TIBET reflection suite loaded and active in 6923ms
filtering 2 computed and 9 specified resources...
building 11 concrete resources...
writing package resource entries...
rolling up assets...
writing 10447 chars to ~app_build/app_baseui.js
writing 5172 chars to ~app_build/app_baseui.min.js
linking build targets...
skipping link for missing file '~app_build/app_baseui.min.js.gz'
skipping link for missing file '~app_build/app_baseui.min.js.br'
build assets linked successfully.
building project documentation...
Task complete: 11327ms.
Releasing
TIBET projects also support releasing (project version stamping and git branch
tagging) out of the box using two commands: tibet version
and tibet release
.
These two commands support a standard pattern for managing project versioning
and releases. The tibet version
command updates internal project files
(package.json
and others) with the latest versioning information using the
semver versioning standard. The tibet release
command
manages git branches to perform a release.
Let's see how this works in practice by trying with a new project.
Versioning
When you clone a TIBET project, it automatically sets the project version at
0.1.0
.
tibet clone testproject
cd testproject
tibet init
`
The package.json file now has a version of 0.1.0
. Before we bump the version,
we need to make sure our project has been initialized with git
and that at
least one commit has been done. This is because the tibet version
command uses
git
metadata.
git init
git add .
git commit -m "Initial commit"
Now that we have an initial commit, we can use the tibet version
command.
tibet version --patch --suffix final
Set version 0.1.1 (+g2ad61f88d4.1618882437935) ? Type 'yes' to continue:
This command will bump the semver
'patch' level and will append no suffix to
the version ('final'). The command can also bump the 'major' and 'minor levels
via the --major
and --minor
flags and can append different suffixes like
'beta' and 'rc' by using different values for the --suffix
flag. See the man
page for tibet version
for more information on all of the flags and values.
If you type 'yes' to the above, TIBET stamps the new version number in 4 different files in the system. These are used by various parts of the TIBET machinery to keep your app up-to-date. It is a good idea to commit these as well:
git add package.json public/TIBET-INF/cfg/testproject.xml public/TIBET-INF/cfg/main.xml public/src/templates/version.js
git commit -m "Bumped patch version for release"
Building a package for release
TIBET supports minification, gzipping and brotliing your application using the
tibet build
command. While you can specify individual flags --minify
,
--zip
and --brotli
, tibet build
also supports a --release
convenience
flag:
tibet build --release
Release branch management
TIBET's authors have found that developing on one branch and releasing on
another is a great way to keep things organized. The tibet release
command
codifies this into a convenient command.
tibet release
While specifics about how this command works can be found in its man page, here's a quick summary:
Verifies that the current branch is the 'source' branch. The source branch defaults to
develop
- override by setting thecli.release.source
config property in your project. See TIBET Configuration.Verifies that the source branch is not dirty (can override with the
--dirty
flag).Verifies that the source branch is up-to-date with its remote (can override with the
--local
flag). The remote defaults toorigin
- override by setting thecli.release.remote
config property in your project.Runs
tibet lint --clean
to lint the codebase. This uses tools such aseslint
andstylelint
to lint your application's codebase.Tags the source branch using
{tag}-{branchname}
. Pushes this to the{remote}/{branchname}
.Checks out the 'target' branch. The target branch defaults to
master
- override by setting thecli.release.target
config property in your project.Verifies that the target branch is up-to-date with its remote.
Merges the source branch into the target branch.
Commits the changes to the target branch.
Tags the commit with the release tag (the value TIBET computed using the
tibet version
command above).Pushes the commit and related tag to the remote.
Checks out the source branch.
Deploying
In TIBET the process of deployment is triggered by invoking the tibet deploy
command from either the CLI or the TIBET Lama.
TIBET's deployment process does not do a build first, it simply invokes whatever
deployment helper you target. TIBET supports shipit
out of the box. It also
supports deployment for Dockerized container systems like AWS Elastic Beanstalk
and Azure WebApps as well as deployment of versioned Electron applications.
To trigger deployment from the command line use something like:
# tibet deploy {helper} [options]
$ tibet deploy shipit '{"environment":"development"}'
In the prior command line we're asking tibet deploy
to leverage the shipit
deployment helper and to provide it with the development
environment target.
The result in that specific case is invocation of the following command line to
activate shipit
(provided it's installed):
shipit deploy development
You can also leverage tibet make
by invoking tibet deploy make
assuming
you've updated your project's _deploy.js
make target. See the discussion on
deploying via tibet make
for more information.
For specific deployment commands see the cookbook entries which follow.
Cookbook
Installing Shipit For Your Project
TIBET projects don't include shipit
functionality by default. You can fix this
quickly by adding shipit-cli
and shipit-deploy
to your project via npm.
$ npm install --save-dev shipit-cli
$ npm install --save-dev shipit-deploy
Once you have installed the shipit-cli
and shipit-deploy
modules you need to
add a shipitfile.js
file to your project.
Here's a sample shipitfile.js
for "deploying" to local machine's /tmp
directory:
module.exports = function(shipit) {
require('shipit-deploy')(shipit);
shipit.initConfig({
default: {
workspace: '/tmp/tibet',
deployTo: '/tmp/deployed-tibet',
repositoryUrl: 'https://github.com/CodeRats-LLC/TIBET.git',
ignores: ['.git', 'node_modules'],
rsync: ['--del'],
keepReleases: 3,
key: '~/.ssh/id_rsa',
shallowClone: true
}
development: {
servers: 'ss@localhost'
}
});
};
You'll want to review the shipit documentation for details on how to configure your specific environments and targets for shipit.
Deploying via shipit
TIBET includes built-in support for invoking targets in a project's
shipitfile.js
file provided such a file exists (and you have installed
shipit
for your project).
All you need to do is invoke tibet deploy
and provide it with shipit
as the
deployment helper along with any parameters you want to pass to shipit
itself.
If you've configured the sample file we provided in the previous "installing shipit" cookbook entry you can try the following:
$ tibet deploy shipit '{"environment":"development"}'
As you create specific targets (environments) for shipit you can deploy to them
quickly and easily from the tibet
command line.
Advanced shipit
deployment
Here is an example of deployment using shipit
in an advanced way that
leverages shipit
events and TIBET's build, version, release cycle to
automatically do project builds, versioning and release branch management when
you deploy:
For the purposes of this discussion assume these values:
Application name: testapp
Remote Domain: mycorp.com
GitHub project name: testapp_proj
Local login name: localuser
Remote login name: remoteuser
Remote server name: remoteserver
Also, assume that the remote system manages a daemon process called tds
with
systemctl
.
Here's the sample shipitfile.js
for this scenario:
module.exports = function (shipit) {
require('shipit-deploy')(shipit);
shipit.initConfig({
default: {
workspace: '/tmp/testapp_deploy',
deployTo: '/var/www/mycorp.com/app',
repositoryUrl: 'https://github.com/mycorp/testapp_proj.git',
ignores: ['.git', 'node_modules'],
keepReleases: 2,
deleteOnRollback: false,
key: '~/.ssh/id_rsa.pub',
shallowClone: true,
branch: 'master'
},
development: {
servers: 'localuser@localhost'
}
production: {
servers: 'remoteuser@remoteserver.mycorp.com'
}
});
// At the beginning of the deployment process, build the release,
// generate a version and stamp it into the proper files and commit
// commit those files to the repo.
shipit.blTask('testapp-deploy',
async function() {
var localCmd;
localCmd =
'tibet build --release; ' +
'tibet version --patch --suffix final; ' +
'git add package.json public/TIBET-INF/cfg/testapp.xml public/TIBET-INF/cfg/main.xml public/src/templates/version.js; ' +
'git commit -m "Bumped patch version for release"; ' +
'git push;';
await shipit.local(localCmd).then(function(res) {
console.log(res[0].stdout);
});
});
// When the repository is fetched, shut down the remote server
// This example assumes that the remote server is a *nix server
// with a 'tds' service defined.
shipit.blTask('testapp-fetched',
async function() {
var remoteCmd;
remoteCmd =
'systemctl stop tds; ';
await shipit.remote(remoteCmd).then(function(res) {
console.log(res[0].stdout);
});
});
// When the repository is published, re-initialize the TIBET project so
// that we have the latest NodeJS modules.
shipit.blTask('testapp-published',
async function() {
var remoteCmd;
remoteCmd =
'cd /var/www/mycorp.com/app; ' +
'tibet init --force; ';
await shipit.remote(remoteCmd).then(function(res) {
console.log(res[0].stdout);
});
});
// The repository contents are deployed. Restart the server.
shipit.blTask('testapp-deployed',
async function() {
var remoteCmd;
remoteCmd =
'systemctl start tds; ';
await shipit.remote(remoteCmd).then(function(res) {
console.log(res[0].stdout);
});
});
shipit.on('deploy', function() {
shipit.start('testapp-deploy');
});
shipit.on('fetched', function() {
shipit.start('testapp-fetched');
});
shipit.on('published', function() {
shipit.start('testapp-published');
});
shipit.on('deployed', function() {
shipit.start('testapp-deployed');
});
};
Deploying to cloud PAAS environments via Docker
TIBET includes built-in support for Dockerized applications. It includes a Dockerfile that installs an up-to-date NodeJS image as well as the parts of TIBET required to run an application.
Additionally, tibet deploy
provides out-of-the-box support for deploying and
running Dockerized TIBET applications in two PAAS environments: AWS Elastic
Beanstalk and Azure WebApps.
- configure the
tds.json
file with the required parameters for the particular PAAS environment being targeted. These entries should go under thedeploy
section which should be placed directly under the desired environment (or thedefault
environment settings if they apply to all environments).
Here is a set of settings for AWS Elastic Beanstalk:
"deploy": {
"awselasticbeanstalk": {
"profile": "development",
"region": "us-east-1",
"appname": "TIBETAWSEBSTest",
"solutionstack": "64bit Amazon Linux 2018.03 v2.16.2 running Docker 19.03.13-ce"
}
}
And here is a set of settings for Azure Webapps:
"deploy": {
"azurewebapps": {
"username": "info@coderats.llc",
"resourcegroupname": "TIBETAzureTestResourceGroup",
"resourcegrouplocation": "Central US",
"containerregistryname": "TIBETAzureTestContainerRegistry",
"containerregistrysku": "Basic",
"appserviceplanname": "TIBETAzureTestPlan",
"appservicesku": "B1",
"appname": "TIBETAzureTest"
}
}
More information about these parameters can be found in the documentation for the respective PAAS services.
- After these settings have been added to the
tds.json
, perform following steps inside of the project directory:
tibet build --release
tibet version --patch --suffix final
git add package.json public/TIBET-INF/cfg/.xml public/TIBET-INF/cfg/main.xml public/src/templates/version.js
git commit -m "Bumped patch version for release"
git push
For more information about these steps, see the Releasing section of this document.
- Run the
tibet deploy
command with the proper subcommand matching the PAAS service that the app will be deployed to:
tibet deploy awselasticbeanstalk
OR
tibet deploy azurewebapps
Deploying via electron-builder
Customized versions of tibet build
and tibet deploy
in TIBET Desktop
projects automatically leverage the electron-builder
package, which is
included as a dependency in electron
projects.
- Configure the
tibet.json
file with the required parameters. These entries should go under thedeploy
section. Currently, the Election version oftibet deploy
supports two environments for deployment of Electron builds to two different kinds of repositories: S3 buckets and GitHub Releases.
Here is a set of settings for S3 buckets:
"deploy": {
"electron": {
"updateURL": "https://helloelectron.s3.amazonaws.com",
"provider": "s3",
"s3": {
"bucket": "helloelectron",
"region": "us-east-1"
}
}
}
And here is a set of settings for GitHub Releases:
"deploy": {
"electron": {
"updateURL": "https://github.com/MyOrganization/MyApp.git",
"provider": "github",
"github": {
"token": "<YOUR_GITHUB_ACCESS_TOKEN>",
}
}
}
- After these settings have been added to the
tds.json
, perform following steps inside of the project directory:
tibet build --release
tibet version --patch --suffix final
git add package.json public/TIBET-INF/cfg/.xml public/TIBET-INF/cfg/main.xml public/src/templates/version.js
git commit -m "Bumped patch version for release"
git push
For more information about these steps, see the Releasing section of this document.
- We also freeze a copy of TIBET into the project to make it self-contained. We
need to specify both the
--standalone
and--source
flags here to get both the 'server' code and the source code for TIBET.
tibet freeze --standalone --source
Go to the
./etc/
directory in the project directory and edit theelectron-builder-config.json
file that contains information for signing and publishing your Electron application and edit theappId
field to match your organization. This will initially be something like:org.electronjs.MyApp
and should be changed to something likecom.MyOrganization.MyApp
.Mac: Set up your certificates in Xcode so that you have both Developer ID Application and Developer ID Installer certificates in your keychain.
Windows: TODO
- Mac: Obtain notarization app login/password and export shell variables to
contain them. You can generate this password by logging into your Apple
Developer account and generating one from there:
https://appleid.apple.com/account/manage
export APPLEID="YOUR_APPLE_DEVELOPER_ID"
export APPLEIDPASS="YOUR_APPLE_NOTARIZATION_PASSWORD"
Windows: TODO
For more information about signing and notarization see:
Mac: Creating and deploying an auto-updating Electron app for Mac and Windows (this article doesn't really have a lot of information on Windows).
Windows: How to Code Sign an Electron App for Windows
- Run
tibet deploy electron
:
tibet deploy electron
TROUBLESHOOTING:
Mac:
If you get the following error message:
xcrun: error: unable to find utility "altool", not a developer tool or in PATH
run the following command from a Terminal:
sudo xcode-select -s /Applications/Xcode.app
If you get an email from Apple that says that they could not notarize the application, you can check the notarization logs by issuing the following command. Note that the notarization request identifier can be found in the email.
"/usr/bin/xcrun" altool --verbose --notarization-info NOTARIZATION_REQUEST_IDENTIFIER -u "YOUR_APPLE_DEVELOPER_ID" -p "YOUR_APPLE_NOTARIZATION_PASSWORD"
Deploying via tibet make
TIBET's tibet make
command will invoke any exported function you create and
place in the ~app_cmd/make
directory. As part of its default configuration all
TIBET projects include a _deploy.js
file in this directory you can use as a
starting point for writing you own custom deployment logic.
Sample _deploy.js
from a default TIBET project:
(function() {
'use strict';
module.exports = function(make, resolve, reject) {
make.log('deploying application...');
// ---
// TODO: replace this with your concrete deployment logic.
make.warn('No concrete deployment logic.');
// ---
resolve();
};
}());
Obviously the previous file doesn't actually deploy, but you can invoke it via tibet make deploy
or tibet deploy make
(either will work). Adjust the logic to suit your needs and you can leverage tibet make
to help you with your deployments.
Extending tibet deploy
The tibet deploy
command supports extensions by creating custom "plugins" and
placing them in the ~app_cmd
directory (typically the cmd
directory at the
root of your TIBET project) in a deploy
subdirectory.
The files in question should be named to match your subcommand name. For our
example here we use this
as our hypothetical deploy helper so we would create
a file this.js
in our project's cmd/deploy
directory.
Plugins for tibet deploy
must be loadable via require
and return a function
which, when invoked, will patch the deploy
command prototype with the desired
implementation. The method should be named executeSomething
where 'Something'
is replaced with the target name.
/**
* A sample 'tibet deploy' command. This one is built to handle invocations of
* the TIBET CLI with a command line of 'tibet deploy this'.
*/
(function() {
'use strict';
module.exports = function(cmdType) {
// NOTE: we patch the prototype since invocation is instance-level.
cmdType.prototype.executeThis = function() {
console.log('deploying this...');
};
};
}(this));
Additional pre-built logic for common deployment targets is under development.
Code
TIBET's build and deployment infrastructure is based on a combination of TIBET CLI commands and tibet make
targets.
Code for tibet make
targets is found in ~app/cmd/make
. In particular, the build
make target which invokes clean
, build_resources
, _rollup
, and _linkup
as part of its processing.
Code for the CLI is contained in ~lib/src/tibet/cli
. The package
, resource
, and rollup
commands do most of the work. Logic in ~lib/etc/common/tibet_package.js
is used by a number of these commands to assist them in processing TIBET's manifest files.