@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 +9 -5
- package/package.json +3 -3
- package/src/registry/decorator.ts +7 -7
- package/src/schema-export.ts +1 -3
- package/src/util.ts +24 -62
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
33
|
-
"@travetto/terminal": "^7.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),
|
package/src/schema-export.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
31
|
+
static async runWithRestartOnChange<T extends CliCommandShapeFields>(cmd: T): Promise<boolean> {
|
|
41
32
|
if (cmd.restartOnChange !== true) {
|
|
42
|
-
|
|
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
|
-
|
|
77
|
-
process.
|
|
78
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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:
|
|
82
|
+
type: '@travetto/cli:run',
|
|
119
83
|
data: {
|
|
120
84
|
name: cmd._cfg!.name,
|
|
121
85
|
env,
|
|
122
|
-
|
|
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
|
};
|