├── README.md
├── config.yaml
├── go.mod
├── main.go
├── samples
├── sample-demo-magic-small-compressed.gif
└── sample-golang-demo-small-compressed.gif
└── template.sh
/README.md:
--------------------------------------------------------------------------------
1 | # Template for CLI-based Demos/Showcases
2 |
3 | Templates and config for running scripted and/or pre-recorded CLI demos.
4 |
5 | You can find more information about this repository and how to use it in following blog post:
6 |
7 | - [Make Your CLI Demos a Breeze with Zero Stress and Zero Mistakes](https://martinheinz.dev/blog/94)
8 |
9 | ---
10 |
11 |
12 |
13 | -----
14 |
15 | If you find this useful, you can support me on Ko-Fi (Donations are always appreciated, but never required):
16 |
17 | [](https://ko-fi.com/K3K6F4XN6)
18 |
19 | ## Scripted Demo
20 |
21 | Repository provides template for 2 scripting tools, below is setup needed for each of them.
22 |
23 | Setup (`demo-magic`):
24 |
25 | ```shell
26 | # Install "Pipe Viewer" for simulated typing
27 | # MacOS:
28 | brew install pv
29 | # Ubuntu:
30 | apt-get install pv
31 |
32 | git clone https://github.com/paxtonhare/demo-magic.git
33 | cp demo-magic/demo-magic.sh demo-magic.sh
34 | ```
35 |
36 | To create and run a new demo:
37 |
38 | ```shell
39 | cp template.sh my-cool-demo.sh
40 | # Edit 'my-cool-demo.sh'
41 |
42 | # Run
43 | ./my-cool-demo.sh
44 | ```
45 |
46 | ---
47 |
48 | Setup (`https://github.com/saschagrunert/demo`):
49 |
50 | ```shell
51 | go get github.com/saschagrunert/demo@latest
52 |
53 | # Edit 'main.go'
54 |
55 | go build
56 | ./cli-demo --all # or change 'all' to name of specific demo run
57 | ```
58 |
59 | ## Recording Demo
60 |
61 | ```shell
62 | # You might need 'sudo'
63 | npm install -g terminalizer
64 |
65 | # https://github.com/faressoft/terminalizer/issues/29
66 | # Very careful with below command!
67 | sudo chown -R /usr/lib/node_modules/terminalizer/render/
68 |
69 | terminalizer init
70 | # The global config directory is created at
71 | # /home/martin/.terminalizer
72 |
73 | # Adjust config.yaml... (e.g., change 'title' field)
74 |
75 | # Create copy of default config in current directory
76 | terminalizer config
77 |
78 | # Start recording:
79 | terminalizer record demo --config config.yaml
80 |
81 | # ... run some commands
82 |
83 | # CTRL+D to stop recording...
84 |
85 | # Successfully Recorded
86 | # The recording data is saved into the file:
87 | # /home/.../demo.yml
88 |
89 | terminalizer play demo # To check
90 | terminalizer render demo # To render
91 | ```
92 |
93 | ## Workflow
94 |
95 | Example usage/streamlined workflow:
96 |
97 | ```shell
98 | # Create demo:
99 |
100 | # ... Edit 'main.go'
101 | go build
102 | # OR
103 | cp template.sh my-cool-demo.sh
104 | # Edit 'my-cool-demo.sh'
105 |
106 | # -----
107 |
108 | # Start recording:
109 | terminalizer record demo
110 |
111 | # Start scripted demo:
112 | ./cli-demo --all -i
113 | # OR
114 | ./my-cool-demo.sh
115 |
116 | # ... Go through the demo
117 | # CTRL+D
118 |
119 | terminalizer play demo # To check
120 | # ... Adjust demo.yml
121 | terminalizer render demo # To render
122 | ```
123 |
124 |
--------------------------------------------------------------------------------
/config.yaml:
--------------------------------------------------------------------------------
1 | # Specify a command to be executed
2 | # like `/bin/bash -l`, `ls`, or any other commands
3 | # the default is bash for Linux
4 | # or powershell.exe for Windows
5 | command: null
6 |
7 | # Specify the current working directory path
8 | # the default is the current working directory path
9 | cwd: null
10 |
11 | # Export additional ENV variables
12 | env:
13 | recording: true
14 |
15 | # Explicitly set the number of columns
16 | # or use `auto` to take the current
17 | # number of columns of your shell
18 | cols: auto
19 |
20 | # Explicitly set the number of rows
21 | # or use `auto` to take the current
22 | # number of rows of your shell
23 | rows: auto
24 |
25 | # Amount of times to repeat GIF
26 | # If value is -1, play once
27 | # If value is 0, loop indefinitely
28 | # If value is a positive number, loop n times
29 | repeat: 0
30 |
31 | # Quality
32 | # 1 - 100
33 | quality: 100
34 |
35 | # Delay between frames in ms
36 | # If the value is `auto` use the actual recording delays
37 | frameDelay: 150
38 |
39 | # Maximum delay between frames in ms
40 | # Ignored if the `frameDelay` isn't set to `auto`
41 | # Set to `auto` to prevent limiting the max idle time
42 | maxIdleTime: 2000
43 |
44 | # The surrounding frame box
45 | # The `type` can be null, window, floating, or solid`
46 | # To hide the title use the value null
47 | # Don't forget to add a backgroundColor style with a null as type
48 | frameBox:
49 | type: floating
50 | title: CLI Showcase
51 | style:
52 | backgroundColor: "#263238"
53 | border: 0px black solid
54 | # boxShadow: none
55 | # margin: 0px
56 |
57 | # Add a watermark image to the rendered gif
58 | # You need to specify an absolute path for
59 | # the image on your machine or a URL, and you can also
60 | # add your own CSS styles
61 | watermark:
62 | imagePath: null
63 | style:
64 | position: absolute
65 | right: 15px
66 | bottom: 15px
67 | width: 100px
68 | opacity: 0.9
69 |
70 | # Cursor style can be one of
71 | # `block`, `underline`, or `bar`
72 | cursorStyle: block
73 |
74 | # Font family
75 | # You can use any font that is installed on your machine
76 | # in CSS-like syntax
77 | fontFamily: "Monaco, Lucida Console, Ubuntu Mono, Monospace"
78 |
79 | # The size of the font
80 | fontSize: 12
81 |
82 | # The height of lines
83 | lineHeight: 1
84 |
85 | # The spacing between letters
86 | letterSpacing: 0
87 |
88 | # Theme
89 | theme:
90 | background: "transparent"
91 | foreground: "#afafaf"
92 | cursor: "#c7c7c7"
93 | black: "#2E3436" # "#232628"
94 | red: "#CC0000" # "#fc4384"
95 | green: "#4E9A06" # "#b3e33b"
96 | yellow: "C4A000" # "#ffa727"
97 | blue: "#3465A4" # "#75dff2"
98 | magenta: "#75507B" # "#ae89fe"
99 | cyan: "#06989A" # "#708387"
100 | white: "#D3D7CF" # "#d5d5d0"
101 | brightBlack: "#555753" # "#626566"
102 | brightRed: "#EF2929" # "#ff7fac"
103 | brightGreen: "#8AE234" # "#c8ed71"
104 | brightYellow: "#FCE94F" # "#ebdf86"
105 | brightBlue: "#729FCF" # "#75dff2"
106 | brightMagenta: "#AD7FA8" # "#ae89fe"
107 | brightCyan: "#34E2E2" # "#b1c6ca"
108 | brightWhite: "#EEEEEC" # "#f9f9f4"
109 |
110 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module cli-demo
2 |
3 | go 1.20
4 |
5 | require (
6 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
7 | github.com/gookit/color v1.5.2 // indirect
8 | github.com/russross/blackfriday/v2 v2.1.0 // indirect
9 | github.com/saschagrunert/demo v0.0.0-20230228152132-acc488841b08 // indirect
10 | github.com/urfave/cli/v2 v2.24.4 // indirect
11 | github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
12 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
13 | golang.org/x/sys v0.5.0 // indirect
14 | )
15 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | demo "github.com/saschagrunert/demo"
5 | "github.com/urfave/cli/v2"
6 | )
7 |
8 | func main() {
9 | d := demo.New()
10 |
11 | d.Name = "A demo of demo"
12 | d.Usage = "Examples of how to use this"
13 | d.HideVersion = true
14 |
15 | d.Setup(setup)
16 | d.Cleanup(cleanup)
17 |
18 | d.Add(simple(), "simple", "Simple commands demo")
19 | d.Add(longRunning(), "long-run", "Long-running commands demo")
20 | d.Add(errorProne(), "error-prone", "Error-prone commands demo")
21 | d.Add(longTyping(), "long-typing", "Commands that require a lot of typing")
22 |
23 | d.Run()
24 | }
25 |
26 | func simple() *demo.Run {
27 | r := demo.NewRun(
28 | "Simple commands demo",
29 | )
30 |
31 | r.Step(demo.S(
32 | "Simple commands:",
33 | ), nil)
34 |
35 | r.Step(nil, demo.S(
36 | "ps aux | head",
37 | ))
38 |
39 | r.Step(nil, demo.S(
40 | "ls -l",
41 | ))
42 |
43 | r.Step(nil, demo.S(
44 | "cat some-file",
45 | ))
46 |
47 | return r
48 | }
49 |
50 | func longRunning() *demo.Run {
51 | r := demo.NewRun(
52 | "Long-running commands demo",
53 | )
54 |
55 | r.Step(demo.S(
56 | "Long running:",
57 | ), demo.S(
58 | "docker build -t some-app .",
59 | ))
60 |
61 | return r
62 | }
63 |
64 | func errorProne() *demo.Run {
65 | r := demo.NewRun(
66 | "Error-prone commands demo",
67 | )
68 |
69 | r.Step(demo.S(
70 | "Error prone (dependant on external factors like network):",
71 | ), nil)
72 |
73 | r.Step(nil, demo.S(
74 | "curl --silent -X GET https://httpstat.us/418 -H \"Accept: application/json\" | jq .",
75 | ))
76 |
77 | r.Step(nil, demo.S(
78 | "curl -i -X POST \"https://httpbin.org/status/204\" --data '{\"some\":\"data\"}'",
79 | ))
80 |
81 | r.Step(nil, demo.S(
82 | "wget -q --show-progress -O some-file.txt https://raw.githubusercontent.com/octocat/Hello-World/master/README",
83 | ))
84 |
85 | return r
86 | }
87 |
88 | func longTyping() *demo.Run {
89 | r := demo.NewRun(
90 | "Commands that require a lot of typing",
91 | )
92 |
93 | r.Step(demo.S(
94 | "Hard to type:",
95 | ), demo.S(
96 | "openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes ",
97 | "-keyout example.key -out example.crt -subj \"/CN=example.com\"",
98 | "-addext \"subjectAltName=DNS:example.com,DNS:www.example.net,IP:10.0.0.1\" 2>/dev/null",
99 | ))
100 |
101 | r.Step(nil, demo.S(
102 | "ls -l | grep example",
103 | ))
104 |
105 | return r
106 | }
107 |
108 | func setup(ctx *cli.Context) error {
109 | // Ensure can be used for easy sequential command execution
110 | return demo.Ensure(
111 | "echo 'Here\nis\nsome\ndata\nfor\nyou' > some-file",
112 | )
113 | }
114 |
115 | func cleanup(ctx *cli.Context) error {
116 | return demo.Ensure(
117 | "rm some-file some-file.txt || true",
118 | "docker rmi some-app || true",
119 | "rm example.crt example.key || true",
120 | )
121 | }
122 |
--------------------------------------------------------------------------------
/samples/sample-demo-magic-small-compressed.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MartinHeinz/cli-showcase-setup/4b9d615077e289f66c96636d63d4ea18dbafdf57/samples/sample-demo-magic-small-compressed.gif
--------------------------------------------------------------------------------
/samples/sample-golang-demo-small-compressed.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MartinHeinz/cli-showcase-setup/4b9d615077e289f66c96636d63d4ea18dbafdf57/samples/sample-golang-demo-small-compressed.gif
--------------------------------------------------------------------------------
/template.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # https://github.com/paxtonhare/demo-magic/blob/master/samples/demo-template.sh
4 |
5 | source demo-magic.sh
6 |
7 | DEMO_PROMPT="${GREEN}➜ ${CYAN}\W ${COLOR_RESET}"
8 | # speed at which to simulate typing. bigger num = faster
9 | TYPE_SPEED=30
10 |
11 | function comment() {
12 | cmd=$DEMO_COMMENT_COLOR$1$COLOR_RESET
13 | echo -en "$cmd"; echo ""
14 | }
15 |
16 | clear
17 |
18 | comment "# Simple commands:"
19 | pe 'ps aux | head'
20 | pe 'ls -l'
21 |
22 | comment "# Print and execute immediately"
23 | pei 'cat some-file'
24 | echo
25 |
26 | comment "# Long running:"
27 | pe 'docker build -t some-app .'
28 |
29 | comment "# Error prone (dependant on external factors like network):"
30 |
31 | pe 'curl --silent -X GET https://httpstat.us/418 -H "Accept: application/json" | jq .'
32 |
33 | pe 'curl -i -X POST "https://httpbin.org/status/204" --data "{'some':'data'}"'
34 |
35 | pe 'wget -q --show-progress -O some-file.txt https://raw.githubusercontent.com/octocat/Hello-World/master/README'
36 |
37 | comment "# Hard to type:"
38 |
39 | pe 'openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes -keyout example.key -out example.crt -subj "/CN=example.com" -addext "subjectAltName=DNS:example.com,DNS:www.example.net,IP:10.0.0.1" 2>/dev/null'
40 |
41 | comment "# Enter interactive mode..."
42 | cmd # Run 'ls -l | grep example' to show result of 'openssl ...'
43 |
44 | pe "clear"
45 |
--------------------------------------------------------------------------------