appwrite-cli 5.0.5 → 6.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +4 -4
  2. package/docs/examples/functions/create-build.md +1 -1
  3. package/docs/examples/functions/create-execution.md +1 -0
  4. package/docs/examples/functions/create.md +1 -0
  5. package/docs/examples/functions/delete-execution.md +3 -0
  6. package/docs/examples/functions/update-deployment-build.md +3 -0
  7. package/docs/examples/functions/update.md +1 -0
  8. package/docs/examples/projects/create-j-w-t.md +4 -0
  9. package/docs/examples/projects/update-mock-numbers.md +3 -0
  10. package/docs/examples/projects/update-session-alerts.md +3 -0
  11. package/docs/examples/users/create-j-w-t.md +4 -0
  12. package/docs/examples/vcs/get-repository-contents.md +4 -0
  13. package/index.js +34 -7
  14. package/install.ps1 +3 -3
  15. package/install.sh +2 -2
  16. package/lib/client.js +17 -3
  17. package/lib/commands/account.js +306 -152
  18. package/lib/commands/assistant.js +8 -5
  19. package/lib/commands/avatars.js +114 -58
  20. package/lib/commands/console.js +8 -5
  21. package/lib/commands/databases.js +353 -164
  22. package/lib/commands/functions.js +310 -100
  23. package/lib/commands/generic.js +206 -54
  24. package/lib/commands/graphql.js +14 -8
  25. package/lib/commands/health.js +140 -71
  26. package/lib/commands/init.js +250 -155
  27. package/lib/commands/locale.js +50 -26
  28. package/lib/commands/messaging.js +334 -156
  29. package/lib/commands/migrations.js +98 -50
  30. package/lib/commands/project.js +38 -20
  31. package/lib/commands/projects.js +449 -144
  32. package/lib/commands/proxy.js +32 -17
  33. package/lib/commands/pull.js +231 -0
  34. package/lib/commands/push.js +1518 -0
  35. package/lib/commands/run.js +282 -0
  36. package/lib/commands/storage.js +160 -76
  37. package/lib/commands/teams.js +102 -50
  38. package/lib/commands/users.js +324 -134
  39. package/lib/commands/vcs.js +102 -29
  40. package/lib/config.js +190 -18
  41. package/lib/emulation/docker.js +187 -0
  42. package/lib/emulation/utils.js +177 -0
  43. package/lib/id.js +30 -0
  44. package/lib/paginate.js +1 -2
  45. package/lib/parser.js +69 -12
  46. package/lib/questions.js +452 -80
  47. package/lib/sdks.js +1 -1
  48. package/lib/spinner.js +103 -0
  49. package/lib/utils.js +242 -4
  50. package/lib/validations.js +17 -0
  51. package/package.json +6 -2
  52. package/scoop/appwrite.json +3 -3
  53. package/lib/commands/deploy.js +0 -940
@@ -0,0 +1,187 @@
1
+ const childProcess = require('child_process');
2
+ const { localConfig } = require("../config");
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const { log,success } = require("../parser");
6
+ const { openRuntimesVersion, systemTools } = require("./utils");
7
+ const ID = require("../id");
8
+
9
+ const activeDockerIds = {};
10
+
11
+ async function dockerStop(id) {
12
+ delete activeDockerIds[id];
13
+ const stopProcess = childProcess.spawn('docker', ['rm', '--force', id], {
14
+ stdio: 'pipe',
15
+ });
16
+
17
+ await new Promise((res) => { stopProcess.on('close', res) });
18
+ }
19
+
20
+ async function dockerPull(func) {
21
+ log('Pulling Docker image of function runtime ...');
22
+
23
+ const runtimeChunks = func.runtime.split("-");
24
+ const runtimeVersion = runtimeChunks.pop();
25
+ const runtimeName = runtimeChunks.join("-");
26
+ const imageName = `openruntimes/${runtimeName}:${openRuntimesVersion}-${runtimeVersion}`;
27
+
28
+ const pullProcess = childProcess.spawn('docker', ['pull', imageName], {
29
+ stdio: 'pipe',
30
+ pwd: path.join(process.cwd(), func.path)
31
+ });
32
+
33
+ pullProcess.stderr.on('data', (data) => {
34
+ process.stderr.write(`\n${data}$ `);
35
+ });
36
+
37
+ await new Promise((res) => { pullProcess.on('close', res) });
38
+ }
39
+
40
+ async function dockerBuild(func, variables) {
41
+ log('Building function using Docker ...');
42
+
43
+ const runtimeChunks = func.runtime.split("-");
44
+ const runtimeVersion = runtimeChunks.pop();
45
+ const runtimeName = runtimeChunks.join("-");
46
+ const imageName = `openruntimes/${runtimeName}:${openRuntimesVersion}-${runtimeVersion}`;
47
+
48
+ const functionDir = path.join(process.cwd(), func.path);
49
+
50
+ const id = ID.unique();
51
+
52
+ const params = [ 'run' ];
53
+ params.push('--name', id);
54
+ params.push('-v', `${functionDir}/:/mnt/code:rw`);
55
+ params.push('-e', 'APPWRITE_ENV=development');
56
+ params.push('-e', 'OPEN_RUNTIMES_ENV=development');
57
+ params.push('-e', 'OPEN_RUNTIMES_SECRET=');
58
+ params.push('-e', `OPEN_RUNTIMES_ENTRYPOINT=${func.entrypoint}`);
59
+
60
+ for(const k of Object.keys(variables)) {
61
+ params.push('-e', `${k}=${variables[k]}`);
62
+ }
63
+
64
+ params.push(imageName, 'sh', '-c', `helpers/build.sh "${func.commands}"`);
65
+
66
+ const buildProcess = childProcess.spawn('docker', params, {
67
+ stdio: 'pipe',
68
+ pwd: functionDir
69
+ });
70
+
71
+ buildProcess.stdout.on('data', (data) => {
72
+ process.stdout.write(`\n${data}`);
73
+ });
74
+
75
+ buildProcess.stderr.on('data', (data) => {
76
+ process.stderr.write(`\n${data}`);
77
+ });
78
+
79
+ await new Promise((res) => { buildProcess.on('close', res) });
80
+
81
+ const copyPath = path.join(process.cwd(), func.path, '.appwrite', 'build.tar.gz');
82
+ const copyDir = path.dirname(copyPath);
83
+ if (!fs.existsSync(copyDir)) {
84
+ fs.mkdirSync(copyDir, { recursive: true });
85
+ }
86
+
87
+ const copyProcess = childProcess.spawn('docker', ['cp', `${id}:/mnt/code/code.tar.gz`, copyPath], {
88
+ stdio: 'pipe',
89
+ pwd: functionDir
90
+ });
91
+
92
+ await new Promise((res) => { copyProcess.on('close', res) });
93
+
94
+ const cleanupProcess = childProcess.spawn('docker', ['rm', '--force', id], {
95
+ stdio: 'pipe',
96
+ pwd: functionDir
97
+ });
98
+
99
+ await new Promise((res) => { cleanupProcess.on('close', res) });
100
+
101
+ delete activeDockerIds[id];
102
+
103
+ const tempPath = path.join(process.cwd(), func.path, 'code.tar.gz');
104
+ if (fs.existsSync(tempPath)) {
105
+ fs.rmSync(tempPath, { force: true });
106
+ }
107
+ }
108
+
109
+ async function dockerStart(func, variables, port) {
110
+ log('Starting function using Docker ...');
111
+
112
+ log("Permissions, events, CRON and timeouts dont apply when running locally.");
113
+
114
+ log('💡 Hint: Function automatically restarts when you edit your code.');
115
+
116
+ success(`Visit http://localhost:${port}/ to execute your function.`);
117
+
118
+
119
+ const runtimeChunks = func.runtime.split("-");
120
+ const runtimeVersion = runtimeChunks.pop();
121
+ const runtimeName = runtimeChunks.join("-");
122
+ const imageName = `openruntimes/${runtimeName}:${openRuntimesVersion}-${runtimeVersion}`;
123
+
124
+ const tool = systemTools[runtimeName];
125
+
126
+ const functionDir = path.join(process.cwd(), func.path);
127
+
128
+ const id = ID.unique();
129
+
130
+ const params = [ 'run' ];
131
+ params.push('--rm');
132
+ params.push('-d');
133
+ params.push('--name', id);
134
+ params.push('-p', `${port}:3000`);
135
+ params.push('-e', 'APPWRITE_ENV=development');
136
+ params.push('-e', 'OPEN_RUNTIMES_ENV=development');
137
+ params.push('-e', 'OPEN_RUNTIMES_SECRET=');
138
+
139
+ for(const k of Object.keys(variables)) {
140
+ params.push('-e', `${k}=${variables[k]}`);
141
+ }
142
+
143
+ params.push('-v', `${functionDir}/.appwrite/logs.txt:/mnt/logs/dev_logs.log:rw`);
144
+ params.push('-v', `${functionDir}/.appwrite/errors.txt:/mnt/logs/dev_errors.log:rw`);
145
+ params.push('-v', `${functionDir}/.appwrite/build.tar.gz:/mnt/code/code.tar.gz:ro`);
146
+ params.push(imageName, 'sh', '-c', `helpers/start.sh "${tool.startCommand}"`);
147
+
148
+ childProcess.spawn('docker', params, {
149
+ stdio: 'pipe',
150
+ pwd: functionDir
151
+ });
152
+
153
+ activeDockerIds[id] = true;
154
+ }
155
+
156
+ async function dockerCleanup() {
157
+ await dockerStop();
158
+
159
+ const functions = localConfig.getFunctions();
160
+ for(const func of functions) {
161
+ const appwritePath = path.join(process.cwd(), func.path, '.appwrite');
162
+ if (fs.existsSync(appwritePath)) {
163
+ fs.rmSync(appwritePath, { recursive: true, force: true });
164
+ }
165
+
166
+ const tempPath = path.join(process.cwd(), func.path, 'code.tar.gz');
167
+ if (fs.existsSync(tempPath)) {
168
+ fs.rmSync(tempPath, { force: true });
169
+ }
170
+ }
171
+ }
172
+
173
+ async function dockerStopActive() {
174
+ const ids = Object.keys(activeDockerIds);
175
+ for await (const id of ids) {
176
+ await dockerStop(id);
177
+ }
178
+ }
179
+
180
+ module.exports = {
181
+ dockerStop,
182
+ dockerPull,
183
+ dockerBuild,
184
+ dockerStart,
185
+ dockerCleanup,
186
+ dockerStopActive,
187
+ }
@@ -0,0 +1,177 @@
1
+ const EventEmitter = require('node:events');
2
+ const { projectsCreateJWT } = require('../commands/projects');
3
+ const { localConfig } = require("../config");
4
+
5
+
6
+ const openRuntimesVersion = 'v3';
7
+
8
+ const runtimeNames = {
9
+ 'node': 'Node.js',
10
+ 'php': 'PHP',
11
+ 'ruby': 'Ruby',
12
+ 'python': 'Python',
13
+ 'python-ml': 'Python (ML)',
14
+ 'deno': 'Deno',
15
+ 'dart': 'Dart',
16
+ 'dotnet': '.NET',
17
+ 'java': 'Java',
18
+ 'swift': 'Swift',
19
+ 'kotlin': 'Kotlin',
20
+ 'bun': 'Bun'
21
+ };
22
+
23
+ const systemTools = {
24
+ 'node': {
25
+ isCompiled: false,
26
+ startCommand: "node src/server.js",
27
+ dependencyFiles: [ "package.json", "package-lock.json" ]
28
+ },
29
+ 'php': {
30
+ isCompiled: false,
31
+ startCommand: "php src/server.php",
32
+ dependencyFiles: [ "composer.json", "composer.lock" ]
33
+ },
34
+ 'ruby': {
35
+ isCompiled: false,
36
+ startCommand: "bundle exec puma -b tcp://0.0.0.0:3000 -e production",
37
+ dependencyFiles: [ "Gemfile", "Gemfile.lock" ]
38
+ },
39
+ 'python': {
40
+ isCompiled: false,
41
+ startCommand: "python3 src/server.py",
42
+ dependencyFiles: [ "requirements.txt", "requirements.lock" ]
43
+ },
44
+ 'python-ml': {
45
+ isCompiled: false,
46
+ startCommand: "python3 src/server.py",
47
+ dependencyFiles: [ "requirements.txt", "requirements.lock" ]
48
+ },
49
+ 'deno': {
50
+ isCompiled: false,
51
+ startCommand: "deno start",
52
+ dependencyFiles: [ ]
53
+ },
54
+ 'dart': {
55
+ isCompiled: true,
56
+ startCommand: "src/function/server",
57
+ dependencyFiles: [ ]
58
+ },
59
+ 'dotnet': {
60
+ isCompiled: true,
61
+ startCommand: "dotnet src/function/DotNetRuntime.dll",
62
+ dependencyFiles: [ ]
63
+ },
64
+ 'java': {
65
+ isCompiled: true,
66
+ startCommand: "java -jar src/function/java-runtime-1.0.0.jar",
67
+ dependencyFiles: [ ]
68
+ },
69
+ 'swift': {
70
+ isCompiled: true,
71
+ startCommand: "src/function/Runtime serve --env production --hostname 0.0.0.0 --port 3000",
72
+ dependencyFiles: [ ]
73
+ },
74
+ 'kotlin': {
75
+ isCompiled: true,
76
+ startCommand: "java -jar src/function/kotlin-runtime-1.0.0.jar",
77
+ dependencyFiles: [ ]
78
+ },
79
+ 'bun': {
80
+ isCompiled: false,
81
+ startCommand: "bun src/server.ts",
82
+ dependencyFiles: [ "package.json", "package-lock.json", "bun.lockb" ]
83
+ },
84
+ };
85
+
86
+ const JwtManager = {
87
+ userJwt: null,
88
+ functionJwt: null,
89
+
90
+ timerWarn: null,
91
+ timerError: null,
92
+
93
+ async setup(userId = null) {
94
+ if(this.timerWarn) {
95
+ clearTimeout(this.timerWarn);
96
+ }
97
+
98
+ if(this.timerError) {
99
+ clearTimeout(this.timerError);
100
+ }
101
+
102
+ this.timerWarn = setTimeout(() => {
103
+ log("Warning: Authorized JWT will expire in 5 minutes. Please stop and re-run the command to refresh tokens for 1 hour.");
104
+ }, 1000 * 60 * 55); // 55 mins
105
+
106
+ this.timerError = setTimeout(() => {
107
+ log("Warning: Authorized JWT just expired. Please stop and re-run the command to obtain new tokens with 1 hour validity.");
108
+ log("Some Appwrite API communication is not authorized now.")
109
+ }, 1000 * 60 * 60); // 60 mins
110
+
111
+ if(userId) {
112
+ await usersGet({
113
+ userId,
114
+ parseOutput: false
115
+ });
116
+ const userResponse = await usersCreateJWT({
117
+ userId,
118
+ duration: 60*60,
119
+ parseOutput: false
120
+ });
121
+ this.userJwt = userResponse.jwt;
122
+ }
123
+
124
+ const functionResponse = await projectsCreateJWT({
125
+ projectId: localConfig.getProject().projectId,
126
+ // TODO: Once we have endpoint for this, use it
127
+ scopes: ["sessions.write","users.read","users.write","teams.read","teams.write","databases.read","databases.write","collections.read","collections.write","attributes.read","attributes.write","indexes.read","indexes.write","documents.read","documents.write","files.read","files.write","buckets.read","buckets.write","functions.read","functions.write","execution.read","execution.write","locale.read","avatars.read","health.read","providers.read","providers.write","messages.read","messages.write","topics.read","topics.write","subscribers.read","subscribers.write","targets.read","targets.write","rules.read","rules.write","migrations.read","migrations.write","vcs.read","vcs.write","assistant.read"],
128
+ duration: 60*60,
129
+ parseOutput: false
130
+ });
131
+ this.functionJwt = functionResponse.jwt;
132
+ }
133
+ };
134
+
135
+ const Queue = {
136
+ files: [],
137
+ locked: false,
138
+ events: new EventEmitter(),
139
+ debounce: null,
140
+ push(file) {
141
+ if(!this.files.includes(file)) {
142
+ this.files.push(file);
143
+ }
144
+
145
+ if(!this.locked) {
146
+ this._trigger();
147
+ }
148
+ },
149
+ lock() {
150
+ this.files = [];
151
+ this.locked = true;
152
+ },
153
+ unlock() {
154
+ this.locked = false;
155
+ if(this.files.length > 0) {
156
+ this._trigger();
157
+ }
158
+ },
159
+ _trigger() {
160
+ if(this.debounce) {
161
+ return;
162
+ }
163
+
164
+ this.debounce = setTimeout(() => {
165
+ this.events.emit('reload', { files: this.files });
166
+ this.debounce = null;
167
+ }, 300);
168
+ }
169
+ };
170
+
171
+ module.exports = {
172
+ openRuntimesVersion,
173
+ runtimeNames,
174
+ systemTools,
175
+ JwtManager,
176
+ Queue
177
+ }
package/lib/id.js ADDED
@@ -0,0 +1,30 @@
1
+ class ID {
2
+ // Generate an hex ID based on timestamp
3
+ // Recreated from https://www.php.net/manual/en/function.uniqid.php
4
+ static #hexTimestamp() {
5
+ const now = new Date();
6
+ const sec = Math.floor(now.getTime() / 1000);
7
+ const msec = now.getMilliseconds();
8
+
9
+ // Convert to hexadecimal
10
+ const hexTimestamp = sec.toString(16) + msec.toString(16).padStart(5, '0');
11
+ return hexTimestamp;
12
+ }
13
+
14
+ static custom(id) {
15
+ return id
16
+ }
17
+
18
+ static unique(padding = 7) {
19
+ // Generate a unique ID with padding to have a longer ID
20
+ const baseId = ID.#hexTimestamp();
21
+ let randomPadding = '';
22
+ for (let i = 0; i < padding; i++) {
23
+ const randomHexDigit = Math.floor(Math.random() * 16).toString(16);
24
+ randomPadding += randomHexDigit;
25
+ }
26
+ return baseId + randomPadding;
27
+ }
28
+ }
29
+
30
+ module.exports = ID;
package/lib/paginate.js CHANGED
@@ -5,7 +5,6 @@ const paginate = async (action, args = {}, limit = 100, wrapper = '') => {
5
5
 
6
6
  while (true) {
7
7
  const offset = pageNumber * limit;
8
-
9
8
  // Merge the limit and offset into the args
10
9
  const response = await action({
11
10
  ...args,
@@ -48,4 +47,4 @@ const paginate = async (action, args = {}, limit = 100, wrapper = '') => {
48
47
 
49
48
  module.exports = {
50
49
  paginate
51
- };
50
+ };
package/lib/parser.js CHANGED
@@ -2,10 +2,18 @@ const chalk = require('chalk');
2
2
  const commander = require('commander');
3
3
  const Table = require('cli-table3');
4
4
  const { description } = require('../package.json');
5
+ const { globalConfig } = require("./config.js");
6
+ const os = require('os');
7
+ const Client = require("./client");
5
8
 
6
9
  const cliConfig = {
7
- verbose: false,
8
- json: false
10
+ verbose: false,
11
+ json: false,
12
+ force: false,
13
+ all: false,
14
+ ids: [],
15
+ report: false,
16
+ reportData: {}
9
17
  };
10
18
 
11
19
  const parse = (data) => {
@@ -110,17 +118,60 @@ const drawJSON = (data) => {
110
118
  }
111
119
 
112
120
  const parseError = (err) => {
113
- if(cliConfig.verbose) {
114
- console.error(err);
115
- }
121
+ if (cliConfig.report) {
122
+ (async () => {
123
+ let appwriteVersion = 'unknown';
124
+ const endpoint = globalConfig.getEndpoint();
125
+ const isCloud = endpoint.includes('cloud.appwrite.io') ? 'Yes' : 'No';
126
+
127
+ try {
128
+ const client = new Client().setEndpoint(endpoint);
129
+ const res = await client.call('get', '/health/version');
130
+ appwriteVersion = res.version;
131
+ } catch {
132
+ }
133
+
134
+ const version = '6.0.0-rc.1';
135
+ const stepsToReproduce = `Running \`appwrite ${cliConfig.reportData.data.args.join(' ')}\``;
136
+ const yourEnvironment = `CLI version: ${version}\nOperation System: ${os.type()}\nAppwrite version: ${appwriteVersion}\nIs Cloud: ${isCloud}`;
137
+
138
+ const stack = '```\n' + err.stack + '\n```';
139
+
140
+ const githubIssueUrl = new URL('https://github.com/appwrite/appwrite/issues/new');
141
+ githubIssueUrl.searchParams.append('labels', 'bug');
142
+ githubIssueUrl.searchParams.append('template', 'bug.yaml');
143
+ githubIssueUrl.searchParams.append('title', `🐛 Bug Report: ${err.message}`);
144
+ githubIssueUrl.searchParams.append('actual-behavior', `CLI Error:\n${stack}`);
145
+ githubIssueUrl.searchParams.append('steps-to-reproduce', stepsToReproduce);
146
+ githubIssueUrl.searchParams.append('environment', yourEnvironment);
147
+
148
+ log(`To report this error you can:\n - Create a support ticket in our Discord server https://appwrite.io/discord \n - Create an issue in our Github\n ${githubIssueUrl.href}\n`);
116
149
 
117
- error(err.message);
118
150
 
119
- process.exit(1)
151
+ error('\n Stack Trace: \n');
152
+ console.error(err);
153
+ process.exit(1);
154
+ })()
155
+ } else {
156
+ if (cliConfig.verbose) {
157
+ console.error(err);
158
+ } else {
159
+ log('For detailed error pass the --verbose or --report flag');
160
+ error(err.message);
161
+ }
162
+ process.exit(1);
163
+ }
164
+
120
165
  }
121
166
 
122
167
  const actionRunner = (fn) => {
123
- return (...args) => fn(...args).catch(parseError);
168
+ return (...args) => {
169
+ if (cliConfig.all && (Array.isArray(cliConfig.ids) && cliConfig.ids.length !== 0)) {
170
+ error(`The '--all' and '--id' flags cannot be used together.`);
171
+ process.exit(1);
172
+ }
173
+ return fn(...args).catch(parseError)
174
+ };
124
175
  }
125
176
 
126
177
  const parseInteger = (value) => {
@@ -156,10 +207,12 @@ const commandDescriptions = {
156
207
  "graphql": `The graphql command allows you to query and mutate any resource type on your Appwrite server.`,
157
208
  "avatars": `The avatars command aims to help you complete everyday tasks related to your app image, icons, and avatars.`,
158
209
  "databases": `The databases command allows you to create structured collections of documents, query and filter lists of documents.`,
159
- "deploy": `The deploy command provides a convenient wrapper for deploying your functions and collections.`,
210
+ "init": `The init command provides a convenient wrapper for creating and initializing project, functions, collections, buckets, teams and messaging in Appwrite.`,
211
+ "push": `The push command provides a convenient wrapper for pushing your functions, collections, buckets, teams and messaging.`,
212
+ "run": `The run command allows you to run project locally to allow easy development and quick debugging.`,
160
213
  "functions": `The functions command allows you view, create and manage your Cloud Functions.`,
161
214
  "health": `The health command allows you to both validate and monitor your Appwrite server's health.`,
162
- "init": `The init command helps you initialize your Appwrite project, functions and collections`,
215
+ "pull": `The pull command helps you pull your Appwrite project, functions, collections, buckets, teams and messaging`,
163
216
  "locale": `The locale command allows you to customize your app based on your users' location.`,
164
217
  "projects": `The projects command allows you to view, create and manage your Appwrite projects.`,
165
218
  "storage": `The storage command allows you to manage your project files.`,
@@ -168,6 +221,8 @@ const commandDescriptions = {
168
221
  "client": `The client command allows you to configure your CLI`,
169
222
  "login": `The login command allows you to authenticate and manage a user account.`,
170
223
  "logout": `The logout command allows you to logout of your Appwrite account.`,
224
+ "whoami": `The whoami command gives information about the currently logged in user.`,
225
+ "register": `Outputs the link to create an Appwrite account..`,
171
226
  "console" : `The console command allows gives you access to the APIs used by the Appwrite console.`,
172
227
  "assistant": `The assistant command allows you to interact with the Appwrite Assistant AI`,
173
228
  "messaging": `The messaging command allows you to send messages.`,
@@ -179,6 +234,7 @@ const commandDescriptions = {
179
234
  }
180
235
 
181
236
  module.exports = {
237
+ drawTable,
182
238
  parse,
183
239
  actionRunner,
184
240
  parseInteger,
@@ -187,5 +243,6 @@ module.exports = {
187
243
  success,
188
244
  error,
189
245
  commandDescriptions,
190
- cliConfig
191
- }
246
+ cliConfig,
247
+ drawTable
248
+ }