@moostjs/event-cli 0.2.26 → 0.2.28

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.
package/README.md CHANGED
@@ -35,8 +35,6 @@ app.init()
35
35
  // "command run with flag test=true"
36
36
  ```
37
37
 
38
-
39
-
40
38
  ## Install
41
39
 
42
40
  `npm install moost @moostjs/event-cli`
package/dist/index.cjs CHANGED
@@ -1,8 +1,8 @@
1
1
  'use strict';
2
2
 
3
- var eventCli = require('@wooksjs/event-cli');
4
- var eventCore = require('@wooksjs/event-core');
5
3
  var moost = require('moost');
4
+ var eventCli = require('@wooksjs/event-cli');
5
+ var cliHelp = require('@prostojs/cli-help');
6
6
 
7
7
  /******************************************************************************
8
8
  Copyright (c) Microsoft Corporation.
@@ -18,6 +18,8 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
18
18
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
19
19
  PERFORMANCE OF THIS SOFTWARE.
20
20
  ***************************************************************************** */
21
+ /* global Reflect, Promise */
22
+
21
23
 
22
24
  function __awaiter(thisArg, _arguments, P, generator) {
23
25
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
@@ -29,106 +31,209 @@ function __awaiter(thisArg, _arguments, P, generator) {
29
31
  });
30
32
  }
31
33
 
34
+ function getCliMate() {
35
+ return moost.getMoostMate();
36
+ }
37
+
38
+ /**
39
+ * ### setCliHelpForEvent
40
+ * Used internally to set CliHelpRenderer instance for an event state
41
+ * @param cliHelp CliHelpRenderer
42
+ */
43
+ function setCliHelpForEvent(cliHelp) {
44
+ eventCli.useCliContext().store('event').set('cliHelp', cliHelp);
45
+ }
46
+ /**
47
+ * ## useCliHelp
48
+ * ### Composable
49
+ * ```js
50
+ * // example of printing cli instructions
51
+ * const { print } = useCliHelp()
52
+ * print(true)
53
+ * ```
54
+ * @returns
55
+ */
56
+ function useCliHelp() {
57
+ const event = eventCli.useCliContext().store('event');
58
+ const getCliHelp = () => event.get('cliHelp');
59
+ return {
60
+ getCliHelp,
61
+ render: (width, withColors) => getCliHelp().render(event.get('pathParams').join(' '), width, withColors),
62
+ print: (withColors) => getCliHelp().print(event.get('pathParams').join(' '), withColors),
63
+ };
64
+ }
65
+
66
+ const LOGGER_TITLE = 'moost-cli';
67
+ const CONTEXT_TYPE = 'CLI';
68
+ /**
69
+ * ## Moost Cli Adapter
70
+ *
71
+ * Moost Adapter for CLI events
72
+ *
73
+ * ```ts
74
+ * │ // Quick example
75
+ * │ import { MoostCli, Cli, CliOption, cliHelpInterceptor } from '@moostjs/event-cli'
76
+ * │ import { Moost, Param } from 'moost'
77
+ * │
78
+ * │ class MyApp extends Moost {
79
+ * │ @Cli('command/:arg')
80
+ * │ command(
81
+ * │ @Param('arg')
82
+ * │ arg: string,
83
+ * │ @CliOption('test', 't')
84
+ * │ test: boolean,
85
+ * │ ) {
86
+ * │ return `command run with flag arg=${ arg }, test=${ test }`
87
+ * │ }
88
+ * │ }
89
+ * │
90
+ * │ const app = new MyApp()
91
+ * │ app.applyGlobalInterceptors(cliHelpInterceptor())
92
+ * │
93
+ * │ const cli = new MoostCli()
94
+ * │ app.adapter(cli)
95
+ * │ app.init()
96
+ * ```
97
+ */
32
98
  class MoostCli {
33
- constructor(cliApp) {
34
- if (cliApp && cliApp instanceof eventCli.WooksCli) {
35
- this.cliApp = cliApp;
99
+ constructor(opts) {
100
+ this.opts = opts;
101
+ const cliAppOpts = opts === null || opts === void 0 ? void 0 : opts.wooksCli;
102
+ if (cliAppOpts && cliAppOpts instanceof eventCli.WooksCli) {
103
+ this.cliApp = cliAppOpts;
36
104
  }
37
- else if (cliApp) {
38
- this.cliApp = eventCli.createCliApp(cliApp);
105
+ else if (cliAppOpts) {
106
+ this.cliApp = eventCli.createCliApp(Object.assign(Object.assign({}, cliAppOpts), { onNotFound: this.onNotFound.bind(this) }));
39
107
  }
40
108
  else {
41
- this.cliApp = eventCli.createCliApp();
109
+ this.cliApp = eventCli.createCliApp({
110
+ onNotFound: this.onNotFound.bind(this),
111
+ });
112
+ }
113
+ const cliHelpOpts = opts === null || opts === void 0 ? void 0 : opts.cliHelp;
114
+ if (cliHelpOpts && cliHelpOpts instanceof cliHelp.CliHelpRenderer) {
115
+ this.cliHelp = cliHelpOpts;
42
116
  }
117
+ else if (cliHelpOpts) {
118
+ this.cliHelp = new cliHelp.CliHelpRenderer(cliHelpOpts);
119
+ }
120
+ else {
121
+ this.cliHelp = new cliHelp.CliHelpRenderer();
122
+ }
123
+ if (!(opts === null || opts === void 0 ? void 0 : opts.debug)) {
124
+ moost.getMoostInfact().silent(true);
125
+ }
126
+ }
127
+ onNotFound() {
128
+ var _a;
129
+ return __awaiter(this, void 0, void 0, function* () {
130
+ const pathParams = eventCli.useCliContext().store('event').get('pathParams');
131
+ const response = yield moost.defineMoostEventHandler({
132
+ loggerTitle: LOGGER_TITLE,
133
+ getIterceptorHandler: () => { var _a; return (_a = this.moost) === null || _a === void 0 ? void 0 : _a.getGlobalInterceptorHandler(); },
134
+ getControllerInstance: () => this.moost,
135
+ callControllerMethod: () => undefined,
136
+ logErrors: (_a = this.opts) === null || _a === void 0 ? void 0 : _a.debug,
137
+ hooks: {
138
+ init: () => setCliHelpForEvent(this.cliHelp),
139
+ },
140
+ })();
141
+ if (typeof response === 'undefined') {
142
+ this.cliApp.onUnknownCommand(pathParams);
143
+ }
144
+ return response;
145
+ });
43
146
  }
44
- onInit() {
147
+ onInit(moost) {
148
+ this.moost = moost;
45
149
  void this.cliApp.run();
46
150
  }
47
151
  bindHandler(opts) {
152
+ var _a, _b, _c;
48
153
  let fn;
49
154
  for (const handler of opts.handlers) {
50
155
  if (handler.type !== 'CLI')
51
156
  continue;
52
- const path = typeof handler.path === 'string' ? handler.path : typeof opts.method === 'string' ? opts.method : '';
53
- const targetPath = `${opts.prefix || ''}/${path}`.replace(/\/\/+/g, '/');
157
+ const path = typeof handler.path === 'string'
158
+ ? handler.path
159
+ : typeof opts.method === 'string'
160
+ ? opts.method
161
+ : '';
162
+ const targetPath = `${opts.prefix.replace(/\s+/g, '/') || ''}/${path}`
163
+ .replace(/\/\/+/g, '/')
164
+ // avoid interpreting "cmd:tail" as "cmd/:tail"
165
+ .replace(/\/\\:/g, '\\:');
166
+ let cliCommand = '';
54
167
  if (!fn) {
55
- fn = () => __awaiter(this, void 0, void 0, function* () {
56
- const { restoreCtx } = eventCli.useCliContext();
57
- const scopeId = eventCore.useEventId().getId();
58
- const unscope = opts.registerEventScope(scopeId);
59
- const instance = yield opts.getInstance();
60
- restoreCtx();
61
- let response;
62
- const interceptorHandler = yield opts.getIterceptorHandler();
63
- restoreCtx();
64
- yield interceptorHandler.init();
65
- // params
66
- let args = [];
67
- try {
68
- restoreCtx();
69
- args = yield opts.resolveArgs();
70
- }
71
- catch (e) {
72
- response = e;
73
- }
74
- if (!response) {
75
- restoreCtx();
76
- // fire before interceptors
77
- response = yield interceptorHandler.fireBefore(response);
78
- // fire request handler
79
- if (!interceptorHandler.responseOverwritten) {
80
- try {
81
- restoreCtx();
82
- response = yield instance[opts.method](...args);
83
- }
84
- catch (e) {
85
- response = e;
86
- }
87
- }
88
- }
89
- restoreCtx();
90
- // fire after interceptors
91
- response = yield interceptorHandler.fireAfter(response);
92
- unscope();
93
- return response;
168
+ fn = moost.defineMoostEventHandler({
169
+ contextType: CONTEXT_TYPE,
170
+ loggerTitle: LOGGER_TITLE,
171
+ getIterceptorHandler: opts.getIterceptorHandler,
172
+ getControllerInstance: opts.getInstance,
173
+ controllerMethod: opts.method,
174
+ resolveArgs: opts.resolveArgs,
175
+ logErrors: (_a = this.opts) === null || _a === void 0 ? void 0 : _a.debug,
176
+ hooks: {
177
+ init: () => setCliHelpForEvent(this.cliHelp),
178
+ },
94
179
  });
95
180
  }
96
- this.cliApp.cli(targetPath, fn);
97
- opts.logHandler(`${''}(CLI)${''}${targetPath}`);
181
+ const { getArgs, getStaticPart } = this.cliApp.cli(targetPath, fn);
182
+ const meta = getCliMate().read(opts.fakeInstance, opts.method);
183
+ const args = {};
184
+ getArgs().forEach(a => {
185
+ var _a;
186
+ const argParam = (_a = meta === null || meta === void 0 ? void 0 : meta.params) === null || _a === void 0 ? void 0 : _a.find(p => p.label === a && p.description);
187
+ args[a] = (argParam === null || argParam === void 0 ? void 0 : argParam.description) || '';
188
+ });
189
+ cliCommand = getStaticPart().replace(/\//g, ' ').trim();
190
+ this.cliHelp.addEntry({
191
+ description: (meta === null || meta === void 0 ? void 0 : meta.description) || '',
192
+ command: cliCommand,
193
+ options: ((_b = meta === null || meta === void 0 ? void 0 : meta.params) === null || _b === void 0 ? void 0 : _b.filter(param => !!param.cliParamKeys && param.cliParamKeys.length > 0).map(param => ({
194
+ keys: param.cliParamKeys,
195
+ value: typeof param.value === 'string' ? param.value : '',
196
+ description: param.description || '',
197
+ }))) || [],
198
+ args,
199
+ aliases: meta === null || meta === void 0 ? void 0 : meta.cliAliases,
200
+ });
201
+ if ((_c = this.opts) === null || _c === void 0 ? void 0 : _c.debug) {
202
+ opts.logHandler(`${''}(CLI)${''}${targetPath}`);
203
+ }
98
204
  }
99
205
  }
100
206
  }
101
207
 
102
- function getCliMate() {
103
- return moost.getMoostMate();
208
+ function formatParams(keys) {
209
+ const names = [keys].flat();
210
+ return names.map((n) => (n.length === 1 ? '-' + n : '--' + n));
104
211
  }
105
212
 
106
213
  /**
107
- * Get Cli Flag
108
- * @decorator
109
- * @param name - flag name
110
- * @paramType string
214
+ * ## Define CLI Option
215
+ * ### @ParameterDecorator
216
+ * Use together with @Description('...') to document cli option
217
+ *
218
+ * ```ts
219
+ * │ @Cli('command')
220
+ * │ command(
221
+ * │ @Description('Test flag...')
222
+ * │ @CliOption('test', 't')
223
+ * │ test: boolean,
224
+ * │ ) {
225
+ * │ return `test=${ test }`
226
+ * │ }
227
+ * ```
228
+ *
229
+ * @param keys list of keys (short and long alternatives)
230
+ * @returns
111
231
  */
112
- function Flag(name) {
113
- return moost.Resolve(() => eventCli.useFlags()[name], name);
114
- }
115
- /**
116
- * Get Cli Flags
117
- * @decorator
118
- * @paramType object
119
- */
120
- function Flags() {
121
- return moost.Resolve(() => eventCli.useFlags(), 'flags');
122
- }
123
- function formatParams(keys) {
124
- const names = [keys].flat();
125
- return names.map(n => n.length === 1 ? '-' + n : '--' + n);
126
- }
127
- function CliParam(keys, descr) {
232
+ function CliOption(...keys) {
128
233
  const mate = getCliMate();
129
- return mate.apply(mate.decorate('cliParams', { keys, descr }, true), moost.Resolve(() => {
234
+ return mate.apply(mate.decorate('cliParamKeys', keys, false), moost.Resolve(() => {
130
235
  const flags = eventCli.useFlags();
131
- const names = [keys].flat();
236
+ const names = keys;
132
237
  const vals = [];
133
238
  for (const name of names) {
134
239
  if (flags[name]) {
@@ -145,12 +250,121 @@ function CliParam(keys, descr) {
145
250
  }, formatParams(keys).join(', ')));
146
251
  }
147
252
 
253
+ /**
254
+ * ## Define CLI Command
255
+ * ### @MethodDecorator
256
+ *
257
+ * Command path segments may be separated by / or space.
258
+ *
259
+ * For example the folowing path are interpreted the same:
260
+ * - "command test use:dev :name"
261
+ * - "command/test/use:dev/:name"
262
+ * Where name will become an argument
263
+ *
264
+ * @param path - command path
265
+ * @returns
266
+ */
148
267
  function Cli(path) {
149
- return getCliMate().decorate('handlers', { path, type: 'CLI' }, true);
268
+ return getCliMate().decorate('handlers', { path: path === null || path === void 0 ? void 0 : path.replace(/\s+/g, '/'), type: 'CLI' }, true);
269
+ }
270
+ /**
271
+ * ## Define CLI Command Alias
272
+ * ### @MethodDecorator
273
+ *
274
+ * Use it to define alias for @Cli('...') command
275
+ *
276
+ * @param path - command alias path
277
+ * @returns
278
+ */
279
+ function CliAlias(alias) {
280
+ return getCliMate().decorate('cliAliases', alias, true);
150
281
  }
151
282
 
283
+ /**
284
+ * ### Interceptor Factory for CliHelpRenderer
285
+ *
286
+ * By default intercepts cli calls with flag --help
287
+ * and prints help.
288
+ *
289
+ * ```js
290
+ * new Moost().applyGlobalInterceptors(cliHelpInterceptor({ colors: true }))
291
+ * ```
292
+ * @param opts {} { helpOptions: ['help', 'h'], colors: true } cli options to invoke help renderer
293
+ * @returns TInterceptorFn
294
+ */
295
+ const cliHelpInterceptor = (opts) => {
296
+ return moost.defineInterceptorFn(() => {
297
+ const helpOptions = (opts === null || opts === void 0 ? void 0 : opts.helpOptions) || ['help'];
298
+ for (const option of helpOptions) {
299
+ if (eventCli.useFlag(option) === true) {
300
+ try {
301
+ useCliHelp().print(opts === null || opts === void 0 ? void 0 : opts.colors);
302
+ return '';
303
+ }
304
+ catch (e) {
305
+ //
306
+ }
307
+ }
308
+ }
309
+ if ((opts === null || opts === void 0 ? void 0 : opts.helpWithArgs) || (opts === null || opts === void 0 ? void 0 : opts.helpWithIncompleteCmd)) {
310
+ const { getMethod } = moost.useControllerContext();
311
+ if (!getMethod()) {
312
+ const pathParams = eventCli.useCliContext().store('event').get('pathParams').join(' ');
313
+ const cliHelp = useCliHelp().getCliHelp();
314
+ const cmd = cliHelp.getCliName();
315
+ let data;
316
+ try {
317
+ data = cliHelp.match(pathParams);
318
+ }
319
+ catch (e) {
320
+ if (opts === null || opts === void 0 ? void 0 : opts.helpWithIncompleteCmd) {
321
+ const variants = cliHelp.lookup(pathParams);
322
+ if (variants.length) {
323
+ throw new Error(`Command is incomplete, did you mean:\n${variants.slice(0, 7).map(c => ` $ ${cmd} ${c.main.command}`).join('\n')}`);
324
+ }
325
+ }
326
+ }
327
+ if (data) {
328
+ const { main, children } = data;
329
+ if ((opts === null || opts === void 0 ? void 0 : opts.helpWithArgs) && main.args && Object.keys(main.args).length) {
330
+ throw new Error(`Arguments expected: ${Object.keys(main.args).map(l => `<${l}>`).join(', ')}`);
331
+ }
332
+ else if ((opts === null || opts === void 0 ? void 0 : opts.helpWithIncompleteCmd) && children) {
333
+ throw new Error(`Command is incomplete, did you mean:\n${children.slice(0, 7).map(c => ` $ ${cmd} ${c.command}`).join('\n')}`);
334
+ }
335
+ }
336
+ }
337
+ }
338
+ }, moost.TInterceptorPriority.BEFORE_ALL);
339
+ };
340
+ /**
341
+ * ## @Decorator
342
+ * ### Interceptor Factory for CliHelpRenderer
343
+ *
344
+ * By default intercepts cli calls with flag --help
345
+ * and prints help.
346
+ *
347
+ * ```ts
348
+ * // default configuration
349
+ * • @CliHelpInterceptor({ helpOptions: 'help', colors: true })
350
+ *
351
+ * // additional option -h to invoke help renderer
352
+ * • @CliHelpInterceptor({ helpOptions: ['help', 'h'], colors: true })
353
+ *
354
+ * // redefine cli option to invoke help renderer
355
+ * • @CliHelpInterceptor({ helpOptions: ['usage'] })
356
+ * ```
357
+ *
358
+ * @param opts {} { helpOptions: ['help', 'h'], colors: true } cli options to invoke help renderer
359
+ * @returns Decorator
360
+ */
361
+ const CliHelpInterceptor = (...opts) => moost.Intercept(cliHelpInterceptor(...opts));
362
+
152
363
  exports.Cli = Cli;
153
- exports.CliParam = CliParam;
154
- exports.Flag = Flag;
155
- exports.Flags = Flags;
364
+ exports.CliAlias = CliAlias;
365
+ exports.CliHelpInterceptor = CliHelpInterceptor;
366
+ exports.CliOption = CliOption;
156
367
  exports.MoostCli = MoostCli;
368
+ exports.cliHelpInterceptor = cliHelpInterceptor;
369
+ exports.setCliHelpForEvent = setCliHelpForEvent;
370
+ exports.useCliHelp = useCliHelp;
package/dist/index.d.ts CHANGED
@@ -1,36 +1,220 @@
1
+ import { CliHelpRenderer } from '@prostojs/cli-help';
2
+ import { Moost } from 'moost';
3
+ import { TCliHelpOptions } from '@prostojs/cli-help';
4
+ import { TInterceptorFn } from 'moost';
1
5
  import { TMoostAdapter } from 'moost';
2
6
  import { TMoostAdapterOptions } from 'moost';
3
7
  import { TWooksCliOptions } from '@wooksjs/event-cli';
4
8
  import { WooksCli } from '@wooksjs/event-cli';
5
9
 
10
+ /**
11
+ * ## Define CLI Command
12
+ * ### @MethodDecorator
13
+ *
14
+ * Command path segments may be separated by / or space.
15
+ *
16
+ * For example the folowing path are interpreted the same:
17
+ * - "command test use:dev :name"
18
+ * - "command/test/use:dev/:name"
19
+ * Where name will become an argument
20
+ *
21
+ * @param path - command path
22
+ * @returns
23
+ */
6
24
  export declare function Cli(path?: string): MethodDecorator;
7
25
 
8
- export declare function CliParam(keys: string | [string, string], descr?: string): MethodDecorator & ClassDecorator & ParameterDecorator & PropertyDecorator;
26
+ /**
27
+ * ## Define CLI Command Alias
28
+ * ### @MethodDecorator
29
+ *
30
+ * Use it to define alias for @Cli('...') command
31
+ *
32
+ * @param path - command alias path
33
+ * @returns
34
+ */
35
+ export declare function CliAlias(alias: string): MethodDecorator;
9
36
 
10
37
  /**
11
- * Get Cli Flag
12
- * @decorator
13
- * @param name - flag name
14
- * @paramType string
38
+ * ## @Decorator
39
+ * ### Interceptor Factory for CliHelpRenderer
40
+ *
41
+ * By default intercepts cli calls with flag --help
42
+ * and prints help.
43
+ *
44
+ * ```ts
45
+ * // default configuration
46
+ * • @CliHelpInterceptor({ helpOptions: 'help', colors: true })
47
+ *
48
+ * // additional option -h to invoke help renderer
49
+ * • @CliHelpInterceptor({ helpOptions: ['help', 'h'], colors: true })
50
+ *
51
+ * // redefine cli option to invoke help renderer
52
+ * • @CliHelpInterceptor({ helpOptions: ['usage'] })
53
+ * ```
54
+ *
55
+ * @param opts {} { helpOptions: ['help', 'h'], colors: true } cli options to invoke help renderer
56
+ * @returns Decorator
15
57
  */
16
- export declare function Flag(name: string): ParameterDecorator & PropertyDecorator;
58
+ export declare const CliHelpInterceptor: (opts?: {
59
+ /**
60
+ * CLI Options that invoke help
61
+ * ```js
62
+ * helpOptions: ['help', 'h']
63
+ * ```
64
+ */
65
+ helpOptions?: string[] | undefined;
66
+ /**
67
+ * Enable colored help
68
+ */
69
+ colors?: boolean | undefined;
70
+ /**
71
+ * Enable help message when arguments are missing
72
+ */
73
+ helpWithArgs?: boolean | undefined;
74
+ /**
75
+ * Enable help message when command is incomplete
76
+ * and it is possible to suggest related commands
77
+ */
78
+ helpWithIncompleteCmd?: boolean | undefined;
79
+ } | undefined) => ClassDecorator & MethodDecorator;
17
80
 
18
81
  /**
19
- * Get Cli Flags
20
- * @decorator
21
- * @paramType object
82
+ * ### Interceptor Factory for CliHelpRenderer
83
+ *
84
+ * By default intercepts cli calls with flag --help
85
+ * and prints help.
86
+ *
87
+ * ```js
88
+ * new Moost().applyGlobalInterceptors(cliHelpInterceptor({ colors: true }))
89
+ * ```
90
+ * @param opts {} { helpOptions: ['help', 'h'], colors: true } cli options to invoke help renderer
91
+ * @returns TInterceptorFn
22
92
  */
23
- export declare function Flags(): ParameterDecorator & PropertyDecorator;
93
+ export declare const cliHelpInterceptor: (opts?: {
94
+ /**
95
+ * CLI Options that invoke help
96
+ * ```js
97
+ * helpOptions: ['help', 'h']
98
+ * ```
99
+ */
100
+ helpOptions?: string[];
101
+ /**
102
+ * Enable colored help
103
+ */
104
+ colors?: boolean;
105
+ /**
106
+ * Enable help message when arguments are missing
107
+ */
108
+ helpWithArgs?: boolean;
109
+ /**
110
+ * Enable help message when command is incomplete
111
+ * and it is possible to suggest related commands
112
+ */
113
+ helpWithIncompleteCmd?: boolean;
114
+ }) => TInterceptorFn;
24
115
 
116
+ /**
117
+ * ## Define CLI Option
118
+ * ### @ParameterDecorator
119
+ * Use together with @Description('...') to document cli option
120
+ *
121
+ * ```ts
122
+ * │ @Cli('command')
123
+ * │ command(
124
+ * │ @Description('Test flag...')
125
+ * │ @CliOption('test', 't')
126
+ * │ test: boolean,
127
+ * │ ) {
128
+ * │ return `test=${ test }`
129
+ * │ }
130
+ * ```
131
+ *
132
+ * @param keys list of keys (short and long alternatives)
133
+ * @returns
134
+ */
135
+ export declare function CliOption(...keys: string[]): ParameterDecorator;
136
+
137
+ /**
138
+ * ## Moost Cli Adapter
139
+ *
140
+ * Moost Adapter for CLI events
141
+ *
142
+ * ```ts
143
+ * │ // Quick example
144
+ * │ import { MoostCli, Cli, CliOption, cliHelpInterceptor } from '@moostjs/event-cli'
145
+ * │ import { Moost, Param } from 'moost'
146
+ * │
147
+ * │ class MyApp extends Moost {
148
+ * │ @Cli('command/:arg')
149
+ * │ command(
150
+ * │ @Param('arg')
151
+ * │ arg: string,
152
+ * │ @CliOption('test', 't')
153
+ * │ test: boolean,
154
+ * │ ) {
155
+ * │ return `command run with flag arg=${ arg }, test=${ test }`
156
+ * │ }
157
+ * │ }
158
+ * │
159
+ * │ const app = new MyApp()
160
+ * │ app.applyGlobalInterceptors(cliHelpInterceptor())
161
+ * │
162
+ * │ const cli = new MoostCli()
163
+ * │ app.adapter(cli)
164
+ * │ app.init()
165
+ * ```
166
+ */
25
167
  export declare class MoostCli implements TMoostAdapter<TCliHandlerMeta> {
168
+ protected opts?: TMoostCliOpts | undefined;
26
169
  protected cliApp: WooksCli;
27
- constructor(cliApp?: WooksCli | TWooksCliOptions);
28
- onInit(): void;
170
+ protected cliHelp: CliHelpRenderer;
171
+ constructor(opts?: TMoostCliOpts | undefined);
172
+ onNotFound(): Promise<unknown>;
173
+ protected moost?: Moost;
174
+ onInit(moost: Moost): void;
29
175
  bindHandler<T extends object = object>(opts: TMoostAdapterOptions<TCliHandlerMeta, T>): void | Promise<void>;
30
176
  }
31
177
 
178
+ /**
179
+ * ### setCliHelpForEvent
180
+ * Used internally to set CliHelpRenderer instance for an event state
181
+ * @param cliHelp CliHelpRenderer
182
+ */
183
+ export declare function setCliHelpForEvent(cliHelp: CliHelpRenderer): void;
184
+
32
185
  export declare interface TCliHandlerMeta {
33
186
  path: string;
34
187
  }
35
188
 
189
+ export declare interface TMoostCliOpts {
190
+ /**
191
+ * WooksCli options or instance
192
+ */
193
+ wooksCli?: WooksCli | TWooksCliOptions;
194
+ /**
195
+ * CliHelpRenderer options or instance
196
+ */
197
+ cliHelp?: CliHelpRenderer | TCliHelpOptions;
198
+ /**
199
+ * more internal logs are printed when true
200
+ */
201
+ debug?: boolean;
202
+ }
203
+
204
+ /**
205
+ * ## useCliHelp
206
+ * ### Composable
207
+ * ```js
208
+ * // example of printing cli instructions
209
+ * const { print } = useCliHelp()
210
+ * print(true)
211
+ * ```
212
+ * @returns
213
+ */
214
+ export declare function useCliHelp(): {
215
+ getCliHelp: () => CliHelpRenderer;
216
+ render: (width?: number, withColors?: boolean) => string[];
217
+ print: (withColors?: boolean) => void;
218
+ };
219
+
36
220
  export { }
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { WooksCli, createCliApp, useCliContext, useFlags } from '@wooksjs/event-cli';
2
- import { useEventId } from '@wooksjs/event-core';
3
- import { getMoostMate, Resolve } from 'moost';
1
+ import { getMoostMate, getMoostInfact, defineMoostEventHandler, Resolve, defineInterceptorFn, useControllerContext, TInterceptorPriority, Intercept } from 'moost';
2
+ import { useCliContext, WooksCli, createCliApp, useFlags, useFlag } from '@wooksjs/event-cli';
3
+ import { CliHelpRenderer } from '@prostojs/cli-help';
4
4
 
5
5
  /******************************************************************************
6
6
  Copyright (c) Microsoft Corporation.
@@ -16,6 +16,8 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
16
16
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
17
17
  PERFORMANCE OF THIS SOFTWARE.
18
18
  ***************************************************************************** */
19
+ /* global Reflect, Promise */
20
+
19
21
 
20
22
  function __awaiter(thisArg, _arguments, P, generator) {
21
23
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
@@ -27,106 +29,209 @@ function __awaiter(thisArg, _arguments, P, generator) {
27
29
  });
28
30
  }
29
31
 
32
+ function getCliMate() {
33
+ return getMoostMate();
34
+ }
35
+
36
+ /**
37
+ * ### setCliHelpForEvent
38
+ * Used internally to set CliHelpRenderer instance for an event state
39
+ * @param cliHelp CliHelpRenderer
40
+ */
41
+ function setCliHelpForEvent(cliHelp) {
42
+ useCliContext().store('event').set('cliHelp', cliHelp);
43
+ }
44
+ /**
45
+ * ## useCliHelp
46
+ * ### Composable
47
+ * ```js
48
+ * // example of printing cli instructions
49
+ * const { print } = useCliHelp()
50
+ * print(true)
51
+ * ```
52
+ * @returns
53
+ */
54
+ function useCliHelp() {
55
+ const event = useCliContext().store('event');
56
+ const getCliHelp = () => event.get('cliHelp');
57
+ return {
58
+ getCliHelp,
59
+ render: (width, withColors) => getCliHelp().render(event.get('pathParams').join(' '), width, withColors),
60
+ print: (withColors) => getCliHelp().print(event.get('pathParams').join(' '), withColors),
61
+ };
62
+ }
63
+
64
+ const LOGGER_TITLE = 'moost-cli';
65
+ const CONTEXT_TYPE = 'CLI';
66
+ /**
67
+ * ## Moost Cli Adapter
68
+ *
69
+ * Moost Adapter for CLI events
70
+ *
71
+ * ```ts
72
+ * │ // Quick example
73
+ * │ import { MoostCli, Cli, CliOption, cliHelpInterceptor } from '@moostjs/event-cli'
74
+ * │ import { Moost, Param } from 'moost'
75
+ * │
76
+ * │ class MyApp extends Moost {
77
+ * │ @Cli('command/:arg')
78
+ * │ command(
79
+ * │ @Param('arg')
80
+ * │ arg: string,
81
+ * │ @CliOption('test', 't')
82
+ * │ test: boolean,
83
+ * │ ) {
84
+ * │ return `command run with flag arg=${ arg }, test=${ test }`
85
+ * │ }
86
+ * │ }
87
+ * │
88
+ * │ const app = new MyApp()
89
+ * │ app.applyGlobalInterceptors(cliHelpInterceptor())
90
+ * │
91
+ * │ const cli = new MoostCli()
92
+ * │ app.adapter(cli)
93
+ * │ app.init()
94
+ * ```
95
+ */
30
96
  class MoostCli {
31
- constructor(cliApp) {
32
- if (cliApp && cliApp instanceof WooksCli) {
33
- this.cliApp = cliApp;
97
+ constructor(opts) {
98
+ this.opts = opts;
99
+ const cliAppOpts = opts === null || opts === void 0 ? void 0 : opts.wooksCli;
100
+ if (cliAppOpts && cliAppOpts instanceof WooksCli) {
101
+ this.cliApp = cliAppOpts;
34
102
  }
35
- else if (cliApp) {
36
- this.cliApp = createCliApp(cliApp);
103
+ else if (cliAppOpts) {
104
+ this.cliApp = createCliApp(Object.assign(Object.assign({}, cliAppOpts), { onNotFound: this.onNotFound.bind(this) }));
37
105
  }
38
106
  else {
39
- this.cliApp = createCliApp();
107
+ this.cliApp = createCliApp({
108
+ onNotFound: this.onNotFound.bind(this),
109
+ });
40
110
  }
111
+ const cliHelpOpts = opts === null || opts === void 0 ? void 0 : opts.cliHelp;
112
+ if (cliHelpOpts && cliHelpOpts instanceof CliHelpRenderer) {
113
+ this.cliHelp = cliHelpOpts;
114
+ }
115
+ else if (cliHelpOpts) {
116
+ this.cliHelp = new CliHelpRenderer(cliHelpOpts);
117
+ }
118
+ else {
119
+ this.cliHelp = new CliHelpRenderer();
120
+ }
121
+ if (!(opts === null || opts === void 0 ? void 0 : opts.debug)) {
122
+ getMoostInfact().silent(true);
123
+ }
124
+ }
125
+ onNotFound() {
126
+ var _a;
127
+ return __awaiter(this, void 0, void 0, function* () {
128
+ const pathParams = useCliContext().store('event').get('pathParams');
129
+ const response = yield defineMoostEventHandler({
130
+ loggerTitle: LOGGER_TITLE,
131
+ getIterceptorHandler: () => { var _a; return (_a = this.moost) === null || _a === void 0 ? void 0 : _a.getGlobalInterceptorHandler(); },
132
+ getControllerInstance: () => this.moost,
133
+ callControllerMethod: () => undefined,
134
+ logErrors: (_a = this.opts) === null || _a === void 0 ? void 0 : _a.debug,
135
+ hooks: {
136
+ init: () => setCliHelpForEvent(this.cliHelp),
137
+ },
138
+ })();
139
+ if (typeof response === 'undefined') {
140
+ this.cliApp.onUnknownCommand(pathParams);
141
+ }
142
+ return response;
143
+ });
41
144
  }
42
- onInit() {
145
+ onInit(moost) {
146
+ this.moost = moost;
43
147
  void this.cliApp.run();
44
148
  }
45
149
  bindHandler(opts) {
150
+ var _a, _b, _c;
46
151
  let fn;
47
152
  for (const handler of opts.handlers) {
48
153
  if (handler.type !== 'CLI')
49
154
  continue;
50
- const path = typeof handler.path === 'string' ? handler.path : typeof opts.method === 'string' ? opts.method : '';
51
- const targetPath = `${opts.prefix || ''}/${path}`.replace(/\/\/+/g, '/');
155
+ const path = typeof handler.path === 'string'
156
+ ? handler.path
157
+ : typeof opts.method === 'string'
158
+ ? opts.method
159
+ : '';
160
+ const targetPath = `${opts.prefix.replace(/\s+/g, '/') || ''}/${path}`
161
+ .replace(/\/\/+/g, '/')
162
+ // avoid interpreting "cmd:tail" as "cmd/:tail"
163
+ .replace(/\/\\:/g, '\\:');
164
+ let cliCommand = '';
52
165
  if (!fn) {
53
- fn = () => __awaiter(this, void 0, void 0, function* () {
54
- const { restoreCtx } = useCliContext();
55
- const scopeId = useEventId().getId();
56
- const unscope = opts.registerEventScope(scopeId);
57
- const instance = yield opts.getInstance();
58
- restoreCtx();
59
- let response;
60
- const interceptorHandler = yield opts.getIterceptorHandler();
61
- restoreCtx();
62
- yield interceptorHandler.init();
63
- // params
64
- let args = [];
65
- try {
66
- restoreCtx();
67
- args = yield opts.resolveArgs();
68
- }
69
- catch (e) {
70
- response = e;
71
- }
72
- if (!response) {
73
- restoreCtx();
74
- // fire before interceptors
75
- response = yield interceptorHandler.fireBefore(response);
76
- // fire request handler
77
- if (!interceptorHandler.responseOverwritten) {
78
- try {
79
- restoreCtx();
80
- response = yield instance[opts.method](...args);
81
- }
82
- catch (e) {
83
- response = e;
84
- }
85
- }
86
- }
87
- restoreCtx();
88
- // fire after interceptors
89
- response = yield interceptorHandler.fireAfter(response);
90
- unscope();
91
- return response;
166
+ fn = defineMoostEventHandler({
167
+ contextType: CONTEXT_TYPE,
168
+ loggerTitle: LOGGER_TITLE,
169
+ getIterceptorHandler: opts.getIterceptorHandler,
170
+ getControllerInstance: opts.getInstance,
171
+ controllerMethod: opts.method,
172
+ resolveArgs: opts.resolveArgs,
173
+ logErrors: (_a = this.opts) === null || _a === void 0 ? void 0 : _a.debug,
174
+ hooks: {
175
+ init: () => setCliHelpForEvent(this.cliHelp),
176
+ },
92
177
  });
93
178
  }
94
- this.cliApp.cli(targetPath, fn);
95
- opts.logHandler(`${''}(CLI)${''}${targetPath}`);
179
+ const { getArgs, getStaticPart } = this.cliApp.cli(targetPath, fn);
180
+ const meta = getCliMate().read(opts.fakeInstance, opts.method);
181
+ const args = {};
182
+ getArgs().forEach(a => {
183
+ var _a;
184
+ const argParam = (_a = meta === null || meta === void 0 ? void 0 : meta.params) === null || _a === void 0 ? void 0 : _a.find(p => p.label === a && p.description);
185
+ args[a] = (argParam === null || argParam === void 0 ? void 0 : argParam.description) || '';
186
+ });
187
+ cliCommand = getStaticPart().replace(/\//g, ' ').trim();
188
+ this.cliHelp.addEntry({
189
+ description: (meta === null || meta === void 0 ? void 0 : meta.description) || '',
190
+ command: cliCommand,
191
+ options: ((_b = meta === null || meta === void 0 ? void 0 : meta.params) === null || _b === void 0 ? void 0 : _b.filter(param => !!param.cliParamKeys && param.cliParamKeys.length > 0).map(param => ({
192
+ keys: param.cliParamKeys,
193
+ value: typeof param.value === 'string' ? param.value : '',
194
+ description: param.description || '',
195
+ }))) || [],
196
+ args,
197
+ aliases: meta === null || meta === void 0 ? void 0 : meta.cliAliases,
198
+ });
199
+ if ((_c = this.opts) === null || _c === void 0 ? void 0 : _c.debug) {
200
+ opts.logHandler(`${''}(CLI)${''}${targetPath}`);
201
+ }
96
202
  }
97
203
  }
98
204
  }
99
205
 
100
- function getCliMate() {
101
- return getMoostMate();
206
+ function formatParams(keys) {
207
+ const names = [keys].flat();
208
+ return names.map((n) => (n.length === 1 ? '-' + n : '--' + n));
102
209
  }
103
210
 
104
211
  /**
105
- * Get Cli Flag
106
- * @decorator
107
- * @param name - flag name
108
- * @paramType string
212
+ * ## Define CLI Option
213
+ * ### @ParameterDecorator
214
+ * Use together with @Description('...') to document cli option
215
+ *
216
+ * ```ts
217
+ * │ @Cli('command')
218
+ * │ command(
219
+ * │ @Description('Test flag...')
220
+ * │ @CliOption('test', 't')
221
+ * │ test: boolean,
222
+ * │ ) {
223
+ * │ return `test=${ test }`
224
+ * │ }
225
+ * ```
226
+ *
227
+ * @param keys list of keys (short and long alternatives)
228
+ * @returns
109
229
  */
110
- function Flag(name) {
111
- return Resolve(() => useFlags()[name], name);
112
- }
113
- /**
114
- * Get Cli Flags
115
- * @decorator
116
- * @paramType object
117
- */
118
- function Flags() {
119
- return Resolve(() => useFlags(), 'flags');
120
- }
121
- function formatParams(keys) {
122
- const names = [keys].flat();
123
- return names.map(n => n.length === 1 ? '-' + n : '--' + n);
124
- }
125
- function CliParam(keys, descr) {
230
+ function CliOption(...keys) {
126
231
  const mate = getCliMate();
127
- return mate.apply(mate.decorate('cliParams', { keys, descr }, true), Resolve(() => {
232
+ return mate.apply(mate.decorate('cliParamKeys', keys, false), Resolve(() => {
128
233
  const flags = useFlags();
129
- const names = [keys].flat();
234
+ const names = keys;
130
235
  const vals = [];
131
236
  for (const name of names) {
132
237
  if (flags[name]) {
@@ -143,8 +248,114 @@ function CliParam(keys, descr) {
143
248
  }, formatParams(keys).join(', ')));
144
249
  }
145
250
 
251
+ /**
252
+ * ## Define CLI Command
253
+ * ### @MethodDecorator
254
+ *
255
+ * Command path segments may be separated by / or space.
256
+ *
257
+ * For example the folowing path are interpreted the same:
258
+ * - "command test use:dev :name"
259
+ * - "command/test/use:dev/:name"
260
+ * Where name will become an argument
261
+ *
262
+ * @param path - command path
263
+ * @returns
264
+ */
146
265
  function Cli(path) {
147
- return getCliMate().decorate('handlers', { path, type: 'CLI' }, true);
266
+ return getCliMate().decorate('handlers', { path: path === null || path === void 0 ? void 0 : path.replace(/\s+/g, '/'), type: 'CLI' }, true);
267
+ }
268
+ /**
269
+ * ## Define CLI Command Alias
270
+ * ### @MethodDecorator
271
+ *
272
+ * Use it to define alias for @Cli('...') command
273
+ *
274
+ * @param path - command alias path
275
+ * @returns
276
+ */
277
+ function CliAlias(alias) {
278
+ return getCliMate().decorate('cliAliases', alias, true);
148
279
  }
149
280
 
150
- export { Cli, CliParam, Flag, Flags, MoostCli };
281
+ /**
282
+ * ### Interceptor Factory for CliHelpRenderer
283
+ *
284
+ * By default intercepts cli calls with flag --help
285
+ * and prints help.
286
+ *
287
+ * ```js
288
+ * new Moost().applyGlobalInterceptors(cliHelpInterceptor({ colors: true }))
289
+ * ```
290
+ * @param opts {} { helpOptions: ['help', 'h'], colors: true } cli options to invoke help renderer
291
+ * @returns TInterceptorFn
292
+ */
293
+ const cliHelpInterceptor = (opts) => {
294
+ return defineInterceptorFn(() => {
295
+ const helpOptions = (opts === null || opts === void 0 ? void 0 : opts.helpOptions) || ['help'];
296
+ for (const option of helpOptions) {
297
+ if (useFlag(option) === true) {
298
+ try {
299
+ useCliHelp().print(opts === null || opts === void 0 ? void 0 : opts.colors);
300
+ return '';
301
+ }
302
+ catch (e) {
303
+ //
304
+ }
305
+ }
306
+ }
307
+ if ((opts === null || opts === void 0 ? void 0 : opts.helpWithArgs) || (opts === null || opts === void 0 ? void 0 : opts.helpWithIncompleteCmd)) {
308
+ const { getMethod } = useControllerContext();
309
+ if (!getMethod()) {
310
+ const pathParams = useCliContext().store('event').get('pathParams').join(' ');
311
+ const cliHelp = useCliHelp().getCliHelp();
312
+ const cmd = cliHelp.getCliName();
313
+ let data;
314
+ try {
315
+ data = cliHelp.match(pathParams);
316
+ }
317
+ catch (e) {
318
+ if (opts === null || opts === void 0 ? void 0 : opts.helpWithIncompleteCmd) {
319
+ const variants = cliHelp.lookup(pathParams);
320
+ if (variants.length) {
321
+ throw new Error(`Command is incomplete, did you mean:\n${variants.slice(0, 7).map(c => ` $ ${cmd} ${c.main.command}`).join('\n')}`);
322
+ }
323
+ }
324
+ }
325
+ if (data) {
326
+ const { main, children } = data;
327
+ if ((opts === null || opts === void 0 ? void 0 : opts.helpWithArgs) && main.args && Object.keys(main.args).length) {
328
+ throw new Error(`Arguments expected: ${Object.keys(main.args).map(l => `<${l}>`).join(', ')}`);
329
+ }
330
+ else if ((opts === null || opts === void 0 ? void 0 : opts.helpWithIncompleteCmd) && children) {
331
+ throw new Error(`Command is incomplete, did you mean:\n${children.slice(0, 7).map(c => ` $ ${cmd} ${c.command}`).join('\n')}`);
332
+ }
333
+ }
334
+ }
335
+ }
336
+ }, TInterceptorPriority.BEFORE_ALL);
337
+ };
338
+ /**
339
+ * ## @Decorator
340
+ * ### Interceptor Factory for CliHelpRenderer
341
+ *
342
+ * By default intercepts cli calls with flag --help
343
+ * and prints help.
344
+ *
345
+ * ```ts
346
+ * // default configuration
347
+ * • @CliHelpInterceptor({ helpOptions: 'help', colors: true })
348
+ *
349
+ * // additional option -h to invoke help renderer
350
+ * • @CliHelpInterceptor({ helpOptions: ['help', 'h'], colors: true })
351
+ *
352
+ * // redefine cli option to invoke help renderer
353
+ * • @CliHelpInterceptor({ helpOptions: ['usage'] })
354
+ * ```
355
+ *
356
+ * @param opts {} { helpOptions: ['help', 'h'], colors: true } cli options to invoke help renderer
357
+ * @returns Decorator
358
+ */
359
+ const CliHelpInterceptor = (...opts) => Intercept(cliHelpInterceptor(...opts));
360
+
361
+ export { Cli, CliAlias, CliHelpInterceptor, CliOption, MoostCli, cliHelpInterceptor, setCliHelpForEvent, useCliHelp };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moostjs/event-cli",
3
- "version": "0.2.26",
3
+ "version": "0.2.28",
4
4
  "description": "@moostjs/event-cli",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",
@@ -28,11 +28,12 @@
28
28
  },
29
29
  "homepage": "https://github.com/moostjs/moostjs/tree/main/packages/event-cli#readme",
30
30
  "peerDependencies": {
31
- "moost": "0.2.26",
32
- "wooks": "^0.2.17",
33
- "@wooksjs/event-core": "^0.2.17"
31
+ "moost": "0.2.28",
32
+ "wooks": "^0.2.22",
33
+ "@wooksjs/event-core": "^0.2.22"
34
34
  },
35
35
  "dependencies": {
36
- "@wooksjs/event-cli": "^0.2.17"
36
+ "@wooksjs/event-cli": "^0.2.22",
37
+ "@prostojs/cli-help": "^0.0.7"
37
38
  }
38
39
  }