@travetto/cli 8.0.0-alpha.27 → 8.0.0-alpha.28
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 +20 -26
- package/__index__.ts +10 -9
- package/bin/trv.js +2 -1
- package/package.json +3 -3
- package/src/color.ts +1 -1
- package/src/execute.ts +2 -3
- package/src/help.ts +25 -27
- package/src/module.ts +9 -10
- package/src/parse.ts +54 -38
- package/src/registry/decorator.ts +36 -26
- package/src/registry/registry-adapter.ts +13 -11
- package/src/registry/registry-index.ts +24 -24
- package/src/schema-export.ts +24 -15
- package/src/schema.ts +15 -10
- package/src/scm.ts +19 -10
- package/src/service.ts +34 -33
- package/src/types.ts +5 -5
- package/src/util.ts +16 -18
- package/support/cli.cli_schema.ts +4 -6
- package/support/cli.main.ts +8 -7
- package/support/cli.service.ts +18 -15
- package/support/entry.trv.ts +2 -1
package/src/scm.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
|
|
5
|
-
import { RuntimeError, ExecUtil, Runtime, RuntimeIndex } from '@travetto/runtime';
|
|
6
5
|
import type { IndexedModule } from '@travetto/manifest';
|
|
6
|
+
import { ExecUtil, Runtime, RuntimeError, RuntimeIndex } from '@travetto/runtime';
|
|
7
7
|
|
|
8
8
|
export class CliScmUtil {
|
|
9
9
|
/**
|
|
@@ -12,17 +12,20 @@ export class CliScmUtil {
|
|
|
12
12
|
* @returns
|
|
13
13
|
*/
|
|
14
14
|
static isRepoRoot(folder: string): Promise<boolean> {
|
|
15
|
-
return fs.stat(path.resolve(folder, '.git')).then(
|
|
15
|
+
return fs.stat(path.resolve(folder, '.git')).then(
|
|
16
|
+
() => true,
|
|
17
|
+
() => false
|
|
18
|
+
);
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
/**
|
|
19
22
|
* Get author information
|
|
20
23
|
* @returns
|
|
21
24
|
*/
|
|
22
|
-
static async getAuthor(): Promise<{ name?: string
|
|
25
|
+
static async getAuthor(): Promise<{ name?: string; email: string }> {
|
|
23
26
|
const [name, email] = await Promise.all([
|
|
24
27
|
ExecUtil.getResult(spawn('git', ['config', 'user.name']), { catch: true }),
|
|
25
|
-
ExecUtil.getResult(spawn('git', ['config', 'user.email']))
|
|
28
|
+
ExecUtil.getResult(spawn('git', ['config', 'user.email']))
|
|
26
29
|
]);
|
|
27
30
|
return {
|
|
28
31
|
name: (name.valid ? name.stdout.trim() : '') || process.env.USER,
|
|
@@ -38,7 +41,8 @@ export class CliScmUtil {
|
|
|
38
41
|
const result = await ExecUtil.getResult(spawn('git', ['log', '--pretty=oneline'], { cwd: Runtime.workspace.path }));
|
|
39
42
|
return result.stdout
|
|
40
43
|
.split(/\n/)
|
|
41
|
-
.find(line => /Publish /.test(line))
|
|
44
|
+
.find(line => /Publish /.test(line))
|
|
45
|
+
?.split(/\s+/)?.[0];
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
/**
|
|
@@ -48,9 +52,15 @@ export class CliScmUtil {
|
|
|
48
52
|
*/
|
|
49
53
|
static async findChangedFiles(fromHash: string, toHash: string = 'HEAD'): Promise<string[]> {
|
|
50
54
|
const rootPath = Runtime.workspace.path;
|
|
51
|
-
const result = await ExecUtil.getResult(
|
|
55
|
+
const result = await ExecUtil.getResult(
|
|
56
|
+
spawn('git', ['diff', '--name-only', `${fromHash}..${toHash}`, ':!**/DOC.*', ':!**/README.*'], { cwd: rootPath }),
|
|
57
|
+
{ catch: true }
|
|
58
|
+
);
|
|
52
59
|
if (!result.valid) {
|
|
53
|
-
throw new RuntimeError('Unable to detect changes between', {
|
|
60
|
+
throw new RuntimeError('Unable to detect changes between', {
|
|
61
|
+
category: 'data',
|
|
62
|
+
details: { fromHash, toHash, output: result.stderr || result.stdout }
|
|
63
|
+
});
|
|
54
64
|
}
|
|
55
65
|
const out = new Set<string>();
|
|
56
66
|
for (const line of result.stdout.split(/\n/g)) {
|
|
@@ -76,8 +86,7 @@ export class CliScmUtil {
|
|
|
76
86
|
.map(file => RuntimeIndex.getModule(file.module))
|
|
77
87
|
.filter(module => !!module);
|
|
78
88
|
|
|
79
|
-
return [...new Set(modules)]
|
|
80
|
-
.toSorted((a, b) => a.name.localeCompare(b.name));
|
|
89
|
+
return [...new Set(modules)].toSorted((a, b) => a.name.localeCompare(b.name));
|
|
81
90
|
}
|
|
82
91
|
|
|
83
92
|
/**
|
|
@@ -103,4 +112,4 @@ export class CliScmUtil {
|
|
|
103
112
|
const res2 = await ExecUtil.getResult(spawn('git', ['diff', '--quiet', '--exit-code', '--cached']), { catch: true });
|
|
104
113
|
return !res1.valid || !res2.valid;
|
|
105
114
|
}
|
|
106
|
-
}
|
|
115
|
+
}
|
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
|
|
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 (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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) {
|
|
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 (
|
|
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.
|
|
68
|
-
)
|
|
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
|
|
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',
|
|
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 *
|
|
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) {
|
|
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/types.ts
CHANGED
|
@@ -3,9 +3,9 @@ import type { Any, Class } from '@travetto/runtime';
|
|
|
3
3
|
export const HELP_FLAG = '--help';
|
|
4
4
|
|
|
5
5
|
type OrProm<T> = T | Promise<T>;
|
|
6
|
-
type ParsedFlag = { type: 'flag'
|
|
7
|
-
type ParsedArg = { type: 'arg'
|
|
8
|
-
type ParsedUnknown = { type: 'unknown'
|
|
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 };
|
|
9
9
|
type ParsedInput = ParsedUnknown | ParsedFlag | ParsedArg;
|
|
10
10
|
|
|
11
11
|
export type ParsedState = {
|
|
@@ -34,7 +34,7 @@ export interface CliCommandShape {
|
|
|
34
34
|
help?(): OrProm<string[]>;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
export type PreMainHandler<T extends Any = Any> = { priority: number
|
|
37
|
+
export type PreMainHandler<T extends Any = Any> = { priority: number; handler: (cmd: T) => Any };
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
40
|
* CLI Command schema shape
|
|
@@ -44,4 +44,4 @@ export interface CliCommandConfig {
|
|
|
44
44
|
name: string;
|
|
45
45
|
runTarget?: boolean;
|
|
46
46
|
preMain: PreMainHandler[];
|
|
47
|
-
}
|
|
47
|
+
}
|
package/src/util.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ChildProcess, spawn } from 'node:child_process';
|
|
2
2
|
|
|
3
|
-
import {
|
|
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
|
-
|
|
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(/[
|
|
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
|
-
|
|
56
|
-
|
|
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) {
|
|
@@ -33,7 +33,6 @@ async function nameValidator(names?: string[]): Promise<ValidationError | undefi
|
|
|
33
33
|
@CliCommand()
|
|
34
34
|
@IsPrivate()
|
|
35
35
|
export class CliSchemaCommand implements CliCommandShape {
|
|
36
|
-
|
|
37
36
|
finalize(): void {
|
|
38
37
|
Env.DEBUG.set(false);
|
|
39
38
|
}
|
|
@@ -42,9 +41,8 @@ export class CliSchemaCommand implements CliCommandShape {
|
|
|
42
41
|
async main(names?: string[]): Promise<void> {
|
|
43
42
|
const resolved = await CliCommandRegistryIndex.load(names);
|
|
44
43
|
|
|
45
|
-
const output = resolved
|
|
46
|
-
.map(result => CliSchemaExportUtil.exportSchema(result.config.cls));
|
|
44
|
+
const output = resolved.map(result => CliSchemaExportUtil.exportSchema(result.config.cls));
|
|
47
45
|
|
|
48
46
|
await CliUtil.writeAndEnsureComplete(output);
|
|
49
47
|
}
|
|
50
|
-
}
|
|
48
|
+
}
|
package/support/cli.main.ts
CHANGED
|
@@ -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,7 +8,7 @@ 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
14
|
* Execute a module `main()` entrypoint directly.
|
|
@@ -19,23 +19,24 @@ async function validateMain(fileOrImport: string): Promise<ValidationError | und
|
|
|
19
19
|
@CliCommand()
|
|
20
20
|
@IsPrivate()
|
|
21
21
|
export class MainCommand implements CliCommandShape {
|
|
22
|
-
|
|
23
22
|
@MethodValidator(validateMain)
|
|
24
23
|
async main(fileOrImport: string, args: string[] = []): Promise<void> {
|
|
25
24
|
const parsed = CliParseUtil.getState(this);
|
|
26
25
|
let result: unknown;
|
|
27
26
|
try {
|
|
28
27
|
const module = await Runtime.importFrom<{ main(..._: unknown[]): Promise<unknown> }>(fileOrImport);
|
|
29
|
-
result = await module.main(...args, ...parsed?.unknown ?? []);
|
|
28
|
+
result = await module.main(...args, ...(parsed?.unknown ?? []));
|
|
30
29
|
} catch (error) {
|
|
31
30
|
result = error;
|
|
32
31
|
process.exitCode = Math.max(process.exitCode ? +process.exitCode : 1, 1);
|
|
33
32
|
}
|
|
34
33
|
|
|
35
34
|
if (result !== undefined) {
|
|
36
|
-
if (process.connected) {
|
|
37
|
-
|
|
35
|
+
if (process.connected) {
|
|
36
|
+
process.send?.(result);
|
|
37
|
+
}
|
|
38
|
+
const payload = typeof result === 'string' ? result : result instanceof Error ? result.stack : JSONUtil.toUTF8(result);
|
|
38
39
|
process[process.exitCode ? 'stderr' : 'stdout'].write(`${payload}\n`);
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
|
-
}
|
|
42
|
+
}
|
package/support/cli.service.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { stripVTControlCharacters } from 'node:util';
|
|
2
2
|
|
|
3
|
-
import { type CliCommandShape,
|
|
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 {
|
|
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);
|
|
@@ -23,7 +23,6 @@ async function validateService(_: ServiceAction, services: string[]): Promise<Va
|
|
|
23
23
|
*/
|
|
24
24
|
@CliCommand()
|
|
25
25
|
export class CliServiceCommand implements CliCommandShape {
|
|
26
|
-
|
|
27
26
|
quiet = false;
|
|
28
27
|
|
|
29
28
|
async help(): Promise<string[]> {
|
|
@@ -41,7 +40,7 @@ export class CliServiceCommand implements CliCommandShape {
|
|
|
41
40
|
const maxName = Math.max(...all.map(service => service.name.length), 'Service'.length) + 3;
|
|
42
41
|
const maxVersion = Math.max(...all.map(service => `${service.version}`.length), 'Version'.length) + 3;
|
|
43
42
|
const maxStatus = 20;
|
|
44
|
-
const queue = new AsyncQueue<{ idx: number
|
|
43
|
+
const queue = new AsyncQueue<{ idx: number; text: string; done?: boolean }>();
|
|
45
44
|
|
|
46
45
|
const failureMessages: string[] = [];
|
|
47
46
|
|
|
@@ -51,7 +50,7 @@ export class CliServiceCommand implements CliCommandShape {
|
|
|
51
50
|
let message: string;
|
|
52
51
|
for await (const [valueType, value] of new ServiceRunner(descriptor).action(action)) {
|
|
53
52
|
const details = { [valueType === 'message' ? 'subtitle' : valueType]: value };
|
|
54
|
-
queue.add({ idx: i, text: message = cliTpl`${{ identifier }} ${{ type }} ${details}` });
|
|
53
|
+
queue.add({ idx: i, text: (message = cliTpl`${{ identifier }} ${{ type }} ${details}`) });
|
|
55
54
|
if (valueType === 'failure') {
|
|
56
55
|
failureMessages.push(message);
|
|
57
56
|
}
|
|
@@ -59,26 +58,30 @@ export class CliServiceCommand implements CliCommandShape {
|
|
|
59
58
|
queue.add({ idx: i, done: true, text: message! });
|
|
60
59
|
});
|
|
61
60
|
|
|
62
|
-
Promise.all(jobs)
|
|
63
|
-
|
|
61
|
+
Promise.all(jobs)
|
|
62
|
+
.then(() => Util.queueMacroTask())
|
|
63
|
+
.then(() => queue.close());
|
|
64
64
|
|
|
65
65
|
if (this.quiet) {
|
|
66
|
-
for await (const _ of queue) {
|
|
66
|
+
for await (const _ of queue) {
|
|
67
|
+
}
|
|
67
68
|
if (failureMessages.length) {
|
|
68
69
|
console.error('Failure');
|
|
69
70
|
failureMessages.map(stripVTControlCharacters).map(item => console.error(item));
|
|
70
71
|
}
|
|
71
72
|
} else {
|
|
72
73
|
const term = new Terminal();
|
|
73
|
-
await term.writer
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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();
|
|
78
81
|
|
|
79
82
|
await term.streamList(queue);
|
|
80
83
|
}
|
|
81
84
|
|
|
82
85
|
process.exitCode = failureMessages.length ? 1 : 0;
|
|
83
86
|
}
|
|
84
|
-
}
|
|
87
|
+
}
|
package/support/entry.trv.ts
CHANGED