@travetto/cli 6.0.1 → 7.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 +31 -33
- package/__index__.ts +4 -2
- package/package.json +3 -3
- package/src/error.ts +1 -1
- package/src/execute.ts +3 -3
- package/src/help.ts +53 -48
- package/src/module.ts +20 -0
- package/src/parse.ts +64 -43
- package/src/registry/decorator.ts +153 -0
- package/src/registry/registry-adapter.ts +96 -0
- package/src/registry/registry-index.ts +124 -0
- package/src/schema-export.ts +90 -0
- package/src/schema.ts +8 -111
- package/src/types.ts +4 -36
- package/src/util.ts +5 -3
- package/support/cli.cli_schema.ts +25 -22
- package/support/cli.main.ts +3 -2
- package/src/decorators.ts +0 -154
- package/src/registry.ts +0 -96
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
import { Env } from '@travetto/runtime';
|
|
2
|
+
import { IsPrivate } from '@travetto/schema';
|
|
2
3
|
|
|
3
|
-
import { CliCommand } from '../src/
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { CliCommandSchemaUtil } from '../src/schema.ts';
|
|
4
|
+
import { CliCommand } from '../src/registry/decorator.ts';
|
|
5
|
+
import { CliCommandShape, CliValidationError } from '../src/types.ts';
|
|
6
|
+
import { CliCommandRegistryIndex } from '../src/registry/registry-index.ts';
|
|
7
7
|
import { CliUtil } from '../src/util.ts';
|
|
8
|
+
import { CliSchemaExportUtil } from '../src/schema-export.ts';
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Generates the schema for all CLI operations
|
|
11
13
|
*/
|
|
12
|
-
@CliCommand(
|
|
14
|
+
@CliCommand()
|
|
15
|
+
@IsPrivate()
|
|
13
16
|
export class CliSchemaCommand implements CliCommandShape {
|
|
14
17
|
|
|
15
|
-
async
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
async validate(names?: string[]): Promise<CliValidationError | undefined> {
|
|
19
|
+
if (!names || names.length === 0) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const resolved = await CliCommandRegistryIndex.load(names);
|
|
23
|
+
const invalid = names.find(x => !resolved.find(r => r.command === x));
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
message: `name: ${name} is not a valid cli command`
|
|
26
|
-
};
|
|
27
|
-
}
|
|
25
|
+
if (invalid) {
|
|
26
|
+
return {
|
|
27
|
+
source: 'arg',
|
|
28
|
+
message: `name: ${invalid} is not a valid cli command`
|
|
29
|
+
};
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -33,10 +35,11 @@ export class CliSchemaCommand implements CliCommandShape {
|
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
async main(names?: string[]): Promise<void> {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
const resolved = await CliCommandRegistryIndex.load(names);
|
|
39
|
+
|
|
40
|
+
const output = resolved
|
|
41
|
+
.map(x => CliSchemaExportUtil.exportSchema(x.config.cls));
|
|
42
|
+
|
|
43
|
+
await CliUtil.writeAndEnsureComplete(output);
|
|
41
44
|
}
|
|
42
45
|
}
|
package/support/cli.main.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Runtime } from '@travetto/runtime';
|
|
2
2
|
import { CliCommandShape, CliCommand, CliValidationError, ParsedState } from '@travetto/cli';
|
|
3
|
-
import { Ignore } from '@travetto/schema';
|
|
3
|
+
import { Ignore, IsPrivate } from '@travetto/schema';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Allows for running of main entry points
|
|
7
7
|
*/
|
|
8
|
-
@CliCommand(
|
|
8
|
+
@CliCommand()
|
|
9
|
+
@IsPrivate()
|
|
9
10
|
export class MainCommand implements CliCommandShape {
|
|
10
11
|
|
|
11
12
|
@Ignore()
|
package/src/decorators.ts
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
import { Class, ClassInstance, Env, Runtime, RuntimeIndex, describeFunction } from '@travetto/runtime';
|
|
2
|
-
import { FieldConfig, SchemaRegistry } from '@travetto/schema';
|
|
3
|
-
|
|
4
|
-
import { CliCommandShape, CliCommandShapeFields } from './types.ts';
|
|
5
|
-
import { CliCommandRegistry } from './registry.ts';
|
|
6
|
-
import { CliModuleUtil } from './module.ts';
|
|
7
|
-
import { CliParseUtil } from './parse.ts';
|
|
8
|
-
import { CliUtil } from './util.ts';
|
|
9
|
-
|
|
10
|
-
type Cmd = CliCommandShape & { env?: string };
|
|
11
|
-
|
|
12
|
-
type CliCommandConfigOptions = {
|
|
13
|
-
hidden?: boolean;
|
|
14
|
-
runTarget?: boolean;
|
|
15
|
-
runtimeModule?: 'current' | 'command';
|
|
16
|
-
with?: {
|
|
17
|
-
/** Application environment */
|
|
18
|
-
env?: boolean;
|
|
19
|
-
/** Module to run for */
|
|
20
|
-
module?: boolean;
|
|
21
|
-
/** Should debug invocation trigger via ipc */
|
|
22
|
-
debugIpc?: boolean;
|
|
23
|
-
/** Should the invocation automatically restart on exit */
|
|
24
|
-
canRestart?: boolean;
|
|
25
|
-
};
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const FIELD_CONFIG: {
|
|
29
|
-
name: keyof Exclude<CliCommandConfigOptions['with'], undefined>;
|
|
30
|
-
field: Partial<FieldConfig>;
|
|
31
|
-
run: (cmd: Cmd) => (Promise<unknown> | unknown);
|
|
32
|
-
}[] =
|
|
33
|
-
[
|
|
34
|
-
{
|
|
35
|
-
name: 'env',
|
|
36
|
-
run: cmd => Env.TRV_ENV.set(cmd.env || Runtime.env),
|
|
37
|
-
field: {
|
|
38
|
-
type: String,
|
|
39
|
-
aliases: ['e', CliParseUtil.toEnvField(Env.TRV_ENV.key)],
|
|
40
|
-
description: 'Application environment',
|
|
41
|
-
required: { active: false },
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
name: 'module',
|
|
46
|
-
run: (): void => { },
|
|
47
|
-
field: {
|
|
48
|
-
type: String,
|
|
49
|
-
aliases: ['m', CliParseUtil.toEnvField(Env.TRV_MODULE.key)],
|
|
50
|
-
description: 'Module to run for',
|
|
51
|
-
specifiers: ['module'],
|
|
52
|
-
required: { active: Runtime.monoRoot },
|
|
53
|
-
},
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
name: 'debugIpc',
|
|
57
|
-
run: cmd => CliUtil.debugIfIpc(cmd).then((v) => v && process.exit(0)),
|
|
58
|
-
field: {
|
|
59
|
-
type: Boolean,
|
|
60
|
-
aliases: ['di'],
|
|
61
|
-
description: 'Should debug invocation trigger via ipc',
|
|
62
|
-
default: true,
|
|
63
|
-
required: { active: false },
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
name: 'canRestart',
|
|
68
|
-
run: cmd => CliUtil.runWithRestart(cmd)?.then((v) => v && process.exit(0)),
|
|
69
|
-
field: {
|
|
70
|
-
type: Boolean,
|
|
71
|
-
aliases: ['cr'],
|
|
72
|
-
description: 'Should the invocation automatically restart on exit',
|
|
73
|
-
default: false,
|
|
74
|
-
required: { active: false },
|
|
75
|
-
},
|
|
76
|
-
}
|
|
77
|
-
];
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Decorator to register a CLI command
|
|
81
|
-
* @augments `@travetto/schema:Schema`
|
|
82
|
-
* @augments `@travetto/cli:CliCommand`
|
|
83
|
-
* @schemaMethods `main`
|
|
84
|
-
*/
|
|
85
|
-
export function CliCommand(cfg: CliCommandConfigOptions = {}) {
|
|
86
|
-
return function <T extends CliCommandShape>(target: Class<T>): void {
|
|
87
|
-
if (!target.Ⲑid || describeFunction(target)?.abstract) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const VALID_FIELDS = FIELD_CONFIG.filter(f => !!cfg.with?.[f.name]);
|
|
92
|
-
|
|
93
|
-
const { commandModule } = CliCommandRegistry.registerClass(target, {
|
|
94
|
-
hidden: cfg.hidden,
|
|
95
|
-
runTarget: cfg.runTarget,
|
|
96
|
-
preMain: async (cmd: Cmd) => {
|
|
97
|
-
for (const field of VALID_FIELDS) {
|
|
98
|
-
await field.run(cmd);
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
const pendingCls = SchemaRegistry.getOrCreatePending(target);
|
|
104
|
-
|
|
105
|
-
for (const { name, field: { type, ...field } } of VALID_FIELDS) {
|
|
106
|
-
SchemaRegistry.registerPendingFieldConfig(target, name, type!, field);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const runtimeModule = cfg.runtimeModule ?? (cfg.with?.module ? 'current' : undefined);
|
|
110
|
-
|
|
111
|
-
if (runtimeModule) { // Validate module
|
|
112
|
-
(pendingCls.validators ??= []).push(async ({ module: mod }: Partial<CliCommandShapeFields>) => {
|
|
113
|
-
const runModule = (runtimeModule === 'command' ? commandModule : mod) || Runtime.main.name;
|
|
114
|
-
|
|
115
|
-
// If we need to run as a specific module
|
|
116
|
-
if (runModule !== Runtime.main.name) {
|
|
117
|
-
try {
|
|
118
|
-
RuntimeIndex.reinitForModule(runModule);
|
|
119
|
-
} catch {
|
|
120
|
-
return { source: 'flag', message: `${runModule} is an unknown module`, kind: 'custom', path: '.' };
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (!(await CliModuleUtil.moduleHasDependency(runModule, commandModule))) {
|
|
125
|
-
return { source: 'flag', message: `${runModule} does not have ${commandModule} as a dependency`, kind: 'custom', path: '.' };
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
};
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Decorator to register a CLI command flag
|
|
134
|
-
*/
|
|
135
|
-
export function CliFlag(cfg: { name?: string, short?: string, desc?: string, fileExtensions?: string[], envVars?: string[] }) {
|
|
136
|
-
return function (target: ClassInstance, prop: string | symbol): void {
|
|
137
|
-
const aliases: string[] = [];
|
|
138
|
-
if (cfg.name) {
|
|
139
|
-
aliases.push(cfg.name.startsWith('-') ? cfg.name : `--${cfg.name}`);
|
|
140
|
-
}
|
|
141
|
-
if (cfg.short) {
|
|
142
|
-
aliases.push(cfg.short.startsWith('-') ? cfg.short : `-${cfg.short}`);
|
|
143
|
-
}
|
|
144
|
-
if (cfg.envVars) {
|
|
145
|
-
aliases.push(...cfg.envVars.map(CliParseUtil.toEnvField));
|
|
146
|
-
}
|
|
147
|
-
if (typeof prop === 'string') {
|
|
148
|
-
SchemaRegistry.registerPendingFieldFacet(target.constructor, prop, {
|
|
149
|
-
aliases, description: cfg.desc,
|
|
150
|
-
specifiers: cfg.fileExtensions?.length ? ['file', ...cfg.fileExtensions.map(x => `ext:${x.replace(/[*.]/g, '')}`)] : undefined
|
|
151
|
-
});
|
|
152
|
-
}
|
|
153
|
-
};
|
|
154
|
-
}
|
package/src/registry.ts
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { asConstructable, Class, classConstruct, describeFunction, Runtime, RuntimeIndex } from '@travetto/runtime';
|
|
2
|
-
|
|
3
|
-
import { CliCommandConfig, CliCommandShape } from './types.ts';
|
|
4
|
-
import { CliUnknownCommandError } from './error.ts';
|
|
5
|
-
|
|
6
|
-
const CLI_FILE_REGEX = /\/cli[.](?<name>.{0,100}?)([.]tsx?)?$/;
|
|
7
|
-
const getName = (s: string): string => (s.match(CLI_FILE_REGEX)?.groups?.name ?? s).replaceAll('_', ':');
|
|
8
|
-
|
|
9
|
-
class $CliCommandRegistry {
|
|
10
|
-
#commands = new Map<Class, CliCommandConfig>();
|
|
11
|
-
#fileMapping: Map<string, string>;
|
|
12
|
-
|
|
13
|
-
getByClass(cls: Class): CliCommandConfig | undefined {
|
|
14
|
-
return this.#commands.get(cls);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
getClass(cmd: CliCommandShape): Class<CliCommandShape> {
|
|
18
|
-
return asConstructable<CliCommandShape>(cmd).constructor;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Get list of all commands available
|
|
23
|
-
*/
|
|
24
|
-
getCommandMapping(): Map<string, string> {
|
|
25
|
-
if (!this.#fileMapping) {
|
|
26
|
-
const all = new Map<string, string>();
|
|
27
|
-
for (const e of RuntimeIndex.find({
|
|
28
|
-
module: m => !Runtime.production || m.prod,
|
|
29
|
-
folder: f => f === 'support',
|
|
30
|
-
file: f => f.role === 'std' && CLI_FILE_REGEX.test(f.sourceFile)
|
|
31
|
-
})) {
|
|
32
|
-
all.set(getName(e.sourceFile), e.import);
|
|
33
|
-
}
|
|
34
|
-
this.#fileMapping = all;
|
|
35
|
-
}
|
|
36
|
-
return this.#fileMapping;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Registers a cli command
|
|
41
|
-
*/
|
|
42
|
-
registerClass<T extends CliCommandShape>(cls: Class<T>, cfg: Partial<CliCommandConfig>): CliCommandConfig {
|
|
43
|
-
const meta = describeFunction(cls);
|
|
44
|
-
this.#commands.set(cls, {
|
|
45
|
-
cls,
|
|
46
|
-
name: getName(meta.import),
|
|
47
|
-
commandModule: meta.module,
|
|
48
|
-
...cfg,
|
|
49
|
-
});
|
|
50
|
-
return this.#commands.get(cls)!;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Get config for a given instance
|
|
55
|
-
*/
|
|
56
|
-
getConfig(cmd: CliCommandShape): CliCommandConfig {
|
|
57
|
-
return this.getByClass(this.getClass(cmd))!;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Get the name of a command from a given instance
|
|
62
|
-
*/
|
|
63
|
-
getName(cmd: CliCommandShape, withModule = false): string | undefined {
|
|
64
|
-
const cfg = this.getConfig(cmd);
|
|
65
|
-
const prefix = withModule ? `${cfg.commandModule}:` : '';
|
|
66
|
-
return `${prefix}${cfg.name}`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Import command into an instance
|
|
71
|
-
*/
|
|
72
|
-
getInstance(name: string, failOnMissing: true): Promise<CliCommandShape>;
|
|
73
|
-
async getInstance(name: string): Promise<CliCommandShape | undefined>;
|
|
74
|
-
async getInstance(name: string, failOnMissing = false): Promise<CliCommandShape | undefined> {
|
|
75
|
-
const found = this.getCommandMapping().get(name);
|
|
76
|
-
if (found) {
|
|
77
|
-
const values = Object.values(await Runtime.importFrom<Record<string, Class>>(found));
|
|
78
|
-
for (const v of values) {
|
|
79
|
-
const cfg = this.getByClass(v);
|
|
80
|
-
if (cfg) {
|
|
81
|
-
const inst = classConstruct(cfg.cls);
|
|
82
|
-
if (!inst.isActive || inst.isActive()) {
|
|
83
|
-
inst._cfg = this.getConfig(inst);
|
|
84
|
-
return inst;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (!failOnMissing) {
|
|
89
|
-
return undefined;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
throw new CliUnknownCommandError(name);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
export const CliCommandRegistry = new $CliCommandRegistry();
|