@travetto/cli 6.0.0 → 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/src/types.ts CHANGED
@@ -6,18 +6,6 @@ type ParsedArg = { type: 'arg', input: string, array?: boolean, index: number };
6
6
  type ParsedUnknown = { type: 'unknown', input: string };
7
7
  type ParsedInput = ParsedUnknown | ParsedFlag | ParsedArg;
8
8
 
9
- /**
10
- * Command configuration
11
- */
12
- export type CliCommandConfig = {
13
- name: string;
14
- commandModule: string;
15
- runTarget?: boolean;
16
- cls: Class<CliCommandShape>;
17
- hidden?: boolean;
18
- preMain?: (cmd: CliCommandShape) => void | Promise<void>;
19
- };
20
-
21
9
  export type ParsedState = {
22
10
  inputs: string[];
23
11
  all: ParsedInput[];
@@ -45,7 +33,6 @@ export interface CliValidationError {
45
33
  * @concrete
46
34
  */
47
35
  export interface CliCommandShape<T extends unknown[] = unknown[]> {
48
-
49
36
  /**
50
37
  * Parsed state
51
38
  */
@@ -110,31 +97,12 @@ export type CliCommandShapeFields = {
110
97
  module?: string;
111
98
  };
112
99
 
113
- /**
114
- * CLI Command argument/flag shape
115
- */
116
- export type CliCommandInput<K extends string = string> = {
117
- name: string;
118
- description?: string;
119
- type: 'string' | 'file' | 'number' | 'boolean' | 'date' | 'regex' | 'module';
120
- fileExtensions?: string[];
121
- choices?: unknown[];
122
- required?: boolean;
123
- array?: boolean;
124
- default?: unknown;
125
- flagNames?: K[];
126
- envVars?: string[];
127
- };
128
-
129
100
  /**
130
101
  * CLI Command schema shape
131
102
  */
132
- export interface CliCommandSchema<K extends string = string> {
103
+ export interface CliCommandConfig {
104
+ cls: Class<CliCommandShape>;
133
105
  name: string;
134
- title: string;
135
- commandModule: string;
136
106
  runTarget?: boolean;
137
- description?: string;
138
- args: CliCommandInput[];
139
- flags: CliCommandInput<K>[];
140
- }
107
+ preMain?: (cmd: CliCommandShape) => void | Promise<void>;
108
+ }
package/src/util.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { spawn } from 'node:child_process';
2
2
 
3
- import { Env, ExecUtil, Runtime } from '@travetto/runtime';
3
+ import { describeFunction, Env, ExecUtil, Runtime } from '@travetto/runtime';
4
4
 
5
5
  import { CliCommandShape, CliCommandShapeFields } from './types.ts';
6
6
 
@@ -61,8 +61,10 @@ export class CliUtil {
61
61
  const req = {
62
62
  type: `@travetto/cli:${action}`,
63
63
  data: {
64
- name: cmd._cfg!.name, env,
65
- commandModule: cmd._cfg!.commandModule,
64
+ name: cmd._cfg!.name,
65
+ env,
66
+ // TODO: Is this needed?
67
+ commandModule: describeFunction(cmd.constructor).module,
66
68
  module: Runtime.main.name,
67
69
  args: process.argv.slice(3),
68
70
  }
@@ -1,30 +1,32 @@
1
1
  import { Env } from '@travetto/runtime';
2
+ import { IsPrivate } from '@travetto/schema';
2
3
 
3
- import { CliCommand } from '../src/decorators.ts';
4
- import { CliCommandSchema, CliCommandShape, CliValidationError } from '../src/types.ts';
5
- import { CliCommandRegistry } from '../src/registry.ts';
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({ hidden: true })
14
+ @CliCommand()
15
+ @IsPrivate()
13
16
  export class CliSchemaCommand implements CliCommandShape {
14
17
 
15
- async #getSchema(name: string): Promise<CliCommandSchema> {
16
- const inst = await CliCommandRegistry.getInstance(name);
17
- return CliCommandSchemaUtil.getSchema(inst!);
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
- async validate(names: string[]): Promise<CliValidationError | undefined> {
21
- for (const name of names ?? []) {
22
- if (name && !CliCommandRegistry.getCommandMapping().has(name)) {
23
- return {
24
- source: 'arg',
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
- if (!names?.length) {
37
- names = [...CliCommandRegistry.getCommandMapping().keys()];
38
- }
39
- const resolved = await Promise.all(names.map(x => this.#getSchema(x)));
40
- await CliUtil.writeAndEnsureComplete(resolved);
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
  }
@@ -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({ hidden: true })
8
+ @CliCommand()
9
+ @IsPrivate()
9
10
  export class MainCommand implements CliCommandShape {
10
11
 
11
12
  @Ignore()
package/src/decorators.ts DELETED
@@ -1,153 +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
- */
84
- export function CliCommand(cfg: CliCommandConfigOptions = {}) {
85
- return function <T extends CliCommandShape>(target: Class<T>): void {
86
- if (!target.Ⲑid || describeFunction(target)?.abstract) {
87
- return;
88
- }
89
-
90
- const VALID_FIELDS = FIELD_CONFIG.filter(f => !!cfg.with?.[f.name]);
91
-
92
- const { commandModule } = CliCommandRegistry.registerClass(target, {
93
- hidden: cfg.hidden,
94
- runTarget: cfg.runTarget,
95
- preMain: async (cmd: Cmd) => {
96
- for (const field of VALID_FIELDS) {
97
- await field.run(cmd);
98
- }
99
- }
100
- });
101
-
102
- const pendingCls = SchemaRegistry.getOrCreatePending(target);
103
-
104
- for (const { name, field: { type, ...field } } of VALID_FIELDS) {
105
- SchemaRegistry.registerPendingFieldConfig(target, name, type!, field);
106
- }
107
-
108
- const runtimeModule = cfg.runtimeModule ?? (cfg.with?.module ? 'current' : undefined);
109
-
110
- if (runtimeModule) { // Validate module
111
- (pendingCls.validators ??= []).push(async ({ module: mod }: Partial<CliCommandShapeFields>) => {
112
- const runModule = (runtimeModule === 'command' ? commandModule : mod) || Runtime.main.name;
113
-
114
- // If we need to run as a specific module
115
- if (runModule !== Runtime.main.name) {
116
- try {
117
- RuntimeIndex.reinitForModule(runModule);
118
- } catch {
119
- return { source: 'flag', message: `${runModule} is an unknown module`, kind: 'custom', path: '.' };
120
- }
121
- }
122
-
123
- if (!(await CliModuleUtil.moduleHasDependency(runModule, commandModule))) {
124
- return { source: 'flag', message: `${runModule} does not have ${commandModule} as a dependency`, kind: 'custom', path: '.' };
125
- }
126
- });
127
- }
128
- };
129
- }
130
-
131
- /**
132
- * Decorator to register a CLI command flag
133
- */
134
- export function CliFlag(cfg: { name?: string, short?: string, desc?: string, fileExtensions?: string[], envVars?: string[] }) {
135
- return function (target: ClassInstance, prop: string | symbol): void {
136
- const aliases: string[] = [];
137
- if (cfg.name) {
138
- aliases.push(cfg.name.startsWith('-') ? cfg.name : `--${cfg.name}`);
139
- }
140
- if (cfg.short) {
141
- aliases.push(cfg.short.startsWith('-') ? cfg.short : `-${cfg.short}`);
142
- }
143
- if (cfg.envVars) {
144
- aliases.push(...cfg.envVars.map(CliParseUtil.toEnvField));
145
- }
146
- if (typeof prop === 'string') {
147
- SchemaRegistry.registerPendingFieldFacet(target.constructor, prop, {
148
- aliases, description: cfg.desc,
149
- specifiers: cfg.fileExtensions?.length ? ['file', ...cfg.fileExtensions.map(x => `ext:${x.replace(/[*.]/g, '')}`)] : undefined
150
- });
151
- }
152
- };
153
- }
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();
@@ -1,53 +0,0 @@
1
- import ts from 'typescript';
2
-
3
- import { TransformerState, DecoratorMeta, AfterClass } from '@travetto/transformer';
4
- import { SchemaTransformUtil } from '@travetto/schema/support/transformer/util.ts';
5
-
6
- /**
7
- * Converts classes with `@CliCommand` to `@Schema` and maps the main method
8
- */
9
- export class CliCommandTransformer {
10
-
11
- /**
12
- * On presence of `@CliCommand`
13
- */
14
- @AfterClass('CliCommand')
15
- static registerMainMethod(state: TransformerState, node: ts.ClassDeclaration, dm?: DecoratorMeta): typeof node {
16
- const dec = dm?.dec;
17
-
18
- if (!dec || !ts.isCallExpression(dec.expression)) { // If not valid
19
- return node;
20
- }
21
-
22
- // Find runnable method
23
- const mainMethod = node.members
24
- .find((x): x is ts.MethodDeclaration =>
25
- ts.isMethodDeclaration(x) && x.name!.getText() === 'main'
26
- );
27
-
28
- if (!mainMethod) {
29
- return node;
30
- }
31
-
32
- const members = node.members.map(x => ts.isMethodDeclaration(x) && x === mainMethod ?
33
- state.factory.updateMethodDeclaration(
34
- x,
35
- x.modifiers,
36
- x.asteriskToken,
37
- x.name,
38
- x.questionToken,
39
- x.typeParameters,
40
- x.parameters.map(y => SchemaTransformUtil.computeField(state, y)),
41
- x.type,
42
- x.body
43
- ) : x);
44
-
45
- return state.factory.updateClassDeclaration(node,
46
- node.modifiers,
47
- node.name,
48
- node.typeParameters,
49
- node.heritageClauses,
50
- members
51
- );
52
- }
53
- }