@travetto/cli 3.4.10 → 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 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 [GlobalEnv](https://github.com/travetto/travetto/tree/main/module/base/src/global-env.ts#L9) flags defined in the [Base](https://github.com/travetto/travetto/tree/main/module/base#readme "Environment config and common utilities for travetto applications.") module.
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#L22) errors that are returned will be shared with the user, and fail to invoke the `main` method.
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
@@ -1,3 +1,4 @@
1
+ /// <reference path="./src/trv.d.ts" />
1
2
  export * from './src/types';
2
3
  export * from './src/decorators';
3
4
  export * from './src/execute';
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.4.10",
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": "^3.4.4",
33
- "@travetto/terminal": "^3.4.0"
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 { Util } from '@travetto/base';
2
- import { GlobalTerminal } from '@travetto/terminal';
1
+ import { StyleUtil } from '@travetto/terminal';
3
2
 
4
- const tplFn = GlobalTerminal.templateFunction({
5
- input: 'oliveDrab',
6
- output: 'pink',
7
- path: 'teal',
8
- success: 'green',
9
- failure: 'red',
10
- param: ['yellow', 'goldenrod'],
11
- type: 'cyan',
12
- description: ['white', 'gray'],
13
- title: ['brightWhite', 'black'],
14
- identifier: 'dodgerBlue',
15
- subtitle: ['lightGray', 'darkGray'],
16
- subsubtitle: 'darkGray'
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, defineEnv } from '@travetto/base';
2
- import { RootIndex } from '@travetto/manifest';
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 = RootIndex.getFunctionMetadata(target);
18
+ const meta = RuntimeIndex.getFunctionMetadata(target);
19
19
  if (!meta || meta.abstract) {
20
20
  return;
21
21
  }
@@ -25,10 +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
- preMain: async (cmd) => {
28
+ runTarget: cfg.runTarget,
29
+ preMain: async (cmd: CliCommandShape & { env?: string }) => {
29
30
  if (addEnv) {
30
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
31
- defineEnv({ envName: (cmd as { env?: string }).env ?? 'dev' });
31
+ Env.TRV_ENV.set(cmd.env || Env.name);
32
32
  }
33
33
  }
34
34
  });
@@ -37,16 +37,15 @@ export function CliCommand(cfg: CliCommandConfigOptions = {}) {
37
37
 
38
38
  if (addEnv) {
39
39
  SchemaRegistry.registerPendingFieldConfig(target, 'env', String, {
40
- aliases: ['e', CliParseUtil.toEnvField('TRV_ENV')],
40
+ aliases: ['e', CliParseUtil.toEnvField(Env.TRV_ENV.key)],
41
41
  description: 'Application environment',
42
- default: 'dev',
43
42
  required: { active: false }
44
43
  });
45
44
  }
46
45
 
47
46
  if (addModule) {
48
47
  SchemaRegistry.registerPendingFieldConfig(target, 'module', String, {
49
- aliases: ['m', CliParseUtil.toEnvField('TRV_MODULE')],
48
+ aliases: ['m', CliParseUtil.toEnvField(Env.TRV_MODULE.key)],
50
49
  description: 'Module to run for',
51
50
  specifiers: ['module'],
52
51
  required: { active: CliUtil.monoRoot }
@@ -57,12 +56,12 @@ export function CliCommand(cfg: CliCommandConfigOptions = {}) {
57
56
  (pendingCls.validators ??= []).push(async item => {
58
57
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
59
58
  const { module: mod } = item as CliCommandShapeFields;
60
- const runModule = (runtimeModule === 'command' ? commandModule : mod) || RootIndex.mainModuleName;
59
+ const runModule = (runtimeModule === 'command' ? commandModule : mod) || RuntimeContext.main.name;
61
60
 
62
61
  // If we need to run as a specific module
63
- if (runModule !== RootIndex.mainModuleName) {
62
+ if (runModule !== RuntimeContext.main.name) {
64
63
  try {
65
- RootIndex.reinitForModule(runModule);
64
+ RuntimeIndex.reinitForModule(runModule);
66
65
  } catch (err) {
67
66
  return { source: 'flag', message: `${runModule} is an unknown module`, kind: 'custom', path: '.' };
68
67
  }
package/src/error.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { RootIndex } from '@travetto/manifest';
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
- let install: string;
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 { GlobalTerminal } from '@travetto/terminal';
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, RunResponse } from './types';
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,53 +13,49 @@ import { CliUtil } from './util';
14
13
  */
15
14
  export class ExecutionManager {
16
15
 
17
- /**
18
- * Run the given command object with the given arguments
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);
22
19
  args = await CliParseUtil.expandArgs(schema, args);
23
20
  cmd._parsed = await CliParseUtil.parse(schema, args);
24
- const cfg = CliCommandRegistry.getConfig(cmd);
25
21
 
26
22
  await cmd.preBind?.();
27
- const known = await CliCommandSchemaUtil.bindInput(cmd, cmd._parsed);
23
+ try {
24
+ const known = await CliCommandSchemaUtil.bindInput(cmd, cmd._parsed);
28
25
 
29
- await cmd.preValidate?.();
30
- await CliCommandSchemaUtil.validate(cmd, known);
26
+ await cmd.preValidate?.();
27
+ await CliCommandSchemaUtil.validate(cmd, known);
31
28
 
32
- await cfg.preMain?.(cmd);
33
- await cmd.preMain?.();
34
- ConsoleManager.setDebug(GlobalEnv.debug, GlobalEnv.devMode);
35
- return cmd.main(...known);
36
- }
29
+ await cmd._cfg!.preMain?.(cmd);
30
+ await cmd.preMain?.();
37
31
 
38
- /**
39
- * On error, handle response
40
- */
41
- static async #onError(command: CliCommandShape | undefined, err: unknown): Promise<void> {
42
- process.exitCode ||= 1; // Trigger error state
43
- switch (true) {
44
- case !(err instanceof Error): {
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 {
45
39
  throw err;
46
40
  }
47
- case command && err instanceof CliValidationResultError: {
48
- console.error!(await HelpUtil.renderValidationError(command, err));
49
- console.error!(await HelpUtil.renderCommandHelp(command));
50
- break;
51
- }
52
- case err instanceof CliUnknownCommandError: {
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) {
53
50
  if (err.help) {
54
51
  console.error!(err.help);
55
52
  } else {
56
53
  console.error!(err.defaultMessage, '\n');
57
54
  console.error!(await HelpUtil.renderAllHelp(''));
58
55
  }
59
- break;
60
- }
61
- default: {
62
- console.error!(err);
63
- console.error!();
56
+ process.exit(1);
57
+ } else {
58
+ throw err;
64
59
  }
65
60
  }
66
61
  }
@@ -70,9 +65,6 @@ export class ExecutionManager {
70
65
  * @param args
71
66
  */
72
67
  static async run(argv: string[]): Promise<void> {
73
- await GlobalTerminal.init();
74
-
75
- let command: CliCommandShape | undefined;
76
68
  try {
77
69
  const { cmd, args, help } = CliParseUtil.getArgs(argv);
78
70
 
@@ -81,16 +73,22 @@ export class ExecutionManager {
81
73
  return;
82
74
  }
83
75
 
84
- // Load a single command
85
- command = await CliCommandRegistry.getInstance(cmd, true);
76
+ const command = await this.#getCommand(cmd);
77
+
86
78
  if (help) {
87
79
  console.log!(await HelpUtil.renderCommandHelp(command));
80
+ return;
88
81
  } else {
89
- const result = await this.#runCommand(command, args);
82
+ const known = await this.#prepareAndBind(command, args);
83
+ ConsoleManager.debug(Env.debug);
84
+ const result = await command.main(...known);
90
85
  await CliUtil.listenForResponse(result);
91
86
  }
92
87
  } catch (err) {
93
- await this.#onError(command, err);
88
+ console.error!(err);
89
+ console.error!();
90
+ } finally {
91
+ await ShutdownManager.gracefulShutdown(process.exitCode);
94
92
  }
95
93
  }
96
94
  }
package/src/help.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Primitive } from '@travetto/base';
2
- import { stripAnsiCodes } from '@travetto/terminal';
2
+ import { StyleUtil } from '@travetto/terminal';
3
3
 
4
4
  import { cliTpl } from './color';
5
5
  import { CliCommandShape } from './types';
@@ -33,7 +33,7 @@ export class HelpUtil {
33
33
 
34
34
  const usage: string[] = [cliTpl`${{ title: 'Usage:' }} ${{ param: commandName }} ${{ input: '[options]' }}`];
35
35
  for (const field of args) {
36
- const type = field.type === 'string' && field.choices && field.choices.length <= 5 ? field.choices?.join('|') : field.type;
36
+ const type = field.type === 'string' && field.choices && field.choices.length <= 7 ? field.choices?.join('|') : field.type;
37
37
  const arg = `${field.name}${field.array ? '...' : ''}:${type}`;
38
38
  usage.push(cliTpl`${{ input: field.required ? `<${arg}>` : `[${arg}]` }}`);
39
39
  }
@@ -45,7 +45,7 @@ export class HelpUtil {
45
45
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
46
46
  const key = flag.name as keyof CliCommandShape;
47
47
  // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
48
- const flagVal = command[key] as unknown as Exclude<Primitive, Error>;
48
+ const flagVal = command[key] as unknown as Primitive;
49
49
 
50
50
  let aliases = flag.flagNames ?? [];
51
51
  if (isBoolFlag(flag)) {
@@ -69,8 +69,8 @@ export class HelpUtil {
69
69
  descs.push(desc.join(' '));
70
70
  }
71
71
 
72
- const paramWidths = params.map(x => stripAnsiCodes(x).length);
73
- const descWidths = descs.map(x => stripAnsiCodes(x).length);
72
+ const paramWidths = params.map(x => StyleUtil.cleanText(x).length);
73
+ const descWidths = descs.map(x => StyleUtil.cleanText(x).length);
74
74
 
75
75
  const paramWidth = Math.max(...paramWidths);
76
76
  const descWidth = Math.max(...descWidths);
@@ -98,7 +98,7 @@ export class HelpUtil {
98
98
  static async renderAllHelp(title?: string): Promise<string> {
99
99
  const rows: string[] = [];
100
100
  const keys = [...CliCommandRegistry.getCommandMapping().keys()].sort((a, b) => a.localeCompare(b));
101
- const maxWidth = keys.reduce((a, b) => Math.max(a, stripAnsiCodes(b).length), 0);
101
+ const maxWidth = keys.reduce((a, b) => Math.max(a, StyleUtil.cleanText(b).length), 0);
102
102
 
103
103
  for (const cmd of keys) {
104
104
  try {
package/src/module.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { IndexedModule, RootIndex } from '@travetto/manifest';
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 RootIndex.getLocalModules();
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 RootIndex.getDependentModules(mod)) {
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
- [...RootIndex.getModuleList('all')].map(x => RootIndex.getModule(x)!)
50
- ).filter(x => x.sourcePath !== RootIndex.manifest.workspacePath);
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 { RootIndex, path } from '@travetto/manifest';
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];
@@ -85,10 +85,10 @@ export class CliParseUtil {
85
85
 
86
86
  // We have a file
87
87
  const rel = (key.includes('/') ? key : `@/support/pack.${key}.flags`)
88
- .replace('@@/', `${RootIndex.manifest.workspacePath}/`)
88
+ .replace('@@/', `${RuntimeContext.workspace.path}/`)
89
89
  .replace('@/', `${mod}/`)
90
90
  .replace(/^(@[^\/]+\/[^\/]+)(\/.*)$/, (_, imp, rest) => {
91
- const val = RootIndex.getModule(imp);
91
+ const val = RuntimeIndex.getModule(imp);
92
92
  if (!val) {
93
93
  throw new Error(`Unknown module file: ${_}, unable to proceed`);
94
94
  }
@@ -144,9 +144,10 @@ export class CliParseUtil {
144
144
  const mod = args.reduce(
145
145
  (m, x, i, arr) =>
146
146
  (i < SEP ? check(arr[i - 1], x) ?? check(...x.split('=')) : undefined) ?? m,
147
- process.env[ENV_KEY] || RootIndex.mainModuleName
147
+ process.env[ENV_KEY] || RuntimeContext.main.name
148
148
  );
149
- return (await Promise.all(args.map((x, i) => x.startsWith(CONFIG_PRE) && (i < SEP || SEP < 0) ? this.readFlagFile(x, mod) : x))).flat();
149
+ return (await Promise.all(args.map((x, i) =>
150
+ x.startsWith(CONFIG_PRE) && (i < SEP || SEP < 0) ? this.readFlagFile(x, mod) : x))).flat();
150
151
  }
151
152
 
152
153
  /**
package/src/registry.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { Class, ConcreteClass, GlobalEnv } from '@travetto/base';
2
- import { RootIndex } from '@travetto/manifest';
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 RootIndex.find({
48
- module: m => GlobalEnv.devMode || m.prod,
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 = RootIndex.getFunctionMetadata(cls)!.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: RootIndex.getModuleFromSource(source)!.name,
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,4 +1,4 @@
1
- import { Class, ConsoleManager, GlobalEnv } from '@travetto/base';
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';
@@ -59,16 +59,11 @@ export class CliCommandSchemaUtil {
59
59
  }
60
60
 
61
61
  // Ensure finalized
62
- try {
63
- ConsoleManager.setDebug(false);
64
- const parent = SchemaRegistry.getParentClass(cls);
65
- if (parent?.Ⲑid) {
66
- SchemaRegistry.onInstall(parent, { type: 'added', curr: parent });
67
- }
68
- SchemaRegistry.onInstall(cls, { type: 'added', curr: cls });
69
- } finally {
70
- ConsoleManager.setDebug(GlobalEnv.debug, GlobalEnv.devMode);
62
+ const parent = SchemaRegistry.getParentClass(cls);
63
+ if (parent?.Ⲑid) {
64
+ SchemaRegistry.onInstall(parent, { type: 'added', curr: parent });
71
65
  }
66
+ SchemaRegistry.onInstall(cls, { type: 'added', curr: cls });
72
67
 
73
68
  const schema = await SchemaRegistry.getViewSchema(cls);
74
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 { Env, ExecUtil } from '@travetto/base';
4
- import { IndexedFile, IndexedModule, RootIndex, path } from '@travetto/manifest';
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() : '') || Env.get('USER'),
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 root = await RootIndex.manifest;
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 = RootIndex.manifest.workspacePath;
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 = RootIndex.getEntry(path.resolve(ws, line));
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 => RootIndex.getFromSource(x))
69
+ .map(x => RuntimeIndex.getFromSource(x))
71
70
  .filter((x): x is IndexedFile => !!x)
72
- .map(x => RootIndex.getModule(x.module))
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 { Closeable } from '@travetto/base';
1
+ import { ConcreteClass } from '@travetto/base';
2
2
 
3
3
  type OrProm<T> = T | Promise<T>;
4
4
 
5
- export type RunResponse = { wait(): Promise<unknown> } | { on(event: 'close', cb: Function): unknown } | Closeable | void | undefined;
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
  */
package/src/util.ts CHANGED
@@ -1,15 +1,18 @@
1
- import { Env, ExecUtil, GlobalEnv, ShutdownManager } from '@travetto/base';
2
- import { RootIndex, path } from '@travetto/manifest';
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
- import { CliCommandRegistry } from './registry';
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 !!RootIndex.manifest.monoRepo && RootIndex.mainModule.sourcePath === RootIndex.manifest.workspacePath;
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 ?? RootIndex.mainModuleName).replace(/[\/]/, '_').replace(/@/, '');
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
- const canRestart = cmd.canRestart ??= GlobalEnv.devMode;
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: { TRV_DYNAMIC: '1', TRV_CAN_RESTART: '0' },
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
- const ipcUrl = process.env.TRV_CLI_IPC;
51
-
52
- if (!ipcUrl) {
54
+ if (!Env.TRV_CLI_IPC.isSet) {
53
55
  return false;
54
56
  }
55
57
 
56
- const info = await fetch(ipcUrl).catch(() => ({ ok: false }));
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 cfg = CliCommandRegistry.getConfig(cmd);
64
+ const env: Record<string, string> = {};
63
65
  const req = {
64
66
  type: `@travetto/cli:${action}`,
65
67
  data: {
66
- name: cfg.name,
67
- commandModule: cfg.commandModule,
68
- module: RootIndex.manifest.mainModule,
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
- const defaultEnvKeys = new Set(['PS1', 'INIT_CWD', 'COLOR', 'LANGUAGE', 'PROFILEHOME', '_']);
76
- const env = Object.fromEntries(
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
- const canDebug = cmd.debugIpc ??= GlobalEnv.devMode;
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.onShutdown(result, result); // Tie shutdown into app close
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) {
@@ -1,8 +1,8 @@
1
- import fs from 'fs/promises';
1
+ import fs from 'node:fs/promises';
2
2
 
3
- import { ShutdownManager } from '@travetto/base';
3
+ import { ExecUtil } from '@travetto/base';
4
4
  import { CliCommandShape, CliCommand, CliValidationError, ParsedState } from '@travetto/cli';
5
- import { path, RootIndex } from '@travetto/manifest';
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(RootIndex.manifest.mainModule, fileOrImport);
21
+ file = path.join(RuntimeContext.main.name, fileOrImport);
22
22
  }
23
23
 
24
- return RootIndex.getFromImport(file)?.import;
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
- await ShutdownManager.exitWithResponse(await mod.main(...args, ...this._parsed.unknown));
39
+ ExecUtil.sendResponse(await mod.main(...args, ...this._parsed.unknown));
40
40
  } catch (err) {
41
- await ShutdownManager.exitWithResponse(err, true);
41
+ ExecUtil.sendResponse(err, true);
42
42
  }
43
43
  }
44
44
  }
@@ -1,14 +1,2 @@
1
- import path from 'path';
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);