@travetto/cli 3.0.0-rc.2 → 3.0.0-rc.20
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 +10 -16
- package/__index__.ts +7 -0
- package/package.json +10 -11
- package/src/color.ts +17 -27
- package/src/command-manager.ts +55 -29
- package/src/command.ts +56 -62
- package/src/execute.ts +51 -33
- package/src/help.ts +123 -38
- package/src/module.ts +189 -0
- package/src/scm.ts +76 -0
- package/support/cli.exec.ts +57 -0
- package/support/cli.list.ts +47 -0
- package/support/cli.ts +3 -0
- package/support/cli.version-sync.ts +17 -0
- package/bin/bash-complete.sh +0 -18
- package/bin/cli.ts +0 -28
- package/bin/trv.js +0 -3
- package/src/types.ts +0 -17
- package/src/util.ts +0 -146
package/src/help.ts
CHANGED
|
@@ -1,6 +1,80 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type commander from 'commander';
|
|
2
|
+
|
|
3
|
+
import { cliTpl } from './color';
|
|
4
|
+
|
|
5
|
+
const TYPE_PATTERN = /(\[[^\]]+\])/g;
|
|
6
|
+
const REQ_TYPE_PATTERN = /(<[^>]+>)/g;
|
|
7
|
+
const TITLE_PATTERN = /^(\S[^:]+:)/gim;
|
|
8
|
+
|
|
9
|
+
const OPTIONS_PATTERN = new RegExp([
|
|
10
|
+
'^',
|
|
11
|
+
'(?<space>[ ]+)',
|
|
12
|
+
'(?<shortName>-[^, ]+)',
|
|
13
|
+
'(?<paramSpace>,?[ ]*)?',
|
|
14
|
+
'(?<longName>--\\S+)?',
|
|
15
|
+
'((?<typeSpace>[ ]+)?(?<type>(?:\\[[^\\]]+\\])|(?:[<][^>]+[>])))?',
|
|
16
|
+
'((?<descriptionSpace>[ ]+)(?<description>.*?))?',
|
|
17
|
+
'((?<defaultPre>[ ]*[(])(?<defaultKey>default)(?<defaultSpace>: )(?<defaultValue>[^)]+)(?<defaultPost>[)]))?',
|
|
18
|
+
'(?:[ ]+)?',
|
|
19
|
+
'$',
|
|
20
|
+
].join(''), 'gim');
|
|
21
|
+
|
|
22
|
+
type OptionsGroup = {
|
|
23
|
+
space: string; shortName: string;
|
|
24
|
+
paramSpace?: string; longName?: string;
|
|
25
|
+
typeSpace?: string; type?: string;
|
|
26
|
+
descriptionSpace?: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
defaultPre?: string; defaultKey?: string; defaultSpace?: string; defaultValue?: string; defaultPost?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const COMMANDS_PATTERN = new RegExp([
|
|
32
|
+
'^',
|
|
33
|
+
'(?<space>[ ]+)',
|
|
34
|
+
'(?<name>\\S+)',
|
|
35
|
+
'(?<optionsSpace>[ ]+)?',
|
|
36
|
+
'(?<options>(?:\\[|<).*(?:\\]|>))?',
|
|
37
|
+
'((?<descriptionSpace>[ ]+)(?<description>[a-z][^\\n\\[]+))?',
|
|
38
|
+
'(?:[ ]+)?',
|
|
39
|
+
'$',
|
|
40
|
+
].join(''), 'gim');
|
|
41
|
+
|
|
42
|
+
type CommandGroup = {
|
|
43
|
+
space: string; name: string;
|
|
44
|
+
optionsSpace?: string; options?: string;
|
|
45
|
+
descriptionSpace?: string; description?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const USAGE_PATTERN = new RegExp([
|
|
49
|
+
'^',
|
|
50
|
+
'(?<title>Usage:)',
|
|
51
|
+
'(?<space>[ ]+)?',
|
|
52
|
+
'(?<name>[^\\[ ]+)?',
|
|
53
|
+
'(?<nameSpace>[ ]+)?',
|
|
54
|
+
'(?<options>(?:\\[|<).*(?:\\]|>))?',
|
|
55
|
+
'(?:[ ]+)?',
|
|
56
|
+
'$',
|
|
57
|
+
].join(''), 'gim');
|
|
58
|
+
|
|
59
|
+
type UsageGroup = {
|
|
60
|
+
title: string; space?: string;
|
|
61
|
+
name?: string; nameSpace?: string;
|
|
62
|
+
options?: string;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function namedReplace<T>(text: string, pattern: RegExp, replacer: (data: T) => (string | (string | undefined)[])): string {
|
|
66
|
+
return text.replace(pattern, (...args: unknown[]): string => {
|
|
67
|
+
const groups = args[args.length - 1];
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
69
|
+
const res = replacer(groups as T);
|
|
70
|
+
if (typeof res === 'string') {
|
|
71
|
+
return res;
|
|
72
|
+
} else {
|
|
73
|
+
return res.filter(x => !!x).join('');
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
2
77
|
|
|
3
|
-
import { color } from './color';
|
|
4
78
|
/**
|
|
5
79
|
* Utilities for formatting help
|
|
6
80
|
*/
|
|
@@ -29,48 +103,60 @@ export class HelpUtil {
|
|
|
29
103
|
* Colorize Usage
|
|
30
104
|
*/
|
|
31
105
|
static colorizeOptions(option: string): string {
|
|
32
|
-
return option
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return line.filter(x => !!x).join('');
|
|
55
|
-
})
|
|
56
|
-
.replace(/Options:/, title => color`${{ title }}`);
|
|
106
|
+
return namedReplace<OptionsGroup>(option, OPTIONS_PATTERN,
|
|
107
|
+
({
|
|
108
|
+
space, shortName, paramSpace, longName, typeSpace, type, descriptionSpace, description,
|
|
109
|
+
defaultPre, defaultKey, defaultSpace, defaultValue, defaultPost
|
|
110
|
+
}) =>
|
|
111
|
+
[
|
|
112
|
+
space,
|
|
113
|
+
cliTpl`${{ param: shortName }}`,
|
|
114
|
+
paramSpace,
|
|
115
|
+
cliTpl`${{ param: longName }}`,
|
|
116
|
+
typeSpace,
|
|
117
|
+
cliTpl`${{ type }}`,
|
|
118
|
+
descriptionSpace,
|
|
119
|
+
cliTpl`${{ description }}`,
|
|
120
|
+
defaultPre,
|
|
121
|
+
cliTpl`${{ description: defaultKey }}`,
|
|
122
|
+
defaultSpace,
|
|
123
|
+
cliTpl`${{ input: defaultValue }}`,
|
|
124
|
+
defaultPost
|
|
125
|
+
]
|
|
126
|
+
)
|
|
127
|
+
.replace(TITLE_PATTERN, title => cliTpl`${{ title }}`);
|
|
57
128
|
}
|
|
58
129
|
|
|
59
130
|
/**
|
|
60
131
|
* Colorize command section
|
|
61
132
|
*/
|
|
62
133
|
static colorizeCommands(commands: string): string {
|
|
63
|
-
return commands
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
134
|
+
return namedReplace<CommandGroup>(commands, COMMANDS_PATTERN,
|
|
135
|
+
({ space, name, optionsSpace, options, descriptionSpace, description }) => [
|
|
136
|
+
space,
|
|
137
|
+
cliTpl`${{ param: name }}`,
|
|
138
|
+
optionsSpace,
|
|
139
|
+
options?.replace(TYPE_PATTERN, input => cliTpl`${{ input }}`).replace(REQ_TYPE_PATTERN, type => cliTpl`${{ type }}`),
|
|
140
|
+
descriptionSpace,
|
|
141
|
+
cliTpl`${{ description }}`
|
|
142
|
+
]
|
|
143
|
+
)
|
|
144
|
+
.replace(TITLE_PATTERN, title => cliTpl`${{ title }}`);
|
|
67
145
|
}
|
|
68
146
|
|
|
69
147
|
/**
|
|
70
148
|
* Colorize usage
|
|
71
149
|
*/
|
|
72
150
|
static colorizeUsage(usage: string): string {
|
|
73
|
-
return usage
|
|
151
|
+
return namedReplace<UsageGroup>(usage, USAGE_PATTERN,
|
|
152
|
+
({ title, space, name, nameSpace, options }) => [
|
|
153
|
+
cliTpl`${{ title }}`,
|
|
154
|
+
space,
|
|
155
|
+
cliTpl`${{ param: name }}`,
|
|
156
|
+
nameSpace,
|
|
157
|
+
options?.replace(TYPE_PATTERN, input => cliTpl`${{ input }}`),
|
|
158
|
+
]
|
|
159
|
+
);
|
|
74
160
|
}
|
|
75
161
|
|
|
76
162
|
/**
|
|
@@ -97,16 +183,15 @@ export class HelpUtil {
|
|
|
97
183
|
/**
|
|
98
184
|
* Show the help
|
|
99
185
|
* @param command
|
|
100
|
-
* @param
|
|
186
|
+
* @param failure
|
|
101
187
|
* @param extra
|
|
102
188
|
*/
|
|
103
|
-
static showHelp(command: commander.Command,
|
|
104
|
-
if (
|
|
105
|
-
console!.error(
|
|
189
|
+
static showHelp(command: commander.Command, failure?: string, extra?: string): void {
|
|
190
|
+
if (failure) {
|
|
191
|
+
console!.error(cliTpl`${{ failure }}\n`);
|
|
106
192
|
}
|
|
107
|
-
console, extra)
|
|
109
195
|
);
|
|
110
|
-
process.exit(message ? 1 : 0);
|
|
111
196
|
}
|
|
112
197
|
}
|
package/src/module.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { ColorDefineUtil, NAMED_COLORS, Terminal, GlobalTerminal, TermLinePosition } from '@travetto/terminal';
|
|
2
|
+
import { Env, ExecutionOptions, ExecutionResult, ExecutionState, TypedObject } from '@travetto/base';
|
|
3
|
+
import { IndexedModule, PackageUtil, RootIndex } from '@travetto/manifest';
|
|
4
|
+
import { IterableWorkSet, WorkPool, type Worker } from '@travetto/worker';
|
|
5
|
+
|
|
6
|
+
import { CliScmUtil } from './scm';
|
|
7
|
+
|
|
8
|
+
type ModuleRunConfig<T = ExecutionResult> = {
|
|
9
|
+
progressMessage?: (mod: IndexedModule | undefined) => string;
|
|
10
|
+
filter?: (mod: IndexedModule) => boolean | Promise<boolean>;
|
|
11
|
+
transformResult?: (mod: IndexedModule, result: ExecutionResult) => T;
|
|
12
|
+
workerCount?: number;
|
|
13
|
+
progressPosition?: TermLinePosition;
|
|
14
|
+
prefixOutput?: boolean;
|
|
15
|
+
showStdout?: boolean;
|
|
16
|
+
showStderr?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const COLORS = TypedObject.keys(NAMED_COLORS)
|
|
20
|
+
.map(k => [k, ColorDefineUtil.defineColor(k).hsl] as const)
|
|
21
|
+
.filter(([, [, s, l]]) => l > .5 && l < .8 && s > .8)
|
|
22
|
+
.map(([k]) => GlobalTerminal.colorer(k));
|
|
23
|
+
|
|
24
|
+
const colorize = (val: string, idx: number): string => COLORS[idx % COLORS.length](val);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Simple utilities for understanding modules for CLI use cases
|
|
28
|
+
*/
|
|
29
|
+
export class CliModuleUtil {
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Generate execution options for running on modules
|
|
33
|
+
*/
|
|
34
|
+
static #buildExecutionOptions<T = ExecutionState>(
|
|
35
|
+
mod: IndexedModule,
|
|
36
|
+
config: ModuleRunConfig<T>,
|
|
37
|
+
prefixes: Record<string, string>,
|
|
38
|
+
stdoutTerm: Terminal,
|
|
39
|
+
stderrTerm: Terminal
|
|
40
|
+
): ExecutionOptions {
|
|
41
|
+
const folder = mod.sourceFolder;
|
|
42
|
+
const opts: ExecutionOptions = {
|
|
43
|
+
stdio: ['ignore', 'pipe', 'pipe', 'ignore'],
|
|
44
|
+
outputMode: 'text',
|
|
45
|
+
catchAsResult: true,
|
|
46
|
+
cwd: folder,
|
|
47
|
+
env: { TRV_MANIFEST: '', TRV_MODULE: mod.name },
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (config.showStdout) {
|
|
51
|
+
opts.onStdOutLine = (line: string): unknown => stdoutTerm.writeLines(`${prefixes[folder] ?? ''}${line}`);
|
|
52
|
+
}
|
|
53
|
+
if (config.showStderr) {
|
|
54
|
+
opts.onStdErrorLine = (line: string): unknown => stderrTerm.writeLines(`${prefixes[folder] ?? ''}${line}`);
|
|
55
|
+
}
|
|
56
|
+
return opts;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Build equal sized prefix labels for outputting
|
|
61
|
+
* @param mods
|
|
62
|
+
* @returns
|
|
63
|
+
*/
|
|
64
|
+
static #buildPrefixes(mods: IndexedModule[]): Record<string, string> {
|
|
65
|
+
const folders = mods.map(x => x.sourceFolder);
|
|
66
|
+
const maxWidth = Math.max(...folders.map(x => x.length));
|
|
67
|
+
return Object.fromEntries(folders.map((x, i) => [x, colorize(x.padStart(maxWidth, ' ').padEnd(maxWidth + 1), i)]));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Find modules that changed, and the dependent modules
|
|
72
|
+
* @param hash
|
|
73
|
+
* @param transitive
|
|
74
|
+
* @returns
|
|
75
|
+
*/
|
|
76
|
+
static async findChangedModulesRecursive(hash?: string, transitive = true): Promise<IndexedModule[]> {
|
|
77
|
+
if (!hash) {
|
|
78
|
+
hash = await CliScmUtil.findLastRelease();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!hash) {
|
|
82
|
+
return RootIndex.getLocalModules();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const out = new Map<string, IndexedModule>();
|
|
86
|
+
for (const mod of await CliScmUtil.findChangedModulesSince(hash)) {
|
|
87
|
+
out.set(mod.name, mod);
|
|
88
|
+
if (transitive) {
|
|
89
|
+
for (const sub of await RootIndex.getDependentModules(mod)) {
|
|
90
|
+
out.set(sub.name, sub);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return [...out.values()]
|
|
96
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Find modules that changed, and the dependent modules
|
|
101
|
+
* @param hash
|
|
102
|
+
* @param transitive
|
|
103
|
+
* @returns
|
|
104
|
+
*/
|
|
105
|
+
static async findModules(mode: 'all' | 'changed'): Promise<IndexedModule[]> {
|
|
106
|
+
return (mode === 'changed' ?
|
|
107
|
+
await this.findChangedModulesRecursive() :
|
|
108
|
+
[...RootIndex.getModuleList('all')].map(x => RootIndex.getModule(x)!)
|
|
109
|
+
).filter(x => x.sourcePath !== RootIndex.manifest.workspacePath);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Synchronize all workspace modules to have the correct versions from the current packages
|
|
114
|
+
*/
|
|
115
|
+
static async synchronizeModuleVersions(): Promise<void> {
|
|
116
|
+
await PackageUtil.syncVersions((await this.findModules('all')).map(x => x.sourcePath));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Run on all modules
|
|
121
|
+
*/
|
|
122
|
+
static async execOnModules<T = ExecutionResult>(
|
|
123
|
+
mode: 'all' | 'changed',
|
|
124
|
+
operation: (mod: IndexedModule, options: ExecutionOptions) => ExecutionState,
|
|
125
|
+
config: ModuleRunConfig<T> = {}
|
|
126
|
+
): Promise<Map<IndexedModule, T>> {
|
|
127
|
+
|
|
128
|
+
config.showStdout = config.showStdout ?? (Env.isSet('DEBUG') && !Env.isFalse('DEBUG'));
|
|
129
|
+
config.showStderr = config.showStderr ?? true;
|
|
130
|
+
|
|
131
|
+
const workerCount = config.workerCount ?? WorkPool.DEFAULT_SIZE;
|
|
132
|
+
|
|
133
|
+
const mods = await CliModuleUtil.findModules(mode);
|
|
134
|
+
const results = new Map<IndexedModule, T>();
|
|
135
|
+
const processes = new Map<IndexedModule, ExecutionState>();
|
|
136
|
+
|
|
137
|
+
const prefixes = config.prefixOutput !== false ? this.#buildPrefixes(mods) : {};
|
|
138
|
+
const stdoutTerm = await Terminal.for({ output: process.stdout });
|
|
139
|
+
const stderrTerm = await Terminal.for({ output: process.stderr });
|
|
140
|
+
|
|
141
|
+
let id = 1;
|
|
142
|
+
const pool = new WorkPool(async () => {
|
|
143
|
+
const worker: Worker<IndexedModule> & { mod?: IndexedModule } = {
|
|
144
|
+
id: id += 1,
|
|
145
|
+
mod: undefined,
|
|
146
|
+
active: false,
|
|
147
|
+
async destroy() {
|
|
148
|
+
this.active = false;
|
|
149
|
+
processes.get(this.mod!)?.process.kill('SIGKILL');
|
|
150
|
+
},
|
|
151
|
+
async execute(mod: IndexedModule) {
|
|
152
|
+
try {
|
|
153
|
+
this.mod = mod;
|
|
154
|
+
this.active = true;
|
|
155
|
+
|
|
156
|
+
if (await config.filter?.(mod) === false) {
|
|
157
|
+
this.active = false;
|
|
158
|
+
} else {
|
|
159
|
+
const opts = CliModuleUtil.#buildExecutionOptions(mod, config, prefixes, stdoutTerm, stderrTerm);
|
|
160
|
+
|
|
161
|
+
const result = await operation(mod, opts).result;
|
|
162
|
+
|
|
163
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
164
|
+
const output = (config.transformResult ? config.transformResult(mod, result) : result) as T;
|
|
165
|
+
results.set(mod, output);
|
|
166
|
+
}
|
|
167
|
+
} finally {
|
|
168
|
+
this.active = false;
|
|
169
|
+
delete this.mod;
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
return worker;
|
|
174
|
+
}, { max: workerCount, min: workerCount });
|
|
175
|
+
|
|
176
|
+
const work = pool.iterateProcess(new IterableWorkSet(mods));
|
|
177
|
+
|
|
178
|
+
if (config.progressMessage) {
|
|
179
|
+
const cfg = { position: config.progressPosition ?? 'bottom' } as const;
|
|
180
|
+
await stdoutTerm.trackProgress(work, ev => ({ ...ev, text: config.progressMessage!(ev.value) }), cfg);
|
|
181
|
+
} else {
|
|
182
|
+
for await (const _ of work) {
|
|
183
|
+
// Ensure its all consumed
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return results;
|
|
188
|
+
}
|
|
189
|
+
}
|
package/src/scm.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
|
|
3
|
+
import { Env, ExecUtil } from '@travetto/base';
|
|
4
|
+
import { IndexedModule, RootIndex, path } from '@travetto/manifest';
|
|
5
|
+
|
|
6
|
+
export class CliScmUtil {
|
|
7
|
+
/**
|
|
8
|
+
* See if folder is a repository root
|
|
9
|
+
* @param folder
|
|
10
|
+
* @returns
|
|
11
|
+
*/
|
|
12
|
+
static isRepoRoot(folder: string): Promise<boolean> {
|
|
13
|
+
return fs.stat(path.resolve(folder, '.git')).then(() => true, () => false);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get author information
|
|
18
|
+
* @returns
|
|
19
|
+
*/
|
|
20
|
+
static async getAuthor(): Promise<{ name?: string, email: string }> {
|
|
21
|
+
const [name, email] = await Promise.all([
|
|
22
|
+
await ExecUtil.spawn('git', ['config', 'user.name'], { catchAsResult: true }).result,
|
|
23
|
+
await ExecUtil.spawn('git', ['config', 'user.email']).result,
|
|
24
|
+
]);
|
|
25
|
+
return {
|
|
26
|
+
name: (name.valid ? name.stdout.trim() : '') || Env.get('USER'),
|
|
27
|
+
email: email.stdout.trim()
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Find the last code release
|
|
33
|
+
* @returns
|
|
34
|
+
*/
|
|
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 });
|
|
38
|
+
return (await result).stdout
|
|
39
|
+
.split(/\n/)
|
|
40
|
+
.find(x => /Publish /.test(x))?.split(/\s+/)?.[0];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Find all modules that changed since hash
|
|
45
|
+
* @param hash
|
|
46
|
+
* @returns
|
|
47
|
+
*/
|
|
48
|
+
static async findChangedModulesSince(hash: string): Promise<IndexedModule[]> {
|
|
49
|
+
const ws = RootIndex.manifest.workspacePath;
|
|
50
|
+
const res = await ExecUtil.spawn('git', ['diff', '--name-only', `HEAD..${hash}`], { cwd: ws }).result;
|
|
51
|
+
const out = new Set<IndexedModule>();
|
|
52
|
+
for (const line of res.stdout.split(/\n/g)) {
|
|
53
|
+
const mod = RootIndex.getFromSource(path.resolve(ws, line));
|
|
54
|
+
if (mod) {
|
|
55
|
+
out.add(RootIndex.getModule(mod.module)!);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return [...out].sort((a, b) => a.name.localeCompare(b.name));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Create a commit
|
|
63
|
+
*/
|
|
64
|
+
static createCommit(message: string): Promise<string> {
|
|
65
|
+
return ExecUtil.spawn('git', ['commit', '.', '-m', message]).result.then(r => r.stdout);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Verify if workspace is dirty
|
|
70
|
+
*/
|
|
71
|
+
static async isWorkspaceDirty(): Promise<boolean> {
|
|
72
|
+
const res1 = await ExecUtil.spawn('git', ['diff', '--quiet', '--exit-code'], { catchAsResult: true }).result;
|
|
73
|
+
const res2 = await ExecUtil.spawn('git', ['diff', '--quiet', '--exit-code', '--cached'], { catchAsResult: true }).result;
|
|
74
|
+
return !res1.valid || !res2.valid;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { CliCommand, CliModuleUtil, OptionConfig } from '@travetto/cli';
|
|
2
|
+
import { WorkPool } from '@travetto/worker';
|
|
3
|
+
import { RootIndex } from '@travetto/manifest';
|
|
4
|
+
import { ExecUtil, GlobalEnvConfig } from '@travetto/base';
|
|
5
|
+
|
|
6
|
+
type Options = {
|
|
7
|
+
changed: OptionConfig<boolean>;
|
|
8
|
+
workers: OptionConfig<number>;
|
|
9
|
+
prefixOutput: OptionConfig<boolean>;
|
|
10
|
+
showStdout: OptionConfig<boolean>;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Repo execution
|
|
15
|
+
*/
|
|
16
|
+
export class RepoExecCommand extends CliCommand<Options> {
|
|
17
|
+
name = 'exec';
|
|
18
|
+
|
|
19
|
+
isActive(): boolean {
|
|
20
|
+
return !!RootIndex.manifest.monoRepo;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getOptions(): Options {
|
|
24
|
+
return {
|
|
25
|
+
changed: this.boolOption({ desc: 'Only changed modules', def: true }),
|
|
26
|
+
workers: this.option({ desc: 'Number of concurrent workers', def: WorkPool.DEFAULT_SIZE }),
|
|
27
|
+
prefixOutput: this.boolOption({ desc: 'Prefix output by folder', def: true }),
|
|
28
|
+
showStdout: this.boolOption({ desc: 'Show stdout', def: true })
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
getArgs(): string {
|
|
33
|
+
return '<command> [...args]';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
envInit(): GlobalEnvConfig {
|
|
37
|
+
return { debug: false };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async action(cmd: string, args: string[]): Promise<void> {
|
|
41
|
+
if (!cmd) {
|
|
42
|
+
return this.showHelp('Command is a required field');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await CliModuleUtil.execOnModules(
|
|
46
|
+
this.cmd.changed ? 'changed' : 'all',
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
48
|
+
(mod, opts) => ExecUtil.spawn(cmd, args, opts),
|
|
49
|
+
{
|
|
50
|
+
progressMessage: mod => `Running '${this.args.join(' ')}' [%idx/%total] ${mod?.sourceFolder ?? ''}`,
|
|
51
|
+
showStdout: this.cmd.showStdout,
|
|
52
|
+
prefixOutput: this.cmd.prefixOutput,
|
|
53
|
+
workerCount: this.cmd.workers,
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { CliCommand, CliModuleUtil, OptionConfig } from '@travetto/cli';
|
|
2
|
+
import { RootIndex } from '@travetto/manifest';
|
|
3
|
+
|
|
4
|
+
type Options = {
|
|
5
|
+
changed: OptionConfig<boolean>;
|
|
6
|
+
graph: OptionConfig<boolean>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* `npx trv list`
|
|
11
|
+
*
|
|
12
|
+
* Allows for listing of modules
|
|
13
|
+
*/
|
|
14
|
+
export class RepoListCommand extends CliCommand<Options> {
|
|
15
|
+
|
|
16
|
+
name = 'list';
|
|
17
|
+
|
|
18
|
+
isActive(): boolean {
|
|
19
|
+
return !!RootIndex.manifest.monoRepo;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
getOptions(): Options {
|
|
23
|
+
return {
|
|
24
|
+
changed: this.boolOption({ desc: 'Only show changed modules', def: false }),
|
|
25
|
+
graph: this.boolOption({ desc: 'Show as a digraph', def: false })
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async action(...args: unknown[]): Promise<void> {
|
|
30
|
+
const mods = await CliModuleUtil.findModules(this.cmd.changed ? 'changed' : 'all');
|
|
31
|
+
if (!this.cmd.graph) {
|
|
32
|
+
for (const mod of mods.map(x => x.sourceFolder).sort()) {
|
|
33
|
+
console.log!(mod);
|
|
34
|
+
}
|
|
35
|
+
} else {
|
|
36
|
+
console.log!('digraph g {');
|
|
37
|
+
for (const el of mods) {
|
|
38
|
+
for (const dep of el.parents) {
|
|
39
|
+
if (dep !== RootIndex.mainPackage.name) {
|
|
40
|
+
console.log!(` "${dep}" -> "${el.name}";`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
console.log!('}');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
package/support/cli.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { CliCommand, CliModuleUtil } from '@travetto/cli';
|
|
2
|
+
import { RootIndex } from '@travetto/manifest';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Enforces all packages to write out their versions and dependencies
|
|
6
|
+
*/
|
|
7
|
+
export class VersionSyncCommand extends CliCommand {
|
|
8
|
+
name = 'version-sync';
|
|
9
|
+
|
|
10
|
+
isActive(): boolean {
|
|
11
|
+
return !!RootIndex.manifest.monoRepo;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async action(): Promise<void> {
|
|
15
|
+
await CliModuleUtil.synchronizeModuleVersions();
|
|
16
|
+
}
|
|
17
|
+
}
|
package/bin/bash-complete.sh
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# Bash Autocompletion
|
|
2
|
-
_travetto()
|
|
3
|
-
{
|
|
4
|
-
local trv="${PWD}/node_modules/.bin/trv";
|
|
5
|
-
local cur=${COMP_WORDS[COMP_CWORD]}
|
|
6
|
-
if [ -f "$trv" ]; then
|
|
7
|
-
local words=`${trv} complete ${COMP_WORDS[@]:1}`
|
|
8
|
-
if [[ -z "$words" ]]; then
|
|
9
|
-
COMPREPLY=( )
|
|
10
|
-
else
|
|
11
|
-
COMPREPLY=( $(compgen -W "$words" -- $cur) )
|
|
12
|
-
fi
|
|
13
|
-
else
|
|
14
|
-
COMPREPLY=( )
|
|
15
|
-
fi
|
|
16
|
-
}
|
|
17
|
-
complete -o default -F _travetto travetto
|
|
18
|
-
complete -o default -F _travetto trv
|
package/bin/cli.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { PathUtil } from '@travetto/boot/src/path';
|
|
2
|
-
import { ModuleManager } from '@travetto/boot/src/internal/module';
|
|
3
|
-
import { SourceIndex } from '@travetto/boot/src/internal/source';
|
|
4
|
-
import { EnvUtil } from '@travetto/boot';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Entry point
|
|
8
|
-
*/
|
|
9
|
-
export async function main(): Promise<void> {
|
|
10
|
-
if (!EnvUtil.isFalse('TRV_CLI_LOCAL') && !PathUtil.toUnix(__filename).includes(PathUtil.cwd)) { // If the current file is not under the working directory
|
|
11
|
-
console.error(`
|
|
12
|
-
The @travetto/cli is not intended to be installed globally. Please install it within your local project
|
|
13
|
-
|
|
14
|
-
npm i --save-dev @travetto/cli
|
|
15
|
-
|
|
16
|
-
and invoke it locally using
|
|
17
|
-
|
|
18
|
-
npx trv
|
|
19
|
-
`);
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Compile CLI for usage
|
|
24
|
-
ModuleManager.transpileAll(SourceIndex.find({ folder: 'bin' }));
|
|
25
|
-
|
|
26
|
-
const { ExecutionManager } = await import('@travetto/cli/src/execute');
|
|
27
|
-
return ExecutionManager.run(process.argv); // Run cli
|
|
28
|
-
}
|
package/bin/trv.js
DELETED
package/src/types.ts
DELETED