@travetto/cli 8.0.0-alpha.9 → 8.0.1

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/service.ts CHANGED
@@ -1,15 +1,12 @@
1
1
  import { spawn } from 'node:child_process';
2
2
  import fs from 'node:fs/promises';
3
- import rl from 'node:readline/promises';
4
3
  import net from 'node:net';
4
+ import rl from 'node:readline/promises';
5
5
 
6
6
  import { ExecUtil, Runtime, RuntimeIndex, TimeUtil, Util } from '@travetto/runtime';
7
7
 
8
8
  const ports = (value: number | `${number}:${number}`): [number, number] =>
9
- typeof value === 'number' ?
10
- [value, value] :
11
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
12
- value.split(':').map(number => parseInt(number, 10)) as [number, number];
9
+ typeof value === 'number' ? [value, value] : (value.split(':').map(number => parseInt(number, 10)) as [number, number]);
13
10
 
14
11
  type BodyCheck = (body: string) => boolean;
15
12
 
@@ -23,7 +20,7 @@ export interface ServiceDescriptor {
23
20
  privileged?: boolean;
24
21
  image: string;
25
22
  args?: string[];
26
- ready?: { url: string, test?: BodyCheck };
23
+ ready?: { url: string; test?: BodyCheck };
27
24
  volumes?: Record<string, string>;
28
25
  env?: Record<string, string>;
29
26
  startupTimeout?: number;
@@ -35,37 +32,39 @@ export type ServiceAction = 'start' | 'stop' | 'status' | 'restart';
35
32
  * Service runner
36
33
  */
37
34
  export class ServiceRunner {
38
-
39
35
  /**
40
36
  * Find all services
41
37
  */
42
38
  static async findServices(services: string[]): Promise<ServiceDescriptor[]> {
43
- return (await Promise.all(
44
- RuntimeIndex.find({
45
- module: module => module.roles.includes('std'),
46
- folder: folder => folder === 'support',
47
- file: file => /support\/service[.]/.test(file.sourceFile)
48
- })
49
- .map(file => Runtime.importFrom<{ service: ServiceDescriptor }>(file.import).then(value => value.service))
50
- ))
39
+ return (
40
+ await Promise.all(
41
+ RuntimeIndex.find({
42
+ module: module => module.roles.includes('std'),
43
+ folder: folder => folder === 'support',
44
+ file: file => /support\/service[.]/.test(file.sourceFile)
45
+ }).map(file => Runtime.importFrom<{ service: ServiceDescriptor }>(file.import).then(value => value.service))
46
+ )
47
+ )
51
48
  .filter(file => !!file)
52
- .filter(file => services?.length ? services.includes(file.name) : true)
49
+ .filter(file => (services?.length ? services.includes(file.name) : true))
53
50
  .toSorted((a, b) => a.name.localeCompare(b.name));
54
51
  }
55
52
 
56
53
  #descriptor: ServiceDescriptor;
57
- constructor(descriptor: ServiceDescriptor) { this.#descriptor = descriptor; }
54
+ constructor(descriptor: ServiceDescriptor) {
55
+ this.#descriptor = descriptor;
56
+ }
58
57
 
59
58
  async #isRunning(full = false): Promise<boolean> {
60
59
  const port = ports(this.#descriptor.port!)[0];
61
60
  const start = Date.now();
62
- const timeoutMs = TimeUtil.duration(full ? this.#descriptor.startupTimeout ?? 5000 : 100, 'ms');
63
- while ((Date.now() - start) < timeoutMs) {
61
+ const timeoutMs = TimeUtil.duration(full ? (this.#descriptor.startupTimeout ?? 5000) : 100, 'ms');
62
+ while (Date.now() - start < timeoutMs) {
64
63
  try {
65
64
  const sock = net.createConnection(port, 'localhost');
66
- await new Promise<void>((resolve, reject) =>
67
- sock.on('connect', resolve).on('timeout', reject).on('error', reject)
68
- ).finally(() => sock.destroy());
65
+ await new Promise<void>((resolve, reject) => sock.on('connect', resolve).on('timeout', reject).on('error', reject)).finally(() =>
66
+ sock.destroy()
67
+ );
69
68
 
70
69
  if (!this.#descriptor.ready?.url || !full) {
71
70
  return true;
@@ -89,7 +88,7 @@ export class ServiceRunner {
89
88
  return result.valid;
90
89
  }
91
90
 
92
- async * #pullImage(): AsyncIterable<string> {
91
+ async *#pullImage(): AsyncIterable<string> {
93
92
  const subProcess = spawn('docker', ['pull', this.#descriptor.image], { stdio: [0, 'pipe', 'pipe'] });
94
93
  yield* rl.createInterface(subProcess.stdout!);
95
94
  await ExecUtil.getResult(subProcess);
@@ -100,13 +99,14 @@ export class ServiceRunner {
100
99
  'run',
101
100
  '--rm',
102
101
  '--detach',
103
- ...this.#descriptor.privileged ? ['--privileged'] : [],
104
- '--label', `trv-${this.#descriptor.name}`,
102
+ ...(this.#descriptor.privileged ? ['--privileged'] : []),
103
+ '--label',
104
+ `trv-${this.#descriptor.name}`,
105
105
  ...Object.entries(this.#descriptor.env ?? {}).flatMap(([key, value]) => ['--env', `${key}=${value}`]),
106
- ...this.#descriptor.port ? ['-p', ports(this.#descriptor.port).join(':')] : [],
106
+ ...(this.#descriptor.port ? ['-p', ports(this.#descriptor.port).join(':')] : []),
107
107
  ...Object.entries(this.#descriptor.volumes ?? {}).flatMap(([key, value]) => ['--volume', `${key}:${value}`]),
108
108
  this.#descriptor.image,
109
- ...this.#descriptor.args ?? [],
109
+ ...(this.#descriptor.args ?? [])
110
110
  ];
111
111
 
112
112
  for (const item of Object.keys(this.#descriptor.volumes ?? {})) {
@@ -124,13 +124,14 @@ export class ServiceRunner {
124
124
  await ExecUtil.getResult(spawn('docker', ['kill', containerId]));
125
125
  }
126
126
 
127
- async * action(operation: ServiceAction): AsyncIterable<['success' | 'failure' | 'message', string]> {
127
+ async *action(operation: ServiceAction): AsyncIterable<['success' | 'failure' | 'message', string]> {
128
128
  try {
129
129
  const containerId = await this.#getContainerId();
130
130
  const port = this.#descriptor.port ? ports(this.#descriptor.port)[0] : 0;
131
- const running = !!containerId && (!port || await this.#isRunning());
131
+ const running = !!containerId && (!port || (await this.#isRunning()));
132
132
 
133
- if (running && !containerId) { // We don't own
133
+ if (running && !containerId) {
134
+ // We don't own
134
135
  return yield [operation === 'status' ? 'message' : 'failure', 'Running but not managed'];
135
136
  }
136
137
 
@@ -149,7 +150,7 @@ export class ServiceRunner {
149
150
  }
150
151
 
151
152
  if (operation === 'restart' || operation === 'start') {
152
- if (!await this.#hasImage()) {
153
+ if (!(await this.#hasImage())) {
153
154
  yield ['message', 'Starting image download'];
154
155
  for await (const line of this.#pullImage()) {
155
156
  yield ['message', `Downloading: ${line}`];
@@ -162,7 +163,7 @@ export class ServiceRunner {
162
163
 
163
164
  if (port) {
164
165
  yield ['message', `Waiting for ${this.#descriptor.ready?.url ?? 'container'}...`];
165
- if (!await this.#isRunning(true)) {
166
+ if (!(await this.#isRunning(true))) {
166
167
  yield ['failure', 'Failed to start service correctly'];
167
168
  }
168
169
  }
@@ -172,4 +173,4 @@ export class ServiceRunner {
172
173
  yield ['failure', 'Failed to start'];
173
174
  }
174
175
  }
175
- }
176
+ }
package/src/trv.d.ts CHANGED
@@ -2,18 +2,18 @@ import '@travetto/runtime';
2
2
 
3
3
  declare module '@travetto/runtime' {
4
4
  interface EnvData {
5
- /**
6
- * Provides an IPC http url for the CLI to communicate with.
5
+ /**
6
+ * Provides an IPC http url for the CLI to communicate with.
7
7
  * This facilitates cli-based invocation for external usage.
8
8
  */
9
9
  TRV_CLI_IPC: string;
10
- /**
10
+ /**
11
11
  * Signals to the child they are the restart target
12
12
  */
13
13
  TRV_RESTART_TARGET: boolean;
14
- /**
14
+ /**
15
15
  * Overrides behavior for triggering debug session via IPC
16
16
  */
17
17
  TRV_DEBUG_IPC: boolean;
18
18
  }
19
- }
19
+ }
package/src/types.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import type { Any, Class } from '@travetto/runtime';
2
2
 
3
+ export const HELP_FLAG = '--help';
4
+
3
5
  type OrProm<T> = T | Promise<T>;
4
- type ParsedFlag = { type: 'flag', input: string, array?: boolean, fieldName: string, value?: unknown };
5
- type ParsedArg = { type: 'arg', input: string, array?: boolean, index: number };
6
- type ParsedUnknown = { type: 'unknown', input: string };
6
+ type ParsedFlag = { type: 'flag'; input: string; array?: boolean; fieldName: string; value?: unknown };
7
+ type ParsedArg = { type: 'arg'; input: string; array?: boolean; index: number };
8
+ type ParsedUnknown = { type: 'unknown'; input: string };
7
9
  type ParsedInput = ParsedUnknown | ParsedFlag | ParsedArg;
8
10
 
9
11
  export type ParsedState = {
@@ -32,7 +34,7 @@ export interface CliCommandShape {
32
34
  help?(): OrProm<string[]>;
33
35
  }
34
36
 
35
- export type PreMainHandler<T extends Any = Any> = { priority: number, handler: (cmd: T) => Any };
37
+ export type PreMainHandler<T extends Any = Any> = { priority: number; handler: (cmd: T) => Any };
36
38
 
37
39
  /**
38
40
  * CLI Command schema shape
@@ -42,4 +44,4 @@ export interface CliCommandConfig {
42
44
  name: string;
43
45
  runTarget?: boolean;
44
46
  preMain: PreMainHandler[];
45
- }
47
+ }
package/src/util.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { spawn, type ChildProcess } from 'node:child_process';
1
+ import { type ChildProcess, spawn } from 'node:child_process';
2
2
 
3
- import { RuntimeError, JSONUtil, Env, ExecUtil, Runtime, ShutdownManager, Util, WatchUtil } from '@travetto/runtime';
3
+ import { Env, ExecUtil, JSONUtil, Runtime, RuntimeError, ShutdownManager, Util, WatchUtil } from '@travetto/runtime';
4
4
 
5
5
  const IPC_VALID_ENV = new Set(['NODE_OPTIONS', 'PATH', Env.DEBUG.key, Env.NODE_ENV.key]);
6
- const IPC_INVALID_ENV = new Set([
7
- Env.TRV_CLI_IPC, Env.TRV_DEBUG_IPC, Env.TRV_DEBUG_BREAK, Env.TRV_MANIFEST, Env.TRV_MODULE, Env.TRV_RESTART_TARGET
8
- ].map(item => item.key));
6
+ const IPC_INVALID_ENV = new Set(
7
+ [Env.TRV_CLI_IPC, Env.TRV_DEBUG_IPC, Env.TRV_DEBUG_BREAK, Env.TRV_MANIFEST, Env.TRV_MODULE, Env.TRV_RESTART_TARGET].map(item => item.key)
8
+ );
9
9
  const validEnv = ([key]: [key: string, value: unknown]): boolean =>
10
10
  IPC_VALID_ENV.has(key) || (key.startsWith('TRV_') && !IPC_INVALID_ENV.has(key));
11
11
 
@@ -14,7 +14,7 @@ export class CliUtil {
14
14
  * Get a simplified version of a module name
15
15
  */
16
16
  static getSimpleModuleName(placeholder: string, module?: string): string {
17
- const simple = (module ?? Runtime.main.name).replace(/[\/]/, '_').replace(/@/, '');
17
+ const simple = (module ?? Runtime.main.name).replace(/[/]/, '_').replace(/@/, '');
18
18
  return simple ? placeholder.replace('<module>', simple) : placeholder;
19
19
  }
20
20
 
@@ -35,9 +35,7 @@ export class CliUtil {
35
35
  let child: ChildProcess | undefined;
36
36
  await WatchUtil.watchCompilerEvents('file', () => ShutdownManager.shutdownChild(child!, { reason: 'restart', mode: 'exit' }));
37
37
 
38
- process
39
- .on('SIGINT', () => ShutdownManager.shutdownChild(child!, { mode: 'exit' }))
40
- .on('message', message => child?.send?.(message!));
38
+ process.on('SIGINT', () => ShutdownManager.shutdownChild(child!, { mode: 'exit' })).on('message', message => child?.send?.(message!));
41
39
 
42
40
  const env = { ...process.env, ...Env.TRV_RESTART_TARGET.export(true) };
43
41
 
@@ -51,12 +49,13 @@ export class CliUtil {
51
49
  maxRetries: 5,
52
50
  onRetry: async (state, config) => {
53
51
  const duration = WatchUtil.computeRestartDelay(state, config);
54
- console.error(
55
- '[cli-restart] Restarting subprocess due to change...',
56
- { waiting: duration, iteration: state.iteration, errorIterations: state.errorIterations || undefined }
57
- );
52
+ console.error('[cli-restart] Restarting subprocess due to change...', {
53
+ waiting: duration,
54
+ iteration: state.iteration,
55
+ errorIterations: state.errorIterations || undefined
56
+ });
58
57
  await Util.nonBlockingTimeout(duration);
59
- },
58
+ }
60
59
  }
61
60
  );
62
61
 
@@ -84,7 +83,7 @@ export class CliUtil {
84
83
  name,
85
84
  env: Object.fromEntries(Object.entries(process.env).filter(validEnv)),
86
85
  cwd: process.cwd(),
87
- args: process.argv.slice(3),
86
+ args: process.argv.slice(3)
88
87
  }
89
88
  };
90
89
 
@@ -102,8 +101,7 @@ export class CliUtil {
102
101
  * Write data to channel and ensure its flushed before continuing
103
102
  */
104
103
  static async writeAndEnsureComplete(data: unknown, channel: 'stdout' | 'stderr' = 'stdout'): Promise<void> {
105
- await new Promise<unknown>(resolve => process[channel].write(typeof data === 'string' ? data :
106
- JSONUtil.toUTF8Pretty(data), resolve));
104
+ await new Promise<unknown>(resolve => process[channel].write(typeof data === 'string' ? data : JSONUtil.toUTF8Pretty(data), resolve));
107
105
  }
108
106
 
109
107
  /**
@@ -112,4 +110,4 @@ export class CliUtil {
112
110
  static readExtendedOptions(options?: string[]): Record<string, string | boolean> {
113
111
  return Object.fromEntries((options ?? [])?.map(option => [...option.split(':'), true]));
114
112
  }
115
- }
113
+ }
@@ -2,10 +2,10 @@ import { Env } from '@travetto/runtime';
2
2
  import { IsPrivate, MethodValidator, type ValidationError } from '@travetto/schema';
3
3
 
4
4
  import { CliCommand } from '../src/registry/decorator.ts';
5
- import type { CliCommandShape } from '../src/types.ts';
6
5
  import { CliCommandRegistryIndex } from '../src/registry/registry-index.ts';
7
- import { CliUtil } from '../src/util.ts';
8
6
  import { CliSchemaExportUtil } from '../src/schema-export.ts';
7
+ import type { CliCommandShape } from '../src/types.ts';
8
+ import { CliUtil } from '../src/util.ts';
9
9
 
10
10
  async function nameValidator(names?: string[]): Promise<ValidationError | undefined> {
11
11
  if (!names || names.length === 0) {
@@ -25,12 +25,14 @@ async function nameValidator(names?: string[]): Promise<ValidationError | undefi
25
25
  }
26
26
 
27
27
  /**
28
- * Generates the schema for all CLI operations
28
+ * Exports machine-readable command metadata for automation and tooling.
29
+ *
30
+ * Used by editor integrations to discover runnable commands and inputs.
31
+ * Used by guidance workflows to validate command signatures.
29
32
  */
30
33
  @CliCommand()
31
34
  @IsPrivate()
32
35
  export class CliSchemaCommand implements CliCommandShape {
33
-
34
36
  finalize(): void {
35
37
  Env.DEBUG.set(false);
36
38
  }
@@ -39,9 +41,8 @@ export class CliSchemaCommand implements CliCommandShape {
39
41
  async main(names?: string[]): Promise<void> {
40
42
  const resolved = await CliCommandRegistryIndex.load(names);
41
43
 
42
- const output = resolved
43
- .map(result => CliSchemaExportUtil.exportSchema(result.config.cls));
44
+ const output = resolved.map(result => CliSchemaExportUtil.exportSchema(result.config.cls));
44
45
 
45
46
  await CliUtil.writeAndEnsureComplete(output);
46
47
  }
47
- }
48
+ }
@@ -1,5 +1,5 @@
1
+ import { CliCommand, type CliCommandShape, CliParseUtil } from '@travetto/cli';
1
2
  import { JSONUtil, Runtime } from '@travetto/runtime';
2
- import { type CliCommandShape, CliCommand, CliParseUtil } from '@travetto/cli';
3
3
  import { IsPrivate, MethodValidator, type ValidationError } from '@travetto/schema';
4
4
 
5
5
  async function validateMain(fileOrImport: string): Promise<ValidationError | undefined> {
@@ -8,31 +8,35 @@ async function validateMain(fileOrImport: string): Promise<ValidationError | und
8
8
  } catch {
9
9
  return { message: `Unknown file: ${fileOrImport}`, source: 'arg', kind: 'invalid', path: 'fileOrImport' };
10
10
  }
11
- };
11
+ }
12
12
 
13
13
  /**
14
- * Allows for running of main entry points
14
+ * Execute a module `main()` entrypoint directly.
15
+ *
16
+ * This internal command resolves an import/source target, invokes its exported
17
+ * `main` function, and forwards unknown CLI args to that function.
15
18
  */
16
19
  @CliCommand()
17
20
  @IsPrivate()
18
21
  export class MainCommand implements CliCommandShape {
19
-
20
22
  @MethodValidator(validateMain)
21
23
  async main(fileOrImport: string, args: string[] = []): Promise<void> {
22
24
  const parsed = CliParseUtil.getState(this);
23
25
  let result: unknown;
24
26
  try {
25
27
  const module = await Runtime.importFrom<{ main(..._: unknown[]): Promise<unknown> }>(fileOrImport);
26
- result = await module.main(...args, ...parsed?.unknown ?? []);
28
+ result = await module.main(...args, ...(parsed?.unknown ?? []));
27
29
  } catch (error) {
28
30
  result = error;
29
31
  process.exitCode = Math.max(process.exitCode ? +process.exitCode : 1, 1);
30
32
  }
31
33
 
32
34
  if (result !== undefined) {
33
- if (process.connected) { process.send?.(result); }
34
- const payload = typeof result === 'string' ? result : (result instanceof Error ? result.stack : JSONUtil.toUTF8(result));
35
+ if (process.connected) {
36
+ process.send?.(result);
37
+ }
38
+ const payload = typeof result === 'string' ? result : result instanceof Error ? result.stack : JSONUtil.toUTF8(result);
35
39
  process[process.exitCode ? 'stderr' : 'stdout'].write(`${payload}\n`);
36
40
  }
37
41
  }
38
- }
42
+ }
@@ -1,11 +1,11 @@
1
1
  import { stripVTControlCharacters } from 'node:util';
2
2
 
3
- import { type CliCommandShape, CliCommand, cliTpl } from '@travetto/cli';
4
- import { Terminal } from '@travetto/terminal';
3
+ import { CliCommand, type CliCommandShape, cliTpl } from '@travetto/cli';
5
4
  import { AsyncQueue, Util } from '@travetto/runtime';
6
5
  import { MethodValidator, type ValidationError } from '@travetto/schema';
6
+ import { Terminal } from '@travetto/terminal';
7
7
 
8
- import { ServiceRunner, type ServiceAction } from '../src/service.ts';
8
+ import { type ServiceAction, ServiceRunner } from '../src/service.ts';
9
9
 
10
10
  async function validateService(_: ServiceAction, services: string[]): Promise<ValidationError | undefined> {
11
11
  const all = await ServiceRunner.findServices(services);
@@ -16,11 +16,13 @@ async function validateService(_: ServiceAction, services: string[]): Promise<Va
16
16
  }
17
17
 
18
18
  /**
19
- * Allows for running services
19
+ * Manage development services (start/stop/restart/status) across the workspace.
20
+ *
21
+ * Services are discovered from registered descriptors and executed with streamed
22
+ * terminal feedback, including optional quiet mode.
20
23
  */
21
24
  @CliCommand()
22
25
  export class CliServiceCommand implements CliCommandShape {
23
-
24
26
  quiet = false;
25
27
 
26
28
  async help(): Promise<string[]> {
@@ -38,7 +40,7 @@ export class CliServiceCommand implements CliCommandShape {
38
40
  const maxName = Math.max(...all.map(service => service.name.length), 'Service'.length) + 3;
39
41
  const maxVersion = Math.max(...all.map(service => `${service.version}`.length), 'Version'.length) + 3;
40
42
  const maxStatus = 20;
41
- const queue = new AsyncQueue<{ idx: number, text: string, done?: boolean }>();
43
+ const queue = new AsyncQueue<{ idx: number; text: string; done?: boolean }>();
42
44
 
43
45
  const failureMessages: string[] = [];
44
46
 
@@ -48,7 +50,7 @@ export class CliServiceCommand implements CliCommandShape {
48
50
  let message: string;
49
51
  for await (const [valueType, value] of new ServiceRunner(descriptor).action(action)) {
50
52
  const details = { [valueType === 'message' ? 'subtitle' : valueType]: value };
51
- queue.add({ idx: i, text: message = cliTpl`${{ identifier }} ${{ type }} ${details}` });
53
+ queue.add({ idx: i, text: (message = cliTpl`${{ identifier }} ${{ type }} ${details}`) });
52
54
  if (valueType === 'failure') {
53
55
  failureMessages.push(message);
54
56
  }
@@ -56,26 +58,30 @@ export class CliServiceCommand implements CliCommandShape {
56
58
  queue.add({ idx: i, done: true, text: message! });
57
59
  });
58
60
 
59
- Promise.all(jobs).then(() => Util.queueMacroTask()).then(() => queue.close());
60
-
61
+ Promise.all(jobs)
62
+ .then(() => Util.queueMacroTask())
63
+ .then(() => queue.close());
61
64
 
62
65
  if (this.quiet) {
63
- for await (const _ of queue) { }
66
+ for await (const _ of queue) {
67
+ }
64
68
  if (failureMessages.length) {
65
69
  console.error('Failure');
66
70
  failureMessages.map(stripVTControlCharacters).map(item => console.error(item));
67
71
  }
68
72
  } else {
69
73
  const term = new Terminal();
70
- await term.writer.writeLines([
71
- '',
72
- cliTpl`${{ title: 'Service'.padEnd(maxName) }} ${{ title: 'Version'.padEnd(maxVersion) }} ${{ title: 'Status' }}`,
73
- ''.padEnd(maxName + maxVersion + maxStatus + 3, '-'),
74
- ]).commit();
74
+ await term.writer
75
+ .writeLines([
76
+ '',
77
+ cliTpl`${{ title: 'Service'.padEnd(maxName) }} ${{ title: 'Version'.padEnd(maxVersion) }} ${{ title: 'Status' }}`,
78
+ ''.padEnd(maxName + maxVersion + maxStatus + 3, '-')
79
+ ])
80
+ .commit();
75
81
 
76
82
  await term.streamList(queue);
77
83
  }
78
84
 
79
85
  process.exitCode = failureMessages.length ? 1 : 0;
80
86
  }
81
- }
87
+ }
@@ -1,4 +1,5 @@
1
1
  // @trv-no-transform
2
2
  import '@travetto/runtime/support/patch.js';
3
3
  import { ExecutionManager } from '@travetto/cli';
4
- ExecutionManager.run(process.argv);
4
+
5
+ ExecutionManager.run(process.argv);