diff --git a/.gitignore b/.gitignore index b717aca5..19646990 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ balenarc.yml build/ build-bin/ build-zip/ + +# Ignore fast-boot cache file +/bin/.fast-boot.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d9f61f0..0808520b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY! This project adheres to [Semantic Versioning](http://semver.org/). +## 9.10.0 - 2019-01-14 + +* Improve startup time by adding fast-boot [Shaun Mulligan] + +## 9.9.4 - 2019-01-13 + +* Lazy load the sdk as much as possible [Pagan Gazzard] + +## 9.9.3 - 2019-01-13 + +* Lazy-load docker-toolbelt [Pagan Gazzard] + ## 9.9.2 - 2019-01-11 * Lazy-load etcher-sdk to speed up startup [Pagan Gazzard] diff --git a/bin/balena b/bin/balena index bb5ccbc3..cc67e77c 100755 --- a/bin/balena +++ b/bin/balena @@ -4,4 +4,8 @@ // operations otherwise, if the pool runs out. process.env.UV_THREADPOOL_SIZE = '64'; +// Use fast-boot to cache require lookups, speeding up startup +require('fast-boot2').start({ + cacheFile: __dirname + '/.fast-boot.json' +}) require('../build/app'); diff --git a/bin/balena-dev b/bin/balena-dev index 77e03cba..c00fc85d 100755 --- a/bin/balena-dev +++ b/bin/balena-dev @@ -9,6 +9,10 @@ // operations otherwise, if the pool runs out. process.env.UV_THREADPOOL_SIZE = '64'; +// Use fast-boot to cache require lookups, speeding up startup +require('fast-boot2').start({ + cacheFile: '.fast-boot.json' +}) process.env['TS_NODE_PROJECT'] = require('path').dirname(__dirname); require('coffeescript/register'); require('ts-node/register'); diff --git a/lib/app.coffee b/lib/app.coffee index 84aa8700..3dca1e67 100644 --- a/lib/app.coffee +++ b/lib/app.coffee @@ -71,8 +71,6 @@ BalenaSdk.setSharedOptions( retries: 2 ) -balena = BalenaSdk.fromSharedOptions() - actions = require('./actions') errors = require('./errors') events = require('./events') @@ -86,6 +84,7 @@ update = require('./utils/update') require('any-promise/register/bluebird') capitano.permission 'user', (done) -> + balena = BalenaSdk.fromSharedOptions() balena.auth.isLoggedIn().then (isLoggedIn) -> if not isLoggedIn exitWithExpectedError(''' diff --git a/lib/events.ts b/lib/events.ts index aff7d26d..2b78b7eb 100644 --- a/lib/events.ts +++ b/lib/events.ts @@ -7,16 +7,17 @@ import Promise = require('bluebird'); import BalenaSdk = require('balena-sdk'); import packageJSON = require('../package.json'); -const balena = BalenaSdk.fromSharedOptions(); +const getBalenaSdk = _.once(() => BalenaSdk.fromSharedOptions()); const getMatchCommandAsync = Promise.promisify(Capitano.state.getMatchCommand); -const getMixpanel = _.memoize(() => - balena.models.config - .getAll() +const getMixpanel = _.once(() => + getBalenaSdk() + .models.config.getAll() .get('mixpanelToken') .then(Mixpanel.init), ); export function trackCommand(capitanoCli: Capitano.Cli) { + const balena = getBalenaSdk(); return Promise.props({ balenaUrl: balena.settings.get('balenaUrl'), username: balena.auth.whoami().catchReturn(undefined), diff --git a/lib/utils/docker.coffee b/lib/utils/docker.coffee index f2c3094e..418197b2 100644 --- a/lib/utils/docker.coffee +++ b/lib/utils/docker.coffee @@ -1,6 +1,7 @@ # Functions to help actions which rely on using docker Promise = require('bluebird') +_ = require('lodash') # Use this function to seed an action's list of capitano options # with the docker options. Using this interface means that @@ -153,14 +154,7 @@ exports.getDocker = (options) -> .then(createClient) .tap(ensureDockerSeemsAccessible) -exports.createClient = createClient = do -> - # docker-toolbelt v3 is not backwards compatible as it removes all *Async - # methods that are in wide use in the CLI. The workaround for now is to - # manually promisify the client and replace all `new Docker()` calls with - # this shared function that returns a promisified client. - # - # **New code must not use the *Async methods.** - # +getDockerToolbelt = _.once -> Docker = require('docker-toolbelt') Promise.promisifyAll Docker.prototype, { filter: (name) -> name == 'run' @@ -169,9 +163,18 @@ exports.createClient = createClient = do -> Promise.promisifyAll(Docker.prototype) Promise.promisifyAll(new Docker({}).getImage().constructor.prototype) Promise.promisifyAll(new Docker({}).getContainer().constructor.prototype) + return Docker - return (opts) -> - return new Docker(opts) +# docker-toolbelt v3 is not backwards compatible as it removes all *Async +# methods that are in wide use in the CLI. The workaround for now is to +# manually promisify the client and replace all `new Docker()` calls with +# this shared function that returns a promisified client. +# +# **New code must not use the *Async methods.** +# +exports.createClient = createClient = (opts) -> + Docker = getDockerToolbelt() + return new Docker(opts) ensureDockerSeemsAccessible = (docker) -> { exitWithExpectedError } = require('./patterns') diff --git a/lib/utils/patterns.ts b/lib/utils/patterns.ts index 1dab1210..e3a3b29e 100644 --- a/lib/utils/patterns.ts +++ b/lib/utils/patterns.ts @@ -23,12 +23,13 @@ import chalk from 'chalk'; import validation = require('./validation'); import messages = require('./messages'); -const balena = BalenaSdk.fromSharedOptions(); +const getBalenaSdk = _.once(() => BalenaSdk.fromSharedOptions()); const getForm = _.once((): typeof _form => require('resin-cli-form')); const getVisuals = _.once((): typeof _visuals => require('resin-cli-visuals')); export function authenticate(options: {}): Promise { + const balena = getBalenaSdk(); return getForm() .run( [ @@ -101,17 +102,19 @@ export function askLoginType() { } export function selectDeviceType() { - return balena.models.config.getDeviceTypes().then(deviceTypes => { - deviceTypes = _.sortBy(deviceTypes, 'name'); - return getForm().ask({ - message: 'Device Type', - type: 'list', - choices: _.map(deviceTypes, ({ slug: value, name }) => ({ - name, - value, - })), + return getBalenaSdk() + .models.config.getDeviceTypes() + .then(deviceTypes => { + deviceTypes = _.sortBy(deviceTypes, 'name'); + return getForm().ask({ + message: 'Device Type', + type: 'list', + choices: _.map(deviceTypes, ({ slug: value, name }) => ({ + name, + value, + })), + }); }); - }); } export function confirm( @@ -142,6 +145,7 @@ export function confirm( export function selectApplication( filter: (app: BalenaSdk.Application) => boolean, ) { + const balena = getBalenaSdk(); return balena.models.application .hasAny() .then(function(hasAnyApplications) { @@ -165,6 +169,7 @@ export function selectApplication( } export function selectOrCreateApplication() { + const balena = getBalenaSdk(); return balena.models.application .hasAny() .then(hasAnyApplications => { @@ -205,6 +210,7 @@ export function selectOrCreateApplication() { } export function awaitDevice(uuid: string) { + const balena = getBalenaSdk(); return balena.models.device.getName(uuid).then(deviceName => { const visuals = getVisuals(); const spinner = new visuals.Spinner( @@ -233,6 +239,7 @@ export function awaitDevice(uuid: string) { } export function inferOrSelectDevice(preferredUuid: string) { + const balena = getBalenaSdk(); return balena.models.device .getAll() .filter(device => device.is_online) diff --git a/lib/utils/visuals/drive-list.ts b/lib/utils/visuals/drive-list.ts index b63341f7..2f5704eb 100644 --- a/lib/utils/visuals/drive-list.ts +++ b/lib/utils/visuals/drive-list.ts @@ -17,7 +17,7 @@ export class DriveList extends CustomDynamicList< } protected *getThings() { - const sdk: typeof _sdk = require('etcher-sdk') + const sdk: typeof _sdk = require('etcher-sdk'); for (const drive of this.scanner.drives) { if (drive instanceof sdk.sourceDestination.BlockDevice) { yield drive; diff --git a/package.json b/package.json index 9cd024c3..f4a13576 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "balena-cli", - "version": "9.9.2", + "version": "9.10.0", "description": "The official balena CLI tool", "main": "./build/actions/index.js", "homepage": "https://github.com/balena-io/balena-cli", @@ -109,7 +109,7 @@ "balena-preload": "^8.0.4", "balena-sdk": "^11.2.0", "balena-settings-client": "^4.0.0", - "balena-sync": "^10.0.0", + "balena-sync": "^10.0.2", "bash": "0.0.1", "bluebird": "^3.3.3", "body-parser": "^1.14.1", @@ -130,6 +130,7 @@ "etcher-sdk": "^0.2.0", "event-stream": "3.3.4", "express": "^4.13.3", + "fast-boot2": "^1.0.9", "global-tunnel-ng": "^2.1.1", "hasbin": "^1.2.3", "humanize": "0.0.9", @@ -153,7 +154,7 @@ "reconfix": "^0.1.0", "request": "^2.81.0", "resin-bundle-resolve": "^0.6.0", - "resin-cli-form": "^2.0.0", + "resin-cli-form": "^2.0.1", "resin-cli-visuals": "^1.4.0", "resin-compose-parse": "^2.0.0", "resin-doodles": "0.0.1",