@densetsuuu/docteur 0.1.1-beta

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 (47) hide show
  1. package/LICENSE.md +9 -0
  2. package/README.md +155 -0
  3. package/build/index.d.ts +23 -0
  4. package/build/index.js +23 -0
  5. package/build/src/cli/commands/diagnose.d.ts +27 -0
  6. package/build/src/cli/commands/diagnose.js +67 -0
  7. package/build/src/cli/commands/xray.d.ts +7 -0
  8. package/build/src/cli/commands/xray.js +49 -0
  9. package/build/src/cli/main.d.ts +2 -0
  10. package/build/src/cli/main.js +30 -0
  11. package/build/src/profiler/collector.d.ts +16 -0
  12. package/build/src/profiler/collector.js +142 -0
  13. package/build/src/profiler/hooks.d.ts +27 -0
  14. package/build/src/profiler/hooks.js +45 -0
  15. package/build/src/profiler/loader.d.ts +1 -0
  16. package/build/src/profiler/loader.js +111 -0
  17. package/build/src/profiler/profiler.d.ts +9 -0
  18. package/build/src/profiler/profiler.js +117 -0
  19. package/build/src/profiler/registries/categories.d.ts +7 -0
  20. package/build/src/profiler/registries/categories.js +87 -0
  21. package/build/src/profiler/registries/index.d.ts +2 -0
  22. package/build/src/profiler/registries/index.js +2 -0
  23. package/build/src/profiler/registries/symbols.d.ts +42 -0
  24. package/build/src/profiler/registries/symbols.js +71 -0
  25. package/build/src/profiler/reporters/base_reporter.d.ts +19 -0
  26. package/build/src/profiler/reporters/base_reporter.js +9 -0
  27. package/build/src/profiler/reporters/console_reporter.d.ts +8 -0
  28. package/build/src/profiler/reporters/console_reporter.js +237 -0
  29. package/build/src/profiler/reporters/format.d.ts +62 -0
  30. package/build/src/profiler/reporters/format.js +84 -0
  31. package/build/src/profiler/reporters/tui_reporter.d.ts +8 -0
  32. package/build/src/profiler/reporters/tui_reporter.js +32 -0
  33. package/build/src/types.d.ts +167 -0
  34. package/build/src/types.js +9 -0
  35. package/build/src/xray/components/ListView.d.ts +9 -0
  36. package/build/src/xray/components/ListView.js +69 -0
  37. package/build/src/xray/components/ModuleView.d.ts +9 -0
  38. package/build/src/xray/components/ModuleView.js +144 -0
  39. package/build/src/xray/components/ProviderListView.d.ts +8 -0
  40. package/build/src/xray/components/ProviderListView.js +57 -0
  41. package/build/src/xray/components/ProviderView.d.ts +7 -0
  42. package/build/src/xray/components/ProviderView.js +35 -0
  43. package/build/src/xray/components/XRayApp.d.ts +7 -0
  44. package/build/src/xray/components/XRayApp.js +78 -0
  45. package/build/src/xray/tree.d.ts +22 -0
  46. package/build/src/xray/tree.js +85 -0
  47. package/package.json +110 -0
@@ -0,0 +1,237 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Console Reporter
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | Renders profiling results to the terminal using @poppinss/cliui.
7
+ |
8
+ */
9
+ import { ProfileCollector } from '../collector.js';
10
+ import { symbols } from '../registries/index.js';
11
+ import { colorDuration, createBar, formatDuration, getCategoryIcon, getEffectiveTime, simplifyUrl, ui, } from './format.js';
12
+ export class ConsoleReporter {
13
+ /**
14
+ * Renders the complete report to console.
15
+ */
16
+ render(context) {
17
+ const { result, config, cwd } = context;
18
+ this.#printHeader();
19
+ this.#printSummary(result);
20
+ this.#printAppFiles(result, cwd);
21
+ this.#printSlowestModules(result, config, cwd);
22
+ if (config.groupByPackage) {
23
+ this.#printPackageGroups(result, config);
24
+ }
25
+ this.#printProviders(result);
26
+ this.#printRecommendations(result, config);
27
+ this.#printFooter();
28
+ }
29
+ #printHeader() {
30
+ ui.logger.log('');
31
+ ui.logger.log(ui.colors.bold(ui.colors.cyan(` ${symbols.stethoscope} Docteur - Cold Start Analysis`)));
32
+ ui.logger.log(ui.colors.dim(` ${symbols.dash}`.repeat(25)));
33
+ ui.logger.log('');
34
+ }
35
+ #printSummary(result) {
36
+ ui.logger.log(ui.colors.bold(` ${symbols.chart} Summary`));
37
+ ui.logger.log('');
38
+ const table = ui.table();
39
+ table
40
+ .row([ui.colors.dim('Total boot time:'), colorDuration(result.totalTime)])
41
+ .row([
42
+ ui.colors.dim('Total modules loaded:'),
43
+ ui.colors.white(result.summary.totalModules.toString()),
44
+ ])
45
+ .row([
46
+ ui.colors.dim(' App modules:'),
47
+ ui.colors.white(result.summary.userModules.toString()),
48
+ ])
49
+ .row([
50
+ ui.colors.dim(' Node modules:'),
51
+ ui.colors.white(result.summary.nodeModules.toString()),
52
+ ])
53
+ .row([
54
+ ui.colors.dim(' AdonisJS modules:'),
55
+ ui.colors.white(result.summary.adonisModules.toString()),
56
+ ])
57
+ .row([ui.colors.dim('Module import time:'), colorDuration(result.summary.totalModuleTime)]);
58
+ if (result.providers.length > 0) {
59
+ table.row([
60
+ ui.colors.dim('Provider exec time:'),
61
+ colorDuration(result.summary.totalProviderTime),
62
+ ]);
63
+ }
64
+ table.render();
65
+ ui.logger.log('');
66
+ }
67
+ #printSlowestModules(result, config, cwd) {
68
+ const collector = new ProfileCollector(result.modules, result.providers);
69
+ const filtered = collector.filterModules(config);
70
+ const slowest = new ProfileCollector(filtered).getTopSlowest(config.topModules);
71
+ if (slowest.length === 0) {
72
+ ui.logger.log(ui.colors.dim(' No modules found above the threshold'));
73
+ return;
74
+ }
75
+ const maxTime = slowest[0] ? getEffectiveTime(slowest[0]) : 1;
76
+ ui.logger.log(ui.colors.bold(` ${symbols.turtle} Slowest Module Imports (top ${config.topModules})`));
77
+ ui.logger.log(ui.colors.dim(' Total = with dependencies, Self = file only'));
78
+ ui.logger.log('');
79
+ const table = ui.table();
80
+ table
81
+ .head([
82
+ ui.colors.dim('#'),
83
+ ui.colors.dim('Module'),
84
+ ui.colors.dim('Total'),
85
+ ui.colors.dim('Self'),
86
+ ui.colors.dim(''),
87
+ ])
88
+ .columnWidths([6, 45, 11, 11, 30]);
89
+ slowest.forEach((module, index) => {
90
+ const simplified = simplifyUrl(module.resolvedUrl, cwd);
91
+ const totalTime = getEffectiveTime(module);
92
+ const selfTime = module.loadTime;
93
+ table.row([
94
+ ui.colors.dim((index + 1).toString()),
95
+ simplified.length > 40 ? simplified.slice(-40) : simplified,
96
+ colorDuration(totalTime),
97
+ ui.colors.dim(formatDuration(selfTime)),
98
+ createBar(totalTime, maxTime, 25),
99
+ ]);
100
+ });
101
+ table.render();
102
+ ui.logger.log('');
103
+ }
104
+ #printPackageGroups(result, config) {
105
+ const collector = new ProfileCollector(result.modules, result.providers);
106
+ const filtered = collector.filterModules(config);
107
+ const groups = new ProfileCollector(filtered).groupModulesByPackage();
108
+ if (groups.length === 0) {
109
+ return;
110
+ }
111
+ const topGroups = groups.slice(0, 10);
112
+ const maxTime = topGroups[0]?.totalTime || 1;
113
+ ui.logger.log(ui.colors.bold(` ${symbols.package} Slowest Packages`));
114
+ ui.logger.log(ui.colors.dim(' Total import time per npm package'));
115
+ ui.logger.log('');
116
+ const table = ui.table();
117
+ table
118
+ .head([
119
+ ui.colors.dim('#'),
120
+ ui.colors.dim('Package'),
121
+ ui.colors.dim('Modules'),
122
+ ui.colors.dim('Total'),
123
+ ui.colors.dim(''),
124
+ ])
125
+ .columnWidths([6, 38, 12, 12, 35]);
126
+ topGroups.forEach((group, index) => {
127
+ table.row([
128
+ ui.colors.dim((index + 1).toString()),
129
+ group.name.length > 33 ? group.name.slice(0, 33) : group.name,
130
+ ui.colors.dim(group.modules.length.toString()),
131
+ colorDuration(group.totalTime),
132
+ createBar(group.totalTime, maxTime),
133
+ ]);
134
+ });
135
+ table.render();
136
+ ui.logger.log('');
137
+ }
138
+ #printProviders(result) {
139
+ if (result.providers.length === 0) {
140
+ return;
141
+ }
142
+ const sorted = [...result.providers].sort((a, b) => b.totalTime - a.totalTime);
143
+ const maxTime = sorted[0]?.totalTime || 1;
144
+ ui.logger.log(ui.colors.bold(` ${symbols.lightning} Provider Execution Times`));
145
+ ui.logger.log(ui.colors.dim(' Time spent in register() and boot() methods'));
146
+ ui.logger.log('');
147
+ const table = ui.table();
148
+ table
149
+ .head([
150
+ ui.colors.dim('#'),
151
+ ui.colors.dim('Provider'),
152
+ ui.colors.dim('Register'),
153
+ ui.colors.dim('Boot'),
154
+ ui.colors.dim('Total'),
155
+ ui.colors.dim(''),
156
+ ])
157
+ .columnWidths([5, 28, 11, 11, 11, 35]);
158
+ sorted.forEach((provider, index) => {
159
+ table.row([
160
+ ui.colors.dim((index + 1).toString()),
161
+ provider.name.length > 26 ? provider.name.slice(0, 26) : provider.name,
162
+ colorDuration(provider.registerTime),
163
+ colorDuration(provider.bootTime),
164
+ colorDuration(provider.totalTime),
165
+ createBar(provider.totalTime, maxTime, 30),
166
+ ]);
167
+ });
168
+ table.render();
169
+ ui.logger.log('');
170
+ }
171
+ #printRecommendations(result, config) {
172
+ const recommendations = [];
173
+ if (result.totalTime > 2000) {
174
+ recommendations.push('Total boot time is over 2s. Consider lazy-loading some providers.');
175
+ }
176
+ if (result.summary.totalModules > 500) {
177
+ recommendations.push(`Loading ${result.summary.totalModules} modules. Consider code splitting or lazy imports.`);
178
+ }
179
+ const collector = new ProfileCollector(result.modules, result.providers);
180
+ const filtered = collector.filterModules(config);
181
+ const verySlowModules = filtered.filter((m) => getEffectiveTime(m) > 100);
182
+ if (verySlowModules.length > 0) {
183
+ recommendations.push(`${verySlowModules.length} module(s) took over 100ms to load. Check for heavy initialization code.`);
184
+ }
185
+ if (recommendations.length === 0) {
186
+ ui.logger.log(ui.colors.bold(` ${symbols.checkmark} `) + ui.colors.green('No major issues detected!'));
187
+ }
188
+ else {
189
+ ui.logger.log(ui.colors.bold(` ${symbols.lightbulb} Recommendations`));
190
+ ui.logger.log('');
191
+ recommendations.forEach((rec) => {
192
+ ui.logger.log(` ${ui.colors.yellow(symbols.bullet)} ${rec}`);
193
+ });
194
+ }
195
+ ui.logger.log('');
196
+ }
197
+ #printAppFiles(result, cwd) {
198
+ const groups = result.summary.appFileGroups;
199
+ if (groups.length === 0) {
200
+ return;
201
+ }
202
+ ui.logger.log(ui.colors.bold(` ${symbols.folder} App Files by Category`));
203
+ ui.logger.log('');
204
+ for (const group of groups) {
205
+ if (group.files.length === 0)
206
+ continue;
207
+ const icon = getCategoryIcon(group.category);
208
+ const header = ` ${icon} ${group.displayName} (${group.files.length} files, ${formatDuration(group.totalTime)})`;
209
+ ui.logger.log(ui.colors.bold(ui.colors.white(header)));
210
+ this.#printAppFileGroup(group, cwd);
211
+ ui.logger.log('');
212
+ }
213
+ }
214
+ #printAppFileGroup(group, cwd) {
215
+ const maxTime = group.files[0] ? getEffectiveTime(group.files[0]) : 1;
216
+ const table = ui.table();
217
+ table.columnWidths([35, 11, 11, 30]);
218
+ for (const file of group.files) {
219
+ const simplified = simplifyUrl(file.resolvedUrl, cwd);
220
+ const fileName = simplified.split('/').pop() || simplified;
221
+ const totalTime = getEffectiveTime(file);
222
+ const selfTime = file.loadTime;
223
+ table.row([
224
+ fileName.length > 33 ? fileName.slice(-33) : fileName,
225
+ colorDuration(totalTime),
226
+ ui.colors.dim(formatDuration(selfTime)),
227
+ createBar(totalTime, maxTime, 25),
228
+ ]);
229
+ }
230
+ table.render();
231
+ }
232
+ #printFooter() {
233
+ ui.logger.log(ui.colors.dim(` ${symbols.dash}`.repeat(25)));
234
+ ui.logger.log(ui.colors.dim(' Run with --help for more options'));
235
+ ui.logger.log('');
236
+ }
237
+ }
@@ -0,0 +1,62 @@
1
+ import type { AppFileCategory, ModuleTiming } from '../../types.js';
2
+ /**
3
+ * Shared UI instance for consistent styling
4
+ */
5
+ export declare const ui: {
6
+ colors: import("@poppinss/colors/types").Colors;
7
+ logger: import("@poppinss/cliui").Logger;
8
+ table: (tableOptions
9
+ /**
10
+ * Colors a duration based on how slow it is
11
+ */
12
+ ? /**
13
+ * Colors a duration based on how slow it is
14
+ */: Partial<import("@poppinss/cliui/types").TableOptions>) => import("@poppinss/cliui").Table;
15
+ tasks: (tasksOptions?: Partial<import("@poppinss/cliui/types").TaskManagerOptions>) => import("@poppinss/cliui").TaskManager;
16
+ steps: () => import("@poppinss/cliui").Steps;
17
+ icons: {
18
+ tick: string;
19
+ cross: string;
20
+ bullet: string;
21
+ nodejs: string;
22
+ pointer: string;
23
+ info: string;
24
+ warning: string;
25
+ squareSmallFilled: string;
26
+ borderVertical: string;
27
+ };
28
+ sticker: () => import("@poppinss/cliui").Instructions;
29
+ instructions: () => import("@poppinss/cliui").Instructions;
30
+ switchMode(modeToUse: "raw" | "silent" | "normal"): void;
31
+ useRenderer(rendererToUse: import("@poppinss/cliui/types").RendererContract): void;
32
+ useColors(colorsToUse: import("@poppinss/colors/types").Colors): void;
33
+ };
34
+ /**
35
+ * Get icon for a category
36
+ */
37
+ export declare function getCategoryIcon(category: AppFileCategory): string;
38
+ /**
39
+ * Gets the effective time for a module.
40
+ * Uses subtreeTime (total including dependencies) if available,
41
+ * otherwise falls back to loadTime.
42
+ */
43
+ export declare function getEffectiveTime(module: ModuleTiming): number;
44
+ /**
45
+ * Formats a duration in milliseconds for display
46
+ */
47
+ export declare function formatDuration(ms: number): string;
48
+ /**
49
+ * Colors a duration based on how slow it is
50
+ */
51
+ export declare function colorDuration(ms: number): string;
52
+ /**
53
+ * Creates a visual bar representing the duration
54
+ */
55
+ export declare function createBar(ms: number, maxMs: number, width?: number): string;
56
+ /**
57
+ * Simplifies a module URL for display:
58
+ * - Strips file:// protocol
59
+ * - Converts absolute paths to relative (using cwd)
60
+ * - For node_modules, shows only the package path (handles pnpm store)
61
+ */
62
+ export declare function simplifyUrl(url: string, cwd: string): string;
@@ -0,0 +1,84 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Formatting Utilities
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | Shared formatting functions for report rendering.
7
+ |
8
+ */
9
+ import { cliui } from '@poppinss/cliui';
10
+ import { categories, symbols } from '../registries/index.js';
11
+ /**
12
+ * Shared UI instance for consistent styling
13
+ */
14
+ export const ui = cliui();
15
+ /**
16
+ * Get icon for a category
17
+ */
18
+ export function getCategoryIcon(category) {
19
+ return categories[category].icon;
20
+ }
21
+ /**
22
+ * Gets the effective time for a module.
23
+ * Uses subtreeTime (total including dependencies) if available,
24
+ * otherwise falls back to loadTime.
25
+ */
26
+ export function getEffectiveTime(module) {
27
+ return module.subtreeTime ?? module.loadTime;
28
+ }
29
+ /**
30
+ * Formats a duration in milliseconds for display
31
+ */
32
+ export function formatDuration(ms) {
33
+ if (ms >= 1000) {
34
+ return `${(ms / 1000).toFixed(2)}s`;
35
+ }
36
+ return `${ms.toFixed(2)}ms`;
37
+ }
38
+ /**
39
+ * Colors a duration based on how slow it is
40
+ */
41
+ export function colorDuration(ms) {
42
+ const formatted = formatDuration(ms);
43
+ if (ms >= 100)
44
+ return ui.colors.red(formatted);
45
+ if (ms >= 50)
46
+ return ui.colors.yellow(formatted);
47
+ if (ms >= 10)
48
+ return ui.colors.cyan(formatted);
49
+ return ui.colors.green(formatted);
50
+ }
51
+ /**
52
+ * Creates a visual bar representing the duration
53
+ */
54
+ export function createBar(ms, maxMs, width = 30) {
55
+ const ratio = Math.min(ms / maxMs, 1);
56
+ const filled = Math.round(ratio * width);
57
+ const bar = symbols.barFull.repeat(filled) + symbols.barEmpty.repeat(width - filled);
58
+ if (ms >= 100)
59
+ return ui.colors.red(bar);
60
+ if (ms >= 50)
61
+ return ui.colors.yellow(bar);
62
+ if (ms >= 10)
63
+ return ui.colors.cyan(bar);
64
+ return ui.colors.green(bar);
65
+ }
66
+ /**
67
+ * Simplifies a module URL for display:
68
+ * - Strips file:// protocol
69
+ * - Converts absolute paths to relative (using cwd)
70
+ * - For node_modules, shows only the package path (handles pnpm store)
71
+ */
72
+ export function simplifyUrl(url, cwd) {
73
+ const withoutProtocol = url.replace(/^file:\/\//, '');
74
+ // Use lastIndexOf to handle pnpm store paths like:
75
+ // .pnpm/@pkg@version/node_modules/@scope/pkg/index.js
76
+ const nodeModulesIndex = withoutProtocol.lastIndexOf('node_modules/');
77
+ if (nodeModulesIndex !== -1) {
78
+ return withoutProtocol.slice(nodeModulesIndex + 'node_modules/'.length);
79
+ }
80
+ if (withoutProtocol.startsWith(cwd)) {
81
+ return '.' + withoutProtocol.slice(cwd.length);
82
+ }
83
+ return withoutProtocol;
84
+ }
@@ -0,0 +1,8 @@
1
+ import type { Reporter, ReportContext } from './base_reporter.js';
2
+ export declare class TuiReporter implements Reporter {
3
+ /**
4
+ * Renders an interactive TUI report using the XRay explorer.
5
+ * Enters alternate screen buffer for a clean experience.
6
+ */
7
+ render(context: ReportContext): Promise<void>;
8
+ }
@@ -0,0 +1,32 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | TUI Reporter
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | Interactive terminal UI reporter using Ink.
7
+ | Renders an explorable dependency tree view.
8
+ |
9
+ */
10
+ import React from 'react';
11
+ import { render } from 'ink';
12
+ import { XRayApp } from '../../xray/components/XRayApp.js';
13
+ export class TuiReporter {
14
+ /**
15
+ * Renders an interactive TUI report using the XRay explorer.
16
+ * Enters alternate screen buffer for a clean experience.
17
+ */
18
+ async render(context) {
19
+ const { result, cwd } = context;
20
+ // Enter alternate screen buffer
21
+ process.stdout.write('\x1b[?1049h');
22
+ process.stdout.write('\x1b[H');
23
+ try {
24
+ const { waitUntilExit } = render(React.createElement(XRayApp, { result, cwd }));
25
+ await waitUntilExit();
26
+ }
27
+ finally {
28
+ // Exit alternate screen buffer
29
+ process.stdout.write('\x1b[?1049l');
30
+ }
31
+ }
32
+ }
@@ -0,0 +1,167 @@
1
+ /**
2
+ * Timing information for a single module import
3
+ */
4
+ export interface ModuleTiming {
5
+ /**
6
+ * The original import specifier (e.g., '@adonisjs/core', './app/services/user.js')
7
+ */
8
+ specifier: string;
9
+ /**
10
+ * The fully resolved URL of the module
11
+ */
12
+ resolvedUrl: string;
13
+ /**
14
+ * Time in milliseconds to load the module (file read + parse)
15
+ */
16
+ loadTime: number;
17
+ /**
18
+ * The URL of the parent module that imported this one
19
+ */
20
+ parentUrl?: string;
21
+ /**
22
+ * Total time including all transitive dependencies.
23
+ * This represents the actual impact of importing this module.
24
+ */
25
+ subtreeTime?: number;
26
+ }
27
+ /**
28
+ * Timing information for an AdonisJS provider
29
+ */
30
+ export interface ProviderTiming {
31
+ /**
32
+ * Name of the provider class
33
+ */
34
+ name: string;
35
+ /**
36
+ * Time in milliseconds for the register phase
37
+ */
38
+ registerTime: number;
39
+ /**
40
+ * Time in milliseconds for the boot phase
41
+ */
42
+ bootTime: number;
43
+ /**
44
+ * Time in milliseconds for the start phase
45
+ */
46
+ startTime: number;
47
+ /**
48
+ * Time in milliseconds for the ready phase
49
+ */
50
+ readyTime: number;
51
+ /**
52
+ * Time in milliseconds for the shutdown phase
53
+ */
54
+ shutdownTime: number;
55
+ /**
56
+ * Total time (register + boot + start + ready)
57
+ */
58
+ totalTime: number;
59
+ }
60
+ /**
61
+ * Complete profiling results
62
+ */
63
+ export interface ProfileResult {
64
+ /**
65
+ * Total cold start time in milliseconds
66
+ */
67
+ totalTime: number;
68
+ /**
69
+ * All module timing data
70
+ */
71
+ modules: ModuleTiming[];
72
+ /**
73
+ * Provider timing data
74
+ */
75
+ providers: ProviderTiming[];
76
+ /**
77
+ * Summary statistics
78
+ */
79
+ summary: ProfileSummary;
80
+ }
81
+ /**
82
+ * App file category types
83
+ */
84
+ export type AppFileCategory = 'controller' | 'service' | 'model' | 'middleware' | 'validator' | 'exception' | 'event' | 'listener' | 'mailer' | 'policy' | 'command' | 'provider' | 'config' | 'start' | 'other';
85
+ /**
86
+ * Grouped app files by category
87
+ */
88
+ export interface AppFileGroup {
89
+ /**
90
+ * Category name (e.g., 'controller', 'service')
91
+ */
92
+ category: AppFileCategory;
93
+ /**
94
+ * Display name for the category
95
+ */
96
+ displayName: string;
97
+ /**
98
+ * Files in this category
99
+ */
100
+ files: ModuleTiming[];
101
+ /**
102
+ * Total load time for all files in this category
103
+ */
104
+ totalTime: number;
105
+ }
106
+ /**
107
+ * Summary statistics for the profile
108
+ */
109
+ export interface ProfileSummary {
110
+ /**
111
+ * Total number of modules loaded
112
+ */
113
+ totalModules: number;
114
+ /**
115
+ * Number of user modules (from the app directory)
116
+ */
117
+ userModules: number;
118
+ /**
119
+ * Number of node_modules dependencies
120
+ */
121
+ nodeModules: number;
122
+ /**
123
+ * Number of AdonisJS core modules
124
+ */
125
+ adonisModules: number;
126
+ /**
127
+ * Total time spent loading modules
128
+ */
129
+ totalModuleTime: number;
130
+ /**
131
+ * Total time spent in provider lifecycle
132
+ */
133
+ totalProviderTime: number;
134
+ /**
135
+ * App files grouped by category
136
+ */
137
+ appFileGroups: AppFileGroup[];
138
+ }
139
+ /**
140
+ * Configuration options for Docteur
141
+ */
142
+ export interface DocteurConfig {
143
+ /**
144
+ * Number of slowest modules to display in the report
145
+ * @default 20
146
+ */
147
+ topModules: number;
148
+ /**
149
+ * Only show modules that took longer than this threshold (in ms)
150
+ * @default 1
151
+ */
152
+ threshold: number;
153
+ /**
154
+ * Include node_modules in the analysis
155
+ * @default true
156
+ */
157
+ includeNodeModules: boolean;
158
+ /**
159
+ * Group modules by package name
160
+ * @default true
161
+ */
162
+ groupByPackage: boolean;
163
+ }
164
+ /**
165
+ * Resolved configuration with defaults applied
166
+ */
167
+ export type ResolvedConfig = Required<DocteurConfig>;
@@ -0,0 +1,9 @@
1
+ /*
2
+ |--------------------------------------------------------------------------
3
+ | Docteur Types
4
+ |--------------------------------------------------------------------------
5
+ |
6
+ | Type definitions for the Docteur profiler package.
7
+ |
8
+ */
9
+ export {};
@@ -0,0 +1,9 @@
1
+ import type { DependencyTree, ModuleNode } from '../tree.js';
2
+ interface Props {
3
+ tree: DependencyTree;
4
+ onSelect: (node: ModuleNode) => void;
5
+ onSwitchToProviders: () => void;
6
+ hasProviders: boolean;
7
+ }
8
+ export declare function ListView({ tree, onSelect, onSwitchToProviders, hasProviders }: Props): import("react/jsx-runtime").JSX.Element;
9
+ export {};
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import SelectInput from 'ink-select-input';
4
+ import { formatDuration, getEffectiveTime, getFileIcon, getTimeColor, isDependency, } from '../tree.js';
5
+ import { symbols } from '../../profiler/registries/index.js';
6
+ function ItemComponent({ isSelected = false, label, timeStr, timeColor, fileIcon, displayName, }) {
7
+ // Spacer
8
+ if (!label) {
9
+ return _jsx(Text, { children: " " });
10
+ }
11
+ // Switch button
12
+ if (label.includes('Switch') || label.includes('Tab')) {
13
+ return (_jsx(Text, { color: isSelected ? 'blue' : 'cyan', bold: isSelected, children: label }));
14
+ }
15
+ if (isSelected) {
16
+ return (_jsxs(Text, { color: "blue", bold: true, children: [timeStr, " ", fileIcon, " ", displayName] }));
17
+ }
18
+ return (_jsxs(Text, { children: [_jsx(Text, { color: timeColor, children: timeStr }), " ", fileIcon, " ", displayName] }));
19
+ }
20
+ export function ListView({ tree, onSelect, onSwitchToProviders, hasProviders }) {
21
+ // Only show app files, not node_modules
22
+ const appModules = tree.sortedByTime.filter((node) => !isDependency(node.timing.resolvedUrl));
23
+ const moduleItems = appModules.slice(0, 30).map((node, index) => {
24
+ const time = getEffectiveTime(node.timing);
25
+ const name = node.displayName.length > 45 ? '...' + node.displayName.slice(-42) : node.displayName;
26
+ const url = node.timing.resolvedUrl;
27
+ return {
28
+ key: `${index}-${url}`,
29
+ label: name,
30
+ value: node,
31
+ timeStr: formatDuration(time).padStart(10),
32
+ timeColor: getTimeColor(time),
33
+ fileIcon: getFileIcon(url),
34
+ displayName: name,
35
+ };
36
+ });
37
+ const items = hasProviders
38
+ ? [
39
+ ...moduleItems,
40
+ {
41
+ key: 'spacer',
42
+ label: '',
43
+ value: 'switch',
44
+ timeStr: '',
45
+ timeColor: 'green',
46
+ fileIcon: '',
47
+ displayName: '',
48
+ },
49
+ {
50
+ key: 'switch',
51
+ label: `${symbols.lightning} Switch to Providers (Tab)`,
52
+ value: 'switch',
53
+ timeStr: '',
54
+ timeColor: 'green',
55
+ fileIcon: '',
56
+ displayName: '',
57
+ },
58
+ ]
59
+ : moduleItems;
60
+ const handleSelect = (item) => {
61
+ if (item.value === 'switch') {
62
+ onSwitchToProviders();
63
+ }
64
+ else {
65
+ onSelect(item.value);
66
+ }
67
+ };
68
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, color: "cyan", children: [' ', '\ue234', " Docteur X-Ray - Module Explorer"] }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { dimColor: true, children: " Use arrows to navigate, Enter to inspect, Tab to switch views" }) }), _jsx(Box, { flexDirection: "column", children: _jsx(SelectInput, { items: items, onSelect: handleSelect, itemComponent: ItemComponent }) })] }));
69
+ }
@@ -0,0 +1,9 @@
1
+ import type { DependencyTree, ModuleNode } from '../tree.js';
2
+ interface Props {
3
+ node: ModuleNode;
4
+ tree: DependencyTree;
5
+ onNavigate: (node: ModuleNode) => void;
6
+ onBack: () => void;
7
+ }
8
+ export declare function ModuleView({ node, tree: _tree, onNavigate, onBack }: Props): import("react/jsx-runtime").JSX.Element;
9
+ export {};