@travetto/cli 3.4.0-rc.7 → 3.4.0-rc.9

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
@@ -59,7 +59,7 @@ This module also has a tight integration with the [VSCode plugin](https://market
59
59
  At it's heart, a cli command is the contract defined by what flags, and what arguments the command supports. Within the framework this requires three criteria to be met:
60
60
  * The file must be located in the `support/` folder, and have a name that matches `cli.*.ts`
61
61
  * The file must be a class that has a main method
62
- * The class must use the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/decorators.ts#L20) decorator
62
+ * The class must use the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/decorators.ts#L15) decorator
63
63
 
64
64
  **Code: Basic Command**
65
65
  ```typescript
@@ -93,7 +93,7 @@ Examples of mappings:
93
93
  The pattern is that underscores(_) translate to colons (:), and the `cli.` prefix, and `.ts` suffix are dropped.
94
94
 
95
95
  ## Binding Flags
96
- [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/decorators.ts#L20) is a wrapper for [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L14), and so every class that uses the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/decorators.ts#L20) decorator is now a full [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L14) class. The fields of the class represent the flags that are available to the command.
96
+ [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/decorators.ts#L15) is a wrapper for [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L14), and so every class that uses the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/decorators.ts#L15) decorator is now a full [@Schema](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/schema.ts#L14) class. The fields of the class represent the flags that are available to the command.
97
97
 
98
98
  **Code: Basic Command with Flag**
99
99
  ```typescript
@@ -130,7 +130,7 @@ $ trv basic:flag --loud
130
130
  HELLO
131
131
  ```
132
132
 
133
- The [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/decorators.ts#L20) supports the following data types for flags:
133
+ The [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/decorators.ts#L15) supports the following data types for flags:
134
134
  * Boolean values
135
135
  * Number values. The [@Integer](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L172), [@Float](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L178), [@Precision](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L166), [@Min](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L107) and [@Max](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L117) decorators help provide additional validation.
136
136
  * String values. [@MinLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L107), [@MaxLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L117), [@Match](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L99) and [@Enum](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L78) provide additional constraints
@@ -360,7 +360,7 @@ CuStOm
360
360
  ```
361
361
 
362
362
  ## VSCode Integration
363
- By default, cli commands do not expose themselves to the VSCode extension, as the majority of them are not intended for that sort of operation. [RESTful API](https://github.com/travetto/travetto/tree/main/module/rest#readme "Declarative api for RESTful APIs with support for the dependency injection module.") does expose a cli target `run:rest` that will show up, to help run/debug a rest application. Any command can mark itself as being a run target, and will be eligible for running from within the [VSCode plugin](https://marketplace.visualstudio.com/items?itemName=arcsine.travetto-plugin). This is achieved by setting the `runTarget` field on the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/decorators.ts#L20) decorator. This means the target will be visible within the editor tooling.
363
+ By default, cli commands do not expose themselves to the VSCode extension, as the majority of them are not intended for that sort of operation. [RESTful API](https://github.com/travetto/travetto/tree/main/module/rest#readme "Declarative api for RESTful APIs with support for the dependency injection module.") does expose a cli target `run:rest` that will show up, to help run/debug a rest application. Any command can mark itself as being a run target, and will be eligible for running from within the [VSCode plugin](https://marketplace.visualstudio.com/items?itemName=arcsine.travetto-plugin). This is achieved by setting the `runTarget` field on the [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/decorators.ts#L15) decorator. This means the target will be visible within the editor tooling.
364
364
 
365
365
  **Code: Simple Run Target**
366
366
  ```typescript
@@ -421,21 +421,20 @@ If the goal is to run a more complex application, which may include depending on
421
421
  ```typescript
422
422
  import { DependencyRegistry } from '@travetto/di';
423
423
  import { CliCommand, CliUtil } from '@travetto/cli';
424
- import { GlobalEnv } from '@travetto/base';
425
424
 
426
425
  import { ServerHandle } from '../src/types';
427
426
 
428
427
  /**
429
428
  * Run a rest server as an application
430
429
  */
431
- @CliCommand({ runTarget: true, fields: ['module', 'env'] })
430
+ @CliCommand({ runTarget: true, addModule: true, addEnv: true })
432
431
  export class RunRestCommand {
433
432
 
434
433
  /** IPC debug is enabled */
435
- debugIpc = true;
434
+ debugIpc?: boolean;
436
435
 
437
436
  /** Should the server be able to run with restart*/
438
- canRestart = GlobalEnv.devMode;
437
+ canRestart?: boolean;
439
438
 
440
439
  /** Port to run on */
441
440
  port?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/cli",
3
- "version": "3.4.0-rc.7",
3
+ "version": "3.4.0-rc.9",
4
4
  "description": "CLI infrastructure for Travetto framework",
5
5
  "keywords": [
6
6
  "cli",
@@ -29,7 +29,7 @@
29
29
  "directory": "module/cli"
30
30
  },
31
31
  "dependencies": {
32
- "@travetto/schema": "^3.4.0-rc.6",
32
+ "@travetto/schema": "^3.4.0-rc.7",
33
33
  "@travetto/terminal": "^3.4.0-rc.0"
34
34
  },
35
35
  "travetto": {
package/src/decorators.ts CHANGED
@@ -1,47 +1,34 @@
1
- import { Class, ClassInstance, ConcreteClass, ConsoleManager, GlobalEnv, defineGlobalEnv } from '@travetto/base';
1
+ import { Class, ClassInstance, ConsoleManager, GlobalEnv, defineGlobalEnv } from '@travetto/base';
2
2
  import { RootIndex } from '@travetto/manifest';
3
3
  import { SchemaRegistry } from '@travetto/schema';
4
4
 
5
- import { CliCommandShape } from './types';
5
+ import { CliCommandShape, CliCommandShapeFields } from './types';
6
6
  import { CliCommandRegistry, CliCommandConfigOptions } from './registry';
7
7
  import { CliModuleUtil } from './module';
8
8
  import { CliUtil } from './util';
9
9
 
10
- type ExtraFields = 'module' | 'env';
11
-
12
- const getName = (source: string): string => (source.match(/cli[.](.*)[.]tsx?$/)?.[1] ?? source).replaceAll('_', ':');
13
- const getMod = (cls: Class): string => RootIndex.getModuleFromSource(RootIndex.getFunctionMetadata(cls)!.source)!.name;
14
-
15
10
  /**
16
11
  * Decorator to register a CLI command
17
12
  * @augments `@travetto/schema:Schema`
18
13
  * @augments `@travetto/cli:CliCommand`
19
14
  */
20
- export function CliCommand({ fields, ...cfg }: { fields?: ExtraFields[] } & CliCommandConfigOptions = {}) {
15
+ export function CliCommand(cfg: CliCommandConfigOptions = {}) {
21
16
  return function <T extends CliCommandShape>(target: Class<T>): void {
22
17
  const meta = RootIndex.getFunctionMetadata(target);
23
18
  if (!meta || meta.abstract) {
24
19
  return;
25
20
  }
26
21
 
27
- const name = getName(meta.source);
28
- const addEnv = fields?.includes('env');
29
- const addModule = fields?.includes('module');
30
-
31
- CliCommandRegistry.registerClass({
32
- module: getMod(target),
33
- name,
34
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
35
- cls: target as ConcreteClass<T>,
36
- ...cfg,
37
- preMain: (cmd: CliCommandShape & { env?: string, module?: string }) => {
38
- if (addEnv) {
22
+ const addModule = cfg.addModule === true || cfg.fields?.includes('module');
23
+ const runtimeModule = cfg.runtimeModule ?? (cfg.addModule ? 'current' : undefined);
24
+ const addEnv = cfg.addEnv ?? cfg.fields?.includes('env');
25
+ const { commandModule } = CliCommandRegistry.registerClass(target, {
26
+ hidden: cfg.hidden,
27
+ preMain: async (cmd) => {
28
+ if (addEnv && 'env' in cmd && typeof cmd.env === 'string') {
39
29
  defineGlobalEnv({ envName: cmd.env });
40
30
  ConsoleManager.setDebug(GlobalEnv.debug, GlobalEnv.devMode);
41
31
  }
42
- if (addModule && cmd.module && cmd.module !== RootIndex.mainModuleName) { // Mono-repo support
43
- RootIndex.reinitForModule(cmd.module); // Reinit with specified module
44
- }
45
32
  }
46
33
  });
47
34
 
@@ -61,12 +48,26 @@ export function CliCommand({ fields, ...cfg }: { fields?: ExtraFields[] } & CliC
61
48
  description: 'Module to run for',
62
49
  required: { active: CliUtil.monoRoot }
63
50
  });
51
+ }
64
52
 
65
- // Register validator for module
53
+ if (runtimeModule) { // Validate module
66
54
  (pendingCls.validators ??= []).push(async item => {
67
55
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
68
- const res = await CliModuleUtil.validateCommandModule(getMod(target), item as { module?: string });
69
- return res ? { ...res, kind: 'custom', path: '.' } : res;
56
+ const { module: mod } = item as CliCommandShapeFields;
57
+ const runModule = (runtimeModule === 'command' ? commandModule : mod) || RootIndex.mainModuleName;
58
+
59
+ // If we need to run as a specific module
60
+ if (runModule !== RootIndex.mainModuleName) {
61
+ try {
62
+ RootIndex.reinitForModule(runModule);
63
+ } catch (err) {
64
+ return { source: 'flag', message: `${runModule} is an unknown module`, kind: 'custom', path: '.' };
65
+ }
66
+ }
67
+
68
+ if (!(await CliModuleUtil.moduleHasDependency(runModule, commandModule))) {
69
+ return { source: 'flag', message: `${runModule} does not have ${commandModule} as a dependency`, kind: 'custom', path: '.' };
70
+ }
70
71
  });
71
72
  }
72
73
  };
package/src/module.ts CHANGED
@@ -1,13 +1,9 @@
1
1
  import { IndexedModule, RootIndex } from '@travetto/manifest';
2
2
 
3
3
  import { CliScmUtil } from './scm';
4
- import { CliValidationError } from './types';
5
- import { CliUtil } from './util';
6
4
 
7
5
  type ModuleGraphEntry = { children: Set<string>, name: string, active: Set<string>, parents?: string[] };
8
6
 
9
- const modError = (message: string): CliValidationError => ({ source: 'flag', message: `module: ${message}` });
10
-
11
7
  /**
12
8
  * Simple utilities for understanding modules for CLI use cases
13
9
  */
@@ -90,23 +86,14 @@ export class CliModuleUtil {
90
86
  }
91
87
 
92
88
  /**
93
- * Validate the module status for a given cli command
89
+ * Determine if module has a given dependency
94
90
  */
95
- static async validateCommandModule(selfMod: string, { module: mod }: { module?: string }): Promise<CliValidationError | undefined> {
96
- if (CliUtil.monoRoot && mod) {
97
- if (!RootIndex.getModule(mod)) {
98
- return modError(`${mod} is an unknown module`);
99
- } else {
100
- if (mod !== selfMod) {
101
- RootIndex.reinitForModule(mod);
102
- }
103
-
104
- const mods = await this.findModules('all');
105
- const graph = this.getDependencyGraph(mods);
106
- if (selfMod !== mod && !graph[mod].includes(selfMod)) {
107
- return modError(`${mod} does not have ${selfMod} as a dependency`);
108
- }
109
- }
91
+ static async moduleHasDependency(modName: string, depModName: string): Promise<boolean> {
92
+ if (modName === depModName) {
93
+ return true;
110
94
  }
95
+ const mods = await this.findModules('all');
96
+ const graph = this.getDependencyGraph(mods);
97
+ return graph[modName].includes(depModName);
111
98
  }
112
99
  }
package/src/registry.ts CHANGED
@@ -5,18 +5,25 @@ import { CliCommandShape } from './types';
5
5
  import { CliUnknownCommandError } from './error';
6
6
 
7
7
  export type CliCommandConfigOptions = {
8
- runTarget?: boolean;
9
8
  hidden?: boolean;
9
+ runTarget?: boolean;
10
+ addModule?: boolean;
11
+ addEnv?: boolean;
12
+ runtimeModule?: 'current' | 'command';
13
+ /** @deprecated */
14
+ fields?: ('module' | 'env')[];
10
15
  };
11
16
 
12
- export type CliCommandConfig = CliCommandConfigOptions & {
17
+ export type CliCommandConfig = {
13
18
  name: string;
14
- module: string;
19
+ commandModule: string;
15
20
  cls: ConcreteClass<CliCommandShape>;
21
+ hidden?: boolean;
16
22
  preMain?: (cmd: CliCommandShape) => void | Promise<void>;
17
23
  };
18
24
 
19
- const CLI_REGEX = /\/cli[.]([^.]+)[.][^.]+?$/;
25
+ const CLI_FILE_REGEX = /\/cli[.](?<name>.*)[.]tsx?$/;
26
+ const getName = (s: string): string => (s.match(CLI_FILE_REGEX)?.groups?.name ?? s).replaceAll('_', ':');
20
27
 
21
28
  class $CliCommandRegistry {
22
29
  #commands = new Map<Class, CliCommandConfig>();
@@ -40,9 +47,9 @@ class $CliCommandRegistry {
40
47
  for (const e of RootIndex.find({
41
48
  module: m => GlobalEnv.devMode || m.prod,
42
49
  folder: f => f === 'support',
43
- file: f => f.role === 'std' && CLI_REGEX.test(f.sourceFile)
50
+ file: f => f.role === 'std' && CLI_FILE_REGEX.test(f.sourceFile)
44
51
  })) {
45
- all.set(e.outputFile.match(CLI_REGEX)![1].replace(/_/g, ':'), e.import);
52
+ all.set(getName(e.sourceFile), e.import);
46
53
  }
47
54
  this.#fileMapping = all;
48
55
  }
@@ -52,8 +59,16 @@ class $CliCommandRegistry {
52
59
  /**
53
60
  * Registers a cli command
54
61
  */
55
- registerClass(cfg: CliCommandConfig): void {
56
- this.#commands.set(cfg.cls, cfg);
62
+ registerClass(cls: Class, cfg: Partial<CliCommandConfig>): CliCommandConfig {
63
+ const source = RootIndex.getFunctionMetadata(cls)!.source;
64
+ this.#commands.set(cls, {
65
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
66
+ cls: cls as ConcreteClass,
67
+ name: getName(source),
68
+ commandModule: RootIndex.getModuleFromSource(source)!.name,
69
+ ...cfg,
70
+ });
71
+ return this.#commands.get(cls)!;
57
72
  }
58
73
 
59
74
  /**
@@ -68,7 +83,7 @@ class $CliCommandRegistry {
68
83
  */
69
84
  getName(cmd: CliCommandShape, withModule = false): string | undefined {
70
85
  const cfg = this.getConfig(cmd);
71
- const prefix = withModule ? `${cfg.module}:` : '';
86
+ const prefix = withModule ? `${cfg.commandModule}:` : '';
72
87
  return `${prefix}${cfg.name}`;
73
88
  }
74
89
 
package/src/types.ts CHANGED
@@ -52,6 +52,28 @@ export interface CliCommandShape {
52
52
  validate?(...unknownArgs: unknown[]): OrProm<CliValidationError | CliValidationError[] | undefined>;
53
53
  }
54
54
 
55
+ /**
56
+ * Command shape common fields
57
+ */
58
+ export type CliCommandShapeFields = {
59
+ /**
60
+ * Environment to run in
61
+ */
62
+ env?: string;
63
+ /**
64
+ * Should the cli invocation trigger a debug session, via IPC
65
+ */
66
+ debugIpc?: boolean;
67
+ /**
68
+ * Should the invocation run with auto-restart
69
+ */
70
+ canRestart?: boolean;
71
+ /**
72
+ * The module to run the command from
73
+ */
74
+ module?: string;
75
+ };
76
+
55
77
  /**
56
78
  * CLI Command argument/flag shape
57
79
  */
@@ -74,7 +96,7 @@ export type CliCommandInput = {
74
96
  export type CliCommandSchema = {
75
97
  name: string;
76
98
  title: string;
77
- module: string;
99
+ commandModule: string;
78
100
  runTarget?: boolean;
79
101
  description?: string;
80
102
  args: CliCommandInput[];
package/src/util.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { CompilerClient, Env, ExecUtil } from '@travetto/base';
1
+ import { Env, ExecUtil, GlobalEnv } from '@travetto/base';
2
2
  import { RootIndex } from '@travetto/manifest';
3
3
 
4
- import { CliCommandShape } from './types';
4
+ import { CliCommandShape, CliCommandShapeFields } from './types';
5
5
  import { CliCommandRegistry } from './registry';
6
6
 
7
7
  export class CliUtil {
@@ -23,8 +23,10 @@ export class CliUtil {
23
23
  /**
24
24
  * Run a command as restartable, linking into self
25
25
  */
26
- static runWithRestart<T extends CliCommandShape & { canRestart?: boolean }>(cmd: T): Promise<unknown> | undefined {
27
- if (cmd.canRestart === false || Env.isFalse('TRV_CAN_RESTART')) {
26
+ static runWithRestart<T extends CliCommandShapeFields & CliCommandShape>(cmd: T): Promise<unknown> | undefined {
27
+ const canRestart = cmd.canRestart ??= GlobalEnv.devMode;
28
+
29
+ if (canRestart === false || Env.isFalse('TRV_CAN_RESTART')) {
28
30
  delete process.env.TRV_CAN_RESTART;
29
31
  return;
30
32
  }
@@ -38,51 +40,50 @@ export class CliUtil {
38
40
  * Dispatch IPC payload
39
41
  */
40
42
  static async triggerIpc<T extends CliCommandShape>(action: 'run', cmd: T): Promise<boolean> {
41
- if (!process.env.TRV_CLI_IPC) {
43
+ const ipcUrl = process.env.TRV_CLI_IPC;
44
+
45
+ if (!ipcUrl) {
42
46
  return false;
43
47
  }
44
48
 
45
- const client = new CompilerClient({});
46
-
47
- const info = await client.getInfo(true);
49
+ const info = await fetch(ipcUrl).catch(() => ({ ok: false }));
48
50
 
49
- if (!info) { // Server not running
51
+ if (!info.ok) { // Server not running
50
52
  return false;
51
53
  }
52
54
 
53
- const defaultEnvKeys = new Set(Object.keys(info.env ?? {}));
54
- defaultEnvKeys.add('PS1').add('INIT_CWD').add('COLOR').add('LANGUAGE').add('PROFILEHOME').add('_');
55
-
56
- const env = Object.fromEntries(
57
- Object.entries(process.env).filter(([k]) =>
58
- !defaultEnvKeys.has(k) && !/^(npm_|GTK|GDK|TRV|NODE|GIT|TERM_)/.test(k) && !/VSCODE/.test(k)
59
- )
60
- );
61
-
62
55
  const cfg = CliCommandRegistry.getConfig(cmd);
63
56
  const req = {
64
57
  type: `@travetto/cli:${action}`,
65
- ipc: process.env.TRV_CLI_IPC,
66
58
  data: {
67
59
  name: cfg.name,
68
- commandModule: cfg.module,
60
+ commandModule: cfg.commandModule,
69
61
  module: RootIndex.manifest.mainModule,
70
62
  args: process.argv.slice(3),
71
- env
72
63
  }
73
64
  };
74
65
 
75
66
  console.log('Triggering IPC request', req);
76
67
 
77
- await client.sendEvent('custom', req);
78
- return true;
68
+ const defaultEnvKeys = new Set(['PS1', 'INIT_CWD', 'COLOR', 'LANGUAGE', 'PROFILEHOME', '_']);
69
+ const env = Object.fromEntries(
70
+ Object.entries(process.env).filter(([k]) =>
71
+ !defaultEnvKeys.has(k) && !/^(npm_|GTK|GDK|TRV|NODE|GIT|TERM_)/.test(k) && !/VSCODE/.test(k)
72
+ )
73
+ );
74
+
75
+ Object.assign(req.data, { env });
76
+
77
+ const sent = await fetch(ipcUrl, { method: 'POST', body: JSON.stringify(req) });
78
+ return sent.ok;
79
79
  }
80
80
 
81
81
  /**
82
82
  * Debug if IPC available
83
83
  */
84
- static async debugIfIpc<T extends CliCommandShape & { debugIpc?: boolean }>(cmd: T): Promise<boolean> {
85
- return cmd.debugIpc !== false && this.triggerIpc('run', cmd);
84
+ static async debugIfIpc<T extends CliCommandShapeFields & CliCommandShape>(cmd: T): Promise<boolean> {
85
+ const canDebug = cmd.debugIpc ??= GlobalEnv.devMode;
86
+ return canDebug !== false && this.triggerIpc('run', cmd);
86
87
  }
87
88
 
88
89
  /**