@travetto/cli 3.4.7 → 3.4.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 +5 -1
- package/package.json +1 -1
- package/src/decorators.ts +1 -1
- package/src/execute.ts +2 -4
- package/src/parse.ts +25 -36
- package/src/types.ts +16 -0
- package/support/cli.main.ts +6 -2
package/README.md
CHANGED
|
@@ -411,6 +411,10 @@ export class RunCommand {
|
|
|
411
411
|
**Code: Anatomy of a Command**
|
|
412
412
|
```typescript
|
|
413
413
|
export interface CliCommandShape<T extends unknown[] = unknown[]> {
|
|
414
|
+
/**
|
|
415
|
+
* Parsed state
|
|
416
|
+
*/
|
|
417
|
+
_parsed?: ParsedState;
|
|
414
418
|
/**
|
|
415
419
|
* Action target of the command
|
|
416
420
|
*/
|
|
@@ -493,7 +497,7 @@ As noted in the example above, `fields` is specified in this execution, with sup
|
|
|
493
497
|
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.
|
|
494
498
|
|
|
495
499
|
### Custom Validation
|
|
496
|
-
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#
|
|
500
|
+
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#L22) errors that are returned will be shared with the user, and fail to invoke the `main` method.
|
|
497
501
|
|
|
498
502
|
**Code: CliValidationError**
|
|
499
503
|
```typescript
|
package/package.json
CHANGED
package/src/decorators.ts
CHANGED
|
@@ -36,7 +36,7 @@ export function CliCommand(cfg: CliCommandConfigOptions = {}) {
|
|
|
36
36
|
|
|
37
37
|
if (addEnv) {
|
|
38
38
|
SchemaRegistry.registerPendingFieldConfig(target, 'env', String, {
|
|
39
|
-
aliases: ['e'],
|
|
39
|
+
aliases: ['e', CliParseUtil.toEnvField('TRV_ENV')],
|
|
40
40
|
description: 'Application environment',
|
|
41
41
|
default: 'dev',
|
|
42
42
|
required: { active: false }
|
package/src/execute.ts
CHANGED
|
@@ -19,13 +19,11 @@ export class ExecutionManager {
|
|
|
19
19
|
*/
|
|
20
20
|
static async #runCommand(cmd: CliCommandShape, args: string[]): Promise<RunResponse> {
|
|
21
21
|
const schema = await CliCommandSchemaUtil.getSchema(cmd);
|
|
22
|
-
|
|
22
|
+
cmd._parsed = await CliParseUtil.parse(schema, args);
|
|
23
23
|
const cfg = CliCommandRegistry.getConfig(cmd);
|
|
24
24
|
|
|
25
|
-
CliParseUtil.setState(cmd, state);
|
|
26
|
-
|
|
27
25
|
await cmd.preBind?.();
|
|
28
|
-
const known = await CliCommandSchemaUtil.bindInput(cmd,
|
|
26
|
+
const known = await CliCommandSchemaUtil.bindInput(cmd, cmd._parsed);
|
|
29
27
|
|
|
30
28
|
await cmd.preValidate?.();
|
|
31
29
|
await CliCommandSchemaUtil.validate(cmd, known);
|
package/src/parse.ts
CHANGED
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
|
|
3
3
|
import { RootIndex, path } from '@travetto/manifest';
|
|
4
|
-
import { CliCommandInput, CliCommandSchema,
|
|
5
|
-
|
|
6
|
-
type
|
|
7
|
-
type ParsedArg = { type: 'arg', input: string, array?: boolean, index: number };
|
|
8
|
-
type ParsedUnknown = { type: 'unknown', input: string };
|
|
9
|
-
type ParsedInput = ParsedUnknown | ParsedFlag | ParsedArg;
|
|
10
|
-
|
|
11
|
-
export type ParsedState = {
|
|
12
|
-
inputs: string[];
|
|
13
|
-
all: ParsedInput[];
|
|
14
|
-
flags: ParsedFlag[];
|
|
15
|
-
unknown: string[];
|
|
16
|
-
};
|
|
4
|
+
import { CliCommandInput, CliCommandSchema, ParsedState } from './types';
|
|
5
|
+
|
|
6
|
+
type ParsedInput = ParsedState['all'][number];
|
|
17
7
|
|
|
18
8
|
const RAW_SEP = '--';
|
|
19
9
|
const VALID_FLAG = /^-{1,2}[a-z]/i;
|
|
@@ -21,8 +11,21 @@ const HELP_FLAG = /^-h|--help$/;
|
|
|
21
11
|
const LONG_FLAG_WITH_EQ = /^--[a-z][^= ]+=\S+/i;
|
|
22
12
|
const CONFIG_PRE = '+=';
|
|
23
13
|
const ENV_PRE = 'env.';
|
|
24
|
-
const ParsedⲐ = Symbol.for('@travetto/cli:parsed');
|
|
25
14
|
const SPACE = new Set([32, 7, 13, 10]);
|
|
15
|
+
const MODULE_FLAG = '--module';
|
|
16
|
+
const MODULE_SHORT = '-m';
|
|
17
|
+
|
|
18
|
+
const getModuleValue = (arr: string[]): string => {
|
|
19
|
+
for (let i = 0; i < arr.length; i += 1) {
|
|
20
|
+
const x = arr[i];
|
|
21
|
+
if (x.startsWith(`${MODULE_FLAG}=`)) {
|
|
22
|
+
return x.split('=')[1];
|
|
23
|
+
} else if (!x.startsWith('-') && (arr[i - 1] === MODULE_SHORT || arr[i - 1] === MODULE_FLAG)) {
|
|
24
|
+
return x;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return process.env.TRV_MODULE || RootIndex.mainModuleName;
|
|
28
|
+
};
|
|
26
29
|
|
|
27
30
|
const isBoolFlag = (x?: CliCommandInput): boolean => x?.type === 'boolean' && !x.array;
|
|
28
31
|
|
|
@@ -90,20 +93,19 @@ export class CliParseUtil {
|
|
|
90
93
|
/**
|
|
91
94
|
* Read configuration file given flag
|
|
92
95
|
*/
|
|
93
|
-
static async readFlagFile(flag: string): Promise<string[]> {
|
|
96
|
+
static async readFlagFile(flag: string, mod: string): Promise<string[]> {
|
|
94
97
|
const key = flag.replace(CONFIG_PRE, '');
|
|
95
|
-
const mod = RootIndex.mainModuleName;
|
|
96
98
|
|
|
97
99
|
// We have a file
|
|
98
100
|
const rel = (key.includes('/') ? key : `@/support/pack.${key}.flags`)
|
|
99
101
|
.replace('@@/', `${RootIndex.manifest.workspacePath}/`)
|
|
100
102
|
.replace('@/', `${mod}/`)
|
|
101
|
-
.replace(/^(@[^\/]+\/[^\/]+)
|
|
103
|
+
.replace(/^(@[^\/]+\/[^\/]+)(\/.*)$/, (_, imp, rest) => {
|
|
102
104
|
const val = RootIndex.getModule(imp);
|
|
103
105
|
if (!val) {
|
|
104
|
-
throw new Error(`Unknown module file: ${
|
|
106
|
+
throw new Error(`Unknown module file: ${_}, unable to proceed`);
|
|
105
107
|
}
|
|
106
|
-
return val.sourcePath
|
|
108
|
+
return `${val.sourcePath}${rest}`;
|
|
107
109
|
});
|
|
108
110
|
|
|
109
111
|
const file = path.resolve(rel);
|
|
@@ -136,12 +138,13 @@ export class CliParseUtil {
|
|
|
136
138
|
const out = argv.slice(offset);
|
|
137
139
|
const max = out.includes(RAW_SEP) ? out.indexOf(RAW_SEP) : out.length;
|
|
138
140
|
const valid = out.slice(0, max);
|
|
141
|
+
const mod = getModuleValue(valid);
|
|
139
142
|
const cmd = valid.length > 0 && !valid[0].startsWith('-') ? valid[0] : undefined;
|
|
140
143
|
const helpIdx = valid.findIndex(x => HELP_FLAG.test(x));
|
|
141
144
|
const args = [];
|
|
142
145
|
for (const item of out.slice(cmd ? 1 : 0)) {
|
|
143
146
|
if (item.startsWith(CONFIG_PRE)) {
|
|
144
|
-
args.push(...await this.readFlagFile(item));
|
|
147
|
+
args.push(...await this.readFlagFile(item, mod));
|
|
145
148
|
} else {
|
|
146
149
|
args.push(item);
|
|
147
150
|
}
|
|
@@ -210,22 +213,8 @@ export class CliParseUtil {
|
|
|
210
213
|
return {
|
|
211
214
|
inputs,
|
|
212
215
|
all: out,
|
|
213
|
-
unknown: out.filter(
|
|
214
|
-
flags: out.filter((x): x is
|
|
216
|
+
unknown: out.filter(x => x.type === 'unknown').map(x => x.input),
|
|
217
|
+
flags: out.filter((x): x is ParsedInput & { type: 'flag' } => x.type === 'flag')
|
|
215
218
|
};
|
|
216
219
|
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Get parse state from the command
|
|
220
|
-
*/
|
|
221
|
-
static getState(cmd: CliCommandShape & { [ParsedⲐ]?: ParsedState }): ParsedState | undefined {
|
|
222
|
-
return cmd[ParsedⲐ];
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Set state for a command
|
|
227
|
-
*/
|
|
228
|
-
static setState(cmd: CliCommandShape & { [ParsedⲐ]?: ParsedState }, state: ParsedState): void {
|
|
229
|
-
cmd[ParsedⲐ] = state;
|
|
230
|
-
}
|
|
231
220
|
}
|
package/src/types.ts
CHANGED
|
@@ -4,6 +4,18 @@ type OrProm<T> = T | Promise<T>;
|
|
|
4
4
|
|
|
5
5
|
export type RunResponse = { wait(): Promise<unknown> } | { on(event: 'close', cb: Function): unknown } | Closeable | void | undefined;
|
|
6
6
|
|
|
7
|
+
type ParsedFlag = { type: 'flag', input: string, array?: boolean, fieldName: string, value?: unknown };
|
|
8
|
+
type ParsedArg = { type: 'arg', input: string, array?: boolean, index: number };
|
|
9
|
+
type ParsedUnknown = { type: 'unknown', input: string };
|
|
10
|
+
type ParsedInput = ParsedUnknown | ParsedFlag | ParsedArg;
|
|
11
|
+
|
|
12
|
+
export type ParsedState = {
|
|
13
|
+
inputs: string[];
|
|
14
|
+
all: ParsedInput[];
|
|
15
|
+
flags: ParsedFlag[];
|
|
16
|
+
unknown: string[];
|
|
17
|
+
};
|
|
18
|
+
|
|
7
19
|
/**
|
|
8
20
|
* Constrained version of Schema's Validation Error
|
|
9
21
|
*/
|
|
@@ -22,6 +34,10 @@ export type CliValidationError = {
|
|
|
22
34
|
* CLI Command Contract
|
|
23
35
|
*/
|
|
24
36
|
export interface CliCommandShape<T extends unknown[] = unknown[]> {
|
|
37
|
+
/**
|
|
38
|
+
* Parsed state
|
|
39
|
+
*/
|
|
40
|
+
_parsed?: ParsedState;
|
|
25
41
|
/**
|
|
26
42
|
* Action target of the command
|
|
27
43
|
*/
|
package/support/cli.main.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
|
|
3
3
|
import { ShutdownManager } from '@travetto/base';
|
|
4
|
-
import { CliCommandShape, CliCommand, CliValidationError,
|
|
4
|
+
import { CliCommandShape, CliCommand, CliValidationError, ParsedState } from '@travetto/cli';
|
|
5
5
|
import { path, RootIndex } from '@travetto/manifest';
|
|
6
|
+
import { Ignore } from '@travetto/schema';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Allows for running of main entry points
|
|
@@ -10,6 +11,9 @@ import { path, RootIndex } from '@travetto/manifest';
|
|
|
10
11
|
@CliCommand({ hidden: true })
|
|
11
12
|
export class MainCommand implements CliCommandShape {
|
|
12
13
|
|
|
14
|
+
@Ignore()
|
|
15
|
+
_parsed: ParsedState;
|
|
16
|
+
|
|
13
17
|
async #getImport(fileOrImport: string): Promise<string | undefined> {
|
|
14
18
|
// If referenced file exists
|
|
15
19
|
let file = fileOrImport;
|
|
@@ -32,7 +36,7 @@ export class MainCommand implements CliCommandShape {
|
|
|
32
36
|
const imp = await this.#getImport(fileOrImport);
|
|
33
37
|
const mod = await import(imp!);
|
|
34
38
|
|
|
35
|
-
await ShutdownManager.exitWithResponse(await mod.main(...args, ...
|
|
39
|
+
await ShutdownManager.exitWithResponse(await mod.main(...args, ...this._parsed.unknown));
|
|
36
40
|
} catch (err) {
|
|
37
41
|
await ShutdownManager.exitWithResponse(err, true);
|
|
38
42
|
}
|