ADAPTIVE INTENT-BASED CLI STATE MACHINES @swyx May 2019
A presentation at OclifConf in May 2019 in San Francisco, CA, USA by swyx
ADAPTIVE INTENT-BASED CLI STATE MACHINES @swyx May 2019
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx rupa/z
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx “Frecency” https://slack.engineering/a-faster-smarter-quick-switcher-77cbc193cb60
ADAPTIVE INTENT-BASED CLI STATE MACHINES
RIDICULOUSLY OVER-ENGINEERED COMMAND LINE APPS
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx PART I NETLIFY NETLIFY CLI NETLIFY DEV
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx DISCLAIMER THIS IS MOSTLY COLLEAGUES’ WORK
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx BitBalloon
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx StaticGen.com
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx CLI Iterations • • • V0: !Commander.js V1: 🐍Cobra V2: ✨Oclif https://www.netlify.com/blog/2018/09/10/netlify-cli-2.0-now-in-beta-/#our-cli-journey
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx JAMstack.org
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx PROBLEM LOCAL EMULATION
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx The Rise of Dev Servers
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Configuring Proxies in Dev Servers
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Configuring Proxies in Dev Servers
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx PROBLEM SOLVING “DEPLOY AND PRAY”
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Netlify Dev: Wrapping the DevServer
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx New Commands, New Config
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Private Dev with Oclif Plugins
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Netlify Dev Requirements • • • • • • • Read netlify.toml config Check login state Check site link state Check functions folder exists Check _redirects folder exists Respect flag overrides Prompt for what’s missing
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Discovering 12 Factor Apps • • • • • • Help Docs Flags > Args —version stdout vs. stderr Errors, DEBUG=* Be FANCY • • • • • • Prompting Tables (Perceived) Speed Contributions Sub:commands XDG-spec
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx CLI Cheatsheet https://github.com/sw-yx/cli-cheatsheet
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx PART II THE 13th FACTOR
@swyx
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Adaptive Interfaces “Instead of designing web pages and content to respond to various devices… developers are trying to create interfaces that respond to individual users. These interfaces would adapt on the fly to the current user and collect data over time to anticipate each user’s actions and preferences.” https://speckyboy.com/adaptive-user-interfaces/
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx THE 13th FACTOR STATE
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx PROBLEM I STATE IS HARD
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx fs.writeFileSync(‘store.json’, JSON.stringify(data)); fs.writeFileSync(“store.json”, JSON.stringify( Object.assign({}, data, { foo: “bar” })) )
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx path.resolve(process.cwd(), ‘foo’, ‘/store.json’) path.join(process.cwd(), ‘foo’, ‘/store.json’) path.resolve(process.cwd(), ‘store.json’) path.join(process.cwd(), ‘store.json’) path.resolve(__dirname, ‘store.json’) path.join(__dirname, ‘store.json’)
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Don’t roll your own • • • https://github.com/sindresorhus/conf https://github.com/jonschlinkert/data-store https://github.com/davidtheclark/cosmiconfig (useful for reading config in .rc files as well but needs config for XDG compliance)
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx const Conf = require(‘conf’); const config = new Conf(); config.set(‘unicorn’, ‘🦄’); console.log(config.get(‘unicorn’)); //=> ‘🦄’ // Use dot-notation to access nested properties config.set(‘foo.bar’, true); console.log(config.get(‘foo’)); //=> {bar: true} config.delete(‘unicorn’); console.log(config.get(‘unicorn’)); //=> undefined
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx A full model of CLI State CLI Flags • Project State • • Project Filesystem • Machine State Remote user account settings • Remote team account settings • • Remote global defaults
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Solution I: cli-state import { initCLIState, globalState, projectState } from ‘cli-state’;
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Offline State = Cache? CLI Flags • Project State • • Project Filesystem • Machine State Cached user account settings • Cached team account settings • • Cached global defaults
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx PROBLEM II CLI’S AS INTOLERANT PROCEDURE CALLS
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Context is hard
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Handling Incomplete State • • • Worst: Silent Exit OK: Throw Error Better: Error Message
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Handling Incomplete State • • • • • Worst: Silent Exit OK: Throw Error Better: Error Message Good: Prompt for fix Great: Adaptive prompting
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx class Example2 extends Command { // … async run() { // … myBusinessLogic(state.name) // throws! } }
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx class Example2 extends Command { // … async run() { // … if (state.name) { myBusinessLogic(state.name) } // silent error } }
netlify
Adaptive Intent-Based CLI State Machines
Oclif Conf, May 2019
@swyx
const chalk = require(“chalk”) class Example2 extends Command { // … async run() { // … if (state.name) { myBusinessLogic(state.name) } else { this.error(you did not provide ${chalk.yellow(name)}. please retry
) } } }
netlify
Adaptive Intent-Based CLI State Machines
Oclif Conf, May 2019
@swyx
const chalk = require(“chalk”) class Example2 extends Command { // … async run() { // … if (state.name) { myBusinessLogic(state.name) } else { this.error(Error code
) this.error(Error title
) this.error(Error description (Optional)
) this.error(How to fix the error URL for more information
) } } }
netlify
Adaptive Intent-Based CLI State Machines
Oclif Conf, May 2019
@swyx
const chalk = require(“chalk”) const { prompt } = require(“enquirer”) class Example2 extends Command { // … async run() { // … if (state.name) { myBusinessLogic(state.name) } else { const name = prompt(“give me a name”) if (name) { myBusinessLogic(name) } else { this.error(Error code
) this.error(Error title
) this.error(Error description (Optional)
) this.error(How to fix the error URL for more information
) } } } }
netlify
Adaptive Intent-Based CLI State Machines
Oclif Conf, May 2019
@swyx
const chalk = require(“chalk”) const { prompt } = require(“enquirer”) class Example2 extends Command { // … async run() { // … if (state.name) { myBusinessLogic(state.name) } else { const name = prompt(“give me a name”) if (name) { console.log(“next time you can pass a —name flag!”) myBusinessLogic(name) } else { this.error(Error code
) this.error(Error title
) this.error(Error description (Optional)
) this.error(How to fix the error URL for more information
) } } } }
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx KEY REALIZATION WE WRITE CLI’S LIKE WE WROTE JQUERY
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx mycli login login.js mycli deploy deploy.js
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx mycli login login.js mycli deploy deploy.js
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 mycli login @swyx login.js utils/login.js mycli deploy deploy.js
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx KEY REALIZATION COMMAND != INTENT
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Use a State Machine https://statecharts.github.io/xstate-viz/
netlify Adaptive Intent-Based CLI State Machines notLoggedIn Oclif Conf, May 2019 Login @swyx isLoggedIn Deploy isDeployed
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx noName getName hasName noFolder makeFolder hasFolder notLoggedIn Login isLoggedIn Deploy isDeployed
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Shortest Paths https://xstate.js.org/docs/packages/xstate-graph/#api
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Solution II: cli-state-machine import { Action, State, initStateMachine, processStateMachine } from ‘cli-state-machine’
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Defining State export const loggedInState: State = { stateId: ‘loggedIn’, getValue: async () => loginStatus, assert: async (status: boolean) => status === true, }
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Defining Action export const loginAction: Action = { actionId: ‘loginAction’, beforeState: loggedOutState, afterState: loggedInState, execute: async () => { // console.log(‘logging in’) loginStatus = true }, }
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Defining another Action export const deployAction: Action = { actionId: ‘deploy’, beforeState: loggedInState, execute: async () => { console.log(‘deployed!’) }, }
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Running the State Machine initStateMachine([ loginAction, logoutAction, deployAction ]) // map intents to actions // let StateMachine search for valid adjacent states await processStateMachine(deployAction, {})
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx More To Do • Binding flags to States Helper functions for: • • file/folder existence • Prompting for required field • ??? Visualization output • Principle: Componentized and Declarative Business Logic
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx the Full State Machine // allActions and otherState defined above initCLIState(); const cliState = { projectState, globalState, …otherState } initStateMachine(allActions) await processStateMachine(deployAction, cliState)
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Help us build it netlify.com/careers github.com/sw-yx/cli-state github.com/sw-yx/cli-state-machine
ADAPTIVE INTENT-BASED CLI STATE MACHINES swyx.io/talks npm i -g netlify-cli
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Principles of HCI
netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx 12 General Principles of HCI