@emeryld/manager 1.5.0 → 1.5.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 +11 -3
- package/dist/docs/index.js +239 -0
- package/dist/helper-cli/multi-select.js +403 -0
- package/dist/helper-cli.js +33 -0
- package/dist/menu.js +0 -18
- package/dist/publish.js +76 -8
- package/dist/ts-trace/cli/options.js +196 -0
- package/dist/ts-trace/cli/prompts.js +148 -0
- package/dist/ts-trace/cli/settings.js +154 -0
- package/dist/ts-trace/config.js +100 -0
- package/dist/ts-trace/generate.js +150 -0
- package/dist/ts-trace/index.js +137 -0
- package/dist/ts-trace/picker.js +373 -0
- package/dist/ts-trace/report.js +718 -0
- package/dist/ts-trace/types.js +1 -0
- package/package.json +1 -1
package/dist/helper-cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { colors } from './helper-cli/colors.js';
|
|
2
2
|
import { printScriptList } from './helper-cli/display.js';
|
|
3
|
+
import { promptForScriptMultiSelect } from './helper-cli/multi-select.js';
|
|
3
4
|
import { promptForScript } from './helper-cli/prompts.js';
|
|
4
5
|
import { findScriptEntry, normalizeScripts } from './helper-cli/scripts.js';
|
|
5
6
|
import { runEntry } from './helper-cli/runner.js';
|
|
@@ -40,3 +41,35 @@ export async function runHelperCli({ scripts, title = 'Helper CLI', argv = proce
|
|
|
40
41
|
}
|
|
41
42
|
return false;
|
|
42
43
|
}
|
|
44
|
+
export async function runHelperCliMultiSelect({ scripts, title = 'Helper CLI', argv = process.argv.slice(2), }) {
|
|
45
|
+
const normalized = normalizeScripts(scripts);
|
|
46
|
+
let args = Array.isArray(argv) ? [...argv] : [];
|
|
47
|
+
const separatorIndex = args.indexOf('--');
|
|
48
|
+
if (separatorIndex !== -1) {
|
|
49
|
+
args = [...args.slice(0, separatorIndex), ...args.slice(separatorIndex + 1)];
|
|
50
|
+
}
|
|
51
|
+
const [firstArg] = args;
|
|
52
|
+
if (firstArg === '--list' || firstArg === '-l') {
|
|
53
|
+
printScriptList(normalized, title);
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
if (firstArg === '--help' || firstArg === '-h') {
|
|
57
|
+
console.log(colors.magenta('Usage:'));
|
|
58
|
+
console.log(' pnpm run <cli> [script]');
|
|
59
|
+
console.log('\nFlags:');
|
|
60
|
+
console.log(' --list Show available scripts');
|
|
61
|
+
console.log(' --help Show this information');
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const argLooksLikeScript = firstArg && !firstArg.startsWith('-');
|
|
65
|
+
if (argLooksLikeScript) {
|
|
66
|
+
const requested = findScriptEntry(normalized, firstArg);
|
|
67
|
+
if (requested)
|
|
68
|
+
return [requested.displayName];
|
|
69
|
+
console.log(colors.yellow(`Unknown script "${firstArg}". Falling back to interactive selection…`));
|
|
70
|
+
}
|
|
71
|
+
const selection = await promptForScriptMultiSelect(normalized, title);
|
|
72
|
+
if (!selection)
|
|
73
|
+
return undefined;
|
|
74
|
+
return selection.map((entry) => entry.displayName);
|
|
75
|
+
}
|
package/dist/menu.js
CHANGED
|
@@ -9,8 +9,6 @@ import { ensureWorkingTreeCommitted } from './preflight.js';
|
|
|
9
9
|
import { openDockerHelper } from './docker.js';
|
|
10
10
|
import { run } from './utils/run.js';
|
|
11
11
|
import { makeBaseScriptEntries, getPackageMarker, getPackageModule } from './menu/script-helpers.js';
|
|
12
|
-
import { runFormatChecker } from './format-checker/index.js';
|
|
13
|
-
import { runRobot } from './robot/index.js';
|
|
14
12
|
import { describeVersionControlScope, makeVersionControlEntries, } from './version-control.js';
|
|
15
13
|
function formatKindLabel(kind) {
|
|
16
14
|
if (kind === 'cli')
|
|
@@ -70,22 +68,6 @@ function makeManagerStepEntries(targets, packages, state) {
|
|
|
70
68
|
state.lastStep = 'test';
|
|
71
69
|
},
|
|
72
70
|
},
|
|
73
|
-
{
|
|
74
|
-
name: 'format checker',
|
|
75
|
-
emoji: '🧮',
|
|
76
|
-
description: 'Scan for long functions, deep indentation, too many files/components, and repeated snippets',
|
|
77
|
-
handler: async () => {
|
|
78
|
-
await runFormatChecker();
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
name: 'robot metadata',
|
|
83
|
-
emoji: '🤖',
|
|
84
|
-
description: 'Collect functions, components, types, consts, and classes across the workspace',
|
|
85
|
-
handler: async () => {
|
|
86
|
-
await runRobot();
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
71
|
{
|
|
90
72
|
name: 'build',
|
|
91
73
|
emoji: '🏗️',
|
package/dist/publish.js
CHANGED
|
@@ -7,10 +7,13 @@ import { runHelperCli } from './helper-cli.js';
|
|
|
7
7
|
import { buildPackageSelectionMenu, runStepLoop } from './menu.js';
|
|
8
8
|
import { getOrderedPackages, loadPackages, resolvePackage } from './packages.js';
|
|
9
9
|
import { releaseMultiple, releaseSingle, } from './release.js';
|
|
10
|
-
import { runFormatCheckerScanCli } from './format-checker/index.js';
|
|
10
|
+
import { runFormatChecker, runFormatCheckerScanCli } from './format-checker/index.js';
|
|
11
11
|
import { ensureWorkingTreeCommitted } from './preflight.js';
|
|
12
12
|
import { publishCliState } from './prompts.js';
|
|
13
13
|
import { createRrrPackage, runCreatePackageCli, } from './create-package/index.js';
|
|
14
|
+
import { runRobot } from './robot/index.js';
|
|
15
|
+
import { runDocsFeature } from './docs/index.js';
|
|
16
|
+
import { runTypeScriptTraceProfiler, runTypeScriptTraceReportCli, } from './ts-trace/index.js';
|
|
14
17
|
import { colors, logGlobal } from './utils/log.js';
|
|
15
18
|
import { run } from './utils/run.js';
|
|
16
19
|
const MANAGER_PACKAGE = '@emeryld/manager';
|
|
@@ -183,12 +186,62 @@ async function runPackageSelectionLoop(packages, helperArgs) {
|
|
|
183
186
|
lastStep = step;
|
|
184
187
|
}),
|
|
185
188
|
{
|
|
186
|
-
name: '
|
|
187
|
-
emoji: '
|
|
188
|
-
description: '
|
|
189
|
+
name: 'utils',
|
|
190
|
+
emoji: '🛠️',
|
|
191
|
+
description: 'Docs, format checker, TypeScript trace profiler, robot metadata, and package scaffolding',
|
|
189
192
|
handler: async () => {
|
|
190
|
-
|
|
191
|
-
|
|
193
|
+
// eslint-disable-next-line no-constant-condition
|
|
194
|
+
while (true) {
|
|
195
|
+
const ranUtility = await runHelperCli({
|
|
196
|
+
title: 'Utilities',
|
|
197
|
+
scripts: [
|
|
198
|
+
{
|
|
199
|
+
name: 'docs',
|
|
200
|
+
emoji: '📚',
|
|
201
|
+
description: 'Pick files from .manager-docs and copy a bundled payload to clipboard',
|
|
202
|
+
handler: async () => {
|
|
203
|
+
await runDocsFeature();
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: 'format checker',
|
|
208
|
+
emoji: '🧮',
|
|
209
|
+
description: 'Scan for long functions, deep indentation, and repeated snippets',
|
|
210
|
+
handler: async () => {
|
|
211
|
+
await runFormatChecker();
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'typescript trace profiler',
|
|
216
|
+
emoji: '⏱️',
|
|
217
|
+
description: 'Analyze TypeScript generateTrace output and rank slow files, spans, and type relations',
|
|
218
|
+
handler: async () => {
|
|
219
|
+
await runTypeScriptTraceProfiler();
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: 'robot metadata',
|
|
224
|
+
emoji: '🤖',
|
|
225
|
+
description: 'Collect functions, components, types, consts, and classes',
|
|
226
|
+
handler: async () => {
|
|
227
|
+
await runRobot();
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: 'Create package',
|
|
232
|
+
emoji: '✨',
|
|
233
|
+
description: 'Scaffold a new rrr package (contract/server/client)',
|
|
234
|
+
handler: async () => {
|
|
235
|
+
await createRrrPackage();
|
|
236
|
+
currentPackages = await loadPackages();
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
argv: [],
|
|
241
|
+
});
|
|
242
|
+
if (!ranUtility)
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
192
245
|
lastStep = 'back';
|
|
193
246
|
},
|
|
194
247
|
},
|
|
@@ -214,6 +267,15 @@ async function main() {
|
|
|
214
267
|
await runFormatCheckerScanCli(cliArgs.slice(1));
|
|
215
268
|
return;
|
|
216
269
|
}
|
|
270
|
+
if (cliArgs[0] === 'ts-trace') {
|
|
271
|
+
if (cliArgs.length === 1) {
|
|
272
|
+
await runTypeScriptTraceProfiler();
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
await runTypeScriptTraceReportCli(cliArgs.slice(1));
|
|
276
|
+
}
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
217
279
|
const parsed = parseCliArgs(cliArgs);
|
|
218
280
|
const packages = await loadPackages();
|
|
219
281
|
// If user provided non-interactive flags, run headless path
|
|
@@ -238,7 +300,7 @@ async function main() {
|
|
|
238
300
|
return;
|
|
239
301
|
}
|
|
240
302
|
if (packages.length === 0) {
|
|
241
|
-
logGlobal('No packages found (no package.json discovered). Use "Create package" to scaffold one.', colors.yellow);
|
|
303
|
+
logGlobal('No packages found (no package.json discovered). Use "utils" -> "Create package" to scaffold one.', colors.yellow);
|
|
242
304
|
}
|
|
243
305
|
// Interactive flow (unchanged): selection menu then step menu
|
|
244
306
|
if (parsed.selectionArg) {
|
|
@@ -250,5 +312,11 @@ async function main() {
|
|
|
250
312
|
}
|
|
251
313
|
main().catch((error) => {
|
|
252
314
|
console.error(error);
|
|
253
|
-
|
|
315
|
+
const exitCode = typeof error === 'object' &&
|
|
316
|
+
error !== null &&
|
|
317
|
+
'exitCode' in error &&
|
|
318
|
+
typeof error.exitCode === 'number'
|
|
319
|
+
? error.exitCode
|
|
320
|
+
: 1;
|
|
321
|
+
process.exit(exitCode);
|
|
254
322
|
});
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { colors } from '../../utils/log.js';
|
|
3
|
+
export function parseTraceReportCliArgs(argv, defaults) {
|
|
4
|
+
let traceDir;
|
|
5
|
+
let targetPath;
|
|
6
|
+
let projectPath = defaults.projectPath;
|
|
7
|
+
let top = defaults.top;
|
|
8
|
+
let minMs = defaults.minMs;
|
|
9
|
+
let json = defaults.includeJson;
|
|
10
|
+
let filterRegex = defaults.filterRegex;
|
|
11
|
+
let eventNames = [...defaults.eventNames];
|
|
12
|
+
let didProvideEventFlag = false;
|
|
13
|
+
let baseDir = defaults.baseDir;
|
|
14
|
+
let outPath = defaults.outPath;
|
|
15
|
+
for (let i = 0; i < argv.length; i++) {
|
|
16
|
+
const token = argv[i];
|
|
17
|
+
if (token === '--help' || token === '-h') {
|
|
18
|
+
return { help: true };
|
|
19
|
+
}
|
|
20
|
+
if (token === '--json') {
|
|
21
|
+
json = true;
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (token === '--traceDir' || token === '--trace-dir') {
|
|
25
|
+
const raw = argv[++i];
|
|
26
|
+
if (!raw)
|
|
27
|
+
throw new Error('Flag "--traceDir" expects a directory path.');
|
|
28
|
+
traceDir = raw;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (token === '--target') {
|
|
32
|
+
const raw = argv[++i];
|
|
33
|
+
if (!raw)
|
|
34
|
+
throw new Error('Flag "--target" expects a file or folder path.');
|
|
35
|
+
targetPath = raw;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (token === '--project') {
|
|
39
|
+
const raw = argv[++i];
|
|
40
|
+
if (!raw)
|
|
41
|
+
throw new Error('Flag "--project" expects a tsconfig path.');
|
|
42
|
+
projectPath = raw;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (token === '--top') {
|
|
46
|
+
const raw = argv[++i];
|
|
47
|
+
const parsed = parsePositiveInteger(raw);
|
|
48
|
+
if (parsed === undefined) {
|
|
49
|
+
throw new Error('Flag "--top" expects a positive integer.');
|
|
50
|
+
}
|
|
51
|
+
top = parsed;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (token === '--minMs' || token === '--min-ms') {
|
|
55
|
+
const raw = argv[++i];
|
|
56
|
+
const parsed = parseNonNegativeNumber(raw);
|
|
57
|
+
if (parsed === undefined) {
|
|
58
|
+
throw new Error('Flag "--minMs" expects a non-negative number.');
|
|
59
|
+
}
|
|
60
|
+
minMs = parsed;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (token === '--filter') {
|
|
64
|
+
const raw = argv[++i];
|
|
65
|
+
if (raw === undefined) {
|
|
66
|
+
throw new Error('Flag "--filter" expects a regex string.');
|
|
67
|
+
}
|
|
68
|
+
ensureRegex(raw);
|
|
69
|
+
filterRegex = raw;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (token === '--event') {
|
|
73
|
+
const raw = argv[++i];
|
|
74
|
+
if (!raw)
|
|
75
|
+
throw new Error('Flag "--event" expects an event name.');
|
|
76
|
+
if (!didProvideEventFlag) {
|
|
77
|
+
eventNames = [];
|
|
78
|
+
}
|
|
79
|
+
didProvideEventFlag = true;
|
|
80
|
+
eventNames.push(raw);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (token === '--baseDir' || token === '--base-dir') {
|
|
84
|
+
const raw = argv[++i];
|
|
85
|
+
if (!raw)
|
|
86
|
+
throw new Error('Flag "--baseDir" expects a path.');
|
|
87
|
+
baseDir = raw;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (token === '--out') {
|
|
91
|
+
const raw = argv[++i];
|
|
92
|
+
if (!raw)
|
|
93
|
+
throw new Error('Flag "--out" expects a file path.');
|
|
94
|
+
outPath = raw;
|
|
95
|
+
json = true;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
throw new Error(`Unknown option "${token}". Use "pnpm manager-cli ts-trace --help".`);
|
|
99
|
+
}
|
|
100
|
+
if (traceDir && targetPath) {
|
|
101
|
+
throw new Error('Use either --traceDir (existing trace) or --target (generate trace), not both.');
|
|
102
|
+
}
|
|
103
|
+
const reportBase = {
|
|
104
|
+
top,
|
|
105
|
+
minMs,
|
|
106
|
+
json,
|
|
107
|
+
filterRegex,
|
|
108
|
+
eventNames: normalizeEventNames(eventNames),
|
|
109
|
+
baseDir: path.resolve(baseDir || process.cwd()),
|
|
110
|
+
outPath: outPath ? path.resolve(outPath) : undefined,
|
|
111
|
+
};
|
|
112
|
+
if (traceDir) {
|
|
113
|
+
return {
|
|
114
|
+
help: false,
|
|
115
|
+
command: {
|
|
116
|
+
mode: 'existing',
|
|
117
|
+
report: {
|
|
118
|
+
traceDir: path.resolve(traceDir),
|
|
119
|
+
...reportBase,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (targetPath) {
|
|
125
|
+
return {
|
|
126
|
+
help: false,
|
|
127
|
+
command: {
|
|
128
|
+
mode: 'generate',
|
|
129
|
+
targetPath: path.resolve(targetPath),
|
|
130
|
+
projectPath: projectPath ? path.resolve(projectPath) : undefined,
|
|
131
|
+
report: reportBase,
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
throw new Error('Provide one of: --target <file|folder> (generate trace) or --traceDir <path> (analyze existing).');
|
|
136
|
+
}
|
|
137
|
+
export function printTraceReportUsage() {
|
|
138
|
+
console.log(colors.bold('Usage: pnpm manager-cli ts-trace [flags]'));
|
|
139
|
+
console.log(colors.dim('Profile TypeScript checker speed by target path, or analyze an existing --generateTrace directory.'));
|
|
140
|
+
console.log('');
|
|
141
|
+
console.log(`${colors.cyan('--target <path>')}\tProfile a file/folder (auto-generates trace then reports offenders).`);
|
|
142
|
+
console.log(`${colors.cyan('--project <tsconfig>')}\tOptional project config for generation (default: auto-detect).`);
|
|
143
|
+
console.log(`${colors.cyan('--traceDir <path>')}\tAnalyze an already-generated trace directory with trace.json/types.json.`);
|
|
144
|
+
console.log(`${colors.cyan('--top <n>')}\tShow top results per table. Defaults to workspace settings or 20.`);
|
|
145
|
+
console.log(`${colors.cyan('--json')}\tPrint machine-readable JSON in addition to tables.`);
|
|
146
|
+
console.log(`${colors.cyan('--filter <regex>')}\tFilter events by matching file path.`);
|
|
147
|
+
console.log(`${colors.cyan('--event <name>')}\tInclude only specified event names (repeatable).`);
|
|
148
|
+
console.log(`${colors.cyan('--minMs <n>')}\tIgnore events shorter than n ms (default 1).`);
|
|
149
|
+
console.log(`${colors.cyan('--baseDir <path>')}\tBase directory for relative display paths.`);
|
|
150
|
+
console.log(`${colors.cyan('--out <path>')}\tWrite JSON report to file (implies --json).`);
|
|
151
|
+
console.log('');
|
|
152
|
+
console.log(colors.dim('Example (auto profile): pnpm manager-cli ts-trace --target src/format-checker --top 30'));
|
|
153
|
+
console.log(colors.dim('Example (existing trace): pnpm manager-cli ts-trace --traceDir /tmp/ts-trace-foo --json --out /tmp/ts-report.json'));
|
|
154
|
+
console.log(colors.dim('Large traces: set NODE_OPTIONS="--max-old-space-size=4096" before running if needed.'));
|
|
155
|
+
}
|
|
156
|
+
function parsePositiveInteger(raw) {
|
|
157
|
+
if (!raw)
|
|
158
|
+
return undefined;
|
|
159
|
+
const parsed = Number(raw);
|
|
160
|
+
if (!Number.isFinite(parsed))
|
|
161
|
+
return undefined;
|
|
162
|
+
const floored = Math.floor(parsed);
|
|
163
|
+
if (floored <= 0)
|
|
164
|
+
return undefined;
|
|
165
|
+
return floored;
|
|
166
|
+
}
|
|
167
|
+
function parseNonNegativeNumber(raw) {
|
|
168
|
+
if (!raw)
|
|
169
|
+
return undefined;
|
|
170
|
+
const parsed = Number(raw);
|
|
171
|
+
if (!Number.isFinite(parsed))
|
|
172
|
+
return undefined;
|
|
173
|
+
if (parsed < 0)
|
|
174
|
+
return undefined;
|
|
175
|
+
return parsed;
|
|
176
|
+
}
|
|
177
|
+
function normalizeEventNames(eventNames) {
|
|
178
|
+
const seen = new Set();
|
|
179
|
+
const normalized = [];
|
|
180
|
+
for (const value of eventNames) {
|
|
181
|
+
const trimmed = value.trim();
|
|
182
|
+
if (!trimmed || seen.has(trimmed))
|
|
183
|
+
continue;
|
|
184
|
+
seen.add(trimmed);
|
|
185
|
+
normalized.push(trimmed);
|
|
186
|
+
}
|
|
187
|
+
return normalized;
|
|
188
|
+
}
|
|
189
|
+
function ensureRegex(raw) {
|
|
190
|
+
try {
|
|
191
|
+
new RegExp(raw);
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
throw new Error(`Flag "--filter" received an invalid regex: ${error instanceof Error ? error.message : String(error)}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { stdin as input } from 'node:process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { colors } from '../../utils/log.js';
|
|
4
|
+
import { askLine, promptSingleKey } from '../../prompts.js';
|
|
5
|
+
import { promptInteractiveSettings, } from '../../cli/interactive-settings.js';
|
|
6
|
+
import { SETTING_DESCRIPTORS, formatValue, parseBooleanInput, parseInteractiveValue, parseNumericValue, parseStringList, validateTraceSettings, } from './settings.js';
|
|
7
|
+
import { rootDir } from '../../helper-cli/env.js';
|
|
8
|
+
const READY_PROMPT = colors.dim('Settings retained. Adjust values and confirm when ready.');
|
|
9
|
+
export async function promptTraceReportSettings(defaults) {
|
|
10
|
+
const supportsInteractive = typeof input.setRawMode === 'function' && input.isTTY;
|
|
11
|
+
let currentSettings = defaults;
|
|
12
|
+
while (true) {
|
|
13
|
+
const chosen = supportsInteractive
|
|
14
|
+
? await promptSettingsInteractive(currentSettings)
|
|
15
|
+
: await promptSettingsSequential(currentSettings);
|
|
16
|
+
const confirmed = await confirmExecution();
|
|
17
|
+
if (confirmed)
|
|
18
|
+
return normalizePromptSettings(chosen);
|
|
19
|
+
currentSettings = normalizePromptSettings(chosen);
|
|
20
|
+
console.log(READY_PROMPT);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
async function confirmExecution() {
|
|
24
|
+
const question = colors.cyan('Save these settings? (Y/n): ');
|
|
25
|
+
return promptSingleKey(question, (key) => {
|
|
26
|
+
const normalized = key.trim().toLowerCase();
|
|
27
|
+
if (!normalized || normalized === 'y' || normalized === 'yes')
|
|
28
|
+
return true;
|
|
29
|
+
if (normalized === 'n' || normalized === 'no')
|
|
30
|
+
return false;
|
|
31
|
+
return undefined;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
async function promptSettingsInteractive(defaults) {
|
|
35
|
+
const descriptors = SETTING_DESCRIPTORS.map((descriptor) => ({
|
|
36
|
+
key: descriptor.key,
|
|
37
|
+
label: descriptor.label,
|
|
38
|
+
unit: descriptor.unit,
|
|
39
|
+
format: (value) => formatValue(value, descriptor),
|
|
40
|
+
parse: (buffer) => parseInteractiveValue(descriptor, buffer),
|
|
41
|
+
}));
|
|
42
|
+
return promptInteractiveSettings({
|
|
43
|
+
title: 'TypeScript trace profiler settings (type to edit values)',
|
|
44
|
+
descriptors,
|
|
45
|
+
initial: defaults,
|
|
46
|
+
instructions: [
|
|
47
|
+
'Use ↑/↓ to change rows, type to replace the highlighted value, Backspace to clear characters, and Enter to validate and confirm the selection.',
|
|
48
|
+
'Press Esc/Ctrl+C to abort, or hit Enter again after reviewing the summary to continue.',
|
|
49
|
+
],
|
|
50
|
+
validate: validateTraceSettings,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async function promptSettingsSequential(defaults) {
|
|
54
|
+
console.log(colors.dim('Enter values to override defaults or press Enter to keep them.'));
|
|
55
|
+
const top = await promptPositiveInteger('Top results', defaults.top);
|
|
56
|
+
const minMs = await promptNonNegativeNumber('Minimum span duration (ms)', defaults.minMs);
|
|
57
|
+
const filterRegex = await promptText('Path filter regex', defaults.filterRegex);
|
|
58
|
+
const eventNames = await promptEvents(defaults.eventNames);
|
|
59
|
+
const includeJson = await promptBoolean('Print JSON report in terminal', defaults.includeJson);
|
|
60
|
+
const outPath = await promptText('JSON output file path', defaults.outPath);
|
|
61
|
+
const baseDir = await promptPath('Display base directory', defaults.baseDir || rootDir);
|
|
62
|
+
const projectPath = await promptText('TypeScript project config path (blank = auto)', defaults.projectPath);
|
|
63
|
+
return {
|
|
64
|
+
top,
|
|
65
|
+
minMs,
|
|
66
|
+
filterRegex,
|
|
67
|
+
eventNames,
|
|
68
|
+
includeJson: includeJson || false,
|
|
69
|
+
outPath,
|
|
70
|
+
baseDir,
|
|
71
|
+
projectPath,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async function promptPositiveInteger(label, fallback) {
|
|
75
|
+
const question = colors.cyan(`${label} [default ${fallback}]: `);
|
|
76
|
+
while (true) {
|
|
77
|
+
const answer = await askLine(question);
|
|
78
|
+
if (!answer)
|
|
79
|
+
return fallback;
|
|
80
|
+
const parsed = parseNumericValue(answer);
|
|
81
|
+
if (parsed !== undefined && parsed > 0)
|
|
82
|
+
return Math.floor(parsed);
|
|
83
|
+
console.log(colors.yellow('Provide a positive number or leave blank.'));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function promptNonNegativeNumber(label, fallback) {
|
|
87
|
+
const question = colors.cyan(`${label} [default ${fallback}]: `);
|
|
88
|
+
while (true) {
|
|
89
|
+
const answer = await askLine(question);
|
|
90
|
+
if (!answer)
|
|
91
|
+
return fallback;
|
|
92
|
+
const parsed = parseNumericValue(answer);
|
|
93
|
+
if (parsed !== undefined && parsed >= 0)
|
|
94
|
+
return parsed;
|
|
95
|
+
console.log(colors.yellow('Provide a non-negative number or leave blank.'));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function promptBoolean(label, fallback) {
|
|
99
|
+
const question = colors.cyan(`${label} [default ${fallback ? 'yes' : 'no'}]: `);
|
|
100
|
+
while (true) {
|
|
101
|
+
const answer = (await askLine(question)).trim();
|
|
102
|
+
if (!answer)
|
|
103
|
+
return fallback;
|
|
104
|
+
const parsed = parseBooleanInput(answer);
|
|
105
|
+
if (parsed.error) {
|
|
106
|
+
console.log(colors.yellow(parsed.error));
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
return parsed.value;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function promptText(label, fallback) {
|
|
113
|
+
const question = colors.cyan(`${label} [default ${fallback || '(none)'}]: `);
|
|
114
|
+
const answer = await askLine(question);
|
|
115
|
+
if (!answer)
|
|
116
|
+
return fallback;
|
|
117
|
+
return answer.trim();
|
|
118
|
+
}
|
|
119
|
+
async function promptEvents(fallback) {
|
|
120
|
+
const fallbackLabel = fallback.length ? fallback.join(', ') : '(all events)';
|
|
121
|
+
const question = colors.cyan(`Event names [default ${fallbackLabel}]: `);
|
|
122
|
+
const answer = await askLine(question);
|
|
123
|
+
if (!answer)
|
|
124
|
+
return fallback;
|
|
125
|
+
return parseStringList(answer);
|
|
126
|
+
}
|
|
127
|
+
async function promptPath(label, fallback) {
|
|
128
|
+
const question = colors.cyan(`${label} [default ${fallback}]: `);
|
|
129
|
+
const answer = await askLine(question);
|
|
130
|
+
if (!answer)
|
|
131
|
+
return fallback;
|
|
132
|
+
const trimmed = answer.trim();
|
|
133
|
+
if (!trimmed)
|
|
134
|
+
return fallback;
|
|
135
|
+
return path.isAbsolute(trimmed) ? trimmed : path.resolve(rootDir, trimmed);
|
|
136
|
+
}
|
|
137
|
+
function normalizePromptSettings(settings) {
|
|
138
|
+
return {
|
|
139
|
+
...settings,
|
|
140
|
+
top: Math.max(1, Math.floor(settings.top)),
|
|
141
|
+
minMs: Math.max(0, settings.minMs),
|
|
142
|
+
filterRegex: settings.filterRegex.trim(),
|
|
143
|
+
eventNames: settings.eventNames.map((name) => name.trim()).filter(Boolean),
|
|
144
|
+
outPath: settings.outPath.trim(),
|
|
145
|
+
baseDir: settings.baseDir.trim() || rootDir,
|
|
146
|
+
projectPath: settings.projectPath.trim(),
|
|
147
|
+
};
|
|
148
|
+
}
|