proteum 2.1.0-5 → 2.1.1

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.
Files changed (56) hide show
  1. package/AGENTS.md +37 -49
  2. package/README.md +52 -1
  3. package/agents/framework/AGENTS.md +104 -236
  4. package/agents/project/AGENTS.md +36 -70
  5. package/cli/commands/command.ts +243 -0
  6. package/cli/commands/commandLocalRunner.js +198 -0
  7. package/cli/commands/dev.ts +95 -1
  8. package/cli/commands/doctor.ts +8 -74
  9. package/cli/commands/explain.ts +8 -194
  10. package/cli/commands/trace.ts +8 -0
  11. package/cli/compiler/artifacts/commands.ts +217 -0
  12. package/cli/compiler/artifacts/manifest.ts +17 -2
  13. package/cli/compiler/artifacts/services.ts +291 -0
  14. package/cli/compiler/client/index.ts +13 -0
  15. package/cli/compiler/common/commands.ts +175 -0
  16. package/cli/compiler/common/proteumManifest.ts +15 -124
  17. package/cli/compiler/index.ts +25 -2
  18. package/cli/compiler/server/index.ts +3 -0
  19. package/cli/presentation/commands.ts +37 -5
  20. package/cli/runtime/commands.ts +29 -1
  21. package/cli/tsconfig.json +4 -1
  22. package/cli/utils/check.ts +1 -1
  23. package/client/app/component.tsx +11 -0
  24. package/client/dev/profiler/index.tsx +1511 -0
  25. package/client/dev/profiler/noop.tsx +5 -0
  26. package/client/dev/profiler/runtime.noop.ts +116 -0
  27. package/client/dev/profiler/runtime.ts +840 -0
  28. package/client/services/router/components/router.tsx +30 -2
  29. package/client/services/router/index.tsx +25 -0
  30. package/client/services/router/request/api.ts +133 -17
  31. package/commands/proteum/diagnostics.ts +11 -0
  32. package/common/dev/commands.ts +50 -0
  33. package/common/dev/diagnostics.ts +298 -0
  34. package/common/dev/profiler.ts +91 -0
  35. package/common/dev/proteumManifest.ts +135 -0
  36. package/common/dev/requestTrace.ts +28 -1
  37. package/docs/dev-commands.md +86 -0
  38. package/docs/request-tracing.md +2 -0
  39. package/package.json +1 -2
  40. package/server/app/commands.ts +35 -370
  41. package/server/app/commandsManager.ts +393 -0
  42. package/server/app/container/console/index.ts +0 -2
  43. package/server/app/container/trace/index.ts +88 -8
  44. package/server/app/devCommands.ts +192 -0
  45. package/server/app/devDiagnostics.ts +53 -0
  46. package/server/app/index.ts +27 -4
  47. package/server/services/cron/CronTask.ts +73 -5
  48. package/server/services/cron/index.ts +34 -11
  49. package/server/services/fetch/index.ts +3 -10
  50. package/server/services/prisma/index.ts +1 -1
  51. package/server/services/router/http/index.ts +132 -21
  52. package/server/services/router/index.ts +40 -4
  53. package/server/services/router/request/api.ts +30 -1
  54. package/skills/clean-project-code/SKILL.md +7 -2
  55. package/test-results/.last-run.json +4 -0
  56. package/types/aliases.d.ts +6 -0
@@ -5,6 +5,8 @@
5
5
  // Npm
6
6
  import path from 'path';
7
7
  import { spawn, ChildProcess } from 'child_process';
8
+ import fs from 'fs-extra';
9
+ import type { FSWatcher } from 'fs';
8
10
 
9
11
  // Cor elibs
10
12
  import cli from '..';
@@ -31,7 +33,7 @@ import { app, App } from '../app';
31
33
  ----------------------------------*/
32
34
 
33
35
  // Watch rules shared by the dev compiler and hot reload gate.
34
- const ignoredWatchPathPatterns = /(node_modules\/(?!proteum\/))|(\.generated\/)|(\.cache\/)|(\.proteum\/)/;
36
+ const ignoredWatchPathPatterns = /(node_modules\/(?!proteum\/))|(\.generated\/)|(\.cache\/)|(\.proteum\/)|(\/var\/traces\/)/;
35
37
  const hotReloadableServerPathPatterns = [
36
38
  /^client\/pages\//,
37
39
  /^client\/components\//,
@@ -50,6 +52,7 @@ let cp: ChildProcess | undefined = undefined;
50
52
  let devSessionStopping = false;
51
53
  let appProcessOperation: Promise<void> = Promise.resolve();
52
54
  type TDevWatching = ReturnType<Awaited<ReturnType<Compiler['create']>>['watch']>;
55
+ type TIndexedSourceWatching = { close: () => Promise<void> };
53
56
 
54
57
  /*----------------------------------
55
58
  - HELPERS
@@ -110,6 +113,22 @@ const createIgnoredWatchPattern = (outputPaths: string[]) =>
110
113
  const getDevAppName = (app: App) =>
111
114
  app.identity.web?.fullTitle || app.identity.web?.title || app.identity.name || app.packageJson.name || app.paths.root;
112
115
 
116
+ const cleanupPersistedDevTraces = async (app: App) => {
117
+ const tracesRoot = path.join(app.paths.root, 'var', 'traces');
118
+ if (!(await fs.pathExists(tracesRoot))) return;
119
+
120
+ const entries = await fs.readdir(tracesRoot);
121
+ const removableEntries = entries.filter((entry) => entry !== 'exports');
122
+ if (removableEntries.length === 0) return;
123
+
124
+ await Promise.all(removableEntries.map((entry) => fs.remove(path.join(tracesRoot, entry))));
125
+
126
+ const remainingEntries = await fs.readdir(tracesRoot).catch(() => []);
127
+ if (remainingEntries.length === 0) {
128
+ await fs.remove(tracesRoot);
129
+ }
130
+ };
131
+
113
132
  const signalAppProcess = (child: ChildProcess, signal: NodeJS.Signals) => {
114
133
  try {
115
134
  if (process.platform !== 'win32' && child.pid !== undefined) {
@@ -238,6 +257,78 @@ function normalizeWatchPath(watchPath: string) {
238
257
  return path.resolve(watchPath).replace(/\\/g, '/').replace(/\/$/, '');
239
258
  }
240
259
 
260
+ const indexedSourceWatchRules: { compilerName: 'server'; root: () => string; relativePathPattern: RegExp }[] = [
261
+ { compilerName: 'server', root: () => app.paths.root, relativePathPattern: /^commands(?:\/|$)/ },
262
+ { compilerName: 'server', root: () => cli.paths.core.root, relativePathPattern: /^commands(?:\/|$)/ },
263
+ ];
264
+
265
+ const closeFsWatcher = async (watcher: FSWatcher) => {
266
+ await new Promise<void>((resolve) => {
267
+ watcher.once('close', () => resolve());
268
+ watcher.close();
269
+ });
270
+ };
271
+
272
+ const createIndexedSourceWatching = ({
273
+ compiler,
274
+ watching,
275
+ }: {
276
+ compiler: Compiler;
277
+ watching: TDevWatching;
278
+ }): TIndexedSourceWatching => {
279
+ const watchers: FSWatcher[] = [];
280
+ const pendingChanges = new Map<'server', Set<string>>();
281
+ let invalidateTimer: NodeJS.Timeout | undefined;
282
+
283
+ const flushInvalidate = () => {
284
+ invalidateTimer = undefined;
285
+
286
+ for (const [compilerName, changedFiles] of pendingChanges) {
287
+ compiler.noteManualModifiedFiles(compilerName, [...changedFiles]);
288
+ }
289
+
290
+ pendingChanges.clear();
291
+ logVerbose('Indexed source files changed. Invalidating the dev compiler to refresh generated artifacts.');
292
+ watching.invalidate();
293
+ };
294
+
295
+ const queueInvalidate = (compilerName: 'server', filepath: string) => {
296
+ const normalizedFilepath = normalizeWatchPath(filepath);
297
+ const changedFiles = pendingChanges.get(compilerName) || new Set<string>();
298
+
299
+ changedFiles.add(normalizedFilepath);
300
+ pendingChanges.set(compilerName, changedFiles);
301
+
302
+ if (invalidateTimer) return;
303
+ invalidateTimer = setTimeout(flushInvalidate, 40);
304
+ };
305
+
306
+ for (const watchRule of indexedSourceWatchRules) {
307
+ const rootPath = watchRule.root();
308
+
309
+ watchers.push(
310
+ fs.watch(rootPath, { recursive: true }, (eventType, filename) => {
311
+ const relativePath = typeof filename === 'string' ? filename.replace(/\\/g, '/').replace(/^\.\//, '') : '';
312
+ if (relativePath && !watchRule.relativePathPattern.test(relativePath)) return;
313
+ if (eventType !== 'rename' && relativePath) return;
314
+
315
+ queueInvalidate(watchRule.compilerName, relativePath ? path.join(rootPath, relativePath) : rootPath);
316
+ }),
317
+ );
318
+ }
319
+
320
+ return {
321
+ close: async () => {
322
+ if (invalidateTimer) {
323
+ clearTimeout(invalidateTimer);
324
+ invalidateTimer = undefined;
325
+ }
326
+
327
+ await Promise.all(watchers.map((watcher) => closeFsWatcher(watcher)));
328
+ },
329
+ };
330
+ };
331
+
241
332
  /*----------------------------------
242
333
  - MAIN PROCESS
243
334
  ----------------------------------*/
@@ -341,6 +432,7 @@ export const run = async () => {
341
432
  logVerbose('Watch callback. No compiler changes were tracked.');
342
433
  },
343
434
  );
435
+ const indexedSourceWatching = createIndexedSourceWatching({ compiler, watching });
344
436
 
345
437
  let shuttingDownPromise: Promise<void> | undefined;
346
438
 
@@ -350,9 +442,11 @@ export const run = async () => {
350
442
  devSessionStopping = true;
351
443
  shuttingDownPromise = (async () => {
352
444
  logVerbose('Stopping the Proteum dev session ...', reason);
445
+ await indexedSourceWatching.close();
353
446
  await closeWatching(watching);
354
447
  compiler.dispose();
355
448
  await stopApp(reason);
449
+ await cleanupPersistedDevTraces(app);
356
450
  await devEventServer.close();
357
451
  })();
358
452
 
@@ -1,12 +1,7 @@
1
- import path from 'path';
2
-
3
1
  import cli from '..';
4
2
  import Compiler from '../compiler';
5
- import {
6
- readProteumManifest,
7
- type TProteumManifest,
8
- type TProteumManifestDiagnostic,
9
- } from '../compiler/common/proteumManifest';
3
+ import { readProteumManifest } from '../compiler/common/proteumManifest';
4
+ import { buildDoctorResponse, renderDoctorHuman } from '@common/dev/diagnostics';
10
5
 
11
6
  const allowedDoctorArgs = new Set(['json', 'strict']);
12
7
 
@@ -24,43 +19,6 @@ const validateDoctorArgs = () => {
24
19
  }
25
20
  };
26
21
 
27
- const normalizePath = (value: string) => value.replace(/\\/g, '/');
28
-
29
- const formatFilepath = (manifest: TProteumManifest, filepath: string) => {
30
- const normalizedFilepath = normalizePath(filepath);
31
- const normalizedAppRoot = normalizePath(manifest.app.root);
32
- const normalizedCoreRoot = normalizePath(manifest.app.coreRoot);
33
-
34
- if (normalizedFilepath === normalizedAppRoot) return '.';
35
- if (normalizedFilepath.startsWith(normalizedAppRoot + '/'))
36
- return normalizePath(path.relative(normalizedAppRoot, normalizedFilepath)) || '.';
37
-
38
- if (normalizedFilepath === normalizedCoreRoot) return 'node_modules/proteum';
39
- if (normalizedFilepath.startsWith(normalizedCoreRoot + '/'))
40
- return normalizePath(path.join('node_modules/proteum', path.relative(normalizedCoreRoot, normalizedFilepath)));
41
-
42
- return normalizedFilepath;
43
- };
44
-
45
- const formatLocation = (diagnostic: TProteumManifestDiagnostic) =>
46
- diagnostic.sourceLocation ? `:${diagnostic.sourceLocation.line}:${diagnostic.sourceLocation.column}` : '';
47
-
48
- const renderGroup = (manifest: TProteumManifest, diagnostics: TProteumManifestDiagnostic[], title: string) => {
49
- if (diagnostics.length === 0) return `${title}\n- none`;
50
-
51
- return [
52
- title,
53
- ...diagnostics.map((diagnostic) => {
54
- const related =
55
- diagnostic.relatedFilepaths && diagnostic.relatedFilepaths.length > 0
56
- ? ` related=${diagnostic.relatedFilepaths.map((filepath) => formatFilepath(manifest, filepath)).join(',')}`
57
- : '';
58
-
59
- return `- ${diagnostic.code} ${diagnostic.message} source=${formatFilepath(manifest, diagnostic.filepath)}${formatLocation(diagnostic)}${related}`;
60
- }),
61
- ].join('\n');
62
- };
63
-
64
22
  export const run = async (): Promise<void> => {
65
23
  validateDoctorArgs();
66
24
 
@@ -68,41 +26,17 @@ export const run = async (): Promise<void> => {
68
26
  await compiler.refreshGeneratedTypings();
69
27
 
70
28
  const manifest = readProteumManifest(cli.paths.appRoot);
71
- const errors = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'error');
72
- const warnings = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'warning');
29
+ const response = buildDoctorResponse(manifest, cli.args.strict === true);
73
30
 
74
31
  if (cli.args.json === true) {
75
- console.log(
76
- JSON.stringify(
77
- {
78
- summary: {
79
- errors: errors.length,
80
- warnings: warnings.length,
81
- strictFailed: cli.args.strict === true && manifest.diagnostics.length > 0,
82
- },
83
- diagnostics: manifest.diagnostics,
84
- },
85
- null,
86
- 2,
87
- ),
88
- );
89
- } else if (manifest.diagnostics.length === 0) {
90
- console.log('Proteum doctor\n- No manifest diagnostics were found.');
32
+ console.log(JSON.stringify(response, null, 2));
91
33
  } else {
92
- console.log(
93
- [
94
- 'Proteum doctor',
95
- `- ${errors.length} errors`,
96
- `- ${warnings.length} warnings`,
97
- '',
98
- renderGroup(manifest, errors, 'Errors'),
99
- '',
100
- renderGroup(manifest, warnings, 'Warnings'),
101
- ].join('\n'),
102
- );
34
+ console.log(renderDoctorHuman(manifest, cli.args.strict === true));
103
35
  }
104
36
 
105
37
  if (cli.args.strict === true && manifest.diagnostics.length > 0) {
106
- throw new Error(`Proteum doctor failed in strict mode with ${errors.length} errors and ${warnings.length} warnings.`);
38
+ throw new Error(
39
+ `Proteum doctor failed in strict mode with ${response.summary.errors} errors and ${response.summary.warnings} warnings.`,
40
+ );
107
41
  }
108
42
  };
@@ -1,22 +1,15 @@
1
- import path from 'path';
2
-
3
1
  import cli from '..';
4
2
  import Compiler from '../compiler';
3
+ import { readProteumManifest } from '../compiler/common/proteumManifest';
5
4
  import {
6
- readProteumManifest,
7
- type TProteumManifestDiagnostic,
8
- type TProteumManifest,
9
- type TProteumManifestController,
10
- type TProteumManifestLayout,
11
- type TProteumManifestRoute,
12
- type TProteumManifestService,
13
- } from '../compiler/common/proteumManifest';
5
+ explainSectionNames,
6
+ pickExplainManifestSections,
7
+ renderExplainHuman,
8
+ type TExplainSectionName,
9
+ } from '@common/dev/diagnostics';
14
10
 
15
- const explainSectionNames = ['app', 'conventions', 'env', 'services', 'controllers', 'routes', 'layouts', 'diagnostics'] as const;
16
11
  const allowedExplainArgs = new Set(['json', 'all', ...explainSectionNames]);
17
12
 
18
- type TExplainSectionName = (typeof explainSectionNames)[number];
19
-
20
13
  const validateExplainArgs = () => {
21
14
  const enabledArgs = Object.entries(cli.args)
22
15
  .filter(([name, value]) => name !== 'workdir' && name !== 'verbose' && value === true)
@@ -37,185 +30,6 @@ const getSelectedSections = (): TExplainSectionName[] => {
37
30
  return explainSectionNames.filter((sectionName) => cli.args[sectionName] === true);
38
31
  };
39
32
 
40
- const pickManifestSections = (manifest: TProteumManifest, sectionNames: TExplainSectionName[]) => {
41
- if (sectionNames.length === 0) return manifest;
42
-
43
- const selected: Record<string, unknown> = {};
44
-
45
- for (const sectionName of sectionNames) {
46
- selected[sectionName] = manifest[sectionName];
47
- }
48
-
49
- return selected;
50
- };
51
-
52
- const normalizePath = (value: string) => value.replace(/\\/g, '/');
53
- const formatLocation = (line?: number, column?: number) =>
54
- line && column ? `:${line}:${column}` : line ? `:${line}` : '';
55
-
56
- const formatFilepath = (manifest: TProteumManifest, filepath: string) => {
57
- const normalizedFilepath = normalizePath(filepath);
58
- const normalizedAppRoot = normalizePath(manifest.app.root);
59
- const normalizedCoreRoot = normalizePath(manifest.app.coreRoot);
60
-
61
- if (normalizedFilepath === normalizedAppRoot) return '.';
62
- if (normalizedFilepath.startsWith(normalizedAppRoot + '/'))
63
- return normalizePath(path.relative(normalizedAppRoot, normalizedFilepath)) || '.';
64
-
65
- if (normalizedFilepath === normalizedCoreRoot) return 'node_modules/proteum';
66
- if (normalizedFilepath.startsWith(normalizedCoreRoot + '/'))
67
- return normalizePath(path.join('node_modules/proteum', path.relative(normalizedCoreRoot, normalizedFilepath)));
68
-
69
- return normalizedFilepath;
70
- };
71
-
72
- const formatService = (manifest: TProteumManifest, service: TProteumManifestService) => {
73
- if (service.kind === 'ref') {
74
- return `- ${service.registeredName} -> ref ${service.refTo} [${service.parent}]`;
75
- }
76
-
77
- const source = service.metasFilepath ? formatFilepath(manifest, service.metasFilepath) : 'unknown';
78
- return `- ${service.registeredName} -> ${service.id} (${service.metaName}) [${service.scope}] priority=${service.priority} source=${source}`;
79
- };
80
-
81
- const formatController = (manifest: TProteumManifest, controller: TProteumManifestController) =>
82
- `- ${controller.clientAccessor} -> POST ${controller.httpPath} [${controller.scope}] input=${controller.hasInput ? 'yes' : 'no'} source=${formatFilepath(manifest, controller.filepath)}${formatLocation(controller.sourceLocation.line, controller.sourceLocation.column)}#${controller.methodName}`;
83
-
84
- const formatRouteTarget = (route: TProteumManifestRoute) => {
85
- if (route.kind === 'client-error') return route.code !== undefined ? String(route.code) : route.codeRaw || '?';
86
- return route.path || route.pathRaw || '?';
87
- };
88
-
89
- const formatRoute = (manifest: TProteumManifest, route: TProteumManifestRoute) => {
90
- const chunk = route.chunkId ? ` chunk=${route.chunkId}` : '';
91
- const setup = route.hasSetup ? ' setup=yes' : ' setup=no';
92
- const options = route.normalizedOptionKeys.length > 0 ? ` options=${route.normalizedOptionKeys.join(',')}` : '';
93
- const resolution = route.targetResolution !== 'literal' ? ` resolution=${route.targetResolution}` : '';
94
-
95
- return `- ${route.kind} ${route.methodName} ${formatRouteTarget(route)} [${route.scope}]${chunk}${setup}${options}${resolution} source=${formatFilepath(manifest, route.filepath)}${formatLocation(route.sourceLocation.line, route.sourceLocation.column)}`;
96
- };
97
-
98
- const formatLayout = (manifest: TProteumManifest, layout: TProteumManifestLayout) =>
99
- `- ${layout.chunkId || 'root'} depth=${layout.depth} [${layout.scope}] source=${formatFilepath(manifest, layout.filepath)}`;
100
-
101
- const formatDiagnostic = (manifest: TProteumManifest, diagnostic: TProteumManifestDiagnostic) => {
102
- const related =
103
- diagnostic.relatedFilepaths && diagnostic.relatedFilepaths.length > 0
104
- ? ` related=${diagnostic.relatedFilepaths.map((filepath) => formatFilepath(manifest, filepath)).join(',')}`
105
- : '';
106
-
107
- return `- [${diagnostic.level}] ${diagnostic.code} ${diagnostic.message} source=${formatFilepath(manifest, diagnostic.filepath)}${formatLocation(diagnostic.sourceLocation?.line, diagnostic.sourceLocation?.column)}${related}`;
108
- };
109
-
110
- const printSection = (title: string, lines: string[]) => {
111
- if (lines.length === 0) return `${title}\n- none`;
112
- return `${title}\n${lines.join('\n')}`;
113
- };
114
-
115
- const renderSummary = (manifest: TProteumManifest) => {
116
- const errorsCount = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'error').length;
117
- const warningsCount = manifest.diagnostics.filter((diagnostic) => diagnostic.level === 'warning').length;
118
- const providedRequiredEnvVariables = manifest.env.requiredVariables.filter((variable) => variable.provided).length;
119
- const lines = [
120
- `Proteum manifest: ${formatFilepath(manifest, path.join(manifest.app.root, '.proteum', 'manifest.json'))}`,
121
- `App: ${manifest.app.identity.name} (${manifest.app.identity.identifier})`,
122
- `Env vars: ${providedRequiredEnvVariables}/${manifest.env.requiredVariables.length} required provided`,
123
- `Services: ${manifest.services.app.length} app, ${manifest.services.routerPlugins.length} router plugins`,
124
- `Controllers: ${manifest.controllers.length}`,
125
- `Routes: ${manifest.routes.client.length} client, ${manifest.routes.server.length} server`,
126
- `Layouts: ${manifest.layouts.length}`,
127
- `Diagnostics: ${errorsCount} errors, ${warningsCount} warnings`,
128
- 'Use `proteum explain --json` for the full machine-readable manifest or pass section flags like `routes` and `services`.',
129
- ];
130
-
131
- return lines.join('\n');
132
- };
133
-
134
- const renderHuman = (manifest: TProteumManifest, sectionNames: TExplainSectionName[]) => {
135
- if (sectionNames.length === 0) return renderSummary(manifest);
136
-
137
- const sections: string[] = [];
138
-
139
- for (const sectionName of sectionNames) {
140
- if (sectionName === 'app') {
141
- sections.push(
142
- printSection('App', [
143
- `- root=${formatFilepath(manifest, manifest.app.root)}`,
144
- `- coreRoot=${formatFilepath(manifest, manifest.app.coreRoot)}`,
145
- `- identity=${formatFilepath(manifest, manifest.app.identityFilepath)}`,
146
- `- name=${manifest.app.identity.name}`,
147
- `- identifier=${manifest.app.identity.identifier}`,
148
- `- title=${manifest.app.identity.fullTitle || manifest.app.identity.title || manifest.app.identity.name}`,
149
- ]),
150
- );
151
- continue;
152
- }
153
-
154
- if (sectionName === 'conventions') {
155
- sections.push(
156
- printSection('Conventions', [
157
- `- routeSetupOptionKeys=${manifest.conventions.routeSetupOptionKeys.join(', ')}`,
158
- `- reservedRouteSetupKeys=${manifest.conventions.reservedRouteSetupKeys.join(', ')}`,
159
- ]),
160
- );
161
- continue;
162
- }
163
-
164
- if (sectionName === 'env') {
165
- sections.push(
166
- printSection('Env', [
167
- `- source=${manifest.env.source}`,
168
- `- loadedVariableKeys=${manifest.env.loadedVariableKeys.join(', ') || 'none'}`,
169
- ...manifest.env.requiredVariables.map(
170
- (variable) =>
171
- `- ${variable.key} possibleValues=${variable.possibleValues.join(' | ')} provided=${variable.provided ? 'yes' : 'no'}`,
172
- ),
173
- `- resolved.name=${manifest.env.resolved.name}`,
174
- `- resolved.profile=${manifest.env.resolved.profile}`,
175
- `- resolved.routerPort=${manifest.env.resolved.routerPort}`,
176
- `- resolved.routerCurrentDomain=${manifest.env.resolved.routerCurrentDomain}`,
177
- ]),
178
- );
179
- continue;
180
- }
181
-
182
- if (sectionName === 'services') {
183
- sections.push(
184
- printSection('App Services', manifest.services.app.map((service) => formatService(manifest, service))),
185
- printSection(
186
- 'Router Plugins',
187
- manifest.services.routerPlugins.map((service) => formatService(manifest, service)),
188
- ),
189
- );
190
- continue;
191
- }
192
-
193
- if (sectionName === 'controllers') {
194
- sections.push(printSection('Controllers', manifest.controllers.map((controller) => formatController(manifest, controller))));
195
- continue;
196
- }
197
-
198
- if (sectionName === 'routes') {
199
- sections.push(
200
- printSection('Client Routes', manifest.routes.client.map((route) => formatRoute(manifest, route))),
201
- printSection('Server Routes', manifest.routes.server.map((route) => formatRoute(manifest, route))),
202
- );
203
- continue;
204
- }
205
-
206
- if (sectionName === 'layouts') {
207
- sections.push(printSection('Layouts', manifest.layouts.map((layout) => formatLayout(manifest, layout))));
208
- continue;
209
- }
210
-
211
- if (sectionName === 'diagnostics') {
212
- sections.push(printSection('Diagnostics', manifest.diagnostics.map((diagnostic) => formatDiagnostic(manifest, diagnostic))));
213
- }
214
- }
215
-
216
- return sections.join('\n\n');
217
- };
218
-
219
33
  export const run = async (): Promise<void> => {
220
34
  validateExplainArgs();
221
35
 
@@ -226,9 +40,9 @@ export const run = async (): Promise<void> => {
226
40
  const selectedSections = getSelectedSections();
227
41
 
228
42
  if (cli.args.json === true) {
229
- console.log(JSON.stringify(pickManifestSections(manifest, selectedSections), null, 2));
43
+ console.log(JSON.stringify(pickExplainManifestSections(manifest, selectedSections), null, 2));
230
44
  return;
231
45
  }
232
46
 
233
- console.log(renderHuman(manifest, selectedSections));
47
+ console.log(renderExplainHuman(manifest, selectedSections));
234
48
  };
@@ -118,6 +118,7 @@ const renderTraceSummary = (request: TRequestTraceListItem) =>
118
118
  `status=${request.statusCode ?? 'pending'}`,
119
119
  `capture=${request.capture}`,
120
120
  `events=${request.eventCount}`,
121
+ `calls=${request.callCount}`,
121
122
  request.user ? `user=${request.user}` : '',
122
123
  request.errorMessage ? `error=${request.errorMessage}` : '',
123
124
  ]
@@ -131,6 +132,13 @@ const renderTrace = (request: TRequestTrace) =>
131
132
  `- started=${request.startedAt} durationMs=${request.durationMs ?? 'pending'} events=${request.events.length} dropped=${request.droppedEvents}`,
132
133
  ...(request.user ? [`- user=${request.user}`] : []),
133
134
  ...(request.persistedFilepath ? [`- persisted=${request.persistedFilepath}`] : []),
135
+ 'Calls',
136
+ ...(request.calls.length === 0
137
+ ? ['- none']
138
+ : request.calls.map(
139
+ (call) =>
140
+ `- ${call.origin} ${call.label} ${call.method} ${call.path} status=${call.statusCode ?? 'pending'} durationMs=${call.durationMs ?? 'pending'} req=${call.requestDataKeys.join(',')} res=${call.resultKeys.join(',')}`,
141
+ )),
134
142
  'Events',
135
143
  ...request.events.map(
136
144
  (event) =>