@travetto/cli 7.0.0 → 7.0.2

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
@@ -461,7 +461,7 @@ If the goal is to run a more complex application, which may include depending on
461
461
 
462
462
  **Code: Simple Run Target**
463
463
  ```typescript
464
- import { Runtime, toConcrete, Util } from '@travetto/runtime';
464
+ import { Runtime, toConcrete } from '@travetto/runtime';
465
465
  import { DependencyRegistryIndex } from '@travetto/di';
466
466
  import { CliCommand, CliCommandShape } from '@travetto/cli';
467
467
  import { NetUtil } from '@travetto/web';
@@ -491,12 +491,16 @@ export class WebHttpCommand implements CliCommandShape {
491
491
  await Registry.init();
492
492
  const instance = await DependencyRegistryIndex.getInstance(toConcrete<WebHttpServer>());
493
493
 
494
- if (this.killConflict) {
495
- const handle = await Util.acquireWithRetry(() => instance.serve(), NetUtil.freePortOnConflict, 5);
496
- return handle.complete;
497
- } else {
494
+ try {
498
495
  const handle = await instance.serve();
499
496
  return handle.complete;
497
+ } catch (err) {
498
+ const result = this.killConflict ? await NetUtil.freePortOnConflict(err) : undefined;
499
+ if (result?.processId) {
500
+ console.warn('Killed process owning port', result);
501
+ process.exit(1);
502
+ }
503
+ throw err;
500
504
  }
501
505
  }
502
506
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/cli",
3
- "version": "7.0.0",
3
+ "version": "7.0.2",
4
4
  "type": "module",
5
5
  "description": "CLI infrastructure for Travetto framework",
6
6
  "keywords": [
@@ -29,8 +29,8 @@
29
29
  "directory": "module/cli"
30
30
  },
31
31
  "dependencies": {
32
- "@travetto/schema": "^7.0.0",
33
- "@travetto/terminal": "^7.0.0"
32
+ "@travetto/schema": "^7.0.2",
33
+ "@travetto/terminal": "^7.0.2"
34
34
  },
35
35
  "travetto": {
36
36
  "displayName": "Command Line Interface",
@@ -29,11 +29,11 @@ type WithHandler<K extends keyof WithConfig> = (config?: WithConfig[K]) => ({
29
29
  name: K;
30
30
  field: Partial<SchemaFieldConfig>;
31
31
  run?: (cmd: Cmd) => (Promise<unknown> | unknown);
32
- } | undefined)
32
+ } | undefined);
33
33
 
34
34
  const FIELD_CONFIG: { [K in keyof WithConfig]: WithHandler<K> } = {
35
35
  env: (config) => {
36
- if (!config) return;
36
+ if (!config) { return; }
37
37
  return {
38
38
  name: 'env',
39
39
  run: cmd => Env.TRV_ENV.set(cmd.env || Runtime.env),
@@ -43,10 +43,10 @@ const FIELD_CONFIG: { [K in keyof WithConfig]: WithHandler<K> } = {
43
43
  description: 'Application environment',
44
44
  required: { active: false },
45
45
  },
46
- }
46
+ };
47
47
  },
48
48
  module: (config) => {
49
- if (!config) return;
49
+ if (!config) { return; }
50
50
  return {
51
51
  name: 'module',
52
52
  field: {
@@ -56,10 +56,10 @@ const FIELD_CONFIG: { [K in keyof WithConfig]: WithHandler<K> } = {
56
56
  specifiers: ['module'],
57
57
  required: { active: Runtime.monoRoot },
58
58
  },
59
- }
59
+ };
60
60
  },
61
61
  debugIpc: (config) => {
62
- if (!config) return;
62
+ if (!config) { return; }
63
63
  return {
64
64
  name: 'debugIpc',
65
65
  run: cmd => CliUtil.runWithDebugIpc(cmd).then((executed) => executed && process.exit(0)),
@@ -73,7 +73,7 @@ const FIELD_CONFIG: { [K in keyof WithConfig]: WithHandler<K> } = {
73
73
  };
74
74
  },
75
75
  restartOnChange: (config) => {
76
- if (!config) return;
76
+ if (!config) { return; }
77
77
  return {
78
78
  name: 'restartOnChange',
79
79
  run: cmd => CliUtil.runWithRestartOnChange(cmd),
@@ -25,7 +25,6 @@ export type CliCommandInput<K extends string = string> = {
25
25
  export interface CliCommandSchema<K extends string = string> {
26
26
  name: string;
27
27
  module: string;
28
- commandModule: string;
29
28
  runTarget?: boolean;
30
29
  description?: string;
31
30
  args: CliCommandInput[];
@@ -83,8 +82,7 @@ export class CliSchemaExportUtil {
83
82
  description: schema.description,
84
83
  flags: processed.filter(value => value.flagNames && value.flagNames.length > 0),
85
84
  args: processed.filter(value => !value.flagNames || value.flagNames.length === 0),
86
- runTarget: config.runTarget ?? false,
87
- commandModule: describeFunction(cls).module,
85
+ runTarget: config.runTarget ?? false
88
86
  };
89
87
  }
90
88
  }
package/src/util.ts CHANGED
@@ -1,24 +1,15 @@
1
1
  import { spawn, type ChildProcess } from 'node:child_process';
2
2
 
3
- import { describeFunction, Env, ExecUtil, Runtime, listenForSourceChanges, type ExecutionResult, ShutdownManager } from '@travetto/runtime';
3
+ import { Env, ExecUtil, Runtime, ShutdownManager, Util, WatchUtil } from '@travetto/runtime';
4
4
 
5
5
  import { CliCommandShape, CliCommandShapeFields } from './types.ts';
6
6
 
7
- const CODE_RESTART = { type: 'code_change', code: 200 };
8
7
  const IPC_ALLOWED_ENV = new Set(['NODE_OPTIONS']);
9
8
  const IPC_INVALID_ENV = new Set(['PS1', 'INIT_CWD', 'COLOR', 'LANGUAGE', 'PROFILEHOME', '_']);
10
9
  const validEnv = (key: string): boolean => IPC_ALLOWED_ENV.has(key) || (
11
10
  !IPC_INVALID_ENV.has(key) && !/^(npm_|GTK|GDK|TRV|NODE|GIT|TERM_)/.test(key) && !/VSCODE/.test(key)
12
11
  );
13
12
 
14
- const isCodeRestart = (input: unknown): input is typeof CODE_RESTART =>
15
- typeof input === 'object' && !!input && 'type' in input && input.type === CODE_RESTART.type;
16
-
17
- type RunWithRestartOptions = {
18
- maxRetriesPerMinute?: number;
19
- relayInterrupt?: boolean;
20
- };
21
-
22
13
  export class CliUtil {
23
14
  /**
24
15
  * Get a simplified version of a module name
@@ -37,63 +28,36 @@ export class CliUtil {
37
28
  /**
38
29
  * Run a command as restartable, linking into self
39
30
  */
40
- static async runWithRestartOnChange<T extends CliCommandShapeFields & CliCommandShape>(cmd: T, config?: RunWithRestartOptions): Promise<boolean> {
31
+ static async runWithRestartOnChange<T extends CliCommandShapeFields>(cmd: T): Promise<boolean> {
41
32
  if (cmd.restartOnChange !== true) {
42
- process.on('message', event => isCodeRestart(event) && process.exit(event.code));
33
+ WatchUtil.listenForRestartSignal();
43
34
  return false;
44
35
  }
45
36
 
46
- let result: ExecutionResult | undefined;
47
- let exhaustedRestarts = false;
48
37
  let subProcess: ChildProcess | undefined;
38
+ void WatchUtil.watchCompilerEvents('file', () => WatchUtil.triggerRestartSignal(subProcess));
49
39
 
50
40
  const env = { ...process.env, ...Env.TRV_RESTART_ON_CHANGE.export(false) };
51
- const maxRetries = config?.maxRetriesPerMinute ?? 5;
52
- const relayInterrupt = config?.relayInterrupt ?? true;
53
- const restarts: number[] = [];
54
- listenForSourceChanges(() => { subProcess?.send(CODE_RESTART); });
55
-
56
- if (!relayInterrupt) {
57
- process.removeAllListeners('SIGINT'); // Remove any existing listeners
58
- process.on('SIGINT', () => { }); // Prevents SIGINT from killing parent process, the child will handle
59
- }
60
-
61
- while (
62
- (result === undefined || result.code === CODE_RESTART.code) &&
63
- !exhaustedRestarts
64
- ) {
65
- if (restarts.length) {
66
- console.error('Restarting...', { pid: process.pid, time: restarts[0] });
67
- }
68
-
69
- // Ensure restarts length is capped
70
- subProcess = spawn(process.argv0, process.argv.slice(1), { env, stdio: [0, 1, 2, 'ipc'] })
71
- .on('message', value => process.send?.(value));
72
-
73
- const interrupt = (): void => { subProcess?.kill('SIGINT'); };
74
- const toMessage = (value: unknown): void => { subProcess?.send(value!); };
75
41
 
76
- // Proxy kill requests
77
- process.on('message', toMessage);
78
- if (relayInterrupt) {
79
- process.on('SIGINT', interrupt);
80
- }
81
-
82
- result = await ExecUtil.getResult(subProcess, { catch: true });
83
- process.exitCode = subProcess.exitCode;
84
- process.off('message', toMessage);
85
- process.off('SIGINT', interrupt);
86
-
87
- if (restarts.length >= maxRetries) {
88
- exhaustedRestarts = (Date.now() - restarts[0]) < (10 * 1000);
89
- restarts.shift();
90
- }
91
- restarts.push(Date.now());
92
- }
42
+ const log = (msg: string, extra?: Record<string, unknown>): void => {
43
+ console.error(`[cli-restart] ${msg}`, { pid: process.pid, ...extra });
44
+ };
93
45
 
94
- if (exhaustedRestarts) {
95
- console.error(`Bailing, due to ${maxRetries} restarts in under 10s`);
96
- }
46
+ await WatchUtil.runWithRetry(
47
+ async () => {
48
+ const { code } = await ExecUtil.deferToSubprocess(
49
+ subProcess = spawn(process.argv0, process.argv.slice(1), { env, stdio: [0, 1, 2, 'ipc'] }),
50
+ );
51
+ return WatchUtil.exitCodeToResult(code);
52
+ },
53
+ {
54
+ maxRetries: 5,
55
+ onRetry: async (state, config) => {
56
+ const duration = WatchUtil.computeRestartDelay(state, config);
57
+ log('Restarting subprocess due to change...', { waiting: duration, iteration: state.iteration, errorIterations: state.errorIterations || undefined });
58
+ await Util.nonBlockingTimeout(duration);
59
+ },
60
+ });
97
61
 
98
62
  await ShutdownManager.gracefulShutdown('cli-restart');
99
63
  process.exit();
@@ -115,13 +79,11 @@ export class CliUtil {
115
79
 
116
80
  const env: Record<string, string> = {};
117
81
  const request = {
118
- type: `@travetto/cli:run`,
82
+ type: '@travetto/cli:run',
119
83
  data: {
120
84
  name: cmd._cfg!.name,
121
85
  env,
122
- // TODO: Is this needed?
123
- commandModule: describeFunction(cmd.constructor).module,
124
- module: Runtime.main.name,
86
+ module: cmd.module ?? Runtime.main.name,
125
87
  args: process.argv.slice(3),
126
88
  }
127
89
  };