@travetto/cli 3.4.9 → 4.0.0-rc.0
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 +6 -2
- package/__index__.ts +1 -0
- package/bin/trv.js +2 -1
- package/package.json +3 -3
- package/src/color.ts +15 -18
- package/src/decorators.ts +13 -12
- package/src/error.ts +2 -7
- package/src/execute.ts +41 -42
- package/src/help.ts +9 -8
- package/src/module.ts +5 -5
- package/src/parse.ts +26 -30
- package/src/registry.ts +8 -15
- package/src/schema.ts +35 -30
- package/src/scm.ts +9 -10
- package/src/trv.d.ts +15 -0
- package/src/types.ts +22 -3
- package/src/util.ts +24 -32
- package/support/cli.cli_schema.ts +8 -2
- package/support/cli.main.ts +7 -7
- package/support/entry.trv.ts +2 -14
package/README.md
CHANGED
|
@@ -415,6 +415,10 @@ export interface CliCommandShape<T extends unknown[] = unknown[]> {
|
|
|
415
415
|
* Parsed state
|
|
416
416
|
*/
|
|
417
417
|
_parsed?: ParsedState;
|
|
418
|
+
/**
|
|
419
|
+
* Config
|
|
420
|
+
*/
|
|
421
|
+
_cfg?: CliCommandConfig;
|
|
418
422
|
/**
|
|
419
423
|
* Action target of the command
|
|
420
424
|
*/
|
|
@@ -492,12 +496,12 @@ export class RunRestCommand implements CliCommandShape {
|
|
|
492
496
|
}
|
|
493
497
|
```
|
|
494
498
|
|
|
495
|
-
As noted in the example above, `fields` is specified in this execution, with support for `module`, and `env`. These env flag is directly tied to the [
|
|
499
|
+
As noted in the example above, `fields` is specified in this execution, with support for `module`, and `env`. These env flag is directly tied to the [Runtime](https://github.com/travetto/travetto/tree/main/module/base/src/env.ts#L104) `name` defined in the [Base](https://github.com/travetto/travetto/tree/main/module/base#readme "Environment config and common utilities for travetto applications.") module.
|
|
496
500
|
|
|
497
501
|
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.
|
|
498
502
|
|
|
499
503
|
### Custom Validation
|
|
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#
|
|
504
|
+
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#L36) errors that are returned will be shared with the user, and fail to invoke the `main` method.
|
|
501
505
|
|
|
502
506
|
**Code: CliValidationError**
|
|
503
507
|
```typescript
|
package/__index__.ts
CHANGED
package/bin/trv.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env -S node --disable-proto=delete --enable-source-maps
|
|
2
|
+
process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS ?? ''} --enable-source-maps --disable-proto=delete`;
|
|
2
3
|
|
|
3
4
|
// @ts-check
|
|
4
5
|
import { getEntry } from '@travetto/compiler/bin/common.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-rc.0",
|
|
4
4
|
"description": "CLI infrastructure for Travetto framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"directory": "module/cli"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@travetto/schema": "^
|
|
33
|
-
"@travetto/terminal": "^
|
|
32
|
+
"@travetto/schema": "^4.0.0-rc.0",
|
|
33
|
+
"@travetto/terminal": "^4.0.0-rc.0"
|
|
34
34
|
},
|
|
35
35
|
"travetto": {
|
|
36
36
|
"displayName": "Command Line Interface"
|
package/src/color.ts
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { GlobalTerminal } from '@travetto/terminal';
|
|
1
|
+
import { StyleUtil } from '@travetto/terminal';
|
|
3
2
|
|
|
4
|
-
const
|
|
5
|
-
input: '
|
|
6
|
-
output: '
|
|
7
|
-
path: '
|
|
8
|
-
success: '
|
|
9
|
-
failure: '
|
|
10
|
-
param: ['
|
|
11
|
-
type: '
|
|
12
|
-
description: ['
|
|
13
|
-
title: ['
|
|
14
|
-
identifier: '
|
|
15
|
-
subtitle: ['
|
|
16
|
-
subsubtitle: '
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
export const cliTpl = Util.makeTemplate(tplFn);
|
|
3
|
+
export const cliTpl = StyleUtil.getTemplate({
|
|
4
|
+
input: '#6b8e23', // Olive drab
|
|
5
|
+
output: '#ffc0cb', // Pink
|
|
6
|
+
path: '#008080', // Teal
|
|
7
|
+
success: '#00ff00', // Green
|
|
8
|
+
failure: '#ff0000', // Red
|
|
9
|
+
param: ['#ffff00', '#daa520'], // Yellow / Goldenrod
|
|
10
|
+
type: '#00ffff', // Teal
|
|
11
|
+
description: ['#e5e5e5', '#808080'], // White / Gray
|
|
12
|
+
title: ['#ffffff', '#000000'], // Bright white / black
|
|
13
|
+
identifier: '#1e90ff', // Dodger blue
|
|
14
|
+
subtitle: ['#d3d3d3', '#a9a9a9'], // Light gray / Dark Gray
|
|
15
|
+
subsubtitle: '#a9a9a9' // Dark gray
|
|
16
|
+
});
|
package/src/decorators.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Class, ClassInstance,
|
|
2
|
-
import {
|
|
1
|
+
import { Class, ClassInstance, Env } from '@travetto/base';
|
|
2
|
+
import { RuntimeIndex, RuntimeContext } from '@travetto/manifest';
|
|
3
3
|
import { SchemaRegistry } from '@travetto/schema';
|
|
4
4
|
|
|
5
5
|
import { CliCommandShape, CliCommandShapeFields } from './types';
|
|
@@ -15,7 +15,7 @@ import { CliParseUtil } from './parse';
|
|
|
15
15
|
*/
|
|
16
16
|
export function CliCommand(cfg: CliCommandConfigOptions = {}) {
|
|
17
17
|
return function <T extends CliCommandShape>(target: Class<T>): void {
|
|
18
|
-
const meta =
|
|
18
|
+
const meta = RuntimeIndex.getFunctionMetadata(target);
|
|
19
19
|
if (!meta || meta.abstract) {
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
@@ -25,9 +25,10 @@ export function CliCommand(cfg: CliCommandConfigOptions = {}) {
|
|
|
25
25
|
const addEnv = cfg.addEnv ?? cfg.fields?.includes('env');
|
|
26
26
|
const { commandModule } = CliCommandRegistry.registerClass(target, {
|
|
27
27
|
hidden: cfg.hidden,
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
runTarget: cfg.runTarget,
|
|
29
|
+
preMain: async (cmd: CliCommandShape & { env?: string }) => {
|
|
30
|
+
if (addEnv) {
|
|
31
|
+
Env.TRV_ENV.set(cmd.env || Env.name);
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
});
|
|
@@ -36,17 +37,17 @@ export function CliCommand(cfg: CliCommandConfigOptions = {}) {
|
|
|
36
37
|
|
|
37
38
|
if (addEnv) {
|
|
38
39
|
SchemaRegistry.registerPendingFieldConfig(target, 'env', String, {
|
|
39
|
-
aliases: ['e', CliParseUtil.toEnvField(
|
|
40
|
+
aliases: ['e', CliParseUtil.toEnvField(Env.TRV_ENV.key)],
|
|
40
41
|
description: 'Application environment',
|
|
41
|
-
default: 'dev',
|
|
42
42
|
required: { active: false }
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
if (addModule) {
|
|
47
47
|
SchemaRegistry.registerPendingFieldConfig(target, 'module', String, {
|
|
48
|
-
aliases: ['m', CliParseUtil.toEnvField(
|
|
48
|
+
aliases: ['m', CliParseUtil.toEnvField(Env.TRV_MODULE.key)],
|
|
49
49
|
description: 'Module to run for',
|
|
50
|
+
specifiers: ['module'],
|
|
50
51
|
required: { active: CliUtil.monoRoot }
|
|
51
52
|
});
|
|
52
53
|
}
|
|
@@ -55,12 +56,12 @@ export function CliCommand(cfg: CliCommandConfigOptions = {}) {
|
|
|
55
56
|
(pendingCls.validators ??= []).push(async item => {
|
|
56
57
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
57
58
|
const { module: mod } = item as CliCommandShapeFields;
|
|
58
|
-
const runModule = (runtimeModule === 'command' ? commandModule : mod) ||
|
|
59
|
+
const runModule = (runtimeModule === 'command' ? commandModule : mod) || RuntimeContext.main.name;
|
|
59
60
|
|
|
60
61
|
// If we need to run as a specific module
|
|
61
|
-
if (runModule !==
|
|
62
|
+
if (runModule !== RuntimeContext.main.name) {
|
|
62
63
|
try {
|
|
63
|
-
|
|
64
|
+
RuntimeIndex.reinitForModule(runModule);
|
|
64
65
|
} catch (err) {
|
|
65
66
|
return { source: 'flag', message: `${runModule} is an unknown module`, kind: 'custom', path: '.' };
|
|
66
67
|
}
|
package/src/error.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { PackageUtil, RuntimeContext } from '@travetto/manifest';
|
|
3
2
|
import { cliTpl } from './color';
|
|
4
3
|
import { CliValidationError } from './types';
|
|
5
4
|
|
|
@@ -23,11 +22,7 @@ export class CliUnknownCommandError extends Error {
|
|
|
23
22
|
const matchedCfg = COMMAND_PACKAGE.find(([re]) => re.test(cmd));
|
|
24
23
|
if (matchedCfg) {
|
|
25
24
|
const [, pkg, prod] = matchedCfg;
|
|
26
|
-
|
|
27
|
-
switch (RootIndex.manifest.packageManager) {
|
|
28
|
-
case 'npm': install = `npm i ${prod ? '' : '--save-dev '}@travetto/${pkg}`; break;
|
|
29
|
-
case 'yarn': install = `yarn add ${prod ? '' : '--dev '}@travetto/${pkg}`; break;
|
|
30
|
-
}
|
|
25
|
+
const install = PackageUtil.getInstallCommand(RuntimeContext, `@travetto/${pkg}`, prod);
|
|
31
26
|
return cliTpl`
|
|
32
27
|
${{ title: 'Missing Package' }}\n${'-'.repeat(20)}\nTo use ${{ input: cmd }} please run:\n
|
|
33
28
|
${{ identifier: install }}
|
package/src/execute.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ConsoleManager, GlobalEnv } from '@travetto/base';
|
|
1
|
+
import { ConsoleManager, Env, ShutdownManager } from '@travetto/base';
|
|
3
2
|
|
|
4
3
|
import { HelpUtil } from './help';
|
|
5
|
-
import { CliCommandShape
|
|
4
|
+
import { CliCommandShape } from './types';
|
|
6
5
|
import { CliCommandRegistry } from './registry';
|
|
7
6
|
import { CliCommandSchemaUtil } from './schema';
|
|
8
7
|
import { CliUnknownCommandError, CliValidationResultError } from './error';
|
|
@@ -14,52 +13,49 @@ import { CliUtil } from './util';
|
|
|
14
13
|
*/
|
|
15
14
|
export class ExecutionManager {
|
|
16
15
|
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
*/
|
|
20
|
-
static async #runCommand(cmd: CliCommandShape, args: string[]): Promise<RunResponse> {
|
|
16
|
+
/** Prepare command for execution */
|
|
17
|
+
static async #prepareAndBind(cmd: CliCommandShape, args: string[]): Promise<unknown[]> {
|
|
21
18
|
const schema = await CliCommandSchemaUtil.getSchema(cmd);
|
|
19
|
+
args = await CliParseUtil.expandArgs(schema, args);
|
|
22
20
|
cmd._parsed = await CliParseUtil.parse(schema, args);
|
|
23
|
-
const cfg = CliCommandRegistry.getConfig(cmd);
|
|
24
21
|
|
|
25
22
|
await cmd.preBind?.();
|
|
26
|
-
|
|
23
|
+
try {
|
|
24
|
+
const known = await CliCommandSchemaUtil.bindInput(cmd, cmd._parsed);
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
await cmd.preValidate?.();
|
|
27
|
+
await CliCommandSchemaUtil.validate(cmd, known);
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
ConsoleManager.setDebug(GlobalEnv.debug, GlobalEnv.devMode);
|
|
34
|
-
return cmd.main(...known);
|
|
35
|
-
}
|
|
29
|
+
await cmd._cfg!.preMain?.(cmd);
|
|
30
|
+
await cmd.preMain?.();
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
return known;
|
|
33
|
+
} catch (err) {
|
|
34
|
+
if (err instanceof CliValidationResultError) {
|
|
35
|
+
console.error!(await HelpUtil.renderValidationError(cmd, err));
|
|
36
|
+
console.error!(await HelpUtil.renderCommandHelp(cmd));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
} else {
|
|
44
39
|
throw err;
|
|
45
40
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Fetch a single command */
|
|
45
|
+
static async #getCommand(cmd: string): Promise<CliCommandShape> {
|
|
46
|
+
try {
|
|
47
|
+
return await CliCommandRegistry.getInstance(cmd, true);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (err instanceof CliUnknownCommandError) {
|
|
52
50
|
if (err.help) {
|
|
53
51
|
console.error!(err.help);
|
|
54
52
|
} else {
|
|
55
53
|
console.error!(err.defaultMessage, '\n');
|
|
56
54
|
console.error!(await HelpUtil.renderAllHelp(''));
|
|
57
55
|
}
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
console.error!(err);
|
|
62
|
-
console.error!();
|
|
56
|
+
process.exit(1);
|
|
57
|
+
} else {
|
|
58
|
+
throw err;
|
|
63
59
|
}
|
|
64
60
|
}
|
|
65
61
|
}
|
|
@@ -69,27 +65,30 @@ export class ExecutionManager {
|
|
|
69
65
|
* @param args
|
|
70
66
|
*/
|
|
71
67
|
static async run(argv: string[]): Promise<void> {
|
|
72
|
-
await GlobalTerminal.init();
|
|
73
|
-
|
|
74
|
-
let command: CliCommandShape | undefined;
|
|
75
68
|
try {
|
|
76
|
-
const { cmd, args, help } =
|
|
69
|
+
const { cmd, args, help } = CliParseUtil.getArgs(argv);
|
|
77
70
|
|
|
78
71
|
if (!cmd) {
|
|
79
72
|
console.info!(await HelpUtil.renderAllHelp());
|
|
80
73
|
return;
|
|
81
74
|
}
|
|
82
75
|
|
|
83
|
-
|
|
84
|
-
|
|
76
|
+
const command = await this.#getCommand(cmd);
|
|
77
|
+
|
|
85
78
|
if (help) {
|
|
86
79
|
console.log!(await HelpUtil.renderCommandHelp(command));
|
|
80
|
+
return;
|
|
87
81
|
} else {
|
|
88
|
-
const
|
|
82
|
+
const known = await this.#prepareAndBind(command, args);
|
|
83
|
+
ConsoleManager.debug(Env.debug);
|
|
84
|
+
const result = await command.main(...known);
|
|
89
85
|
await CliUtil.listenForResponse(result);
|
|
90
86
|
}
|
|
91
87
|
} catch (err) {
|
|
92
|
-
|
|
88
|
+
console.error!(err);
|
|
89
|
+
console.error!();
|
|
90
|
+
} finally {
|
|
91
|
+
await ShutdownManager.gracefulShutdown(process.exitCode);
|
|
93
92
|
}
|
|
94
93
|
}
|
|
95
94
|
}
|
package/src/help.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Primitive } from '@travetto/base';
|
|
2
|
-
import {
|
|
2
|
+
import { StyleUtil } from '@travetto/terminal';
|
|
3
3
|
|
|
4
4
|
import { cliTpl } from './color';
|
|
5
5
|
import { CliCommandShape } from './types';
|
|
6
6
|
import { CliCommandRegistry } from './registry';
|
|
7
7
|
import { CliCommandSchemaUtil } from './schema';
|
|
8
8
|
import { CliValidationResultError } from './error';
|
|
9
|
+
import { isBoolFlag } from './parse';
|
|
9
10
|
|
|
10
11
|
const validationSourceMap = {
|
|
11
12
|
custom: '',
|
|
@@ -32,7 +33,7 @@ export class HelpUtil {
|
|
|
32
33
|
|
|
33
34
|
const usage: string[] = [cliTpl`${{ title: 'Usage:' }} ${{ param: commandName }} ${{ input: '[options]' }}`];
|
|
34
35
|
for (const field of args) {
|
|
35
|
-
const type = field.type === 'string' && field.choices && field.choices.length <=
|
|
36
|
+
const type = field.type === 'string' && field.choices && field.choices.length <= 7 ? field.choices?.join('|') : field.type;
|
|
36
37
|
const arg = `${field.name}${field.array ? '...' : ''}:${type}`;
|
|
37
38
|
usage.push(cliTpl`${{ input: field.required ? `<${arg}>` : `[${arg}]` }}`);
|
|
38
39
|
}
|
|
@@ -44,10 +45,10 @@ export class HelpUtil {
|
|
|
44
45
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
45
46
|
const key = flag.name as keyof CliCommandShape;
|
|
46
47
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
47
|
-
const flagVal = command[key] as unknown as
|
|
48
|
+
const flagVal = command[key] as unknown as Primitive;
|
|
48
49
|
|
|
49
50
|
let aliases = flag.flagNames ?? [];
|
|
50
|
-
if (flag
|
|
51
|
+
if (isBoolFlag(flag)) {
|
|
51
52
|
if (flagVal === true) {
|
|
52
53
|
aliases = (flag.flagNames ?? []).filter(x => !/^[-][^-]/.test(x));
|
|
53
54
|
} else {
|
|
@@ -55,7 +56,7 @@ export class HelpUtil {
|
|
|
55
56
|
}
|
|
56
57
|
}
|
|
57
58
|
const param = [cliTpl`${{ param: aliases.join(', ') }}`];
|
|
58
|
-
if (!(flag
|
|
59
|
+
if (!isBoolFlag(flag)) {
|
|
59
60
|
const type = flag.type === 'string' && flag.choices && flag.choices.length <= 3 ? flag.choices?.join('|') : flag.type;
|
|
60
61
|
param.push(cliTpl`${{ type: `<${type}>` }}`);
|
|
61
62
|
}
|
|
@@ -68,8 +69,8 @@ export class HelpUtil {
|
|
|
68
69
|
descs.push(desc.join(' '));
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
const paramWidths = params.map(x =>
|
|
72
|
-
const descWidths = descs.map(x =>
|
|
72
|
+
const paramWidths = params.map(x => StyleUtil.cleanText(x).length);
|
|
73
|
+
const descWidths = descs.map(x => StyleUtil.cleanText(x).length);
|
|
73
74
|
|
|
74
75
|
const paramWidth = Math.max(...paramWidths);
|
|
75
76
|
const descWidth = Math.max(...descWidths);
|
|
@@ -97,7 +98,7 @@ export class HelpUtil {
|
|
|
97
98
|
static async renderAllHelp(title?: string): Promise<string> {
|
|
98
99
|
const rows: string[] = [];
|
|
99
100
|
const keys = [...CliCommandRegistry.getCommandMapping().keys()].sort((a, b) => a.localeCompare(b));
|
|
100
|
-
const maxWidth = keys.reduce((a, b) => Math.max(a,
|
|
101
|
+
const maxWidth = keys.reduce((a, b) => Math.max(a, StyleUtil.cleanText(b).length), 0);
|
|
101
102
|
|
|
102
103
|
for (const cmd of keys) {
|
|
103
104
|
try {
|
package/src/module.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IndexedModule,
|
|
1
|
+
import { IndexedModule, RuntimeIndex, RuntimeContext } from '@travetto/manifest';
|
|
2
2
|
|
|
3
3
|
import { CliScmUtil } from './scm';
|
|
4
4
|
|
|
@@ -20,14 +20,14 @@ export class CliModuleUtil {
|
|
|
20
20
|
fromHash ??= await CliScmUtil.findLastRelease();
|
|
21
21
|
|
|
22
22
|
if (!fromHash) {
|
|
23
|
-
return
|
|
23
|
+
return RuntimeIndex.getLocalModules();
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
const out = new Map<string, IndexedModule>();
|
|
27
27
|
for (const mod of await CliScmUtil.findChangedModules(fromHash, toHash)) {
|
|
28
28
|
out.set(mod.name, mod);
|
|
29
29
|
if (transitive) {
|
|
30
|
-
for (const sub of await
|
|
30
|
+
for (const sub of await RuntimeIndex.getDependentModules(mod)) {
|
|
31
31
|
out.set(sub.name, sub);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
@@ -46,8 +46,8 @@ export class CliModuleUtil {
|
|
|
46
46
|
static async findModules(mode: 'all' | 'changed', fromHash?: string, toHash?: string): Promise<IndexedModule[]> {
|
|
47
47
|
return (mode === 'changed' ?
|
|
48
48
|
await this.findChangedModulesRecursive(fromHash, toHash) :
|
|
49
|
-
[...
|
|
50
|
-
).filter(x => x.sourcePath !==
|
|
49
|
+
[...RuntimeIndex.getModuleList('all')].map(x => RuntimeIndex.getModule(x)!)
|
|
50
|
+
).filter(x => x.sourcePath !== RuntimeContext.workspace.path);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/**
|
package/src/parse.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { RuntimeIndex, RuntimeContext, path } from '@travetto/manifest';
|
|
4
4
|
import { CliCommandInput, CliCommandSchema, ParsedState } from './types';
|
|
5
5
|
|
|
6
6
|
type ParsedInput = ParsedState['all'][number];
|
|
@@ -12,22 +12,8 @@ const LONG_FLAG_WITH_EQ = /^--[a-z][^= ]+=\S+/i;
|
|
|
12
12
|
const CONFIG_PRE = '+=';
|
|
13
13
|
const ENV_PRE = 'env.';
|
|
14
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
|
-
};
|
|
29
15
|
|
|
30
|
-
const isBoolFlag = (x?: CliCommandInput): boolean => x?.type === 'boolean' && !x.array;
|
|
16
|
+
export const isBoolFlag = (x?: CliCommandInput): boolean => x?.type === 'boolean' && !x.array;
|
|
31
17
|
|
|
32
18
|
const getInput = (cfg: { field?: CliCommandInput, rawText?: string, input: string, index?: number, value?: string }): ParsedInput => {
|
|
33
19
|
const { field, input, rawText = input, value, index } = cfg;
|
|
@@ -46,6 +32,7 @@ const getInput = (cfg: { field?: CliCommandInput, rawText?: string, input: strin
|
|
|
46
32
|
}
|
|
47
33
|
};
|
|
48
34
|
|
|
35
|
+
|
|
49
36
|
/**
|
|
50
37
|
* Parsing support for the cli
|
|
51
38
|
*/
|
|
@@ -98,10 +85,10 @@ export class CliParseUtil {
|
|
|
98
85
|
|
|
99
86
|
// We have a file
|
|
100
87
|
const rel = (key.includes('/') ? key : `@/support/pack.${key}.flags`)
|
|
101
|
-
.replace('@@/', `${
|
|
88
|
+
.replace('@@/', `${RuntimeContext.workspace.path}/`)
|
|
102
89
|
.replace('@/', `${mod}/`)
|
|
103
90
|
.replace(/^(@[^\/]+\/[^\/]+)(\/.*)$/, (_, imp, rest) => {
|
|
104
|
-
const val =
|
|
91
|
+
const val = RuntimeIndex.getModule(imp);
|
|
105
92
|
if (!val) {
|
|
106
93
|
throw new Error(`Unknown module file: ${_}, unable to proceed`);
|
|
107
94
|
}
|
|
@@ -130,7 +117,7 @@ export class CliParseUtil {
|
|
|
130
117
|
* Parse args to extract command from argv along with other params. Will skip
|
|
131
118
|
* argv[0] and argv[1] if equal to process.argv[0:2]
|
|
132
119
|
*/
|
|
133
|
-
static
|
|
120
|
+
static getArgs(argv: string[]): { cmd?: string, args: string[], help?: boolean } {
|
|
134
121
|
let offset = 0;
|
|
135
122
|
if (argv[0] === process.argv[0] && argv[1] === process.argv[1]) {
|
|
136
123
|
offset = 2;
|
|
@@ -138,22 +125,31 @@ export class CliParseUtil {
|
|
|
138
125
|
const out = argv.slice(offset);
|
|
139
126
|
const max = out.includes(RAW_SEP) ? out.indexOf(RAW_SEP) : out.length;
|
|
140
127
|
const valid = out.slice(0, max);
|
|
141
|
-
const mod = getModuleValue(valid);
|
|
142
128
|
const cmd = valid.length > 0 && !valid[0].startsWith('-') ? valid[0] : undefined;
|
|
143
129
|
const helpIdx = valid.findIndex(x => HELP_FLAG.test(x));
|
|
144
|
-
const args =
|
|
145
|
-
for (const item of out.slice(cmd ? 1 : 0)) {
|
|
146
|
-
if (item.startsWith(CONFIG_PRE)) {
|
|
147
|
-
args.push(...await this.readFlagFile(item, mod));
|
|
148
|
-
} else {
|
|
149
|
-
args.push(item);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
args.push(...out.slice(max));
|
|
130
|
+
const args = out.slice(cmd ? 1 : 0);
|
|
153
131
|
const res = { cmd, args, help: helpIdx >= 0 };
|
|
154
132
|
return res;
|
|
155
133
|
}
|
|
156
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Expand flag arguments into full argument list
|
|
137
|
+
*/
|
|
138
|
+
static async expandArgs(schema: CliCommandSchema, args: string[]): Promise<string[]> {
|
|
139
|
+
const SEP = args.includes(RAW_SEP) ? args.indexOf(RAW_SEP) : args.length;
|
|
140
|
+
const input = schema.flags.find(x => x.type === 'module');
|
|
141
|
+
const ENV_KEY = input?.flagNames?.filter(x => x.startsWith(ENV_PRE)).map(x => x.replace(ENV_PRE, ''))[0] ?? '';
|
|
142
|
+
const flags = new Set(input?.flagNames ?? []);
|
|
143
|
+
const check = (k?: string, v?: string): string | undefined => flags.has(k!) ? v : undefined;
|
|
144
|
+
const mod = args.reduce(
|
|
145
|
+
(m, x, i, arr) =>
|
|
146
|
+
(i < SEP ? check(arr[i - 1], x) ?? check(...x.split('=')) : undefined) ?? m,
|
|
147
|
+
process.env[ENV_KEY] || RuntimeContext.main.name
|
|
148
|
+
);
|
|
149
|
+
return (await Promise.all(args.map((x, i) =>
|
|
150
|
+
x.startsWith(CONFIG_PRE) && (i < SEP || SEP < 0) ? this.readFlagFile(x, mod) : x))).flat();
|
|
151
|
+
}
|
|
152
|
+
|
|
157
153
|
/**
|
|
158
154
|
* Parse inputs to command
|
|
159
155
|
*/
|
package/src/registry.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Class, ConcreteClass,
|
|
2
|
-
import {
|
|
1
|
+
import { Class, ConcreteClass, Env } from '@travetto/base';
|
|
2
|
+
import { RuntimeIndex } from '@travetto/manifest';
|
|
3
3
|
|
|
4
|
-
import { CliCommandShape } from './types';
|
|
4
|
+
import { CliCommandConfig, CliCommandShape } from './types';
|
|
5
5
|
import { CliUnknownCommandError } from './error';
|
|
6
6
|
|
|
7
7
|
export type CliCommandConfigOptions = {
|
|
@@ -14,14 +14,6 @@ export type CliCommandConfigOptions = {
|
|
|
14
14
|
fields?: ('module' | 'env')[];
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
-
export type CliCommandConfig = {
|
|
18
|
-
name: string;
|
|
19
|
-
commandModule: string;
|
|
20
|
-
cls: ConcreteClass<CliCommandShape>;
|
|
21
|
-
hidden?: boolean;
|
|
22
|
-
preMain?: (cmd: CliCommandShape) => void | Promise<void>;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
17
|
const CLI_FILE_REGEX = /\/cli[.](?<name>.*)[.]tsx?$/;
|
|
26
18
|
const getName = (s: string): string => (s.match(CLI_FILE_REGEX)?.groups?.name ?? s).replaceAll('_', ':');
|
|
27
19
|
|
|
@@ -44,8 +36,8 @@ class $CliCommandRegistry {
|
|
|
44
36
|
getCommandMapping(): Map<string, string> {
|
|
45
37
|
if (!this.#fileMapping) {
|
|
46
38
|
const all = new Map<string, string>();
|
|
47
|
-
for (const e of
|
|
48
|
-
module: m =>
|
|
39
|
+
for (const e of RuntimeIndex.find({
|
|
40
|
+
module: m => !Env.production || m.prod,
|
|
49
41
|
folder: f => f === 'support',
|
|
50
42
|
file: f => f.role === 'std' && CLI_FILE_REGEX.test(f.sourceFile)
|
|
51
43
|
})) {
|
|
@@ -60,12 +52,12 @@ class $CliCommandRegistry {
|
|
|
60
52
|
* Registers a cli command
|
|
61
53
|
*/
|
|
62
54
|
registerClass(cls: Class, cfg: Partial<CliCommandConfig>): CliCommandConfig {
|
|
63
|
-
const source =
|
|
55
|
+
const source = RuntimeIndex.getFunctionMetadata(cls)!.source;
|
|
64
56
|
this.#commands.set(cls, {
|
|
65
57
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
66
58
|
cls: cls as ConcreteClass,
|
|
67
59
|
name: getName(source),
|
|
68
|
-
commandModule:
|
|
60
|
+
commandModule: RuntimeIndex.getModuleFromSource(source)!.name,
|
|
69
61
|
...cfg,
|
|
70
62
|
});
|
|
71
63
|
return this.#commands.get(cls)!;
|
|
@@ -101,6 +93,7 @@ class $CliCommandRegistry {
|
|
|
101
93
|
if (cfg) {
|
|
102
94
|
const inst = new cfg.cls();
|
|
103
95
|
if (!inst.isActive || inst.isActive()) {
|
|
96
|
+
inst._cfg = this.getConfig(inst);
|
|
104
97
|
return inst;
|
|
105
98
|
}
|
|
106
99
|
}
|
package/src/schema.ts
CHANGED
|
@@ -1,36 +1,46 @@
|
|
|
1
|
-
import { Class
|
|
1
|
+
import { Class } from '@travetto/base';
|
|
2
2
|
import { BindUtil, FieldConfig, SchemaRegistry, SchemaValidator, ValidationResultError } from '@travetto/schema';
|
|
3
3
|
|
|
4
4
|
import { CliCommandRegistry } from './registry';
|
|
5
|
-
import { CliCommandInput, CliCommandSchema, CliCommandShape } from './types';
|
|
5
|
+
import { ParsedState, CliCommandInput, CliCommandSchema, CliCommandShape } from './types';
|
|
6
6
|
import { CliValidationResultError } from './error';
|
|
7
|
-
import { ParsedState } from './parse';
|
|
8
7
|
|
|
9
8
|
const LONG_FLAG = /^--[a-z][^= ]+/i;
|
|
10
9
|
const SHORT_FLAG = /^-[a-z]/i;
|
|
11
10
|
|
|
12
11
|
const isBoolFlag = (x?: CliCommandInput): boolean => x?.type === 'boolean' && !x.array;
|
|
13
12
|
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
});
|
|
13
|
+
function baseType(x: FieldConfig): Pick<CliCommandInput, 'type' | 'fileExtensions'> {
|
|
14
|
+
switch (x.type) {
|
|
15
|
+
case Date: return { type: 'date' };
|
|
16
|
+
case Boolean: return { type: 'boolean' };
|
|
17
|
+
case Number: return { type: 'number' };
|
|
18
|
+
case RegExp: return { type: 'regex' };
|
|
19
|
+
case String: {
|
|
20
|
+
switch (true) {
|
|
21
|
+
case x.specifiers?.includes('module'): return { type: 'module' };
|
|
22
|
+
case x.specifiers?.includes('file'): return {
|
|
23
|
+
type: 'file',
|
|
24
|
+
fileExtensions: x.specifiers?.map(s => s.split('ext:')[1]).filter(s => !!s)
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { type: 'string' };
|
|
32
30
|
}
|
|
33
31
|
|
|
32
|
+
const fieldToInput = (x: FieldConfig): CliCommandInput => ({
|
|
33
|
+
...baseType(x),
|
|
34
|
+
name: x.name,
|
|
35
|
+
description: x.description,
|
|
36
|
+
array: x.array,
|
|
37
|
+
required: x.required?.active,
|
|
38
|
+
choices: x.enum?.values,
|
|
39
|
+
default: Array.isArray(x.default) ? x.default.slice(0) : x.default,
|
|
40
|
+
flagNames: (x.aliases ?? []).slice(0).filter(v => !v.startsWith('env.')),
|
|
41
|
+
envVars: (x.aliases ?? []).slice(0).filter(v => v.startsWith('env.')).map(v => v.replace('env.', ''))
|
|
42
|
+
});
|
|
43
|
+
|
|
34
44
|
/**
|
|
35
45
|
* Allows binding describing/binding inputs for commands
|
|
36
46
|
*/
|
|
@@ -49,16 +59,11 @@ export class CliCommandSchemaUtil {
|
|
|
49
59
|
}
|
|
50
60
|
|
|
51
61
|
// Ensure finalized
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (parent?.Ⲑid) {
|
|
56
|
-
SchemaRegistry.onInstall(parent, { type: 'added', curr: parent });
|
|
57
|
-
}
|
|
58
|
-
SchemaRegistry.onInstall(cls, { type: 'added', curr: cls });
|
|
59
|
-
} finally {
|
|
60
|
-
ConsoleManager.setDebug(GlobalEnv.debug, GlobalEnv.devMode);
|
|
62
|
+
const parent = SchemaRegistry.getParentClass(cls);
|
|
63
|
+
if (parent?.Ⲑid) {
|
|
64
|
+
SchemaRegistry.onInstall(parent, { type: 'added', curr: parent });
|
|
61
65
|
}
|
|
66
|
+
SchemaRegistry.onInstall(cls, { type: 'added', curr: cls });
|
|
62
67
|
|
|
63
68
|
const schema = await SchemaRegistry.getViewSchema(cls);
|
|
64
69
|
const flags = Object.values(schema.schema).map(fieldToInput);
|
package/src/scm.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import { IndexedFile, IndexedModule,
|
|
3
|
+
import { ExecUtil } from '@travetto/base';
|
|
4
|
+
import { IndexedFile, IndexedModule, RuntimeIndex, RuntimeContext, path } from '@travetto/manifest';
|
|
5
5
|
|
|
6
6
|
export class CliScmUtil {
|
|
7
7
|
/**
|
|
@@ -23,7 +23,7 @@ export class CliScmUtil {
|
|
|
23
23
|
await ExecUtil.spawn('git', ['config', 'user.email']).result,
|
|
24
24
|
]);
|
|
25
25
|
return {
|
|
26
|
-
name: (name.valid ? name.stdout.trim() : '') ||
|
|
26
|
+
name: (name.valid ? name.stdout.trim() : '') || process.env.USER,
|
|
27
27
|
email: email.stdout.trim()
|
|
28
28
|
};
|
|
29
29
|
}
|
|
@@ -33,8 +33,7 @@ export class CliScmUtil {
|
|
|
33
33
|
* @returns
|
|
34
34
|
*/
|
|
35
35
|
static async findLastRelease(): Promise<string | undefined> {
|
|
36
|
-
const
|
|
37
|
-
const { result } = ExecUtil.spawn('git', ['log', '--pretty=oneline'], { cwd: root.workspacePath });
|
|
36
|
+
const { result } = ExecUtil.spawn('git', ['log', '--pretty=oneline'], { cwd: RuntimeContext.workspace.path });
|
|
38
37
|
return (await result).stdout
|
|
39
38
|
.split(/\n/)
|
|
40
39
|
.find(x => /Publish /.test(x))?.split(/\s+/)?.[0];
|
|
@@ -46,11 +45,11 @@ export class CliScmUtil {
|
|
|
46
45
|
* @returns
|
|
47
46
|
*/
|
|
48
47
|
static async findChangedFiles(fromHash: string, toHash: string = 'HEAD'): Promise<string[]> {
|
|
49
|
-
const ws =
|
|
48
|
+
const ws = RuntimeContext.workspace.path;
|
|
50
49
|
const res = await ExecUtil.spawn('git', ['diff', '--name-only', `${fromHash}..${toHash}`, ':!**/DOC.*', ':!**/README.*'], { cwd: ws }).result;
|
|
51
50
|
const out = new Set<string>();
|
|
52
51
|
for (const line of res.stdout.split(/\n/g)) {
|
|
53
|
-
const entry =
|
|
52
|
+
const entry = RuntimeIndex.getEntry(path.resolve(ws, line));
|
|
54
53
|
if (entry) {
|
|
55
54
|
out.add(entry.sourceFile);
|
|
56
55
|
}
|
|
@@ -67,9 +66,9 @@ export class CliScmUtil {
|
|
|
67
66
|
static async findChangedModules(fromHash: string, toHash?: string): Promise<IndexedModule[]> {
|
|
68
67
|
const files = await this.findChangedFiles(fromHash, toHash);
|
|
69
68
|
const mods = files
|
|
70
|
-
.map(x =>
|
|
69
|
+
.map(x => RuntimeIndex.getFromSource(x))
|
|
71
70
|
.filter((x): x is IndexedFile => !!x)
|
|
72
|
-
.map(x =>
|
|
71
|
+
.map(x => RuntimeIndex.getModule(x.module))
|
|
73
72
|
.filter((x): x is IndexedModule => !!x);
|
|
74
73
|
|
|
75
74
|
return [...new Set(mods)]
|
package/src/trv.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import '@travetto/base';
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
interface TravettoEnv {
|
|
5
|
+
/**
|
|
6
|
+
* Provides an IPC http url for the CLI to communicate with.
|
|
7
|
+
* This facilitates cli-based invocation for external usage.
|
|
8
|
+
*/
|
|
9
|
+
TRV_CLI_IPC: string;
|
|
10
|
+
/**
|
|
11
|
+
* Determines (assuming the operation supports it), that restart behavior can trigger
|
|
12
|
+
*/
|
|
13
|
+
TRV_CAN_RESTART: boolean;
|
|
14
|
+
}
|
|
15
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,14 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ConcreteClass } from '@travetto/base';
|
|
2
2
|
|
|
3
3
|
type OrProm<T> = T | Promise<T>;
|
|
4
4
|
|
|
5
|
-
export type RunResponse =
|
|
5
|
+
export type RunResponse =
|
|
6
|
+
{ wait(): Promise<unknown> } |
|
|
7
|
+
{ on(event: 'close', cb: Function): unknown } |
|
|
8
|
+
{ close: () => (void | Promise<void>) } | void | undefined;
|
|
6
9
|
|
|
7
10
|
type ParsedFlag = { type: 'flag', input: string, array?: boolean, fieldName: string, value?: unknown };
|
|
8
11
|
type ParsedArg = { type: 'arg', input: string, array?: boolean, index: number };
|
|
9
12
|
type ParsedUnknown = { type: 'unknown', input: string };
|
|
10
13
|
type ParsedInput = ParsedUnknown | ParsedFlag | ParsedArg;
|
|
11
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Command configuration
|
|
17
|
+
*/
|
|
18
|
+
export type CliCommandConfig = {
|
|
19
|
+
name: string;
|
|
20
|
+
commandModule: string;
|
|
21
|
+
runTarget?: boolean;
|
|
22
|
+
cls: ConcreteClass<CliCommandShape>;
|
|
23
|
+
hidden?: boolean;
|
|
24
|
+
preMain?: (cmd: CliCommandShape) => void | Promise<void>;
|
|
25
|
+
};
|
|
26
|
+
|
|
12
27
|
export type ParsedState = {
|
|
13
28
|
inputs: string[];
|
|
14
29
|
all: ParsedInput[];
|
|
@@ -38,6 +53,10 @@ export interface CliCommandShape<T extends unknown[] = unknown[]> {
|
|
|
38
53
|
* Parsed state
|
|
39
54
|
*/
|
|
40
55
|
_parsed?: ParsedState;
|
|
56
|
+
/**
|
|
57
|
+
* Config
|
|
58
|
+
*/
|
|
59
|
+
_cfg?: CliCommandConfig;
|
|
41
60
|
/**
|
|
42
61
|
* Action target of the command
|
|
43
62
|
*/
|
|
@@ -100,7 +119,7 @@ export type CliCommandShapeFields = {
|
|
|
100
119
|
export type CliCommandInput = {
|
|
101
120
|
name: string;
|
|
102
121
|
description?: string;
|
|
103
|
-
type: 'string' | 'file' | 'number' | 'boolean' | 'date' | 'regex';
|
|
122
|
+
type: 'string' | 'file' | 'number' | 'boolean' | 'date' | 'regex' | 'module';
|
|
104
123
|
fileExtensions?: string[];
|
|
105
124
|
choices?: unknown[];
|
|
106
125
|
required?: boolean;
|
package/src/util.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import { Env, ExecUtil,
|
|
2
|
-
import {
|
|
1
|
+
import { Env, ExecUtil, ShutdownManager } from '@travetto/base';
|
|
2
|
+
import { RuntimeIndex, RuntimeContext } from '@travetto/manifest';
|
|
3
3
|
|
|
4
4
|
import { CliCommandShape, CliCommandShapeFields, RunResponse } from './types';
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
const IPC_ALLOWED_ENV = new Set(['NODE_OPTIONS']);
|
|
7
|
+
const IPC_INVALID_ENV = new Set(['PS1', 'INIT_CWD', 'COLOR', 'LANGUAGE', 'PROFILEHOME', '_']);
|
|
8
|
+
const validEnv = (k: string): boolean => IPC_ALLOWED_ENV.has(k) || (!IPC_INVALID_ENV.has(k) && !/^(npm_|GTK|GDK|TRV|NODE|GIT|TERM_)/.test(k) && !/VSCODE/.test(k));
|
|
6
9
|
|
|
7
10
|
export class CliUtil {
|
|
8
11
|
/**
|
|
9
12
|
* Are we running from a mono-root?
|
|
10
13
|
*/
|
|
11
14
|
static get monoRoot(): boolean {
|
|
12
|
-
return !!
|
|
15
|
+
return !!RuntimeContext.workspace.mono && RuntimeIndex.mainModule.sourcePath === RuntimeContext.workspace.path;
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
/**
|
|
@@ -17,7 +20,7 @@ export class CliUtil {
|
|
|
17
20
|
* @returns
|
|
18
21
|
*/
|
|
19
22
|
static getSimpleModuleName(placeholder: string, module?: string): string {
|
|
20
|
-
const simple = (module ??
|
|
23
|
+
const simple = (module ?? RuntimeContext.main.name).replace(/[\/]/, '_').replace(/@/, '');
|
|
21
24
|
if (!simple) {
|
|
22
25
|
return placeholder;
|
|
23
26
|
} else if (!module && this.monoRoot) {
|
|
@@ -31,14 +34,15 @@ export class CliUtil {
|
|
|
31
34
|
* Run a command as restartable, linking into self
|
|
32
35
|
*/
|
|
33
36
|
static runWithRestart<T extends CliCommandShapeFields & CliCommandShape>(cmd: T): Promise<unknown> | undefined {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (canRestart === false || Env.isFalse('TRV_CAN_RESTART')) {
|
|
37
|
-
delete process.env.TRV_CAN_RESTART;
|
|
37
|
+
if (Env.TRV_CAN_RESTART.isFalse || !(cmd.canRestart ?? !Env.production)) {
|
|
38
|
+
Env.TRV_CAN_RESTART.clear();
|
|
38
39
|
return;
|
|
39
40
|
}
|
|
40
41
|
return ExecUtil.spawnWithRestart(process.argv0, process.argv.slice(1), {
|
|
41
|
-
env: {
|
|
42
|
+
env: {
|
|
43
|
+
...Env.TRV_DYNAMIC.export(true),
|
|
44
|
+
...Env.TRV_CAN_RESTART.export(false)
|
|
45
|
+
},
|
|
42
46
|
stdio: [0, 1, 2, 'ipc']
|
|
43
47
|
});
|
|
44
48
|
}
|
|
@@ -47,41 +51,30 @@ export class CliUtil {
|
|
|
47
51
|
* Dispatch IPC payload
|
|
48
52
|
*/
|
|
49
53
|
static async triggerIpc<T extends CliCommandShape>(action: 'run', cmd: T): Promise<boolean> {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (!ipcUrl) {
|
|
54
|
+
if (!Env.TRV_CLI_IPC.isSet) {
|
|
53
55
|
return false;
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
const info = await fetch(
|
|
58
|
+
const info = await fetch(Env.TRV_CLI_IPC.val!).catch(() => ({ ok: false }));
|
|
57
59
|
|
|
58
60
|
if (!info.ok) { // Server not running
|
|
59
61
|
return false;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
const
|
|
64
|
+
const env: Record<string, string> = {};
|
|
63
65
|
const req = {
|
|
64
66
|
type: `@travetto/cli:${action}`,
|
|
65
67
|
data: {
|
|
66
|
-
name:
|
|
67
|
-
commandModule:
|
|
68
|
-
module:
|
|
68
|
+
name: cmd._cfg!.name, env,
|
|
69
|
+
commandModule: cmd._cfg!.commandModule,
|
|
70
|
+
module: RuntimeContext.main.name,
|
|
69
71
|
args: process.argv.slice(3),
|
|
70
72
|
}
|
|
71
73
|
};
|
|
72
|
-
|
|
73
74
|
console.log('Triggering IPC request', req);
|
|
74
75
|
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
Object.entries(process.env).filter(([k]) =>
|
|
78
|
-
!defaultEnvKeys.has(k) && !/^(npm_|GTK|GDK|TRV|NODE|GIT|TERM_)/.test(k) && !/VSCODE/.test(k)
|
|
79
|
-
)
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
Object.assign(req.data, { env });
|
|
83
|
-
|
|
84
|
-
const sent = await fetch(ipcUrl, { method: 'POST', body: JSON.stringify(req) });
|
|
76
|
+
Object.entries(process.env).forEach(([k, v]) => validEnv(k) && (env[k] = v!));
|
|
77
|
+
const sent = await fetch(Env.TRV_CLI_IPC.val!, { method: 'POST', body: JSON.stringify(req) });
|
|
85
78
|
return sent.ok;
|
|
86
79
|
}
|
|
87
80
|
|
|
@@ -89,8 +82,7 @@ export class CliUtil {
|
|
|
89
82
|
* Debug if IPC available
|
|
90
83
|
*/
|
|
91
84
|
static async debugIfIpc<T extends CliCommandShapeFields & CliCommandShape>(cmd: T): Promise<boolean> {
|
|
92
|
-
|
|
93
|
-
return canDebug !== false && this.triggerIpc('run', cmd);
|
|
85
|
+
return (cmd.debugIpc ?? !Env.production) && this.triggerIpc('run', cmd);
|
|
94
86
|
}
|
|
95
87
|
|
|
96
88
|
/**
|
|
@@ -107,7 +99,7 @@ export class CliUtil {
|
|
|
107
99
|
// Listen to result if non-empty
|
|
108
100
|
if (result !== undefined && result !== null) {
|
|
109
101
|
if ('close' in result) {
|
|
110
|
-
ShutdownManager.
|
|
102
|
+
ShutdownManager.onGracefulShutdown(async () => result.close()); // Tie shutdown into app close
|
|
111
103
|
}
|
|
112
104
|
if ('wait' in result) {
|
|
113
105
|
await result.wait(); // Wait for close signal
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { Env } from '@travetto/base';
|
|
2
|
+
|
|
1
3
|
import { CliCommand } from '../src/decorators';
|
|
2
|
-
import { CliCommandSchema, CliValidationError } from '../src/types';
|
|
4
|
+
import { CliCommandSchema, CliCommandShape, CliValidationError } from '../src/types';
|
|
3
5
|
import { CliCommandRegistry } from '../src/registry';
|
|
4
6
|
import { CliCommandSchemaUtil } from '../src/schema';
|
|
5
7
|
import { CliUtil } from '../src/util';
|
|
@@ -8,7 +10,7 @@ import { CliUtil } from '../src/util';
|
|
|
8
10
|
* Generates the schema for all CLI operations
|
|
9
11
|
*/
|
|
10
12
|
@CliCommand({ hidden: true })
|
|
11
|
-
export class CliSchemaCommand {
|
|
13
|
+
export class CliSchemaCommand implements CliCommandShape {
|
|
12
14
|
|
|
13
15
|
async #getSchema(name: string): Promise<CliCommandSchema> {
|
|
14
16
|
const inst = await CliCommandRegistry.getInstance(name);
|
|
@@ -24,6 +26,10 @@ export class CliSchemaCommand {
|
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
preMain(): void {
|
|
30
|
+
Env.DEBUG.set(false);
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
async main(name?: string): Promise<void> {
|
|
28
34
|
let output: unknown = undefined;
|
|
29
35
|
if (name) {
|
package/support/cli.main.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import fs from 'fs/promises';
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { ExecUtil } from '@travetto/base';
|
|
4
4
|
import { CliCommandShape, CliCommand, CliValidationError, ParsedState } from '@travetto/cli';
|
|
5
|
-
import { path,
|
|
5
|
+
import { path, RuntimeIndex, RuntimeContext } from '@travetto/manifest';
|
|
6
6
|
import { Ignore } from '@travetto/schema';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -18,10 +18,10 @@ export class MainCommand implements CliCommandShape {
|
|
|
18
18
|
// If referenced file exists
|
|
19
19
|
let file = fileOrImport;
|
|
20
20
|
if (await (fs.stat(path.resolve(fileOrImport)).then(() => true, () => false))) {
|
|
21
|
-
file = path.join(
|
|
21
|
+
file = path.join(RuntimeContext.main.name, fileOrImport);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
return
|
|
24
|
+
return RuntimeIndex.getFromImport(file)?.import;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
async validate(fileOrImport: string): Promise<CliValidationError | undefined> {
|
|
@@ -36,9 +36,9 @@ export class MainCommand implements CliCommandShape {
|
|
|
36
36
|
const imp = await this.#getImport(fileOrImport);
|
|
37
37
|
const mod = await import(imp!);
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
ExecUtil.sendResponse(await mod.main(...args, ...this._parsed.unknown));
|
|
40
40
|
} catch (err) {
|
|
41
|
-
|
|
41
|
+
ExecUtil.sendResponse(err, true);
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
}
|
package/support/entry.trv.ts
CHANGED
|
@@ -1,14 +1,2 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
async function entry(): Promise<void> {
|
|
4
|
-
const { init, cleanup } = await import('@travetto/base/support/init.js');
|
|
5
|
-
await init();
|
|
6
|
-
try {
|
|
7
|
-
const { ExecutionManager } = await import('@travetto/cli/src/execute.js');
|
|
8
|
-
await ExecutionManager.run(process.argv);
|
|
9
|
-
} finally {
|
|
10
|
-
await cleanup();
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
entry().then(() => path);
|
|
1
|
+
import { ExecutionManager } from '@travetto/cli';
|
|
2
|
+
ExecutionManager.run(process.argv);
|