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