@travetto/cli 3.1.0-rc.0 → 3.1.0-rc.10

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
@@ -22,32 +22,32 @@ $ trv --help
22
22
  Usage: [options] [command]
23
23
 
24
24
  Commands:
25
- doc Command line support for generating module docs.
26
- doc:angular Generate documentation into the angular webapp under related/travetto.github.io
27
- doc:mapping Generate module mapping for @travetto/doc
28
- email:compile CLI Entry point for running the email server
29
- email:editor The email editor compilation service and output serving
30
- exec Repo execution
31
- lint Command line support for linting
32
- lint:register Writes the lint configuration file
33
- list Allows for listing of modules
34
- model:export Exports model schemas
35
- model:install Installing models
36
- openapi:client CLI for generating the cli client
37
- openapi:spec CLI for outputting the open api spec to a local file
38
- pack Standard pack support
39
- pack:docker Standard docker support for pack
40
- pack:lambda Standard lambda support for pack
41
- pack:zip Standard zip support for pack
42
- repo:publish Publish all pending modules
43
- repo:version Version all changed dependencies
44
- run:double Doubles a number
45
- run:rest Run a rest server as an application
46
- scaffold Command to run scaffolding
47
- service Allows for running services
48
- test Launch test framework and execute tests
49
- test:watch Invoke the test watcher
50
- version-sync Enforces all packages to write out their versions and dependencies
25
+ doc Command line support for generating module docs.
26
+ doc:angular Generate documentation into the angular webapp under related/travetto.github.io
27
+ doc:mapping Generate module mapping for @travetto/doc
28
+ email:compile CLI Entry point for running the email server
29
+ email:editor The email editor compilation service and output serving
30
+ lint Command line support for linting
31
+ lint:register Writes the lint configuration file
32
+ model:export Exports model schemas
33
+ model:install Installing models
34
+ openapi:client CLI for generating the cli client
35
+ openapi:spec CLI for outputting the open api spec to a local file
36
+ pack Standard pack support
37
+ pack:docker Standard docker support for pack
38
+ pack:lambda Standard lambda support for pack
39
+ pack:zip Standard zip support for pack
40
+ repo:exec Repo execution
41
+ repo:list Allows for listing of modules
42
+ repo:publish Publish all pending modules
43
+ repo:version Version all changed dependencies
44
+ repo:version-sync Enforces all packages to write out their versions and dependencies
45
+ run:double Doubles a number
46
+ run:rest Run a rest server as an application
47
+ scaffold Command to run scaffolding
48
+ service Allows for running services
49
+ test Launch test framework and execute tests
50
+ test:watch Invoke the test watcher
51
51
  ```
52
52
 
53
53
  This listing is from the [Travetto](https://travetto.dev) monorepo, and represents the majority of tools that can be invoked from the command line.
@@ -130,9 +130,9 @@ HELLO
130
130
 
131
131
  The [@CliCommand](https://github.com/travetto/travetto/tree/main/module/cli/src/decorators.ts#L20) supports the following data types for flags:
132
132
  * Boolean values
133
- * Number values. The [@Integer](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L179), [@Float](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L185), [@Precision](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L173), [@Min](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L114) and [@Max](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L124) decorators help provide additional validation.
134
- * String values. [@MinLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L114), [@MaxLength](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L124), [@Match](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L106) and [@Enum](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L85) provide additional constraints
135
- * Date values. The [@Min](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L114) and [@Max](https://github.com/travetto/travetto/tree/main/module/schema/src/decorator/field.ts#L124) decorators help provide additional validation.
133
+ * 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.
134
+ * 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
135
+ * Date values. The [@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 lists. Same as String, but allowing multiple values.
137
137
  * Numeric lists. Same as Number, but allowing multiple values.
138
138
 
@@ -168,7 +168,7 @@ Options:
168
168
  $ trv basic:arg 20
169
169
 
170
170
  Execution failed:
171
- * volume is bigger than (10). [1]
171
+ * Argument volume is bigger than (10)
172
172
 
173
173
  Usage: doc/cli.basic:arg [options] [volume:number]
174
174
 
@@ -231,7 +231,7 @@ $ trv basic:arglist 10 5 3 9 8 1
231
231
  $ trv basic:arglist 10 5 3 9 20 1
232
232
 
233
233
  Execution failed:
234
- * volumes[4] is bigger than (10). [1]
234
+ * Argument volumes[4] is bigger than (10)
235
235
 
236
236
  Usage: doc/cli.basic:arglist [options] <volumes...:number>
237
237
 
@@ -358,7 +358,7 @@ CuStOm
358
358
  ```
359
359
 
360
360
  ## VSCode Integration
361
- 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).
361
+ 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.
362
362
 
363
363
  **Code: Simple Run Target**
364
364
  ```typescript
@@ -376,9 +376,43 @@ export class RunCommand {
376
376
  }
377
377
  ```
378
378
 
379
- Also, any command name that starts with `run:` (i.e. `support/cli.run_*.ts`), will be opted-in to the run behavior unless explicitly disabled.
380
-
381
379
  ## Advanced Usage
380
+
381
+ **Code: Anatomy of a Command**
382
+ ```typescript
383
+ export interface CliCommandShape {
384
+ /**
385
+ * Action target of the command
386
+ */
387
+ main(...args: unknown[]): OrProm<RunResponse>;
388
+ /**
389
+ * Setup environment before command runs
390
+ */
391
+ envInit?(): OrProm<GlobalEnvConfig>;
392
+ /**
393
+ * Extra help
394
+ */
395
+ help?(): OrProm<string[]>;
396
+ /**
397
+ * Is the command active/eligible for usage
398
+ */
399
+ isActive?(): boolean;
400
+ /**
401
+ * Run before binding occurs
402
+ */
403
+ initialize?(): OrProm<void>;
404
+ /**
405
+ * Run before validation occurs
406
+ */
407
+ finalize?(unknownArgs: string[]): OrProm<void>;
408
+ /**
409
+ * Validation method
410
+ */
411
+ validate?(...unknownArgs: unknown[]): OrProm<CliValidationError | CliValidationError[] | undefined>;
412
+ }
413
+ ```
414
+
415
+ ### Dependency Injection
382
416
  If the goal is to run a more complex application, which may include depending on [Dependency Injection](https://github.com/travetto/travetto/tree/main/module/di#readme "Dependency registration/management and injection support."), we can take a look at [RESTful API](https://github.com/travetto/travetto/tree/main/module/rest#readme "Declarative api for RESTful APIs with support for the dependency injection module.")'s target:
383
417
 
384
418
  **Code: Simple Run Target**
@@ -390,7 +424,7 @@ import { ServerHandle } from '../src/types';
390
424
  /**
391
425
  * Run a rest server as an application
392
426
  */
393
- @CliCommand({ fields: ['module', 'env', 'profile'] })
427
+ @CliCommand({ runTarget: true, fields: ['module', 'env', 'profile'] })
394
428
  export class RunRestCommand {
395
429
 
396
430
  /** Port to run on */
@@ -410,3 +444,32 @@ export class RunRestCommand {
410
444
  As noted in the example above, `fields` is specified in this execution, with support for `module`, `env`, and `profile`. These env and profile flags are directly tied to the GlobalEnv flags defined in the [Base](https://github.com/travetto/travetto/tree/main/module/base#readme "Environment config and common utilities for travetto applications.") module.
411
445
 
412
446
  The `module` field is slightly more complex, but is geared towards supporting commands within a monorepo context. This flag ensures that a module is specified if running from the root of the monorepo, and that the module provided is real, and can run the desired command. When running from an explicit module folder in the monorepo, the module flag is ignored.
447
+
448
+ ### Custom Validation
449
+ In addition to dependency injection, the command contract also allows for a custom validation function, which will have access to bound command (flags, and args) as well as the unknown arguments. When a command implements this method, any [CliValidationError](https://github.com/travetto/travetto/tree/main/module/cli/src/types.ts#L10) errors that are returned will be shared with the user, and fail to invoke the `main` method.
450
+
451
+ **Code: CliValidationError**
452
+ ```typescript
453
+ export type CliValidationError = {
454
+ /**
455
+ * The error message
456
+ */
457
+ message: string;
458
+ /**
459
+ * Source of validation
460
+ */
461
+ source?: 'flag' | 'arg' | 'custom';
462
+ };
463
+ ```
464
+
465
+ A simple example of the validation can be found in the `doc` command:
466
+
467
+ **Code: Simple Validation Example**
468
+ ```typescript
469
+ async validate(...args: unknown[]): Promise<CliValidationError | undefined> {
470
+ const docFile = path.resolve(this.input);
471
+ if (!(await fs.stat(docFile).catch(() => false))) {
472
+ return { message: `input: ${this.input} does not exist`, source: 'flag' };
473
+ }
474
+ }
475
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/cli",
3
- "version": "3.1.0-rc.0",
3
+ "version": "3.1.0-rc.10",
4
4
  "description": "CLI infrastructure for Travetto framework",
5
5
  "keywords": [
6
6
  "cli",
@@ -24,9 +24,8 @@
24
24
  "directory": "module/cli"
25
25
  },
26
26
  "dependencies": {
27
- "@travetto/schema": "^3.1.0-rc.0",
28
- "@travetto/terminal": "^3.1.0-rc.0",
29
- "@travetto/worker": "^3.1.0-rc.0"
27
+ "@travetto/schema": "^3.1.0-rc.6",
28
+ "@travetto/terminal": "^3.1.0-rc.1"
30
29
  },
31
30
  "travetto": {
32
31
  "displayName": "Command Line Interface"
package/src/decorators.ts CHANGED
@@ -35,12 +35,12 @@ export function CliCommand(cfg: { fields?: ExtraFields[], runTarget?: boolean, h
35
35
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
36
36
  cls: target as ConcreteClass<T>,
37
37
  hidden: cfg.hidden,
38
- runTarget: cfg.runTarget ?? name.startsWith('run:'),
38
+ runTarget: cfg.runTarget,
39
39
  preMain: (cmd: CliCommandShape & { env?: string, profile?: string[], module?: string }) => {
40
40
  if (addEnv) { defineGlobalEnv({ envName: cmd.env }); }
41
41
  if (addProfile) { defineGlobalEnv({ profiles: cmd.profile }); }
42
42
  if (addEnv || addProfile) { ConsoleManager.setDebugFromEnv(); }
43
- if (addModule && cmd.module && cmd.module !== RootIndex.mainModule.name) { // Mono-repo support
43
+ if (addModule && cmd.module && cmd.module !== RootIndex.mainModuleName) { // Mono-repo support
44
44
  RootIndex.reinitForModule(cmd.module); // Reinit with specified module
45
45
  }
46
46
  }
@@ -72,10 +72,11 @@ export function CliCommand(cfg: { fields?: ExtraFields[], runTarget?: boolean, h
72
72
  });
73
73
 
74
74
  // Register validator for module
75
- (pendingCls.validators ??= []).push(item =>
75
+ (pendingCls.validators ??= []).push(async item => {
76
76
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
77
- CliModuleUtil.validateCommandModule(getMod(target), item as { module?: string })
78
- );
77
+ const res = await CliModuleUtil.validateCommandModule(getMod(target), item as { module?: string });
78
+ return res ? { ...res, kind: 'custom', path: '.' } : res;
79
+ });
79
80
  }
80
81
  };
81
82
  }
@@ -83,7 +84,7 @@ export function CliCommand(cfg: { fields?: ExtraFields[], runTarget?: boolean, h
83
84
  /**
84
85
  * Decorator to register a CLI command flag
85
86
  */
86
- export function CliFlag(cfg: { name?: string, short?: string, desc?: string, file?: boolean, envVars?: string[] }) {
87
+ export function CliFlag(cfg: { name?: string, short?: string, desc?: string, fileExtensions?: string[], envVars?: string[] }) {
87
88
  return function (target: ClassInstance, prop: string | symbol): void {
88
89
  const aliases: string[] = [];
89
90
  if (cfg.name) {
@@ -97,7 +98,8 @@ export function CliFlag(cfg: { name?: string, short?: string, desc?: string, fil
97
98
  }
98
99
  if (typeof prop === 'string') {
99
100
  SchemaRegistry.registerPendingFieldFacet(target.constructor, prop, {
100
- aliases, description: cfg.desc, specifier: cfg.file ? 'file' : undefined
101
+ aliases, description: cfg.desc,
102
+ specifiers: cfg.fileExtensions?.length ? ['file', ...cfg.fileExtensions.map(x => `ext:${x.replace(/[*.]/g, '')}`)] : undefined
101
103
  });
102
104
  }
103
105
  };
package/src/execute.ts CHANGED
@@ -7,7 +7,6 @@ import { ConsoleManager, defineGlobalEnv, ShutdownManager } from '@travetto/base
7
7
  import { HelpUtil } from './help';
8
8
  import { CliCommandShape, CliValidationResultError } from './types';
9
9
  import { CliCommandRegistry } from './registry';
10
- import { cliTpl } from './color';
11
10
  import { CliCommandSchemaUtil } from './schema';
12
11
 
13
12
  /**
@@ -116,7 +115,7 @@ export class ExecutionManager {
116
115
  console.error(await HelpUtil.renderValidationError(command, err));
117
116
  console.error!(await HelpUtil.renderHelp(command));
118
117
  } else {
119
- console.error!(cliTpl`${{ failure: err.message }}`);
118
+ console.error!(err);
120
119
  console.error!();
121
120
  }
122
121
  }
package/src/help.ts CHANGED
@@ -6,6 +6,12 @@ import { CliCommandShape, CliValidationResultError } from './types';
6
6
  import { CliCommandRegistry } from './registry';
7
7
  import { CliCommandSchemaUtil } from './schema';
8
8
 
9
+ const validationSourceMap = {
10
+ custom: '',
11
+ arg: 'Argument',
12
+ flag: 'Flag'
13
+ };
14
+
9
15
  /**
10
16
  * Utilities for showing help
11
17
  */
@@ -93,12 +99,20 @@ export class HelpUtil {
93
99
  const maxWidth = keys.reduce((a, b) => Math.max(a, stripAnsiCodes(b).length), 0);
94
100
 
95
101
  for (const cmd of keys) {
96
- const inst = await CliCommandRegistry.getInstance(cmd);
97
- if (inst) {
98
- const cfg = await CliCommandRegistry.getConfig(inst);
99
- if (!cfg.hidden) {
100
- const schema = await CliCommandSchemaUtil.getSchema(inst);
101
- rows.push(cliTpl` ${{ param: cmd.padEnd(maxWidth, ' ') }} ${{ title: schema.title }}`);
102
+ try {
103
+ const inst = await CliCommandRegistry.getInstance(cmd);
104
+ if (inst) {
105
+ const cfg = await CliCommandRegistry.getConfig(inst);
106
+ if (!cfg.hidden) {
107
+ const schema = await CliCommandSchemaUtil.getSchema(inst);
108
+ rows.push(cliTpl` ${{ param: cmd.padEnd(maxWidth, ' ') }} ${{ title: schema.title }}`);
109
+ }
110
+ }
111
+ } catch (err) {
112
+ if (err instanceof Error) {
113
+ rows.push(cliTpl` ${{ param: cmd.padEnd(maxWidth, ' ') }} ${{ failure: err.message.split(/\n/)[0] }}`);
114
+ } else {
115
+ throw err;
102
116
  }
103
117
  }
104
118
  }
@@ -124,7 +138,9 @@ export class HelpUtil {
124
138
  static renderValidationError(cmd: CliCommandShape, err: CliValidationResultError): string {
125
139
  return [
126
140
  cliTpl`${{ failure: 'Execution failed' }}:`,
127
- ...err.errors.map(e => cliTpl` * ${{ failure: e.message }}`),
141
+ ...err.errors.map(e => e.source && e.source !== 'custom' ?
142
+ cliTpl` * ${{ identifier: validationSourceMap[e.source] }} ${{ subtitle: e.message }}` :
143
+ cliTpl` * ${{ failure: e.message }}`),
128
144
  '',
129
145
  ].join('\n');
130
146
  }
package/src/module.ts CHANGED
@@ -1,78 +1,18 @@
1
- import { ColorDefineUtil, NAMED_COLORS, Terminal, GlobalTerminal, TermLinePosition } from '@travetto/terminal';
2
- import { Env, ExecutionOptions, ExecutionResult, ExecutionState, TypedObject } from '@travetto/base';
3
- import { IndexedModule, PackageUtil, RootIndex } from '@travetto/manifest';
4
- import { IterableWorkSet, WorkPool, type Worker } from '@travetto/worker';
1
+ import { IndexedModule, RootIndex } from '@travetto/manifest';
5
2
 
6
3
  import { CliScmUtil } from './scm';
7
4
  import { CliValidationError } from './types';
8
5
  import { CliUtil } from './util';
9
6
 
10
- type ModuleRunConfig<T = ExecutionResult> = {
11
- progressMessage?: (mod: IndexedModule | undefined) => string;
12
- filter?: (mod: IndexedModule) => boolean | Promise<boolean>;
13
- transformResult?: (mod: IndexedModule, result: ExecutionResult) => T;
14
- workerCount?: number;
15
- progressPosition?: TermLinePosition;
16
- prefixOutput?: boolean;
17
- showStdout?: boolean;
18
- showStderr?: boolean;
19
- };
20
-
21
7
  type ModuleGraphEntry = { children: Set<string>, name: string, active: Set<string>, parents?: string[] };
22
8
 
23
- const COLORS = TypedObject.keys(NAMED_COLORS)
24
- .map(k => [k, ColorDefineUtil.defineColor(k).hsl] as const)
25
- .filter(([, [, s, l]]) => l > .5 && l < .8 && s > .8)
26
- .map(([k]) => GlobalTerminal.colorer(k));
27
-
28
- const colorize = (val: string, idx: number): string => COLORS[idx % COLORS.length](val);
29
-
30
- const modError = (message: string): CliValidationError => ({ kind: 'required', path: 'module', message });
9
+ const modError = (message: string): CliValidationError => ({ source: 'flag', message: `module: ${message}` });
31
10
 
32
11
  /**
33
12
  * Simple utilities for understanding modules for CLI use cases
34
13
  */
35
14
  export class CliModuleUtil {
36
15
 
37
- /**
38
- * Generate execution options for running on modules
39
- */
40
- static #buildExecutionOptions<T = ExecutionState>(
41
- mod: IndexedModule,
42
- config: ModuleRunConfig<T>,
43
- prefixes: Record<string, string>,
44
- stdoutTerm: Terminal,
45
- stderrTerm: Terminal
46
- ): ExecutionOptions {
47
- const folder = mod.sourceFolder;
48
- const opts: ExecutionOptions = {
49
- stdio: ['ignore', 'pipe', 'pipe', 'ignore'],
50
- outputMode: 'text',
51
- catchAsResult: true,
52
- cwd: folder,
53
- env: { TRV_MANIFEST: '', TRV_MODULE: mod.name },
54
- };
55
-
56
- if (config.showStdout) {
57
- opts.onStdOutLine = (line: string): unknown => stdoutTerm.writeLines(`${prefixes[folder] ?? ''}${line}`);
58
- }
59
- if (config.showStderr) {
60
- opts.onStdErrorLine = (line: string): unknown => stderrTerm.writeLines(`${prefixes[folder] ?? ''}${line}`);
61
- }
62
- return opts;
63
- }
64
-
65
- /**
66
- * Build equal sized prefix labels for outputting
67
- * @param mods
68
- * @returns
69
- */
70
- static #buildPrefixes(mods: IndexedModule[]): Record<string, string> {
71
- const folders = mods.map(x => x.sourceFolder);
72
- const maxWidth = Math.max(...folders.map(x => x.length));
73
- return Object.fromEntries(folders.map((x, i) => [x, colorize(x.padStart(maxWidth, ' ').padEnd(maxWidth + 1), i)]));
74
- }
75
-
76
16
  /**
77
17
  * Find modules that changed, and the dependent modules
78
18
  * @param hash
@@ -115,86 +55,6 @@ export class CliModuleUtil {
115
55
  ).filter(x => x.sourcePath !== RootIndex.manifest.workspacePath);
116
56
  }
117
57
 
118
- /**
119
- * Synchronize all workspace modules to have the correct versions from the current packages
120
- */
121
- static async synchronizeModuleVersions(): Promise<Record<string, string>> {
122
- const versions = {};
123
- await PackageUtil.syncVersions((await this.findModules('all')).map(x => x.sourcePath), versions);
124
- return versions;
125
- }
126
-
127
- /**
128
- * Run on all modules
129
- */
130
- static async execOnModules<T = ExecutionResult>(
131
- mode: 'all' | 'changed',
132
- operation: (mod: IndexedModule, options: ExecutionOptions) => ExecutionState,
133
- config: ModuleRunConfig<T> = {}
134
- ): Promise<Map<IndexedModule, T>> {
135
-
136
- config.showStdout = config.showStdout ?? (Env.isSet('DEBUG') && !Env.isFalse('DEBUG'));
137
- config.showStderr = config.showStderr ?? true;
138
-
139
- const workerCount = config.workerCount ?? WorkPool.DEFAULT_SIZE;
140
-
141
- const mods = await CliModuleUtil.findModules(mode);
142
- const results = new Map<IndexedModule, T>();
143
- const processes = new Map<IndexedModule, ExecutionState>();
144
-
145
- const prefixes = config.prefixOutput !== false ? this.#buildPrefixes(mods) : {};
146
- const stdoutTerm = await Terminal.for({ output: process.stdout });
147
- const stderrTerm = await Terminal.for({ output: process.stderr });
148
-
149
- let id = 1;
150
- const pool = new WorkPool(async () => {
151
- const worker: Worker<IndexedModule> & { mod?: IndexedModule } = {
152
- id: id += 1,
153
- mod: undefined,
154
- active: false,
155
- async destroy() {
156
- this.active = false;
157
- processes.get(this.mod!)?.process.kill('SIGKILL');
158
- },
159
- async execute(mod: IndexedModule) {
160
- try {
161
- this.mod = mod;
162
- this.active = true;
163
-
164
- if (await config.filter?.(mod) === false) {
165
- this.active = false;
166
- } else {
167
- const opts = CliModuleUtil.#buildExecutionOptions(mod, config, prefixes, stdoutTerm, stderrTerm);
168
-
169
- const result = await operation(mod, opts).result;
170
-
171
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
172
- const output = (config.transformResult ? config.transformResult(mod, result) : result) as T;
173
- results.set(mod, output);
174
- }
175
- } finally {
176
- this.active = false;
177
- delete this.mod;
178
- }
179
- },
180
- };
181
- return worker;
182
- }, { max: workerCount, min: workerCount });
183
-
184
- const work = pool.iterateProcess(new IterableWorkSet(mods));
185
-
186
- if (config.progressMessage) {
187
- const cfg = { position: config.progressPosition ?? 'bottom' } as const;
188
- await stdoutTerm.trackProgress(work, ev => ({ ...ev, text: config.progressMessage!(ev.value) }), cfg);
189
- } else {
190
- for await (const _ of work) {
191
- // Ensure its all consumed
192
- }
193
- }
194
-
195
- return results;
196
- }
197
-
198
58
  /**
199
59
  * Get module dependency graph, fully collapsed
200
60
  */
package/src/schema.ts CHANGED
@@ -5,23 +5,29 @@ import { CliCommandRegistry } from './registry';
5
5
  import { CliCommandInput, CliCommandSchema, CliCommandShape, CliValidationResultError } from './types';
6
6
 
7
7
  function fieldToInput(x: FieldConfig): CliCommandInput {
8
+ const type = x.type === Date ? 'date' :
9
+ x.type === Boolean ? 'boolean' :
10
+ x.type === String ? (x.specifiers?.includes('file') ? 'file' : 'string') :
11
+ x.type === Number ? 'number' :
12
+ x.type === RegExp ? 'regex' : 'string';
8
13
  return ({
9
14
  name: x.name,
10
15
  description: x.description,
11
16
  array: x.array,
12
17
  required: x.required?.active,
13
18
  choices: x.enum?.values,
14
- type: x.type === Date ? 'date' :
15
- x.type === Boolean ? 'boolean' :
16
- x.type === String ? (x.specifier === 'file' ? 'file' : 'string') :
17
- x.type === Number ? 'number' :
18
- x.type === RegExp ? 'regex' : 'string',
19
+ fileExtensions: type === 'file' ? x.specifiers?.filter(s => s.startsWith('ext:')).map(s => s.split('ext:')[1]) : undefined,
20
+ type,
19
21
  default: x.default,
20
22
  flagNames: (x.aliases ?? []).slice(0).filter(v => !v.startsWith('env.')),
21
23
  envVars: (x.aliases ?? []).slice(0).filter(v => v.startsWith('env.')).map(v => v.replace('env.', ''))
22
24
  });
23
25
  }
24
26
 
27
+ const VALID_FLAG = /^-{1,2}[a-z]/i;
28
+ const LONG_FLAG = /^--[a-z]/i;
29
+ const SHORT_FLAG = /^-[a-z]/i;
30
+
25
31
  const isBoolFlag = (x: CliCommandInput): boolean => x.type === 'boolean' && !x.array;
26
32
 
27
33
  /**
@@ -55,7 +61,7 @@ export class CliCommandSchemaUtil {
55
61
  }
56
62
 
57
63
  const schema = await SchemaRegistry.getViewSchema(cls);
58
- const flags = [...Object.values(schema.schema)].filter(v => !v.forMethod).map(fieldToInput);
64
+ const flags = Object.values(schema.schema).map(fieldToInput);
59
65
 
60
66
  // Add help command
61
67
  flags.push({ name: 'help', flagNames: ['h'], description: 'display help for command', type: 'boolean' });
@@ -64,13 +70,13 @@ export class CliCommandSchemaUtil {
64
70
 
65
71
  const used = new Set(flags
66
72
  .flatMap(f => f.flagNames ?? [])
67
- .filter(x => /^-[^-]/.test(x) || x.replaceAll('-', '').length < 3)
73
+ .filter(x => SHORT_FLAG.test(x) || x.replaceAll('-', '').length < 3)
68
74
  .map(x => x.replace(/^-+/, ''))
69
75
  );
70
76
 
71
77
  for (const flag of flags) {
72
- let short = (flag.flagNames ?? []).find(x => /^-[^-]/.test(x) || x.replaceAll('-', '').length < 3)?.replace(/^-+/, '');
73
- const long = (flag.flagNames ?? []).find(x => /^--[^-]/.test(x) || x.replaceAll('-', '').length > 2)?.replace(/^-+/, '') ??
78
+ let short = (flag.flagNames ?? []).find(x => SHORT_FLAG.test(x) || x.replaceAll('-', '').length < 3)?.replace(/^-+/, '');
79
+ const long = (flag.flagNames ?? []).find(x => LONG_FLAG.test(x) || x.replaceAll('-', '').length > 2)?.replace(/^-+/, '') ??
74
80
  flag.name.replace(/([a-z])([A-Z])/g, (_, l, r: string) => `${l}-${r.toLowerCase()}`);
75
81
  const aliases: string[] = flag.flagNames = [];
76
82
 
@@ -123,7 +129,7 @@ export class CliCommandSchemaUtil {
123
129
 
124
130
  for (const el of schema.args) {
125
131
  // Siphon off unrecognized flags, in order
126
- while (i < copy.length && copy[i].startsWith('-')) {
132
+ while (i < copy.length && VALID_FLAG.test(copy[i])) {
127
133
  i += 1;
128
134
  }
129
135
 
@@ -132,7 +138,7 @@ export class CliCommandSchemaUtil {
132
138
  } else if (el.array) {
133
139
  const sub: string[] = [];
134
140
  while (i < copy.length) {
135
- if (!copy[i].startsWith('-')) {
141
+ if (!VALID_FLAG.test(copy[i])) {
136
142
  sub.push(copy[i]);
137
143
  found[i] = true;
138
144
  }
@@ -196,7 +202,7 @@ export class CliCommandSchemaUtil {
196
202
  } else if (isBoolFlag(input)) {
197
203
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
198
204
  template[key] = !arg.startsWith('--no') as T[typeof key];
199
- } else if (next === undefined || next.startsWith('-')) {
205
+ } else if (next === undefined || VALID_FLAG.test(next)) {
200
206
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
201
207
  template[key] = null as T[typeof key];
202
208
  } else if (input.array) {
@@ -232,16 +238,18 @@ export class CliCommandSchemaUtil {
232
238
  async (): Promise<void> => {
233
239
  const res = await cmd.validate?.(...args);
234
240
  if (res) {
235
- throw new ValidationResultError(Array.isArray(res) ? res : [res]);
241
+ throw new CliValidationResultError(Array.isArray(res) ? res : [res]);
236
242
  }
237
243
  },
238
244
  ];
239
245
 
246
+ const SOURCES = ['flag', 'arg', 'custom'] as const;
247
+
240
248
  const results = validators.map((x, i) => x().catch(err => {
241
- if (!(err instanceof ValidationResultError)) {
249
+ if (!(err instanceof CliValidationResultError) && !(err instanceof ValidationResultError)) {
242
250
  throw err;
243
251
  }
244
- return err.errors.map(v => ({ ...v, message: `${v.message}. [${i}]`, index: i }));
252
+ return err.errors.map(v => ({ source: SOURCES[i], ...v }));
245
253
  }));
246
254
 
247
255
  const errors = (await Promise.all(results)).flatMap(x => (x ?? []));
package/src/scm.ts CHANGED
@@ -47,7 +47,7 @@ export class CliScmUtil {
47
47
  */
48
48
  static async findChangedModulesSince(hash: string): Promise<IndexedModule[]> {
49
49
  const ws = RootIndex.manifest.workspacePath;
50
- const res = await ExecUtil.spawn('git', ['diff', '--name-only', `HEAD..${hash}`], { cwd: ws }).result;
50
+ const res = await ExecUtil.spawn('git', ['diff', '--name-only', `HEAD..${hash}`, ':!**/DOC.tsx', ':!**/DOC.html', ':!**/README.md'], { cwd: ws }).result;
51
51
  const out = new Set<IndexedModule>();
52
52
  for (const line of res.stdout.split(/\n/g)) {
53
53
  const mod = RootIndex.getFromSource(path.resolve(ws, line));
package/src/types.ts CHANGED
@@ -13,13 +13,9 @@ export type CliValidationError = {
13
13
  */
14
14
  message: string;
15
15
  /**
16
- * The object path of the error
16
+ * Source of validation
17
17
  */
18
- path: string;
19
- /**
20
- * The kind of validation
21
- */
22
- kind: string;
18
+ source?: 'flag' | 'arg' | 'custom';
23
19
  };
24
20
 
25
21
  /**
@@ -65,7 +61,7 @@ export interface CliCommandShape {
65
61
  /**
66
62
  * Validation method
67
63
  */
68
- validate?(...args: unknown[]): OrProm<CliValidationError | CliValidationError[] | undefined>;
64
+ validate?(...unknownArgs: unknown[]): OrProm<CliValidationError | CliValidationError[] | undefined>;
69
65
  }
70
66
 
71
67
  /**
@@ -75,6 +71,7 @@ export type CliCommandInput = {
75
71
  name: string;
76
72
  description?: string;
77
73
  type: 'string' | 'file' | 'number' | 'boolean' | 'date' | 'regex';
74
+ fileExtensions?: string[];
78
75
  choices?: unknown[];
79
76
  required?: boolean;
80
77
  array?: boolean;
package/src/util.ts CHANGED
@@ -12,7 +12,7 @@ export class CliUtil {
12
12
  * Get a simplified version of a module name
13
13
  * @returns
14
14
  */
15
- static getSimpleModuleName(name = RootIndex.mainPackage.name): string {
15
+ static getSimpleModuleName(name = RootIndex.mainModuleName): string {
16
16
  return name.replace(/[\/]/, '_').replace(/@/, '');
17
17
  }
18
18
  }
@@ -17,9 +17,8 @@ export class CliSchemaCommand {
17
17
  async validate(name?: string): Promise<CliValidationError | undefined> {
18
18
  if (name && !CliCommandRegistry.getCommandMapping().has(name)) {
19
19
  return {
20
- kind: 'invalid',
21
- path: 'name',
22
- message: `${name} is not a valid cli command`
20
+ source: 'arg',
21
+ message: `name: ${name} is not a valid cli command`
23
22
  };
24
23
  }
25
24
  }
@@ -25,11 +25,7 @@ export class MainCommand implements CliCommandShape {
25
25
  async validate(fileOrImport: string): Promise<CliValidationError | undefined> {
26
26
  const imp = await this.#getImport(fileOrImport);
27
27
  if (!imp) {
28
- return {
29
- message: `Unknown file: ${fileOrImport}`,
30
- kind: 'required',
31
- path: 'fileOrImport'
32
- };
28
+ return { message: `Unknown file: ${fileOrImport}` };
33
29
  }
34
30
  }
35
31
 
@@ -1,55 +0,0 @@
1
- import { CliCommand, CliCommandShape, CliModuleUtil } from '@travetto/cli';
2
- import { WorkPool } from '@travetto/worker';
3
- import { RootIndex } from '@travetto/manifest';
4
- import { ExecUtil, GlobalEnvConfig } from '@travetto/base';
5
- import { Max, Min } from '@travetto/schema';
6
-
7
- /**
8
- * Repo execution
9
- */
10
- @CliCommand()
11
- export class RepoExecCommand implements CliCommandShape {
12
-
13
- #unknownArgs?: string[];
14
-
15
- /** Only changed modules */
16
- changed = true;
17
-
18
- /** Number of concurrent workers */
19
- @Min(1) @Max(WorkPool.MAX_SIZE)
20
- workers = WorkPool.DEFAULT_SIZE;
21
-
22
- /** Prefix output by folder */
23
- prefixOutput = true;
24
-
25
- /** Show stdout */
26
- showStdout = true;
27
-
28
- isActive(): boolean {
29
- return !!RootIndex.manifest.monoRepo;
30
- }
31
-
32
- envInit(): GlobalEnvConfig {
33
- return { debug: false };
34
- }
35
-
36
- finalize(unknownArgs?: string[] | undefined): void | Promise<void> {
37
- this.#unknownArgs = unknownArgs;
38
- }
39
-
40
- async main(cmd: string, args: string[]): Promise<void> {
41
- const finalArgs = [...args, ...this.#unknownArgs ?? []];
42
-
43
- await CliModuleUtil.execOnModules(
44
- this.changed ? 'changed' : 'all',
45
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
46
- (mod, opts) => ExecUtil.spawn(cmd, finalArgs, opts),
47
- {
48
- progressMessage: mod => `Running '${cmd} ${finalArgs.join(' ')}' [%idx/%total] ${mod?.sourceFolder ?? ''}`,
49
- showStdout: this.showStdout,
50
- prefixOutput: this.prefixOutput,
51
- workerCount: this.workers,
52
- }
53
- );
54
- }
55
- }
@@ -1,51 +0,0 @@
1
- import { CliCommandShape, CliCommand, CliModuleUtil } from '@travetto/cli';
2
- import { RootIndex } from '@travetto/manifest';
3
-
4
- const write = (line: string): Promise<void> => new Promise(r => process.stdout.write(`${line}\n`, () => r()));
5
-
6
- /**
7
- * Allows for listing of modules
8
- */
9
- @CliCommand()
10
- export class ListModuleCommand implements CliCommandShape {
11
-
12
- /** Only show changed modules */
13
- changed = false;
14
-
15
- /** Output format */
16
- format: 'graph' | 'json' | 'list' = 'list';
17
-
18
- isActive(): boolean {
19
- return !!RootIndex.manifest.monoRepo;
20
- }
21
-
22
- async main(): Promise<void> {
23
-
24
- const mods = await CliModuleUtil.findModules(this.changed ? 'changed' : 'all');
25
- switch (this.format) {
26
- case 'list': {
27
- for (const mod of mods.map(x => x.sourceFolder).sort()) {
28
- await write(mod);
29
- }
30
- break;
31
- }
32
- case 'json': {
33
- const outputMap = CliModuleUtil.getDependencyGraph(mods);
34
- await write(JSON.stringify(Object.entries(outputMap).map(([name, children]) => ({ name, children })), null, 2));
35
- break;
36
- }
37
- case 'graph': {
38
- await write('digraph g {');
39
- for (const el of mods) {
40
- for (const dep of el.parents) {
41
- if (dep !== RootIndex.mainPackage.name) {
42
- await write(` "${dep}" -> "${el.name}";`);
43
- }
44
- }
45
- }
46
- await write('}');
47
- break;
48
- }
49
- }
50
- }
51
- }
@@ -1,16 +0,0 @@
1
- import { CliCommandShape, CliCommand, CliModuleUtil } from '@travetto/cli';
2
- import { RootIndex } from '@travetto/manifest';
3
-
4
- /**
5
- * Enforces all packages to write out their versions and dependencies
6
- */
7
- @CliCommand()
8
- export class VersionSyncCommand implements CliCommandShape {
9
- isActive(): boolean {
10
- return !!RootIndex.manifest.monoRepo;
11
- }
12
-
13
- async main(): Promise<void> {
14
- await CliModuleUtil.synchronizeModuleVersions();
15
- }
16
- }